summaryrefslogtreecommitdiff
path: root/demos/gtk-demo/changedisplay.c
diff options
context:
space:
mode:
authorOwen Taylor <otaylor@redhat.com>2002-08-04 20:26:36 +0000
committerOwen Taylor <otaylor@src.gnome.org>2002-08-04 20:26:36 +0000
commit73df45641820f6514a0b540b80ad54299e410ac9 (patch)
tree77a20e2bfcbbad709fd461c2a4288a179a30fbeb /demos/gtk-demo/changedisplay.c
parent8c46927bf35e70106e90fa9308bfb5c8c1de764d (diff)
downloadgtk+-73df45641820f6514a0b540b80ad54299e410ac9.tar.gz
Missed file:
Thu Aug 1 19:09:39 2002 Owen Taylor <otaylor@redhat.com> * demos/gtk-demo/changedisplay.c: New demo for window migration between different displays and screens.
Diffstat (limited to 'demos/gtk-demo/changedisplay.c')
-rw-r--r--demos/gtk-demo/changedisplay.c627
1 files changed, 627 insertions, 0 deletions
diff --git a/demos/gtk-demo/changedisplay.c b/demos/gtk-demo/changedisplay.c
new file mode 100644
index 0000000000..c6a0fde23c
--- /dev/null
+++ b/demos/gtk-demo/changedisplay.c
@@ -0,0 +1,627 @@
+/* Change Display
+ *
+ * Demonstrates migrating a window between different displays and
+ * screens. A display is a mouse and keyboard with some number of
+ * associated monitors. A screen is a set of monitors grouped
+ * into a single physical work area. The neat thing about having
+ * multiple displays is that they can be on a completely separate
+ * computers, as long as there is a network connection to the
+ * computer where the application is running.
+ *
+ * Only some of the windowing systems where GTK+ runs have the
+ * concept of multiple displays and screens. (The X Window System
+ * is the main example.) Other windowing systems can only
+ * handle one keyboard and mouse, and combine all monitors into
+ * a single screen.
+ *
+ * This is a moderately complex example, and demonstrates:
+ *
+ * - Tracking the currently open displays and screens
+ *
+ * - Changing the screen for a window
+ *
+ * - Letting the user choose a window by clicking on it
+ *
+ * - Using GtkListStore and GtkTreeView
+ *
+ * - Using GtkDialog
+ */
+#include <string.h>
+#include <gtk/gtk.h>
+#include "demo-common.h"
+
+/* The ChangeDisplayInfo structure corresponds to a toplevel window and
+ * holds pointers to widgets inside the toplevel window along with other
+ * information about the contents of the window.
+ * This is a common organizational structure in real applications.
+ */
+typedef struct _ChangeDisplayInfo ChangeDisplayInfo;
+
+struct _ChangeDisplayInfo
+{
+ GtkWidget *window;
+ GtkSizeGroup *size_group;
+
+ GtkTreeModel *display_model;
+ GtkTreeModel *screen_model;
+ GtkTreeSelection *screen_selection;
+
+ GdkDisplay *current_display;
+ GdkScreen *current_screen;
+};
+
+/* These enumerations provide symbolic names for the columns
+ * in the two GtkListStore models.
+ */
+enum
+{
+ DISPLAY_COLUMN_NAME,
+ DISPLAY_COLUMN_DISPLAY,
+ DISPLAY_NUM_COLUMNS
+};
+
+enum
+{
+ SCREEN_COLUMN_NUMBER,
+ SCREEN_COLUMN_SCREEN,
+ SCREEN_NUM_COLUMNS
+};
+
+/* Finds the toplevel window under the mouse pointer, if any.
+ */
+static GtkWidget *
+find_toplevel_at_pointer (GdkScreen *screen)
+{
+ GdkWindow *pointer_window;
+ GtkWidget *widget = NULL;
+
+ pointer_window = gdk_screen_get_window_at_pointer (screen, NULL, NULL);
+
+ /* The user data field of a GdkWindow is used to store a pointer
+ * to the widget that created it.
+ */
+ if (pointer_window)
+ gdk_window_get_user_data (pointer_window, (gpointer*) &widget);
+
+ return widget ? gtk_widget_get_toplevel (widget) : NULL;
+}
+
+static gboolean
+button_release_event_cb (GtkWidget *widget,
+ GdkEventButton *event,
+ gboolean *clicked)
+{
+ *clicked = TRUE;
+ return TRUE;
+}
+
+/* Asks the user to click on a window, then waits for them click
+ * the mouse. When the mouse is released, returns the toplevel
+ * window under the pointer, or NULL, if there is none.
+ */
+static GtkWidget *
+query_for_toplevel (GdkScreen *screen,
+ const char *prompt)
+{
+ GdkDisplay *display = gdk_screen_get_display (screen);
+ GtkWidget *popup, *label, *frame;
+ GdkCursor *cursor;
+ GtkWidget *toplevel = NULL;
+
+ popup = gtk_window_new (GTK_WINDOW_POPUP);
+ gtk_window_set_screen (GTK_WINDOW (popup), screen);
+ gtk_window_set_modal (GTK_WINDOW (popup), TRUE);
+ gtk_window_set_position (GTK_WINDOW (popup), GTK_WIN_POS_CENTER);
+
+ frame = gtk_frame_new (NULL);
+ gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
+ gtk_container_add (GTK_CONTAINER (popup), frame);
+
+ label = gtk_label_new (prompt);
+ gtk_misc_set_padding (GTK_MISC (label), 10, 10);
+ gtk_container_add (GTK_CONTAINER (frame), label);
+
+ gtk_widget_show_all (popup);
+ cursor = gdk_cursor_new_for_display (display, GDK_CROSSHAIR);
+
+ if (gdk_pointer_grab (popup->window, FALSE,
+ GDK_BUTTON_RELEASE_MASK,
+ NULL,
+ cursor,
+ GDK_CURRENT_TIME) == GDK_GRAB_SUCCESS)
+ {
+ gboolean clicked = FALSE;
+
+ g_signal_connect (popup, "button-release-event",
+ G_CALLBACK (button_release_event_cb), &clicked);
+
+ /* Process events until clicked is set by button_release_event_cb.
+ * We pass in may_block=TRUE since we want to wait if there
+ * are no events currently.
+ */
+ while (!clicked)
+ g_main_context_iteration (NULL, TRUE);
+
+ toplevel = find_toplevel_at_pointer (screen);
+ if (toplevel == popup)
+ toplevel = NULL;
+ }
+
+ gdk_cursor_unref (cursor);
+ gtk_widget_destroy (popup);
+ gdk_flush (); /* Really release the grab */
+
+ return toplevel;
+}
+
+/* Prompts the user for a toplevel window to move, and then moves
+ * that window to the currently selected display
+ */
+static void
+query_change_display (ChangeDisplayInfo *info)
+{
+ GdkScreen *screen = gtk_widget_get_screen (info->window);
+ GtkWidget *toplevel;
+
+ toplevel = query_for_toplevel (screen,
+ "Please select the toplevel\n"
+ "to move to the new screen");
+
+ if (toplevel)
+ gtk_window_set_screen (GTK_WINDOW (toplevel), info->current_screen);
+ else
+ gdk_display_beep (gdk_screen_get_display (screen));
+}
+
+/* Fills in the screen list based on the current display
+ */
+static void
+fill_screens (ChangeDisplayInfo *info)
+{
+ gtk_list_store_clear (GTK_LIST_STORE (info->screen_model));
+
+ if (info->current_display)
+ {
+ gint n_screens = gdk_display_get_n_screens (info->current_display);
+ gint i;
+
+ for (i = 0; i < n_screens; i++)
+ {
+ GdkScreen *screen = gdk_display_get_screen (info->current_display, i);
+ GtkTreeIter iter;
+
+ gtk_list_store_append (GTK_LIST_STORE (info->screen_model), &iter);
+ gtk_list_store_set (GTK_LIST_STORE (info->screen_model), &iter,
+ SCREEN_COLUMN_NUMBER, i,
+ SCREEN_COLUMN_SCREEN, screen,
+ -1);
+
+ if (i == 0)
+ gtk_tree_selection_select_iter (info->screen_selection, &iter);
+ }
+ }
+}
+
+/* Called when the user clicks on a button in our dialog or
+ * closes the dialog through the window manager. Unless the
+ * "Change" button was clicked, we destroy the dialog.
+ */
+static void
+response_cb (GtkDialog *dialog,
+ gint response_id,
+ ChangeDisplayInfo *info)
+{
+ if (response_id == GTK_RESPONSE_OK)
+ query_change_display (info);
+ else
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+/* Called when the user clicks on "Open..." in the display
+ * frame. Prompts for a new display, and then opens a connection
+ * to that display.
+ */
+static void
+open_display_cb (GtkWidget *button,
+ ChangeDisplayInfo *info)
+{
+ GtkWidget *dialog;
+ GtkWidget *display_entry;
+ GtkWidget *dialog_label;
+ gchar *new_screen_name = NULL;
+ GdkDisplay *result = NULL;
+
+ dialog = gtk_dialog_new_with_buttons ("Open Display",
+ GTK_WINDOW (info->window),
+ GTK_DIALOG_MODAL,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OK, GTK_RESPONSE_OK,
+ NULL);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+ display_entry = gtk_entry_new ();
+ gtk_entry_set_activates_default (GTK_ENTRY (display_entry), TRUE);
+ dialog_label =
+ gtk_label_new ("Please enter the name of\nthe new display\n");
+
+ gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), dialog_label);
+ gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), display_entry);
+
+ gtk_widget_grab_focus (display_entry);
+ gtk_widget_show_all (GTK_BIN (dialog)->child);
+
+ while (!result)
+ {
+ gint response_id = gtk_dialog_run (GTK_DIALOG (dialog));
+ if (response_id != GTK_RESPONSE_OK)
+ break;
+
+ new_screen_name = gtk_editable_get_chars (GTK_EDITABLE (display_entry),
+ 0, -1);
+
+ if (strcmp (new_screen_name, "") != 0)
+ {
+ result = gdk_display_open (new_screen_name);
+ if (!result)
+ {
+ gchar *error_msg =
+ g_strdup_printf ("Can't open display :\n\t%s\nplease try another one\n",
+ new_screen_name);
+ gtk_label_set_text (GTK_LABEL (dialog_label), error_msg);
+ g_free (error_msg);
+ }
+
+ g_free (new_screen_name);
+ }
+ }
+
+ gtk_widget_destroy (dialog);
+}
+
+/* Called when the user clicks on the "Close" button in the
+ * "Display" frame. Closes the selected display.
+ */
+static void
+close_display_cb (GtkWidget *button,
+ ChangeDisplayInfo *info)
+{
+ if (info->current_display)
+ gdk_display_close (info->current_display);
+}
+
+/* Called when the selected row in the display list changes.
+ * Updates info->current_display, then refills the list of
+ * screens.
+ */
+static void
+display_changed_cb (GtkTreeSelection *selection,
+ ChangeDisplayInfo *info)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+
+ if (gtk_tree_selection_get_selected (selection, &model, &iter))
+ gtk_tree_model_get (model, &iter,
+ DISPLAY_COLUMN_DISPLAY, &info->current_display,
+ -1);
+ else
+ info->current_display = NULL;
+
+ fill_screens (info);
+}
+
+/* Called when the selected row in the sceen list changes.
+ * Updates info->current_screen.
+ */
+static void
+screen_changed_cb (GtkTreeSelection *selection,
+ ChangeDisplayInfo *info)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+
+ if (gtk_tree_selection_get_selected (selection, &model, &iter))
+ gtk_tree_model_get (model, &iter,
+ SCREEN_COLUMN_SCREEN, &info->current_screen,
+ -1);
+ else
+ info->current_screen = NULL;
+}
+
+/* This function is used both for creating the "Display" and
+ * "Screen" frames, since they have a similar structure. The
+ * caller hooks up the right context for the value returned
+ * in tree_view, and packs any relevant buttons into button_vbox.
+ */
+static void
+create_frame (ChangeDisplayInfo *info,
+ const char *title,
+ GtkWidget **frame,
+ GtkWidget **tree_view,
+ GtkWidget **button_vbox)
+{
+ GtkTreeSelection *selection;
+ GtkWidget *scrollwin;
+ GtkWidget *hbox;
+
+ *frame = gtk_frame_new (title);
+
+ hbox = gtk_hbox_new (FALSE, 8);
+ gtk_container_set_border_width (GTK_CONTAINER (hbox), 8);
+ gtk_container_add (GTK_CONTAINER (*frame), hbox);
+
+ scrollwin = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrollwin),
+ GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrollwin),
+ GTK_SHADOW_IN);
+ gtk_box_pack_start (GTK_BOX (hbox), scrollwin, TRUE, TRUE, 0);
+
+ *tree_view = gtk_tree_view_new ();
+ gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (*tree_view), FALSE);
+ gtk_container_add (GTK_CONTAINER (scrollwin), *tree_view);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (*tree_view));
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
+
+ *button_vbox = gtk_vbox_new (FALSE, 5);
+ gtk_box_pack_start (GTK_BOX (hbox), *button_vbox, FALSE, FALSE, 0);
+
+ if (!info->size_group)
+ info->size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+
+ gtk_size_group_add_widget (GTK_SIZE_GROUP (info->size_group), *button_vbox);
+}
+
+/* If we have a stack of buttons, it often looks better if their contents
+ * are left-aligned, rather than centered. This function creates a button
+ * and left-aligns it contents.
+ */
+GtkWidget *
+left_align_button_new (const char *label)
+{
+ GtkWidget *button = gtk_button_new_with_mnemonic (label);
+ GtkWidget *child = gtk_bin_get_child (GTK_BIN (button));
+
+ gtk_misc_set_alignment (GTK_MISC (child), 0., 0.5);
+
+ return button;
+}
+
+/* Creates the "Display" frame in the main window.
+ */
+GtkWidget *
+create_display_frame (ChangeDisplayInfo *info)
+{
+ GtkWidget *frame;
+ GtkWidget *tree_view;
+ GtkWidget *button_vbox;
+ GtkTreeViewColumn *column;
+ GtkTreeSelection *selection;
+ GtkWidget *button;
+
+ create_frame (info, "Display", &frame, &tree_view, &button_vbox);
+
+ button = left_align_button_new ("_Open...");
+ g_signal_connect (button, "clicked", G_CALLBACK (open_display_cb), info);
+ gtk_box_pack_start (GTK_BOX (button_vbox), button, FALSE, FALSE, 0);
+
+ button = left_align_button_new ("_Close");
+ g_signal_connect (button, "clicked", G_CALLBACK (close_display_cb), info);
+ gtk_box_pack_start (GTK_BOX (button_vbox), button, FALSE, FALSE, 0);
+
+ info->display_model = (GtkTreeModel *)gtk_list_store_new (DISPLAY_NUM_COLUMNS,
+ G_TYPE_STRING,
+ GDK_TYPE_DISPLAY);
+
+ gtk_tree_view_set_model (GTK_TREE_VIEW (tree_view), info->display_model);
+
+ column = gtk_tree_view_column_new_with_attributes ("Name",
+ gtk_cell_renderer_text_new (),
+ "text", DISPLAY_COLUMN_NAME,
+ NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view));
+ g_signal_connect (selection, "changed",
+ G_CALLBACK (display_changed_cb), info);
+
+ return frame;
+}
+
+/* Creates the "Screen" frame in the main window.
+ */
+GtkWidget *
+create_screen_frame (ChangeDisplayInfo *info)
+{
+ GtkWidget *frame;
+ GtkWidget *tree_view;
+ GtkWidget *button_vbox;
+ GtkTreeViewColumn *column;
+
+ create_frame (info, "Screen", &frame, &tree_view, &button_vbox);
+
+ info->screen_model = (GtkTreeModel *)gtk_list_store_new (SCREEN_NUM_COLUMNS,
+ G_TYPE_INT,
+ GDK_TYPE_SCREEN);
+
+ gtk_tree_view_set_model (GTK_TREE_VIEW (tree_view), info->screen_model);
+
+ column = gtk_tree_view_column_new_with_attributes ("Number",
+ gtk_cell_renderer_text_new (),
+ "text", SCREEN_COLUMN_NUMBER,
+ NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column);
+
+ info->screen_selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view));
+ g_signal_connect (info->screen_selection, "changed",
+ G_CALLBACK (screen_changed_cb), info);
+
+ return frame;
+}
+
+/* Called when one of the currently open displays is closed.
+ * Remove it from our list of displays.
+ */
+static void
+display_closed_cb (GdkDisplay *display,
+ gboolean is_error,
+ ChangeDisplayInfo *info)
+{
+ GtkTreeIter iter;
+ gboolean valid;
+
+ for (valid = gtk_tree_model_get_iter_first (info->display_model, &iter);
+ valid;
+ valid = gtk_tree_model_iter_next (info->display_model, &iter))
+ {
+ GdkDisplay *tmp_display;
+
+ gtk_tree_model_get (info->display_model, &iter,
+ DISPLAY_COLUMN_DISPLAY, &tmp_display,
+ -1);
+ if (tmp_display == display)
+ {
+ gtk_list_store_remove (GTK_LIST_STORE (info->display_model), &iter);
+ break;
+ }
+ }
+}
+
+/* Adds a new display to our list of displays, and connects
+ * to the "closed" signal so that we can remove it from the
+ * list of displays again.
+ */
+static void
+add_display (ChangeDisplayInfo *info,
+ GdkDisplay *display)
+{
+ const gchar *name = gdk_display_get_name (display);
+ GtkTreeIter iter;
+
+ gtk_list_store_append (GTK_LIST_STORE (info->display_model), &iter);
+ gtk_list_store_set (GTK_LIST_STORE (info->display_model), &iter,
+ DISPLAY_COLUMN_NAME, name,
+ DISPLAY_COLUMN_DISPLAY, display,
+ -1);
+
+ g_signal_connect (display, "closed",
+ G_CALLBACK (display_closed_cb), info);
+}
+
+/* Called when a new display is opened
+ */
+static void
+display_opened_cb (GdkDisplayManager *manager,
+ GdkDisplay *display,
+ ChangeDisplayInfo *info)
+{
+ add_display (info, display);
+}
+
+/* Adds all currently open displays to our list of displays,
+ * and set up a signal connection so that we'll be notified
+ * when displays are opened in the future as well.
+ */
+static void
+initialize_displays (ChangeDisplayInfo *info)
+{
+ GdkDisplayManager *manager = gdk_display_manager_get ();
+ GSList *displays = gdk_display_manager_list_displays (manager);
+ GSList *tmp_list;
+
+ for (tmp_list = displays; tmp_list; tmp_list = tmp_list->next)
+ add_display (info, tmp_list->data);
+
+ g_slist_free (tmp_list);
+
+ g_signal_connect (manager, "display_opened",
+ G_CALLBACK (display_opened_cb), info);
+}
+
+/* Cleans up when the toplevel is destroyed; we remove the
+ * connections we use to track currently open displays, then
+ * free the ChangeDisplayInfo structure.
+ */
+static void
+destroy_info (ChangeDisplayInfo *info)
+{
+ GdkDisplayManager *manager = gdk_display_manager_get ();
+ GSList *displays = gdk_display_manager_list_displays (manager);
+ GSList *tmp_list;
+
+ g_signal_handlers_disconnect_by_func (manager,
+ (gpointer) display_opened_cb,
+ info);
+
+ for (tmp_list = displays; tmp_list; tmp_list = tmp_list->next)
+ g_signal_handlers_disconnect_by_func (tmp_list->data,
+ (gpointer) display_closed_cb,
+ info);
+
+ g_slist_free (tmp_list);
+
+ g_object_unref (info->size_group);
+ g_free (info);
+}
+
+static void
+destroy_cb (GtkObject *object,
+ ChangeDisplayInfo **info)
+{
+ destroy_info (*info);
+ *info = NULL;
+}
+
+/* Main entry point. If the dialog for this demo doesn't yet exist, creates
+ * it. Otherwise, destroys it.
+ */
+GtkWidget *
+do_changedisplay (void)
+{
+ static ChangeDisplayInfo *info = NULL;
+
+ if (!info)
+ {
+ GtkWidget *vbox;
+ GtkWidget *frame;
+
+ info = g_new0 (ChangeDisplayInfo, 1);
+
+ info->window = gtk_dialog_new_with_buttons ("Change Screen or display",
+ NULL, /* parent */
+ GTK_DIALOG_NO_SEPARATOR,
+ GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
+ "Change", GTK_RESPONSE_OK,
+ NULL);
+
+ gtk_window_set_default_size (GTK_WINDOW (info->window), 300, 400);
+
+ g_signal_connect (info->window, "response",
+ G_CALLBACK (response_cb), info);
+ g_signal_connect (info->window, "destroy",
+ G_CALLBACK (destroy_cb), &info);
+
+ vbox = gtk_vbox_new (FALSE, 5);
+ gtk_container_set_border_width (GTK_CONTAINER (vbox), 8);
+
+ gtk_box_pack_start (GTK_BOX (GTK_DIALOG (info->window)->vbox), vbox,
+ TRUE, TRUE, 0);
+
+ frame = create_display_frame (info);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0);
+
+ frame = create_screen_frame (info);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0);
+
+ initialize_displays (info);
+
+ gtk_widget_show_all (info->window);
+ return info->window;
+ }
+ else
+ {
+ gtk_widget_destroy (info->window);
+ return NULL;
+ }
+}