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