gliv-1.7
[gliv.git] / src / windows.c
blob4c8a067d361c68697dc810a7082d79cb5265eb2c
1 /*
2 * This program is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU General Public License
4 * as published by the Free Software Foundation; either version 2
5 * of the License, or (at your option) any later version.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
16 * See the COPYING file for license information.
18 * Guillaume Chazarain <gfc@altern.org>
21 /*************************
22 * The top level widgets. *
23 *************************/
25 #include "gliv.h"
27 #include <gdk/gdkx.h> /* gdk_net_wm_supports() */
29 extern rt_struct *rt;
30 extern options_struct *options;
31 extern gliv_image *current_image;
32 extern GtkWidget *gl_widget;
33 extern GtkMenuBar *menu_bar;
35 static GtkVBox *widgets;
37 static GtkWindow *main_window;
38 static GtkWindow *fs_window;
40 /* The main status bar and its subdivisions. */
41 static GtkHBox *status_bar;
42 static GtkEntry *entry_ident;
43 static GtkEntry *entry_size;
44 static GtkEntry *entry_state;
46 static GtkHScrollbar *hscroll;
47 static GtkVScrollbar *vscroll;
49 static gboolean window_resized = FALSE;
50 static gint last_width = 0;
51 static gint last_height = 0;
54 * We determine whether the window has been manually resized in order
55 * to prevent automatic resizing in this case.
57 static gboolean check_window_resized(void)
59 gint current_width, current_height;
61 if (window_resized)
62 return TRUE;
64 process_events();
65 gtk_window_get_size(main_window, &current_width, &current_height);
67 window_resized = last_width != 0 &&
68 (last_width != current_width || last_height != current_height);
70 return window_resized;
73 static void resize_window(gint width, gint height)
75 gtk_window_resize(main_window, width, height);
77 last_width = width;
78 last_height = height;
80 process_events();
83 void show_dialog(GtkWidget * widget, const gchar * name, gboolean unused)
85 /* In windowed mode we let the WM choose the position. */
86 if (options->fullscreen)
87 gtk_window_set_position(GTK_WINDOW(widget), GTK_WIN_POS_MOUSE);
89 if (name != NULL)
90 gtk_window_set_title(GTK_WINDOW(widget), name);
92 gtk_widget_show_all(widget);
95 static GtkEntry *add_entry(gboolean grow, const gchar * init)
97 GtkEntry *entry;
99 entry = GTK_ENTRY(gtk_entry_new());
100 gtk_editable_set_editable(GTK_EDITABLE(entry), FALSE);
101 gtk_box_pack_start(GTK_BOX(status_bar), GTK_WIDGET(entry), grow, TRUE, 0);
103 /* We don't want the blinking cursor. */
104 GTK_WIDGET_UNSET_FLAGS(GTK_WIDGET(entry), GTK_CAN_FOCUS);
106 gtk_entry_set_text(entry, init);
108 return entry;
111 static void create_status_bar(void)
113 status_bar = GTK_HBOX(gtk_hbox_new(FALSE, 0));
115 entry_ident = add_entry(TRUE, _("No image loaded"));
116 entry_size = add_entry(FALSE, _("width x height"));
117 entry_state = add_entry(FALSE, _("zoom% (angle)"));
120 void set_loading_entry(const gchar * filename)
122 static gchar *old_ident = NULL;
124 if (filename == NULL) {
125 gtk_entry_set_text(entry_ident, old_ident);
126 g_free(old_ident);
127 } else {
128 gchar *new_ident;
130 old_ident = gtk_editable_get_chars(GTK_EDITABLE(entry_ident), 0, -1);
132 new_ident = g_strconcat(old_ident, " ", _("loading"), ":",
133 locale_to_utf8(filename), NULL);
135 gtk_entry_set_text(entry_ident, new_ident);
136 g_free(new_ident);
139 if (options->status_bar) {
140 /* Make sure the widget is updated now. */
141 gtk_widget_queue_draw(GTK_WIDGET(entry_ident));
142 gdk_window_process_updates(GTK_WIDGET(entry_ident)->window, TRUE);
146 /* Returns TRUE if the widget has been toggled. */
147 static gboolean toggle_widget(GtkWidget * widget, gboolean * flag,
148 gboolean horizontal)
150 gint new_width, new_height, coeff;
152 /* We are not reentrant. */
153 static gboolean toggling = FALSE;
155 if (toggling)
156 return FALSE;
158 toggling = TRUE;
160 process_events();
161 gtk_window_get_size(main_window, &new_width, &new_height);
163 if (*flag == FALSE) {
164 /* Show. */
165 gtk_widget_show_all(widget);
166 coeff = 1;
167 } else {
168 /* Hide. */
169 gtk_widget_hide_all(widget);
170 coeff = -1;
173 if (horizontal)
174 new_width += coeff * widget->allocation.width;
175 else
176 new_height += coeff * widget->allocation.height;
178 *flag ^= TRUE;
180 if (options->fullscreen == FALSE)
181 resize_window(new_width, new_height);
183 toggling = FALSE;
184 return TRUE;
187 void toggle_menu_bar(void)
189 toggle_widget(GTK_WIDGET(menu_bar), &options->menu_bar, FALSE);
192 void toggle_status_bar(void)
194 toggle_widget(GTK_WIDGET(status_bar), &options->status_bar, FALSE);
197 void toggle_scrollbars(void)
199 if (!toggle_widget(GTK_WIDGET(hscroll), &options->scrollbars, FALSE))
200 return;
202 /* To undo the toggle_widget() side effect. */
203 options->scrollbars ^= TRUE;
205 if (!toggle_widget(GTK_WIDGET(vscroll), &options->scrollbars, TRUE))
206 return;
208 if (options->scrollbars)
209 update_scrollbars(TRUE);
212 void update_status_bar(void)
214 gchar *size_str, *state_str;
215 const gchar *sym;
216 gfloat zoom, angle;
218 if (current_image == NULL)
219 return;
221 if (current_image->ident != NULL) {
222 /* Filename and dimensions status, only the first time. */
223 gtk_entry_set_text(entry_ident, current_image->ident);
224 gtk_editable_set_position(GTK_EDITABLE(entry_ident), 0);
226 size_str = g_strdup_printf("%dx%d",
227 current_image->width, current_image->height);
228 gtk_entry_set_text(entry_size, size_str);
229 g_free(size_str);
232 /* The state */
234 zoom = get_matrix_zoom() * 100.0;
235 angle = get_matrix_angle() * 180.0 / PI;
236 sym = is_matrix_symmetry()? " /" : "";
238 /* We don't want -0.000 */
239 if (float_equal(angle, 0.0))
240 angle = 0.0;
242 state_str = g_strdup_printf(_("%.3f%% (%.3f deg%s)"), zoom, angle, sym);
243 gtk_entry_set_text(entry_state, state_str);
245 g_free(state_str);
248 static void update_scroll(GtkScrollbar * scroll, gfloat min, gfloat max,
249 gfloat dim, gboolean redraw)
251 GtkAdjustment *adj;
252 gboolean update_needed;
254 adj = gtk_range_get_adjustment(GTK_RANGE(scroll));
256 update_needed = (adj->lower < 0.0 || adj->upper > dim ||
257 float_equal(adj->value, 0.0) == FALSE);
259 /* smallest length containing the image and the window = window U image. */
260 adj->lower = MIN(min, 0.0);
261 adj->upper = MAX(max, dim);
263 /* window size. */
264 adj->page_size = dim;
266 /* the offset is actually computed with adj->lower and adj->upper. */
267 adj->value = 0.0;
269 if (update_needed == FALSE && (adj->lower < 0.0 || adj->upper > dim))
270 update_needed = TRUE;
272 adj->step_increment = MOVE_OFFSET;
273 adj->page_increment = dim;
275 gtk_range_set_adjustment(GTK_RANGE(scroll), adj);
277 if (options->scrollbars && update_needed && redraw &&
278 GTK_WIDGET_REALIZED(GTK_WIDGET(scroll))) {
280 gtk_widget_queue_draw(GTK_WIDGET(scroll));
281 gdk_window_process_updates(GTK_WIDGET(scroll)->window, FALSE);
285 void update_scrollbars(gboolean redraw)
287 gfloat min_x, max_x, min_y, max_y;
289 if (current_image == NULL)
290 return;
292 get_matrix_bounding_box(&min_x, &max_x, &min_y, &max_y);
294 update_scroll(GTK_SCROLLBAR(hscroll), min_x, max_x,
295 (gfloat) rt->wid_size->width, redraw);
297 update_scroll(GTK_SCROLLBAR(vscroll), min_y, max_y,
298 (gfloat) rt->wid_size->height, redraw);
301 static void scroll_val(GtkRange * range)
303 gfloat offset;
305 offset = -1.0 * gtk_range_get_value(range);
307 if (range == GTK_RANGE(hscroll))
308 matrix_move(offset, 0.0);
309 else
310 /* range == GTK_RANGE(vscroll) */
311 matrix_move(0.0, offset);
313 update_scrollbars(FALSE);
314 refresh(REFRESH_IMAGE | APPEND_HISTORY);
317 static gboolean has_net_wm(void)
319 return gdk_net_wm_supports(gdk_atom_intern("_NET_WM_STATE_FULLSCREEN",
320 FALSE));
323 static GtkRange *scroll_new(gboolean horiz)
325 GtkRange *range;
327 if (horiz)
328 range = GTK_RANGE(gtk_hscrollbar_new(NULL));
329 else
330 range = GTK_RANGE(gtk_vscrollbar_new(NULL));
332 gtk_range_set_update_policy(range, GTK_UPDATE_CONTINUOUS);
334 g_signal_connect(range, "value-changed", G_CALLBACK(scroll_val), NULL);
336 return range;
339 void refresh_all(void)
341 gtk_widget_queue_draw(GTK_WIDGET(widgets));
342 gdk_window_process_updates(GTK_WIDGET(widgets)->window, TRUE);
343 process_events();
346 static gboolean first_map(void)
348 if (options->menu_bar == FALSE)
349 gtk_widget_hide(GTK_WIDGET(menu_bar));
351 if (options->status_bar == FALSE)
352 gtk_widget_hide(GTK_WIDGET(status_bar));
354 if (options->scrollbars == FALSE) {
355 gtk_widget_hide(GTK_WIDGET(hscroll));
356 gtk_widget_hide(GTK_WIDGET(vscroll));
359 if (options->fullscreen && has_net_wm())
360 toggle_fullscreen(TRUE);
362 return FALSE;
365 void create_windows(void)
367 GtkWindow *first_window;
368 GtkAccelGroup *accel_group;
369 GtkHBox *hbox;
372 * The main window: seen when not in fullscreen,
373 * or when using the NET WM protocol.
375 main_window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
376 gtk_window_set_title(main_window, "GLiv");
378 install_callbacks(main_window);
380 /* The window seen in fullscreen mode. */
381 fs_window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
382 gtk_window_set_title(fs_window, _("GLiv in fullscreen"));
384 gtk_window_move(fs_window, 0, 0);
385 gtk_window_set_default_size(fs_window, rt->scr_width, rt->scr_height);
386 gtk_window_set_decorated(fs_window, FALSE);
387 install_callbacks(fs_window);
389 /* The widgets. */
390 create_gl_widget();
391 accel_group = create_menus();
392 create_status_bar();
394 hscroll = GTK_HSCROLLBAR(scroll_new(TRUE));
395 vscroll = GTK_VSCROLLBAR(scroll_new(FALSE));
397 /* Bind keyboard accelerators to windows. */
398 gtk_window_add_accel_group(main_window, accel_group);
399 gtk_window_add_accel_group(fs_window, accel_group);
401 /* Collection. */
402 widgets = GTK_VBOX(gtk_vbox_new(FALSE, 0));
403 gtk_box_pack_start(GTK_BOX(widgets), GTK_WIDGET(menu_bar), FALSE, FALSE, 0);
405 hbox = GTK_HBOX(gtk_hbox_new(FALSE, 0));
406 gtk_box_pack_start_defaults(GTK_BOX(hbox), gl_widget);
407 gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vscroll), FALSE, FALSE, 0);
409 gtk_box_pack_start_defaults(GTK_BOX(widgets), GTK_WIDGET(hbox));
410 gtk_box_pack_start(GTK_BOX(widgets), GTK_WIDGET(hscroll), FALSE, FALSE, 0);
411 gtk_box_pack_end(GTK_BOX(widgets), GTK_WIDGET(status_bar), FALSE, FALSE, 0);
413 gtk_widget_show_all(GTK_WIDGET(widgets));
415 first_window = (options->fullscreen && !has_net_wm())?
416 fs_window : main_window;
418 gtk_container_add(GTK_CONTAINER(first_window), GTK_WIDGET(widgets));
419 g_signal_connect_after(first_window, "map", G_CALLBACK(first_map), NULL);
420 gtk_widget_show_all(GTK_WIDGET(first_window));
423 void goto_window(gboolean force_resize)
425 gint new_width, new_height;
427 if (current_image == NULL)
428 return;
430 if (force_resize)
431 window_resized = FALSE;
433 if (force_resize == FALSE && check_window_resized())
434 return;
436 new_width = current_image->width;
437 new_height = current_image->height;
439 if (options->menu_bar) {
440 new_height += GTK_WIDGET(menu_bar)->requisition.height;
441 if (new_width < GTK_WIDGET(menu_bar)->requisition.width)
442 new_width = GTK_WIDGET(menu_bar)->requisition.width;
445 if (options->status_bar) {
446 new_height += GTK_WIDGET(status_bar)->requisition.height;
447 if (new_width < GTK_WIDGET(status_bar)->requisition.width)
448 new_width = GTK_WIDGET(status_bar)->requisition.width;
451 if (options->scrollbars) {
452 new_height += GTK_WIDGET(hscroll)->requisition.height;
453 new_width += GTK_WIDGET(vscroll)->requisition.width;
456 if (new_width >= rt->scr_width || new_height >= rt->scr_height) {
457 new_width = 3 * rt->scr_width / 4;
458 new_height = 3 * rt->scr_height / 4;
461 resize_window(new_width, new_height);
464 static void reparent(GtkWidget * new_parent)
466 /* Be sure all widgets are displayed. */
468 /* gtk_widget_show_all() would also show the '(Rebuilding images menus)'. */
469 gtk_widget_show(GTK_WIDGET(menu_bar));
471 if (options->status_bar && GTK_WIDGET_REALIZED(status_bar) == FALSE)
472 gtk_container_foreach(GTK_CONTAINER(status_bar),
473 (GtkCallback) gtk_widget_realize, NULL);
475 gtk_widget_show_all(GTK_WIDGET(status_bar));
477 gtk_widget_show_all(GTK_WIDGET(hscroll));
478 gtk_widget_show_all(GTK_WIDGET(vscroll));
480 process_events();
482 /* Then reparent them. */
483 gtk_widget_reparent(GTK_WIDGET(widgets), new_parent);
485 process_events();
487 /* Finally, hide widgets previously hidden. */
489 if (options->menu_bar == FALSE)
490 gtk_widget_hide(GTK_WIDGET(menu_bar));
492 if (options->status_bar == FALSE)
493 gtk_widget_hide(GTK_WIDGET(status_bar));
495 if (options->scrollbars == FALSE) {
496 gtk_widget_hide(GTK_WIDGET(hscroll));
497 gtk_widget_hide(GTK_WIDGET(vscroll));
501 static gboolean net_wm_toggle_fullscreen(gboolean enable)
503 if (get_current_window() != main_window || has_net_wm() == FALSE)
504 return FALSE;
506 if (enable) {
507 gtk_window_fullscreen(main_window);
508 gdk_window_raise(GTK_WIDGET(main_window)->window);
509 } else
510 gtk_window_unfullscreen(main_window);
512 return TRUE;
515 void toggle_fullscreen(gboolean enable)
517 /* We first try using the NET WM protocol, then with the old method. */
518 if (net_wm_toggle_fullscreen(enable) == FALSE) {
519 GtkWindow *new, *old;
521 if (enable) {
522 /* Go to fullscreen mode. */
523 new = fs_window;
524 old = main_window;
525 } else {
526 /* Go to window mode. */
527 new = main_window;
528 old = fs_window;
531 gtk_widget_show_all(GTK_WIDGET(new));
532 gtk_widget_hide(GTK_WIDGET(old));
534 reparent(GTK_WIDGET(new));
537 if (enable == FALSE)
538 goto_window(TRUE);
540 options->fullscreen = enable;
541 schedule_hide_cursor();
544 void update_window_title(void)
546 const gchar *title;
548 title = locale_to_utf8(current_image->node->data);
549 gtk_window_set_title(main_window, title);
552 GtkWindow *get_current_window(void)
554 return GTK_WINDOW(gtk_widget_get_toplevel(gl_widget));