]> Pileus Git - ~andy/gtk/blob - demos/gtk-demo/changedisplay.c
Deprecation cleanup
[~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 (gtk_tree_selection_get_selected (selection, &model, &iter))
304     gtk_tree_model_get (model, &iter,
305                         DISPLAY_COLUMN_DISPLAY, &info->current_display,
306                         -1);
307   else
308     info->current_display = NULL;
309
310   fill_screens (info);
311 }
312
313 /* Called when the selected row in the sceen list changes.
314  * Updates info->current_screen.
315  */
316 static void
317 screen_changed_cb (GtkTreeSelection  *selection,
318                    ChangeDisplayInfo *info)
319 {
320   GtkTreeModel *model;
321   GtkTreeIter iter;
322
323   if (gtk_tree_selection_get_selected (selection, &model, &iter))
324     gtk_tree_model_get (model, &iter,
325                         SCREEN_COLUMN_SCREEN, &info->current_screen,
326                         -1);
327   else
328     info->current_screen = NULL;
329 }
330
331 /* This function is used both for creating the "Display" and
332  * "Screen" frames, since they have a similar structure. The
333  * caller hooks up the right context for the value returned
334  * in tree_view, and packs any relevant buttons into button_vbox.
335  */
336 static void
337 create_frame (ChangeDisplayInfo *info,
338               const char        *title,
339               GtkWidget        **frame,
340               GtkWidget        **tree_view,
341               GtkWidget        **button_vbox)
342 {
343   GtkTreeSelection *selection;
344   GtkWidget *scrollwin;
345   GtkWidget *hbox;
346   
347   *frame = gtk_frame_new (title);
348
349   hbox = gtk_hbox_new (FALSE, 8);
350   gtk_container_set_border_width (GTK_CONTAINER (hbox), 8);
351   gtk_container_add (GTK_CONTAINER (*frame), hbox);
352
353   scrollwin = gtk_scrolled_window_new (NULL, NULL);
354   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrollwin),
355                                   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
356   gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrollwin),
357                                        GTK_SHADOW_IN);
358   gtk_box_pack_start (GTK_BOX (hbox), scrollwin, TRUE, TRUE, 0);
359
360   *tree_view = gtk_tree_view_new ();
361   gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (*tree_view), FALSE);
362   gtk_container_add (GTK_CONTAINER (scrollwin), *tree_view);
363
364   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (*tree_view));
365   gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
366
367   *button_vbox = gtk_vbox_new (FALSE, 5);
368   gtk_box_pack_start (GTK_BOX (hbox), *button_vbox, FALSE, FALSE, 0);
369
370   if (!info->size_group)
371     info->size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
372   
373   gtk_size_group_add_widget (GTK_SIZE_GROUP (info->size_group), *button_vbox);
374 }
375
376 /* If we have a stack of buttons, it often looks better if their contents
377  * are left-aligned, rather than centered. This function creates a button
378  * and left-aligns it contents.
379  */
380 GtkWidget *
381 left_align_button_new (const char *label)
382 {
383   GtkWidget *button = gtk_button_new_with_mnemonic (label);
384   GtkWidget *child = gtk_bin_get_child (GTK_BIN (button));
385
386   gtk_misc_set_alignment (GTK_MISC (child), 0., 0.5);
387
388   return button;
389 }
390
391 /* Creates the "Display" frame in the main window.
392  */
393 GtkWidget *
394 create_display_frame (ChangeDisplayInfo *info)
395 {
396   GtkWidget *frame;
397   GtkWidget *tree_view;
398   GtkWidget *button_vbox;
399   GtkTreeViewColumn *column;
400   GtkTreeSelection *selection;
401   GtkWidget *button;
402
403   create_frame (info, "Display", &frame, &tree_view, &button_vbox);
404
405   button = left_align_button_new ("_Open...");
406   g_signal_connect (button, "clicked",  G_CALLBACK (open_display_cb), info);
407   gtk_box_pack_start (GTK_BOX (button_vbox), button, FALSE, FALSE, 0);
408   
409   button = left_align_button_new ("_Close");
410   g_signal_connect (button, "clicked",  G_CALLBACK (close_display_cb), info);
411   gtk_box_pack_start (GTK_BOX (button_vbox), button, FALSE, FALSE, 0);
412
413   info->display_model = (GtkTreeModel *)gtk_list_store_new (DISPLAY_NUM_COLUMNS,
414                                                             G_TYPE_STRING,
415                                                             GDK_TYPE_DISPLAY);
416
417   gtk_tree_view_set_model (GTK_TREE_VIEW (tree_view), info->display_model);
418
419   column = gtk_tree_view_column_new_with_attributes ("Name",
420                                                      gtk_cell_renderer_text_new (),
421                                                      "text", DISPLAY_COLUMN_NAME,
422                                                      NULL);
423   gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column);
424
425   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view));
426   g_signal_connect (selection, "changed",
427                     G_CALLBACK (display_changed_cb), info);
428
429   return frame;
430 }
431
432 /* Creates the "Screen" frame in the main window.
433  */
434 GtkWidget *
435 create_screen_frame (ChangeDisplayInfo *info)
436 {
437   GtkWidget *frame;
438   GtkWidget *tree_view;
439   GtkWidget *button_vbox;
440   GtkTreeViewColumn *column;
441
442   create_frame (info, "Screen", &frame, &tree_view, &button_vbox);
443
444   info->screen_model = (GtkTreeModel *)gtk_list_store_new (SCREEN_NUM_COLUMNS,
445                                                            G_TYPE_INT,
446                                                            GDK_TYPE_SCREEN);
447
448   gtk_tree_view_set_model (GTK_TREE_VIEW (tree_view), info->screen_model);
449
450   column = gtk_tree_view_column_new_with_attributes ("Number",
451                                                      gtk_cell_renderer_text_new (),
452                                                      "text", SCREEN_COLUMN_NUMBER,
453                                                      NULL);
454   gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column);
455
456   info->screen_selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view));
457   g_signal_connect (info->screen_selection, "changed",
458                     G_CALLBACK (screen_changed_cb), info);
459
460   return frame;
461 }
462
463 /* Called when one of the currently open displays is closed.
464  * Remove it from our list of displays.
465  */
466 static void
467 display_closed_cb (GdkDisplay        *display,
468                    gboolean           is_error,
469                    ChangeDisplayInfo *info)
470 {
471   GtkTreeIter iter;
472   gboolean valid;
473
474   for (valid = gtk_tree_model_get_iter_first (info->display_model, &iter);
475        valid;
476        valid = gtk_tree_model_iter_next (info->display_model, &iter))
477     {
478       GdkDisplay *tmp_display;
479       
480       gtk_tree_model_get (info->display_model, &iter,
481                           DISPLAY_COLUMN_DISPLAY, &tmp_display,
482                           -1);
483       if (tmp_display == display)
484         {
485           gtk_list_store_remove (GTK_LIST_STORE (info->display_model), &iter);
486           break;
487         }
488     }
489 }
490
491 /* Adds a new display to our list of displays, and connects
492  * to the "closed" signal so that we can remove it from the
493  * list of displays again.
494  */
495 static void
496 add_display (ChangeDisplayInfo *info,
497              GdkDisplay        *display)
498 {
499   const gchar *name = gdk_display_get_name (display);
500   GtkTreeIter iter;
501   
502   gtk_list_store_append (GTK_LIST_STORE (info->display_model), &iter);
503   gtk_list_store_set (GTK_LIST_STORE (info->display_model), &iter,
504                       DISPLAY_COLUMN_NAME, name,
505                       DISPLAY_COLUMN_DISPLAY, display,
506                       -1);
507
508   g_signal_connect (display, "closed",
509                     G_CALLBACK (display_closed_cb), info); 
510 }
511
512 /* Called when a new display is opened
513  */
514 static void
515 display_opened_cb (GdkDisplayManager *manager,
516                    GdkDisplay        *display,
517                    ChangeDisplayInfo *info)
518 {
519   add_display (info, display);
520 }
521
522 /* Adds all currently open displays to our list of displays,
523  * and set up a signal connection so that we'll be notified
524  * when displays are opened in the future as well.
525  */
526 static void
527 initialize_displays (ChangeDisplayInfo *info)
528 {
529   GdkDisplayManager *manager = gdk_display_manager_get ();
530   GSList *displays = gdk_display_manager_list_displays (manager);
531   GSList *tmp_list;
532
533   for (tmp_list = displays; tmp_list; tmp_list = tmp_list->next)
534     add_display (info, tmp_list->data);
535
536   g_slist_free (tmp_list);
537
538   g_signal_connect (manager, "display_opened",
539                     G_CALLBACK (display_opened_cb), info);
540 }
541
542 /* Cleans up when the toplevel is destroyed; we remove the
543  * connections we use to track currently open displays, then
544  * free the ChangeDisplayInfo structure.
545  */
546 static void
547 destroy_info (ChangeDisplayInfo *info)
548 {
549   GdkDisplayManager *manager = gdk_display_manager_get ();
550   GSList *displays = gdk_display_manager_list_displays (manager);
551   GSList *tmp_list;
552
553   g_signal_handlers_disconnect_by_func (manager,
554                                         display_opened_cb,
555                                         info);
556
557   for (tmp_list = displays; tmp_list; tmp_list = tmp_list->next)
558     g_signal_handlers_disconnect_by_func (tmp_list->data,
559                                           display_closed_cb,
560                                           info);
561   
562   g_slist_free (tmp_list);
563
564   g_object_unref (info->size_group);
565   g_free (info);
566 }
567
568 static void
569 destroy_cb (GtkObject          *object,
570             ChangeDisplayInfo **info)
571 {
572   destroy_info (*info);
573   *info = NULL;
574 }
575
576 /* Main entry point. If the dialog for this demo doesn't yet exist, creates
577  * it. Otherwise, destroys it.
578  */
579 GtkWidget *
580 do_changedisplay (void)
581 {
582   static ChangeDisplayInfo *info = NULL;
583
584   if (!info)
585     {
586       GtkWidget *vbox;
587       GtkWidget *frame;
588
589       info = g_new0 (ChangeDisplayInfo, 1);
590
591       info->window = gtk_dialog_new_with_buttons ("Change Screen or display",
592                                             NULL, /* parent */
593                                             GTK_DIALOG_NO_SEPARATOR,
594                                             GTK_STOCK_CLOSE,  GTK_RESPONSE_CLOSE,
595                                             "Change",         GTK_RESPONSE_OK,
596                                             NULL);
597
598       gtk_window_set_default_size (GTK_WINDOW (info->window), 300, 400);
599
600       g_signal_connect (info->window, "response",
601                         G_CALLBACK (response_cb), info);
602       g_signal_connect (info->window, "destroy",
603                         G_CALLBACK (destroy_cb), &info);
604
605       vbox = gtk_vbox_new (FALSE, 5);
606       gtk_container_set_border_width (GTK_CONTAINER (vbox), 8);
607         
608       gtk_box_pack_start (GTK_BOX (GTK_DIALOG (info->window)->vbox), vbox,
609                           TRUE, TRUE, 0);
610
611       frame = create_display_frame (info);
612       gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0);
613       
614       frame = create_screen_frame (info);
615       gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0);
616
617       initialize_displays (info);
618
619       gtk_widget_show_all (info->window);
620       return info->window;
621     }
622   else
623     {
624       gtk_widget_destroy (info->window);
625       return NULL;
626     }
627 }