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