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