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