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() */
31 #include "gliv-image.h"
32 #include "callbacks.h"
34 #include "math_floats.h"
35 #include "str_utils.h"
37 #include "scrollbars.h"
38 #include "gl_widget.h"
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
)
70 if (options
->resize_win
== FALSE
&& first_time
== FALSE
)
73 if (first_time
&& options
->initial_geometry
)
76 width
= MAX(width
, 320);
77 height
= MAX(height
, 240);
79 if (width
== rt
->wid_size
->width
&& height
== rt
->wid_size
->height
)
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
);
101 gtk_window_set_title(GTK_WINDOW(widget
), name
);
103 gtk_widget_show_all(widget
);
106 gint
run_modal_dialog(GtkDialog
* dialog
)
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
);
125 static gint
get_entry_border(GtkEntry
* entry
)
127 gint interior_focus
, focus_width
;
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;
137 border
+= focus_width
;
139 return border
+ 2; /* + 2 : INNER_BORDER */
142 static gint
status_bar_free_space(void)
145 GtkEntry
*entries
[] = { entry_ident
, entry_size
, entry_state
, NULL
};
148 for (ptr
= entries
; *ptr
!= NULL
; ptr
++) {
151 gtk_widget_size_request(GTK_WIDGET(*ptr
), &req
);
152 space
+= GTK_WIDGET(*ptr
)->allocation
.width
- req
.width
;
158 static void set_entry_text(GtkEntry
* entry
, const gchar
* text
)
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
;
177 accept_resize
= old
.width
- req
.width
> 20;
180 gtk_widget_set_size_request(GTK_WIDGET(entry
), req
.width
, req
.height
);
183 static gint
ident_basename_pos(const gchar
* ident
)
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
) == '/') {
199 static void set_ident_text(const gchar
* ident
)
202 const gchar
*loading
= currently_loading();
204 pos
= ident_basename_pos(ident
);
207 gchar
*concat
= g_strconcat(ident
, " ", _("loading"), ":",
209 gtk_entry_set_text(entry_ident
, concat
);
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
)
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
);
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
;
255 for (i
= 0; i
< nb_widgets
; i
++) {
257 gtk_widget_show(widgets
[i
]);
259 gtk_widget_hide(widgets
[i
]);
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
);
278 gboolean
toggle_status_bar(void)
280 toggle_widget(GTK_WIDGET(status_bar
), &options
->status_bar
);
284 /* To take into account the new filename, zoom, angle, symmetry. */
285 void update_status_bar(void)
287 gchar
*size_str
, *state_str
;
291 if (current_image
== NULL
)
292 /* Loading first image */
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
);
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))
316 state_str
= g_strdup_printf(_("%.3f%% (%.3f deg%s)"), zoom
, angle
, sym
);
317 set_entry_text(entry_state
, 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",
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
)
344 if (options
->fullscreen
&& has_net_wm())
345 toggle_fullscreen(TRUE
);
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
)
359 if (options
->fullscreen
) {
360 g_printerr(_("Ignoring initial geometry (%s) as"
361 " fullscreen mode is requested\n"),
362 options
->initial_geometry
);
366 gtk_window_set_geometry_hints(main_window
, GTK_WIDGET(main_window
),
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
);
379 g_free(options
->initial_geometry
);
380 options
->initial_geometry
= NULL
;
383 void create_windows(void)
385 GtkWindow
*first_window
;
386 GtkAccelGroup
*accel_group
;
388 GdkPixbuf
*logo
= get_gliv_logo();
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
);
416 accel_group
= create_menus();
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
);
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
;
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
485 static gboolean
net_wm_toggle_fullscreen(gboolean enable
)
487 if (get_current_window() != main_window
|| has_net_wm() == FALSE
)
491 gtk_window_fullscreen(main_window
);
493 gtk_window_unfullscreen(main_window
);
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
;
505 /* Go to fullscreen mode. */
509 /* Go to window mode. */
514 gtk_widget_show(GTK_WIDGET(new));
515 gtk_widget_hide(GTK_WIDGET(old
));
517 gtk_widget_reparent(GTK_WIDGET(widgets
), GTK_WIDGET(new));
522 goto_window(NULL
, FALSE
);
524 options
->fullscreen
= enable
;
525 schedule_hide_cursor();
528 void update_window_title(void)
531 gchar
*basename
, *title
;
533 if (current_image
->node
->data
== NULL
)
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
);
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
));
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
);
565 gboolean
toggle_floating_windows(void)
567 static GList
*hidden_windows
;
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
;
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
);