Add zoom ratio display to OSD in fullscreen mode.
[geeqie.git] / src / fullscreen.c
1 /*
2  * Geeqie
3  * (C) 2004 John Ellis
4  *
5  * Author: John Ellis
6  *
7  * This software is released under the GNU General Public License (GNU GPL).
8  * Please read the included file COPYING for more information.
9  * This software comes with no warranty of any kind, use at your own risk!
10  */
11
12
13 #include "main.h"
14 #include "fullscreen.h"
15
16 #include "image.h"
17 #include "ui_fileops.h"
18 #include "ui_menu.h"
19 #include "ui_misc.h"
20
21
22 enum {
23         FULLSCREEN_CURSOR_HIDDEN = 1 << 0,
24         FULLSCREEN_CURSOR_NORMAL = 1 << 1,
25         FULLSCREEN_CURSOR_BUSY   = 1 << 2
26 };
27
28
29 /*
30  *----------------------------------------------------------------------------
31  * full screen functions
32  *----------------------------------------------------------------------------
33  */
34
35 void set_default_fullscreen_info(ConfOptions *options)
36 {
37         if (options->fullscreen.info) g_free(options->fullscreen.info);
38         options->fullscreen.info = g_strdup(DEFAULT_OVERLAY_INFO);
39 }
40
41 static void clear_mouse_cursor(GtkWidget *widget, gint state)
42 {
43         if (!widget->window) return;
44
45         if (state & FULLSCREEN_CURSOR_BUSY)
46                 {
47                 GdkCursor *cursor;
48
49                 cursor = gdk_cursor_new(GDK_WATCH);
50                 gdk_window_set_cursor (widget->window, cursor);
51                 gdk_cursor_unref(cursor);
52                 }
53         else if (state & FULLSCREEN_CURSOR_NORMAL)
54                 {
55                 gdk_window_set_cursor (widget->window, NULL);
56                 }
57         else
58                 {
59                 GdkCursor *cursor;
60                 GdkPixmap *p;
61
62                 p = gdk_bitmap_create_from_data(widget->window, "\0\0\0", 1, 1);
63
64                 cursor = gdk_cursor_new_from_pixmap(p, p,
65                                                     &widget->style->fg[GTK_STATE_ACTIVE],
66                                                     &widget->style->bg[GTK_STATE_ACTIVE],
67                                                     0, 0);
68
69                 gdk_window_set_cursor (widget->window, cursor);
70
71                 gdk_cursor_unref(cursor);
72                 g_object_unref(p);
73                 }
74 }
75
76 static gint fullscreen_hide_mouse_cb(gpointer data)
77 {
78         FullScreenData *fs = data;
79
80         if (fs->hide_mouse_id == -1) return FALSE;
81
82         fs->cursor_state &= ~FULLSCREEN_CURSOR_NORMAL;
83         if (!(fs->cursor_state & FULLSCREEN_CURSOR_BUSY)) clear_mouse_cursor(fs->window, fs->cursor_state);
84
85         fs->hide_mouse_id = -1;
86         return FALSE;
87 }
88
89 static void fullscreen_hide_mouse_disable(FullScreenData *fs)
90 {
91         if (fs->hide_mouse_id != -1)
92                 {
93                 g_source_remove(fs->hide_mouse_id);
94                 fs->hide_mouse_id = -1;
95                 }
96 }
97
98 static void fullscreen_hide_mouse_reset(FullScreenData *fs)
99 {
100         fullscreen_hide_mouse_disable(fs);
101         fs->hide_mouse_id = g_timeout_add(FULL_SCREEN_HIDE_MOUSE_DELAY, fullscreen_hide_mouse_cb, fs);
102 }
103
104 static gint fullscreen_mouse_moved(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
105 {
106         FullScreenData *fs = data;
107
108         if (!(fs->cursor_state & FULLSCREEN_CURSOR_NORMAL))
109                 {
110                 fs->cursor_state |= FULLSCREEN_CURSOR_NORMAL;
111                 if (!(fs->cursor_state & FULLSCREEN_CURSOR_BUSY)) clear_mouse_cursor(fs->window, fs->cursor_state);
112                 }
113         fullscreen_hide_mouse_reset(fs);
114
115         return FALSE;
116 }
117
118 static void fullscreen_busy_mouse_disable(FullScreenData *fs)
119 {
120         if (fs->busy_mouse_id != -1)
121                 {
122                 g_source_remove(fs->busy_mouse_id);
123                 fs->busy_mouse_id = -1;
124                 }
125 }
126
127 static void fullscreen_mouse_set_busy(FullScreenData *fs, gint busy)
128 {
129         fullscreen_busy_mouse_disable(fs);
130
131         if ((fs->cursor_state & FULLSCREEN_CURSOR_BUSY) == (busy)) return;
132
133         if (busy)
134                 {
135                 fs->cursor_state |= FULLSCREEN_CURSOR_BUSY;
136                 }
137         else
138                 {
139                 fs->cursor_state &= ~FULLSCREEN_CURSOR_BUSY;
140                 }
141
142         clear_mouse_cursor(fs->window, fs->cursor_state);
143 }
144
145 static gboolean fullscreen_mouse_set_busy_cb(gpointer data)
146 {
147         FullScreenData *fs = data;
148
149         fs->busy_mouse_id = -1;
150         fullscreen_mouse_set_busy(fs, TRUE);
151         return FALSE;
152 }
153
154 static void fullscreen_mouse_set_busy_idle(FullScreenData *fs)
155 {
156         if (fs->busy_mouse_id == -1)
157                 {
158                 fs->busy_mouse_id = g_timeout_add(FULL_SCREEN_BUSY_MOUSE_DELAY,
159                                                   fullscreen_mouse_set_busy_cb, fs);
160                 }
161 }
162
163 static void fullscreen_image_update_cb(ImageWindow *imd, gpointer data)
164 {
165         FullScreenData *fs = data;
166
167         if (fs->imd->il &&
168             fs->imd->il->pixbuf != image_get_pixbuf(fs->imd))
169                 {
170                 fullscreen_mouse_set_busy_idle(fs);
171                 }
172 }
173
174 static void fullscreen_image_complete_cb(ImageWindow *imd, gint preload, gpointer data)
175 {
176         FullScreenData *fs = data;
177
178         if (!preload) fullscreen_mouse_set_busy(fs, FALSE);
179 }
180
181 #define XSCREENSAVER_BINARY     "xscreensaver-command"
182 #define XSCREENSAVER_COMMAND    "xscreensaver-command -deactivate >&- 2>&- &"
183
184 static void fullscreen_saver_deactivate(void)
185 {
186         static gint checked = FALSE;
187         static gint found = FALSE;
188
189         if (!checked)
190                 {
191                 checked = TRUE;
192                 found = file_in_path(XSCREENSAVER_BINARY);
193                 }
194
195         if (found)
196                 {
197                 system (XSCREENSAVER_COMMAND);
198                 }
199 }
200
201 static gboolean fullscreen_saver_block_cb(gpointer data)
202 {
203         if (options->fullscreen.disable_saver)
204                 {
205                 fullscreen_saver_deactivate();
206                 }
207
208         return TRUE;
209 }
210
211 static gint fullscreen_delete_cb(GtkWidget *widget, GdkEventAny *event, gpointer data)
212 {
213         FullScreenData *fs = data;
214
215         fullscreen_stop(fs);
216         return TRUE;
217 }
218
219 FullScreenData *fullscreen_start(GtkWidget *window, ImageWindow *imd,
220                                  void (*stop_func)(FullScreenData *, gpointer), gpointer stop_data)
221 {
222         FullScreenData *fs;
223         GdkScreen *screen;
224         gint same;
225         gint x, y;
226         gint w, h;
227         GdkGeometry geometry;
228
229         if (!window || !imd) return NULL;
230
231         fs = g_new0(FullScreenData, 1);
232
233         fs->hide_mouse_id = -1;
234         fs->busy_mouse_id = -1;
235         fs->cursor_state = FULLSCREEN_CURSOR_HIDDEN;
236
237         fs->normal_window = window;
238         fs->normal_imd = imd;
239
240         fs->stop_func = stop_func;
241         fs->stop_data = stop_data;
242
243         if (debug) printf("full screen requests screen %d\n", options->fullscreen.screen);
244         fullscreen_prefs_get_geometry(options->fullscreen.screen, window, &x, &y, &w, &h,
245                                       &screen, &same);
246
247         fs->window = window_new(GTK_WINDOW_TOPLEVEL, "fullscreen", NULL, NULL, _("Full screen"));
248
249         /* this requests no decorations, if you still have them complain to the window manager author(s) */
250         gtk_window_set_decorated(GTK_WINDOW(fs->window), FALSE);
251
252         if (options->fullscreen.screen < 0)
253                 {
254                 /* If we want control of the window size and position this is not what we want.
255                  * Geeqie needs control of which monitor(s) to use for full screen.
256                  */
257                 gtk_window_fullscreen(GTK_WINDOW(fs->window));
258                 }
259         else if (options->fullscreen.above)
260                 {
261                 /* request to be above other windows */
262                 gtk_window_set_keep_above(GTK_WINDOW(fs->window), TRUE);
263                 }
264
265         gtk_window_set_resizable(GTK_WINDOW(fs->window), FALSE);
266
267         gtk_window_set_screen(GTK_WINDOW(fs->window), screen);
268         gtk_container_set_border_width(GTK_CONTAINER(fs->window), 0);
269         g_signal_connect(G_OBJECT(fs->window), "delete_event",
270                          G_CALLBACK(fullscreen_delete_cb), fs);
271
272         geometry.min_width = w;
273         geometry.min_height = h;
274         geometry.max_width = w;
275         geometry.max_height = h;
276         geometry.base_width = w;
277         geometry.base_height = h;
278         geometry.win_gravity = GDK_GRAVITY_STATIC;
279         /* By setting USER_POS and USER_SIZE, most window managers will
280          * not request positioning of the full screen window (for example twm).
281          *
282          * In addition, setting gravity to STATIC will result in the
283          * decorations of twm to not effect the requested window position,
284          * the decorations will simply be off screen, except in multi monitor setups :-/
285          */
286         gtk_window_set_geometry_hints(GTK_WINDOW(fs->window), fs->window, &geometry,
287                                       GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE | GDK_HINT_BASE_SIZE |
288                                       GDK_HINT_WIN_GRAVITY |
289                                       GDK_HINT_USER_POS);
290
291         gtk_window_set_default_size(GTK_WINDOW(fs->window), w, h);
292         gtk_window_move(GTK_WINDOW(fs->window), x, y);
293
294         fs->imd = image_new(FALSE);
295
296         gtk_container_add(GTK_CONTAINER(fs->window), fs->imd->widget);
297
298         if (options->image.use_custom_border_color)
299                 {
300                 image_background_set_color(fs->imd, &options->image.border_color);
301                 }
302
303         image_set_delay_flip(fs->imd, options->fullscreen.clean_flip);
304         image_auto_refresh(fs->imd, fs->normal_imd->auto_refresh_interval);
305
306         if (options->fullscreen.clean_flip)
307                 {
308                 image_set_update_func(fs->imd, fullscreen_image_update_cb, fs);
309                 image_set_complete_func(fs->imd, fullscreen_image_complete_cb, fs);
310                 }
311
312         gtk_widget_show(fs->imd->widget);
313
314         image_change_from_image(fs->imd, fs->normal_imd);
315
316         gtk_widget_show(fs->window);
317
318         /* for hiding the mouse */
319         g_signal_connect(G_OBJECT(fs->imd->pr), "motion_notify_event",
320                            G_CALLBACK(fullscreen_mouse_moved), fs);
321         clear_mouse_cursor(fs->window, fs->cursor_state);
322
323         /* set timer to block screen saver */
324         fs->saver_block_id = g_timeout_add(60 * 1000, fullscreen_saver_block_cb, fs);
325
326         /* hide normal window
327          * FIXME: properly restore this window on show
328          */
329 #ifdef HIDE_WINDOW_IN_FULLSCREEN
330         gtk_widget_hide(fs->normal_window);
331 #endif
332         image_change_fd(fs->normal_imd, NULL, image_zoom_get(fs->normal_imd));
333
334         return fs;
335 }
336
337 void fullscreen_stop(FullScreenData *fs)
338 {
339         if (!fs) return;
340
341         g_source_remove(fs->saver_block_id);
342
343         fullscreen_hide_mouse_disable(fs);
344         fullscreen_busy_mouse_disable(fs);
345         gdk_keyboard_ungrab(GDK_CURRENT_TIME);
346
347         image_change_from_image(fs->normal_imd, fs->imd);
348 #ifdef HIDE_WINDOW_IN_FULLSCREEN
349         gtk_widget_show(fs->normal_window);
350 #endif
351         if (fs->stop_func) fs->stop_func(fs, fs->stop_data);
352
353         gtk_widget_destroy(fs->window);
354
355         g_free(fs);
356 }
357
358
359 /*
360  *----------------------------------------------------------------------------
361  * full screen preferences and utils
362  *----------------------------------------------------------------------------
363  */
364
365 GList *fullscreen_prefs_list(void)
366 {
367         GList *list = NULL;
368         GdkDisplay *display;
369         gint number;
370         gint i;
371
372         display = gdk_display_get_default();
373         number = gdk_display_get_n_screens(display);
374
375         for (i = 0; i < number ; i++)
376                 {
377                 GdkScreen *screen;
378                 gint monitors;
379                 gint j;
380
381                 screen = gdk_display_get_screen(display, i);
382                 monitors = gdk_screen_get_n_monitors(screen);
383
384                 for (j = -1; j < monitors; j++)
385                         {
386                         ScreenData *sd;
387                         GdkRectangle rect;
388                         gchar *name;
389                         gchar *subname;
390
391                         name = gdk_screen_make_display_name(screen);
392
393                         if (j < 0)
394                                 {
395                                 rect.x = 0;
396                                 rect.y = 0;
397                                 rect.width = gdk_screen_get_width(screen);
398                                 rect.height = gdk_screen_get_height(screen);
399                                 subname = g_strdup(_("Full size"));
400                                 }
401                         else
402                                 {
403                                 gdk_screen_get_monitor_geometry(screen, j, &rect);
404                                 subname = g_strdup_printf("%s %d", _("Monitor"), j + 1);
405                                 }
406
407                         sd = g_new0(ScreenData, 1);
408                         sd->number = (i+1) * 100 + j + 1;
409                         sd->description = g_strdup_printf("%s %s, %s", _("Screen"), name, subname);
410                         sd->x = rect.x;
411                         sd->y = rect.y;
412                         sd->width = rect.width;
413                         sd->height = rect.height;
414
415                         if (debug) printf("Screen %d %30s %4d,%4d (%4dx%4d)\n",
416                                           sd->number, sd->description, sd->x, sd->y, sd->width, sd->height);
417
418                         list = g_list_append(list, sd);
419
420                         g_free(name);
421                         g_free(subname);
422                         }
423                 }
424
425         return list;
426 }
427
428 void fullscreen_prefs_list_free(GList *list)
429 {
430         GList *work;
431
432         work = list;
433         while (work)
434                 {
435                 ScreenData *sd = work->data;
436                 work = work->next;
437
438                 g_free(sd->description);        
439                 g_free(sd);
440                 }
441
442         g_list_free(list);
443 }
444
445 ScreenData *fullscreen_prefs_list_find(GList *list, gint screen)
446 {
447         GList *work;
448
449         work = list;
450         while (work)
451                 {
452                 ScreenData *sd = work->data;
453                 work = work->next;
454
455                 if (sd->number == screen) return sd;
456                 }
457
458         return NULL;
459 }
460
461 /* screen is interpreted as such:
462  *  -1  window manager determines size and position, fallback is (1) active monitor
463  *   0  full size of screen containing widget
464  *   1  size of monitor containing widget
465  * 100  full size of screen 1 (screen, monitor counts start at 1)
466  * 101  size of monitor 1 on screen 1
467  * 203  size of monitor 3 on screen 2
468  * returns:
469  * dest_screen: screen to place widget [use gtk_window_set_screen()]
470  * same_region: the returned region will overlap the current location of widget.
471  */
472 void fullscreen_prefs_get_geometry(gint screen, GtkWidget *widget, gint *x, gint *y, gint *width, gint *height,
473                                    GdkScreen **dest_screen, gint *same_region)
474 {
475         GList *list;
476         ScreenData *sd;
477
478         list = fullscreen_prefs_list();
479         if (screen >= 100)
480                 {
481                 sd = fullscreen_prefs_list_find(list, screen);
482                 }
483         else
484                 {
485                 sd = NULL;
486                 if (screen < 0) screen = 1;
487                 }
488
489         if (sd)
490                 {
491                 GdkDisplay *display;
492                 GdkScreen *screen;
493                 gint n;
494
495                 display = gdk_display_get_default();
496                 n = sd->number / 100 - 1;
497                 if (n >= 0 && n < gdk_display_get_n_screens(display))
498                         {
499                         screen = gdk_display_get_screen(display, n);
500                         }
501                 else
502                         {
503                         screen = gdk_display_get_default_screen(display);
504                         }
505
506                 if (x) *x = sd->x;
507                 if (y) *y = sd->y;
508                 if (width) *width = sd->width;
509                 if (height) *height = sd->height;
510
511                 if (dest_screen) *dest_screen = screen;
512                 if (same_region) *same_region = (!widget || !widget->window ||
513                                         (screen == gtk_widget_get_screen(widget) &&
514                                         (sd->number%100 == 0 ||
515                                          sd->number%100 == gdk_screen_get_monitor_at_window(screen, widget->window)+1)));
516
517                 }
518         else if (screen != 1 || !widget || !widget->window)
519                 {
520                 GdkScreen *screen;
521
522                 if (widget)
523                         {
524                         screen = gtk_widget_get_screen(widget);
525                         }
526                 else
527                         {
528                         screen = gdk_screen_get_default();
529                         }
530
531                 if (x) *x = 0;
532                 if (y) *y = 0;
533                 if (width) *width = gdk_screen_get_width(screen);
534                 if (height) *height = gdk_screen_get_height(screen);
535
536                 if (dest_screen) *dest_screen = screen;
537                 if (same_region) *same_region = TRUE;
538                 }
539         else
540                 {
541                 GdkScreen *screen;
542                 gint monitor;
543                 GdkRectangle rect;
544
545                 screen = gtk_widget_get_screen(widget);
546                 monitor = gdk_screen_get_monitor_at_window(screen, widget->window);
547
548                 gdk_screen_get_monitor_geometry(screen, monitor, &rect);
549
550                 if (x) *x = rect.x;
551                 if (y) *y = rect.y;
552                 if (width) *width = rect.width;
553                 if (height) *height = rect.height;
554
555                 if (dest_screen) *dest_screen = screen;
556                 if (same_region) *same_region = TRUE;
557                 }
558
559         fullscreen_prefs_list_free(list);
560 }
561
562 gint fullscreen_prefs_find_screen_for_widget(GtkWidget *widget)
563 {
564         GdkScreen *screen;
565         gint monitor;
566         gint n;
567
568         if (!widget || !widget->window) return 0;
569
570         screen = gtk_widget_get_screen(widget);
571         monitor = gdk_screen_get_monitor_at_window(screen, widget->window);
572
573         n = (gdk_screen_get_number(screen)+1) * 100 + monitor + 1;
574
575         if (debug || TRUE) printf("Screen appears to be %d\n", n);
576
577         return n;
578 }
579
580 enum {
581         FS_MENU_COLUMN_NAME = 0,
582         FS_MENU_COLUMN_VALUE
583 };
584
585 #define BUTTON_ABOVE_KEY  "button_above"
586
587 static void fullscreen_prefs_selection_cb(GtkWidget *combo, gpointer data)
588 {
589         gint *value = data;
590         GtkTreeModel *store;
591         GtkTreeIter iter;
592         GtkWidget *button;
593
594         if (!value) return;
595
596         store = gtk_combo_box_get_model(GTK_COMBO_BOX(combo));
597         if (!gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combo), &iter)) return;
598         gtk_tree_model_get(store, &iter, FS_MENU_COLUMN_VALUE, value, -1);
599
600         button = g_object_get_data(G_OBJECT(combo), BUTTON_ABOVE_KEY);
601         if (button)
602                 {
603                 gtk_widget_set_sensitive(button, *value != -1);
604                 }
605 }
606
607 static void fullscreen_prefs_selection_add(GtkListStore *store, const gchar *text, gint value)
608 {
609         GtkTreeIter iter;
610
611         gtk_list_store_append(store, &iter);
612         gtk_list_store_set(store, &iter, FS_MENU_COLUMN_NAME, text,
613                                          FS_MENU_COLUMN_VALUE, value, -1);
614 }
615
616 GtkWidget *fullscreen_prefs_selection_new(const gchar *text, gint *screen_value, gint *above_value)
617 {
618         GtkWidget *vbox;
619         GtkWidget *hbox;
620         GtkWidget *combo;
621         GtkListStore *store;
622         GtkCellRenderer *renderer;
623         GtkWidget *button = NULL;
624         GList *list;
625         GList *work;
626         gint current = 0;
627         gint n;
628
629         if (!screen_value) return NULL;
630
631         vbox = gtk_vbox_new(FALSE, PREF_PAD_GAP);
632         hbox = pref_box_new(vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
633         if (text) pref_label_new(hbox, text);
634
635         store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT);
636         combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
637         g_object_unref(store);
638
639         renderer = gtk_cell_renderer_text_new();
640         gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, TRUE);
641         gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo), renderer,
642                                        "text", FS_MENU_COLUMN_NAME, NULL);
643
644         if (above_value)
645                 {
646                 button = pref_checkbox_new_int(vbox, _("Stay above other windows"),
647                                                *above_value, above_value);
648                 gtk_widget_set_sensitive(button, *screen_value != -1);
649
650                 g_object_set_data(G_OBJECT(combo), BUTTON_ABOVE_KEY, button);
651                 }
652
653         fullscreen_prefs_selection_add(store, _("Determined by Window Manager"), -1);
654         fullscreen_prefs_selection_add(store, _("Active screen"), 0);
655         if (*screen_value == 0) current = 1;
656         fullscreen_prefs_selection_add(store, _("Active monitor"), 1);
657         if (*screen_value == 1) current = 2;
658
659         n = 3;
660         list = fullscreen_prefs_list();
661         work = list;
662         while (work)
663                 {
664                 ScreenData *sd = work->data;
665
666                 fullscreen_prefs_selection_add(store, sd->description, sd->number);
667                 if (*screen_value == sd->number) current = n;
668
669                 work = work->next;
670                 n++;
671                 }
672         fullscreen_prefs_list_free(list);
673
674         gtk_combo_box_set_active(GTK_COMBO_BOX(combo), current);
675
676         gtk_box_pack_start(GTK_BOX(hbox), combo, FALSE, FALSE, 0);
677         gtk_widget_show(combo);
678
679         g_signal_connect(G_OBJECT(combo), "changed",
680                          G_CALLBACK(fullscreen_prefs_selection_cb), screen_value);
681
682         return vbox;
683 }
684