- Set LIBS instead of LDFLAGS (LIBS are appended while LDFLAGS are prepended
[gliv.git] / src / windows.c
blob2b2f9343f94de7aa0b0db269fac73e77aa183aba
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 <guichaz@gmail.com>
21 /*************************
22 * The top level widgets *
23 *************************/
25 #include <string.h> /* memcpy() */
26 #include <gdk/gdkx.h> /* gdk_net_wm_supports() */
28 #include "gliv.h"
29 #include "windows.h"
30 #include "options.h"
31 #include "gliv-image.h"
32 #include "callbacks.h"
33 #include "messages.h"
34 #include "math_floats.h"
35 #include "str_utils.h"
36 #include "matrix.h"
37 #include "scrollbars.h"
38 #include "gl_widget.h"
39 #include "menus.h"
40 #include "cursors.h"
41 #include "loading.h"
42 #include "help.h"
44 extern rt_struct *rt;
45 extern options_struct *options;
46 extern GlivImage *current_image;
47 extern GtkWidget *gl_widget;
48 extern GtkMenuBar *menu_bar;
50 /* What is in the window. */
51 static GtkVBox *widgets;
53 static GtkWindow *main_window;
54 static GtkWindow *fs_window;
56 /* The main status bar and its subdivisions. */
57 static GtkHBox *status_bar;
58 static GtkEntry *entry_ident;
59 static GtkEntry *entry_size;
60 static GtkEntry *entry_state;
63 * Called when resizing the window because the image changed or a
64 * widget has been toggled.
66 static void resize_window(gint width, gint height, gboolean first_time)
68 GtkRequisition req;
70 if (options->resize_win == FALSE && first_time == FALSE)
71 return;
73 if (first_time && options->initial_geometry)
74 return;
76 width = MAX(width, 320);
77 height = MAX(height, 240);
79 if (width == rt->wid_size->width && height == rt->wid_size->height)
80 return;
83 * First, we let GTK resize the window around the
84 * width x height widget,
86 gtk_widget_set_size_request(gl_widget, width, height);
87 gtk_widget_size_request(GTK_WIDGET(main_window), &req);
88 gtk_window_resize(main_window, req.width, req.height);
90 /* then, we make the widget downsizable. */
91 gtk_widget_set_size_request(gl_widget, 1, 1);
94 void show_message(GtkWidget * widget, const gchar * name)
96 /* In windowed mode we let the WM choose the position. */
97 if (options->fullscreen)
98 gtk_window_set_position(GTK_WINDOW(widget), GTK_WIN_POS_MOUSE);
100 if (name != NULL)
101 gtk_window_set_title(GTK_WINDOW(widget), name);
103 gtk_widget_show_all(widget);
106 gint run_modal_dialog(GtkDialog * dialog)
108 gint response;
110 gtk_window_set_transient_for(GTK_WINDOW(dialog), get_current_window());
111 gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
113 /* In windowed mode we let the WM choose the position. */
114 if (options->fullscreen)
115 gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_MOUSE);
117 /* We want the cursor to be visible when the dialog is shown. */
118 set_hide_cursor_enabled(FALSE);
119 response = gtk_dialog_run(GTK_DIALOG(dialog));
120 set_hide_cursor_enabled(TRUE);
122 return response;
125 static gint get_entry_border(GtkEntry * entry)
127 gint interior_focus, focus_width;
128 gint border;
130 gtk_widget_style_get(GTK_WIDGET(entry),
131 "interior-focus", &interior_focus,
132 "focus-line-width", &focus_width, NULL);
134 border = entry->has_frame ? GTK_WIDGET(entry)->style->xthickness : 0;
136 if (!interior_focus)
137 border += focus_width;
139 return border + 2; /* + 2 : INNER_BORDER */
142 static gint status_bar_free_space(void)
144 gint space = 0;
145 GtkEntry *entries[] = { entry_ident, entry_size, entry_state, NULL };
146 GtkEntry **ptr;
148 for (ptr = entries; *ptr != NULL; ptr++) {
149 GtkRequisition req;
151 gtk_widget_size_request(GTK_WIDGET(*ptr), &req);
152 space += GTK_WIDGET(*ptr)->allocation.width - req.width;
155 return space;
158 static void set_entry_text(GtkEntry * entry, const gchar * text)
160 GtkRequisition old;
161 GtkRequisition req;
162 gboolean accept_resize;
164 gtk_entry_set_text(entry, text);
166 /* Compute the full width */
167 gtk_widget_size_request(GTK_WIDGET(entry), &old);
168 memcpy(&req, &old, sizeof(GtkRequisition));
169 pango_layout_get_pixel_size(gtk_entry_get_layout(entry), &req.width, NULL);
170 req.width += 2 * get_entry_border(entry);
172 if (req.width > old.width) {
173 gint free_space = status_bar_free_space();
175 accept_resize = req.width - old.width <= free_space;
176 } else
177 accept_resize = old.width - req.width > 20;
179 if (accept_resize)
180 gtk_widget_set_size_request(GTK_WIDGET(entry), req.width, req.height);
183 static gint ident_basename_pos(const gchar * ident)
185 gint pos;
186 gint last1 = 0, last2 = 0;
188 for (pos = 0; *ident != '\0'; ident = g_utf8_next_char(ident)) {
189 if (g_utf8_get_char(ident) == '/') {
190 last2 = last1;
191 last1 = pos;
193 pos++;
196 return last2;
199 static void set_ident_text(const gchar * ident)
201 gint pos;
202 const gchar *loading = currently_loading();
204 pos = ident_basename_pos(ident);
206 if (loading) {
207 gchar *concat = g_strconcat(ident, " ", _("loading"), ":",
208 loading, NULL);
209 gtk_entry_set_text(entry_ident, concat);
210 g_free(concat);
211 } else
212 gtk_entry_set_text(entry_ident, ident);
214 gtk_editable_set_position(GTK_EDITABLE(entry_ident), pos);
217 /* To add an entry in the status bar. */
218 static GtkEntry *add_entry(gboolean grow, const gchar * init)
220 GtkEntry *entry;
222 entry = GTK_ENTRY(gtk_entry_new());
223 gtk_editable_set_editable(GTK_EDITABLE(entry), FALSE);
224 gtk_box_pack_start(GTK_BOX(status_bar), GTK_WIDGET(entry), grow, TRUE, 0);
226 /* We don't want the blinking cursor. */
227 GTK_WIDGET_UNSET_FLAGS(GTK_WIDGET(entry), GTK_CAN_FOCUS);
229 set_entry_text(entry, init);
231 return entry;
234 static void create_status_bar(void)
236 status_bar = GTK_HBOX(gtk_hbox_new(FALSE, 0));
238 entry_ident = add_entry(TRUE, _("No image loaded"));
239 entry_size = add_entry(FALSE, _("width x height"));
240 entry_state = add_entry(FALSE, _("zoom% (angle)"));
243 /* Returns TRUE if the widgets has been toggled. */
244 gboolean toggle_widgets(GtkWidget ** widgets, gint nb_widgets, gboolean * flag)
246 /* We are not reentrant. */
247 static gboolean toggling = FALSE;
248 gint i;
250 if (toggling)
251 return FALSE;
253 toggling = TRUE;
255 for (i = 0; i < nb_widgets; i++) {
256 if (*flag == FALSE)
257 gtk_widget_show(widgets[i]);
258 else
259 gtk_widget_hide(widgets[i]);
262 *flag ^= TRUE;
263 toggling = FALSE;
264 return TRUE;
267 static gboolean toggle_widget(GtkWidget * widget, gboolean * flag)
269 return toggle_widgets(&widget, 1, flag);
272 gboolean toggle_menu_bar(void)
274 toggle_widget(GTK_WIDGET(menu_bar), &options->menu_bar);
275 return FALSE;
278 gboolean toggle_status_bar(void)
280 toggle_widget(GTK_WIDGET(status_bar), &options->status_bar);
281 return FALSE;
284 /* To take into account the new filename, zoom, angle, symmetry. */
285 void update_status_bar(void)
287 gchar *size_str, *state_str;
288 const gchar *sym;
289 gfloat zoom, angle;
291 if (current_image == NULL)
292 /* Loading first image */
293 set_ident_text("");
294 else if (current_image->ident != NULL) {
295 /* Filename and dimensions status, only the first time. */
296 set_ident_text(current_image->ident);
297 g_free(current_image->ident);
298 current_image->ident = NULL;
300 size_str = g_strdup_printf("%dx%d",
301 current_image->width, current_image->height);
302 set_entry_text(entry_size, size_str);
303 g_free(size_str);
306 /* The state */
308 zoom = get_matrix_zoom() * 100.0;
309 angle = get_matrix_angle() * 180.0 / PI;
310 sym = is_matrix_symmetry()? " /" : "";
312 /* We don't want -0.000 */
313 if (float_equal(angle, 0.0))
314 angle = 0.0;
316 state_str = g_strdup_printf(_("%.3f%% (%.3f deg%s)"), zoom, angle, sym);
317 set_entry_text(entry_state, state_str);
319 g_free(state_str);
322 /* Does the WM support the EWMH protocol? */
323 static gboolean has_net_wm(void)
325 return gdk_net_wm_supports(gdk_atom_intern("_NET_WM_STATE_FULLSCREEN",
326 FALSE));
330 * On the first map, all widgets are shown, we have to hide
331 * those that are not requested.
333 static gboolean first_map(void)
335 if (options->menu_bar == FALSE)
336 gtk_widget_hide(GTK_WIDGET(menu_bar));
338 if (options->status_bar == FALSE)
339 gtk_widget_hide(GTK_WIDGET(status_bar));
341 if (options->scrollbars == FALSE)
342 hide_scrollbars();
344 if (options->fullscreen && has_net_wm())
345 toggle_fullscreen(TRUE);
347 return FALSE;
350 static void set_initial_geometry(void)
352 GdkGeometry size_hints = {
353 1, 1, 0, 0, 1, 1, 1, 1, 0.0, 0.0, GDK_GRAVITY_NORTH_WEST
356 if (!options->initial_geometry)
357 goto fail;
359 if (options->fullscreen) {
360 g_printerr(_("Ignoring initial geometry (%s) as"
361 " fullscreen mode is requested\n"),
362 options->initial_geometry);
363 goto fail;
366 gtk_window_set_geometry_hints(main_window, GTK_WIDGET(main_window),
367 &size_hints,
368 GDK_HINT_MIN_SIZE | GDK_HINT_BASE_SIZE |
369 GDK_HINT_RESIZE_INC);
371 if (!gtk_window_parse_geometry(main_window, options->initial_geometry)) {
372 g_printerr(_("Cannot parse geometry: %s\n"), options->initial_geometry);
373 goto fail;
376 return;
378 fail:
379 g_free(options->initial_geometry);
380 options->initial_geometry = NULL;
383 void create_windows(void)
385 GtkWindow *first_window;
386 GtkAccelGroup *accel_group;
387 GtkHBox *hbox;
388 GdkPixbuf *logo = get_gliv_logo();
390 if (logo != NULL)
391 gtk_window_set_default_icon(logo);
393 rt->scr_width = gdk_screen_width();
394 rt->scr_height = gdk_screen_height();
397 * The main window: seen when not in fullscreen,
398 * or when using the NET WM protocol.
400 main_window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
401 gtk_window_set_title(main_window, "GLiv");
402 install_callbacks(main_window);
404 /* The window seen in fullscreen mode. */
405 fs_window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
406 gtk_window_set_title(fs_window, _("GLiv in fullscreen"));
408 /* Make it fullscreen. */
409 gtk_window_move(fs_window, 0, 0);
410 gtk_window_set_default_size(fs_window, rt->scr_width, rt->scr_height);
411 gtk_window_set_decorated(fs_window, FALSE);
412 install_callbacks(fs_window);
414 /* The widgets. */
415 create_gl_widget();
416 accel_group = create_menus();
417 create_status_bar();
419 /* Bind keyboard accelerators to windows. */
420 gtk_window_add_accel_group(main_window, accel_group);
421 gtk_window_add_accel_group(fs_window, accel_group);
423 /* Collection. */
424 widgets = GTK_VBOX(gtk_vbox_new(FALSE, 0));
426 /* The menu bar on the top. */
427 gtk_box_pack_start(GTK_BOX(widgets), GTK_WIDGET(menu_bar), FALSE, FALSE, 0);
429 /* The OpenGL widget and the vertical scrollbar under the menu bar. */
430 hbox = GTK_HBOX(gtk_hbox_new(FALSE, 0));
431 gtk_box_pack_start_defaults(GTK_BOX(hbox), gl_widget);
432 gtk_box_pack_start(GTK_BOX(hbox), get_new_scrollbar(FALSE), FALSE, FALSE,
434 gtk_box_pack_start_defaults(GTK_BOX(widgets), GTK_WIDGET(hbox));
436 /* The horizontal scrollbar under them. */
437 gtk_box_pack_start(GTK_BOX(widgets), get_new_scrollbar(TRUE), FALSE, FALSE,
440 /* The status bar in the bottom. */
441 gtk_box_pack_end(GTK_BOX(widgets), GTK_WIDGET(status_bar), FALSE, FALSE, 0);
443 first_window = (options->fullscreen && !has_net_wm())?
444 fs_window : main_window;
446 gtk_container_add(GTK_CONTAINER(first_window), GTK_WIDGET(widgets));
447 g_signal_connect_after(first_window, "map", G_CALLBACK(first_map), NULL);
448 gtk_widget_show_all(GTK_WIDGET(widgets));
449 set_initial_geometry();
450 gtk_widget_show_all(GTK_WIDGET(first_window));
454 * Called when going from fullscreen mode to window mode, or when
455 * the displayed image changed in window mode.
457 void goto_window(GlivImage * im, gboolean first_time)
459 gint new_width, new_height;
461 if (im == NULL)
462 im = current_image;
464 if (im == NULL) {
465 new_width = 0;
466 new_height = 0;
467 } else {
468 new_width = im->width;
469 new_height = im->height;
472 if (new_width >= rt->scr_width)
473 new_width = 3 * rt->scr_width / 4;
475 if (new_height >= rt->scr_height)
476 new_height = 3 * rt->scr_height / 4;
478 resize_window(new_width, new_height, first_time);
482 * Return TRUE if we managed to toggle the fullscreen mode using the
483 * NET WM protocol.
485 static gboolean net_wm_toggle_fullscreen(gboolean enable)
487 if (get_current_window() != main_window || has_net_wm() == FALSE)
488 return FALSE;
490 if (enable)
491 gtk_window_fullscreen(main_window);
492 else
493 gtk_window_unfullscreen(main_window);
495 return TRUE;
498 void toggle_fullscreen(gboolean enable)
500 /* We first try using the NET WM protocol, then with the old method. */
501 if (net_wm_toggle_fullscreen(enable) == FALSE) {
502 GtkWindow *new, *old;
504 if (enable) {
505 /* Go to fullscreen mode. */
506 new = fs_window;
507 old = main_window;
508 } else {
509 /* Go to window mode. */
510 new = main_window;
511 old = fs_window;
514 gtk_widget_show(GTK_WIDGET(new));
515 gtk_widget_hide(GTK_WIDGET(old));
517 gtk_widget_reparent(GTK_WIDGET(widgets), GTK_WIDGET(new));
521 if (enable == FALSE)
522 goto_window(NULL, FALSE);
524 options->fullscreen = enable;
525 schedule_hide_cursor();
528 void update_window_title(void)
530 const gchar *path;
531 gchar *basename, *title;
533 if (current_image->node->data == NULL)
534 return;
536 path = filename_to_utf8(current_image->node->data);
537 basename = g_path_get_basename(path);
538 title = g_strdup_printf("GLiv - %s", basename);
539 gtk_window_set_title(main_window, title);
540 g_free(basename);
541 g_free(title);
544 GtkWindow *get_current_window(void)
546 if (GTK_IS_WIDGET(gl_widget) && GTK_WIDGET_VISIBLE(gl_widget))
547 return GTK_WINDOW(gtk_widget_get_toplevel(gl_widget));
549 return NULL;
552 GtkWindow *new_window(const gchar * title)
554 GtkWindow *win = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
555 GtkWindow *parent = get_current_window();
557 gtk_window_set_title(win, title);
558 gtk_window_set_transient_for(win, parent);
559 gtk_window_set_destroy_with_parent(win, TRUE);
560 gtk_window_set_type_hint(win, GDK_WINDOW_TYPE_HINT_DIALOG);
562 return win;
565 gboolean toggle_floating_windows(void)
567 static GList *hidden_windows;
568 GList *ptr;
570 if (hidden_windows) {
571 for (ptr = hidden_windows; ptr; ptr = ptr->next) {
572 gtk_widget_show(GTK_WIDGET(ptr->data));
573 g_object_unref(ptr->data);
576 g_list_free(hidden_windows);
577 hidden_windows = NULL;
578 } else {
579 GList *toplevels = gtk_window_list_toplevels();
581 /* First two windows are the main window and the full screen one */
582 for (ptr = toplevels->next->next; ptr; ptr = ptr->next) {
583 GtkWidget *widget = GTK_WIDGET(ptr->data);
584 if (GTK_WIDGET_VISIBLE(widget)) {
585 gtk_widget_hide(widget);
586 hidden_windows = g_list_prepend(hidden_windows, widget);
587 g_object_ref(widget);
590 g_list_free(toplevels);
593 return FALSE;