]> Pileus Git - ~andy/gtk/blob - demos/gtk-demo/changedisplay.c
a3b523c987e81ecb3ad259dbb899338232e65c46
[~andy/gtk] / demos / gtk-demo / changedisplay.c
1 /* Change Display
2  *
3  * Demonstrates migrating a window between different displays and
4  * screens. A display is a mouse and keyboard with some number of
5  * associated monitors. A screen is a set of monitors grouped
6  * into a single physical work area. The neat thing about having
7  * multiple displays is that they can be on a completely separate
8  * computers, as long as there is a network connection to the
9  * computer where the application is running.
10  *
11  * Only some of the windowing systems where GTK+ runs have the
12  * concept of multiple displays and screens. (The X Window System
13  * is the main example.) Other windowing systems can only
14  * handle one keyboard and mouse, and combine all monitors into
15  * a single screen.
16  *
17  * This is a moderately complex example, and demonstrates:
18  *
19  *  - Tracking the currently open displays and screens
20  *
21  *  - Changing the screen for a window
22  *
23  *  - Letting the user choose a window by clicking on it
24  *
25  *  - Using GtkListStore and GtkTreeView
26  *
27  *  - Using GtkDialog
28  */
29 #include <string.h>
30 #include <gtk/gtk.h>
31 #include "demo-common.h"
32
33 /* The ChangeDisplayInfo structure corresponds to a toplevel window and
34  * holds pointers to widgets inside the toplevel window along with other
35  * information about the contents of the window.
36  * This is a common organizational structure in real applications.
37  */
38 typedef struct _ChangeDisplayInfo ChangeDisplayInfo;
39
40 struct _ChangeDisplayInfo
41 {
42   GtkWidget *window;
43   GtkSizeGroup *size_group;
44
45   GtkTreeModel *display_model;
46   GtkTreeModel *screen_model;
47   GtkTreeSelection *screen_selection;
48
49   GdkDisplay *current_display;
50   GdkScreen *current_screen;
51 };
52
53 /* These enumerations provide symbolic names for the columns
54  * in the two GtkListStore models.
55  */
56 enum
57 {
58   DISPLAY_COLUMN_NAME,
59   DISPLAY_COLUMN_DISPLAY,
60   DISPLAY_NUM_COLUMNS
61 };
62
63 enum
64 {
65   SCREEN_COLUMN_NUMBER,
66   SCREEN_COLUMN_SCREEN,
67   SCREEN_NUM_COLUMNS
68 };
69
70 /* Finds the toplevel window under the mouse pointer, if any.
71  */
72 static GtkWidget *
73 find_toplevel_at_pointer (GdkDisplay *display)
74 {
75   GdkWindow *pointer_window;
76   GtkWidget *widget = NULL;
77
78   pointer_window = gdk_display_get_window_at_pointer (display, NULL, NULL);
79
80   /* The user data field of a GdkWindow is used to store a pointer
81    * to the widget that created it.
82    */
83   if (pointer_window)
84     {
85       gpointer widget_ptr;
86       gdk_window_get_user_data (pointer_window, &widget_ptr);
87       widget = widget_ptr;
88     }
89
90   return widget ? gtk_widget_get_toplevel (widget) : NULL;
91 }
92
93 static gboolean
94 button_release_event_cb (GtkWidget       *widget,
95                          GdkEventButton  *event,
96                          gboolean        *clicked)
97 {
98   *clicked = TRUE;
99   return TRUE;
100 }
101
102 /* Asks the user to click on a window, then waits for them click
103  * the mouse. When the mouse is released, returns the toplevel
104  * window under the pointer, or NULL, if there is none.
105  */
106 static GtkWidget *
107 query_for_toplevel (GdkScreen  *screen,
108                     const char *prompt)
109 {
110   GdkDisplay *display = gdk_screen_get_display (screen);
111   GtkWidget *popup, *label, *frame;
112   GdkCursor *cursor;
113   GtkWidget *toplevel = NULL;
114
115   popup = gtk_window_new (GTK_WINDOW_POPUP);
116   gtk_window_set_screen (GTK_WINDOW (popup), screen);
117   gtk_window_set_modal (GTK_WINDOW (popup), TRUE);
118   gtk_window_set_position (GTK_WINDOW (popup), GTK_WIN_POS_CENTER);
119
120   frame = gtk_frame_new (NULL);
121   gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
122   gtk_container_add (GTK_CONTAINER (popup), frame);
123
124   label = gtk_label_new (prompt);
125   gtk_misc_set_padding (GTK_MISC (label), 10, 10);
126   gtk_container_add (GTK_CONTAINER (frame), label);
127
128   gtk_widget_show_all (popup);
129   cursor = gdk_cursor_new_for_display (display, GDK_CROSSHAIR);
130
131   if (gdk_pointer_grab (gtk_widget_get_window (popup), FALSE,
132                         GDK_BUTTON_RELEASE_MASK,
133                         NULL,
134                         cursor,
135                         GDK_CURRENT_TIME) == GDK_GRAB_SUCCESS)
136     {
137       gboolean clicked = FALSE;
138
139       g_signal_connect (popup, "button-release-event",
140                         G_CALLBACK (button_release_event_cb), &clicked);
141
142       /* Process events until clicked is set by button_release_event_cb.
143        * We pass in may_block=TRUE since we want to wait if there
144        * are no events currently.
145        */
146       while (!clicked)
147         g_main_context_iteration (NULL, TRUE);
148
149       toplevel = find_toplevel_at_pointer (gdk_screen_get_display (screen));
150       if (toplevel == popup)
151         toplevel = NULL;
152     }
153
154   gdk_cursor_unref (cursor);
155   gtk_widget_destroy (popup);
156   gdk_flush ();                 /* Really release the grab */
157
158   return toplevel;
159 }
160
161 /* Prompts the user for a toplevel window to move, and then moves
162  * that window to the currently selected display
163  */
164 static void
165 query_change_display (ChangeDisplayInfo *info)
166 {
167   GdkScreen *screen = gtk_widget_get_screen (info->window);
168   GtkWidget *toplevel;
169
170   toplevel = query_for_toplevel (screen,
171                                  "Please select the toplevel\n"
172                                  "to move to the new screen");
173
174   if (toplevel)
175     gtk_window_set_screen (GTK_WINDOW (toplevel), info->current_screen);
176   else
177     gdk_display_beep (gdk_screen_get_display (screen));
178 }
179
180 /* Fills in the screen list based on the current display
181  */
182 static void
183 fill_screens (ChangeDisplayInfo *info)
184 {
185   gtk_list_store_clear (GTK_LIST_STORE (info->screen_model));
186
187   if (info->current_display)
188     {
189       gint n_screens = gdk_display_get_n_screens (info->current_display);
190       gint i;
191
192       for (i = 0; i < n_screens; i++)
193         {
194           GdkScreen *screen = gdk_display_get_screen (info->current_display, i);
195           GtkTreeIter iter;
196
197           gtk_list_store_append (GTK_LIST_STORE (info->screen_model), &iter);
198           gtk_list_store_set (GTK_LIST_STORE (info->screen_model), &iter,
199                               SCREEN_COLUMN_NUMBER, i,
200                               SCREEN_COLUMN_SCREEN, screen,
201                               -1);
202
203           if (i == 0)
204             gtk_tree_selection_select_iter (info->screen_selection, &iter);
205         }
206     }
207 }
208
209 /* Called when the user clicks on a button in our dialog or
210  * closes the dialog through the window manager. Unless the
211  * "Change" button was clicked, we destroy the dialog.
212  */
213 static void
214 response_cb (GtkDialog         *dialog,
215              gint               response_id,
216              ChangeDisplayInfo *info)
217 {
218   if (response_id == GTK_RESPONSE_OK)
219     query_change_display (info);
220   else
221     gtk_widget_destroy (GTK_WIDGET (dialog));
222 }
223
224 /* Called when the user clicks on "Open..." in the display
225  * frame. Prompts for a new display, and then opens a connection
226  * to that display.
227  */
228 static void
229 open_display_cb (GtkWidget         *button,
230                  ChangeDisplayInfo *info)
231 {
232   GtkWidget *content_area;
233   GtkWidget *dialog;
234   GtkWidget *display_entry;
235   GtkWidget *dialog_label;
236   gchar *new_screen_name = NULL;
237   GdkDisplay *result = NULL;
238
239   dialog = gtk_dialog_new_with_buttons ("Open Display",
240                                         GTK_WINDOW (info->window),
241                                         GTK_DIALOG_MODAL,
242                                         GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
243                                         GTK_STOCK_OK, GTK_RESPONSE_OK,
244                                         NULL);
245
246   gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
247   display_entry = gtk_entry_new ();
248   gtk_entry_set_activates_default (GTK_ENTRY (display_entry), TRUE);
249   dialog_label =
250     gtk_label_new ("Please enter the name of\nthe new display\n");
251
252   content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
253
254   gtk_container_add (GTK_CONTAINER (content_area), dialog_label);
255   gtk_container_add (GTK_CONTAINER (content_area), display_entry);
256
257   gtk_widget_grab_focus (display_entry);
258   gtk_widget_show_all (gtk_bin_get_child (GTK_BIN (dialog)));
259
260   while (!result)
261     {
262       gint response_id = gtk_dialog_run (GTK_DIALOG (dialog));
263       if (response_id != GTK_RESPONSE_OK)
264         break;
265
266       new_screen_name = gtk_editable_get_chars (GTK_EDITABLE (display_entry),
267                                                 0, -1);
268
269       if (strcmp (new_screen_name, "") != 0)
270         {
271           result = gdk_display_open (new_screen_name);
272           if (!result)
273             {
274               gchar *error_msg =
275                 g_strdup_printf  ("Can't open display :\n\t%s\nplease try another one\n",
276                                   new_screen_name);
277               gtk_label_set_text (GTK_LABEL (dialog_label), error_msg);
278               g_free (error_msg);
279             }
280
281           g_free (new_screen_name);
282         }
283     }
284
285   gtk_widget_destroy (dialog);
286 }
287
288 /* Called when the user clicks on the "Close" button in the
289  * "Display" frame. Closes the selected display.
290  */
291 static void
292 close_display_cb (GtkWidget         *button,
293                   ChangeDisplayInfo *info)
294 {
295   if (info->current_display)
296     gdk_display_close (info->current_display);
297 }
298
299 /* Called when the selected row in the display list changes.
300  * Updates info->current_display, then refills the list of
301  * screens.
302  */
303 static void
304 display_changed_cb (GtkTreeSelection  *selection,
305                     ChangeDisplayInfo *info)
306 {
307   GtkTreeModel *model;
308   GtkTreeIter iter;
309
310   if (info->current_display)
311     g_object_unref (info->current_display);
312   if (gtk_tree_selection_get_selected (selection, &model, &iter))
313     gtk_tree_model_get (model, &iter,
314                         DISPLAY_COLUMN_DISPLAY, &info->current_display,
315                         -1);
316   else
317     info->current_display = NULL;
318
319   fill_screens (info);
320 }
321
322 /* Called when the selected row in the sceen list changes.
323  * Updates info->current_screen.
324  */
325 static void
326 screen_changed_cb (GtkTreeSelection  *selection,
327                    ChangeDisplayInfo *info)
328 {
329   GtkTreeModel *model;
330   GtkTreeIter iter;
331
332   if (info->current_screen)
333     g_object_unref (info->current_screen);
334   if (gtk_tree_selection_get_selected (selection, &model, &iter))
335     gtk_tree_model_get (model, &iter,
336                         SCREEN_COLUMN_SCREEN, &info->current_screen,
337                         -1);
338   else
339     info->current_screen = NULL;
340 }
341
342 /* This function is used both for creating the "Display" and
343  * "Screen" frames, since they have a similar structure. The
344  * caller hooks up the right context for the value returned
345  * in tree_view, and packs any relevant buttons into button_vbox.
346  */
347 static void
348 create_frame (ChangeDisplayInfo *info,
349               const char        *title,
350               GtkWidget        **frame,
351               GtkWidget        **tree_view,
352               GtkWidget        **button_vbox)
353 {
354   GtkTreeSelection *selection;
355   GtkWidget *scrollwin;
356   GtkWidget *hbox;
357
358   *frame = gtk_frame_new (title);
359
360   hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 8);
361   gtk_container_set_border_width (GTK_CONTAINER (hbox), 8);
362   gtk_container_add (GTK_CONTAINER (*frame), hbox);
363
364   scrollwin = gtk_scrolled_window_new (NULL, NULL);
365   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrollwin),
366                                   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
367   gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrollwin),
368                                        GTK_SHADOW_IN);
369   gtk_box_pack_start (GTK_BOX (hbox), scrollwin, TRUE, TRUE, 0);
370
371   *tree_view = gtk_tree_view_new ();
372   gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (*tree_view), FALSE);
373   gtk_container_add (GTK_CONTAINER (scrollwin), *tree_view);
374
375   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (*tree_view));
376   gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
377
378   *button_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, FALSE, 5);
379   gtk_box_pack_start (GTK_BOX (hbox), *button_vbox, FALSE, FALSE, 0);
380
381   if (!info->size_group)
382     info->size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
383
384   gtk_size_group_add_widget (GTK_SIZE_GROUP (info->size_group), *button_vbox);
385 }
386
387 /* If we have a stack of buttons, it often looks better if their contents
388  * are left-aligned, rather than centered. This function creates a button
389  * and left-aligns it contents.
390  */
391 GtkWidget *
392 left_align_button_new (const char *label)
393 {
394   GtkWidget *button = gtk_button_new_with_mnemonic (label);
395   GtkWidget *child = gtk_bin_get_child (GTK_BIN (button));
396
397   gtk_misc_set_alignment (GTK_MISC (child), 0., 0.5);
398
399   return button;
400 }
401
402 /* Creates the "Display" frame in the main window.
403  */
404 GtkWidget *
405 create_display_frame (ChangeDisplayInfo *info)
406 {
407   GtkWidget *frame;
408   GtkWidget *tree_view;
409   GtkWidget *button_vbox;
410   GtkTreeViewColumn *column;
411   GtkTreeSelection *selection;
412   GtkWidget *button;
413
414   create_frame (info, "Display", &frame, &tree_view, &button_vbox);
415
416   button = left_align_button_new ("_Open...");
417   g_signal_connect (button, "clicked",  G_CALLBACK (open_display_cb), info);
418   gtk_box_pack_start (GTK_BOX (button_vbox), button, FALSE, FALSE, 0);
419
420   button = left_align_button_new ("_Close");
421   g_signal_connect (button, "clicked",  G_CALLBACK (close_display_cb), info);
422   gtk_box_pack_start (GTK_BOX (button_vbox), button, FALSE, FALSE, 0);
423
424   info->display_model = (GtkTreeModel *)gtk_list_store_new (DISPLAY_NUM_COLUMNS,
425                                                             G_TYPE_STRING,
426                                                             GDK_TYPE_DISPLAY);
427
428   gtk_tree_view_set_model (GTK_TREE_VIEW (tree_view), info->display_model);
429
430   column = gtk_tree_view_column_new_with_attributes ("Name",
431                                                      gtk_cell_renderer_text_new (),
432                                                      "text", DISPLAY_COLUMN_NAME,
433                                                      NULL);
434   gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column);
435
436   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view));
437   g_signal_connect (selection, "changed",
438                     G_CALLBACK (display_changed_cb), info);
439
440   return frame;
441 }
442
443 /* Creates the "Screen" frame in the main window.
444  */
445 GtkWidget *
446 create_screen_frame (ChangeDisplayInfo *info)
447 {
448   GtkWidget *frame;
449   GtkWidget *tree_view;
450   GtkWidget *button_vbox;
451   GtkTreeViewColumn *column;
452
453   create_frame (info, "Screen", &frame, &tree_view, &button_vbox);
454
455   info->screen_model = (GtkTreeModel *)gtk_list_store_new (SCREEN_NUM_COLUMNS,
456                                                            G_TYPE_INT,
457                                                            GDK_TYPE_SCREEN);
458
459   gtk_tree_view_set_model (GTK_TREE_VIEW (tree_view), info->screen_model);
460
461   column = gtk_tree_view_column_new_with_attributes ("Number",
462                                                      gtk_cell_renderer_text_new (),
463                                                      "text", SCREEN_COLUMN_NUMBER,
464                                                      NULL);
465   gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column);
466
467   info->screen_selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view));
468   g_signal_connect (info->screen_selection, "changed",
469                     G_CALLBACK (screen_changed_cb), info);
470
471   return frame;
472 }
473
474 /* Called when one of the currently open displays is closed.
475  * Remove it from our list of displays.
476  */
477 static void
478 display_closed_cb (GdkDisplay        *display,
479                    gboolean           is_error,
480                    ChangeDisplayInfo *info)
481 {
482   GtkTreeIter iter;
483   gboolean valid;
484
485   for (valid = gtk_tree_model_get_iter_first (info->display_model, &iter);
486        valid;
487        valid = gtk_tree_model_iter_next (info->display_model, &iter))
488     {
489       GdkDisplay *tmp_display;
490
491       gtk_tree_model_get (info->display_model, &iter,
492                           DISPLAY_COLUMN_DISPLAY, &tmp_display,
493                           -1);
494       if (tmp_display == display)
495         {
496           gtk_list_store_remove (GTK_LIST_STORE (info->display_model), &iter);
497           break;
498         }
499     }
500 }
501
502 /* Adds a new display to our list of displays, and connects
503  * to the "closed" signal so that we can remove it from the
504  * list of displays again.
505  */
506 static void
507 add_display (ChangeDisplayInfo *info,
508              GdkDisplay        *display)
509 {
510   const gchar *name = gdk_display_get_name (display);
511   GtkTreeIter iter;
512
513   gtk_list_store_append (GTK_LIST_STORE (info->display_model), &iter);
514   gtk_list_store_set (GTK_LIST_STORE (info->display_model), &iter,
515                       DISPLAY_COLUMN_NAME, name,
516                       DISPLAY_COLUMN_DISPLAY, display,
517                       -1);
518
519   g_signal_connect (display, "closed",
520                     G_CALLBACK (display_closed_cb), info);
521 }
522
523 /* Called when a new display is opened
524  */
525 static void
526 display_opened_cb (GdkDisplayManager *manager,
527                    GdkDisplay        *display,
528                    ChangeDisplayInfo *info)
529 {
530   add_display (info, display);
531 }
532
533 /* Adds all currently open displays to our list of displays,
534  * and set up a signal connection so that we'll be notified
535  * when displays are opened in the future as well.
536  */
537 static void
538 initialize_displays (ChangeDisplayInfo *info)
539 {
540   GdkDisplayManager *manager = gdk_display_manager_get ();
541   GSList *displays = gdk_display_manager_list_displays (manager);
542   GSList *tmp_list;
543
544   for (tmp_list = displays; tmp_list; tmp_list = tmp_list->next)
545     add_display (info, tmp_list->data);
546
547   g_slist_free (tmp_list);
548
549   g_signal_connect (manager, "display-opened",
550                     G_CALLBACK (display_opened_cb), info);
551 }
552
553 /* Cleans up when the toplevel is destroyed; we remove the
554  * connections we use to track currently open displays, then
555  * free the ChangeDisplayInfo structure.
556  */
557 static void
558 destroy_info (ChangeDisplayInfo *info)
559 {
560   GdkDisplayManager *manager = gdk_display_manager_get ();
561   GSList *displays = gdk_display_manager_list_displays (manager);
562   GSList *tmp_list;
563
564   g_signal_handlers_disconnect_by_func (manager,
565                                         display_opened_cb,
566                                         info);
567
568   for (tmp_list = displays; tmp_list; tmp_list = tmp_list->next)
569     g_signal_handlers_disconnect_by_func (tmp_list->data,
570                                           display_closed_cb,
571                                           info);
572
573   g_slist_free (tmp_list);
574
575   g_object_unref (info->size_group);
576   g_object_unref (info->display_model);
577   g_object_unref (info->screen_model);
578
579   if (info->current_display)
580     g_object_unref (info->current_display);
581   if (info->current_screen)
582     g_object_unref (info->current_screen);
583
584   g_free (info);
585 }
586
587 static void
588 destroy_cb (GObject            *object,
589             ChangeDisplayInfo **info)
590 {
591   destroy_info (*info);
592   *info = NULL;
593 }
594
595 /* Main entry point. If the dialog for this demo doesn't yet exist, creates
596  * it. Otherwise, destroys it.
597  */
598 GtkWidget *
599 do_changedisplay (GtkWidget *do_widget)
600 {
601   static ChangeDisplayInfo *info = NULL;
602
603   if (!info)
604     {
605       GtkWidget *content_area;
606       GtkWidget *vbox;
607       GtkWidget *frame;
608
609       info = g_new0 (ChangeDisplayInfo, 1);
610
611       info->window = gtk_dialog_new_with_buttons ("Change Screen or display",
612                                             GTK_WINDOW (do_widget),
613                                             0,
614                                             GTK_STOCK_CLOSE,  GTK_RESPONSE_CLOSE,
615                                             "Change",         GTK_RESPONSE_OK,
616                                             NULL);
617
618       gtk_window_set_default_size (GTK_WINDOW (info->window), 300, 400);
619
620       g_signal_connect (info->window, "response",
621                         G_CALLBACK (response_cb), info);
622       g_signal_connect (info->window, "destroy",
623                         G_CALLBACK (destroy_cb), &info);
624
625       content_area = gtk_dialog_get_content_area (GTK_DIALOG (info->window));
626
627       vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, FALSE, 5);
628       gtk_container_set_border_width (GTK_CONTAINER (vbox), 8);
629       gtk_box_pack_start (GTK_BOX (content_area), vbox, TRUE, TRUE, 0);
630
631       frame = create_display_frame (info);
632       gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0);
633
634       frame = create_screen_frame (info);
635       gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0);
636
637       initialize_displays (info);
638
639       gtk_widget_show_all (info->window);
640       return info->window;
641     }
642   else
643     {
644       gtk_widget_destroy (info->window);
645       return NULL;
646     }
647 }