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@yahoo.fr>
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 w
= rt
->wid_size
->width
;
256 h
= rt
->wid_size
->height
;
258 for (i
= 0; i
< nb_widgets
; i
++) {
260 gtk_widget_show(widgets
[i
]);
262 gtk_widget_hide(widgets
[i
]);
270 static gboolean
toggle_widget(GtkWidget
* widget
, gboolean
* flag
)
272 return toggle_widgets(&widget
, 1, flag
);
275 gboolean
toggle_menu_bar(void)
277 toggle_widget(GTK_WIDGET(menu_bar
), &options
->menu_bar
);
281 gboolean
toggle_status_bar(void)
283 toggle_widget(GTK_WIDGET(status_bar
), &options
->status_bar
);
287 /* To take into account the new filename, zoom, angle, symmetry. */
288 void update_status_bar(void)
290 gchar
*size_str
, *state_str
;
294 if (current_image
== NULL
)
295 /* Loading first image */
297 else if (current_image
->ident
!= NULL
) {
298 /* Filename and dimensions status, only the first time. */
299 set_ident_text(current_image
->ident
);
300 g_free(current_image
->ident
);
301 current_image
->ident
= NULL
;
303 size_str
= g_strdup_printf("%dx%d",
304 current_image
->width
, current_image
->height
);
305 set_entry_text(entry_size
, size_str
);
311 zoom
= get_matrix_zoom() * 100.0;
312 angle
= get_matrix_angle() * 180.0 / PI
;
313 sym
= is_matrix_symmetry()? " /" : "";
315 /* We don't want -0.000 */
316 if (float_equal(angle
, 0.0))
319 state_str
= g_strdup_printf(_("%.3f%% (%.3f deg%s)"), zoom
, angle
, sym
);
320 set_entry_text(entry_state
, state_str
);
325 /* Does the WM support the EWMH protocol? */
326 static gboolean
has_net_wm(void)
328 return gdk_net_wm_supports(gdk_atom_intern("_NET_WM_STATE_FULLSCREEN",
333 * On the first map, all widgets are shown, we have to hide
334 * those that are not requested.
336 static gboolean
first_map(void)
338 if (options
->menu_bar
== FALSE
)
339 gtk_widget_hide(GTK_WIDGET(menu_bar
));
341 if (options
->status_bar
== FALSE
)
342 gtk_widget_hide(GTK_WIDGET(status_bar
));
344 if (options
->scrollbars
== FALSE
)
347 if (options
->fullscreen
&& has_net_wm())
348 toggle_fullscreen(TRUE
);
353 static void set_initial_geometry(void)
355 GdkGeometry size_hints
= {
356 1, 1, 0, 0, 1, 1, 1, 1, 0.0, 0.0, GDK_GRAVITY_NORTH_WEST
359 if (!options
->initial_geometry
)
362 if (options
->fullscreen
) {
363 g_printerr(_("Ignoring initial geometry (%s) as"
364 " fullscreen mode is requested\n"),
365 options
->initial_geometry
);
369 gtk_window_set_geometry_hints(main_window
, GTK_WIDGET(main_window
),
371 GDK_HINT_MIN_SIZE
| GDK_HINT_BASE_SIZE
|
372 GDK_HINT_RESIZE_INC
);
374 if (!gtk_window_parse_geometry(main_window
, options
->initial_geometry
)) {
375 g_printerr(_("Cannot parse geometry: %s\n"), options
->initial_geometry
);
382 g_free(options
->initial_geometry
);
383 options
->initial_geometry
= NULL
;
386 void create_windows(void)
388 GtkWindow
*first_window
;
389 GtkAccelGroup
*accel_group
;
391 GdkPixbuf
*logo
= get_gliv_logo();
394 gtk_window_set_default_icon(logo
);
396 rt
->scr_width
= gdk_screen_width();
397 rt
->scr_height
= gdk_screen_height();
400 * The main window: seen when not in fullscreen,
401 * or when using the NET WM protocol.
403 main_window
= GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL
));
404 gtk_window_set_title(main_window
, "GLiv");
405 install_callbacks(main_window
);
407 /* The window seen in fullscreen mode. */
408 fs_window
= GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL
));
409 gtk_window_set_title(fs_window
, _("GLiv in fullscreen"));
411 /* Make it fullscreen. */
412 gtk_window_move(fs_window
, 0, 0);
413 gtk_window_set_default_size(fs_window
, rt
->scr_width
, rt
->scr_height
);
414 gtk_window_set_decorated(fs_window
, FALSE
);
415 install_callbacks(fs_window
);
419 accel_group
= create_menus();
422 /* Bind keyboard accelerators to windows. */
423 gtk_window_add_accel_group(main_window
, accel_group
);
424 gtk_window_add_accel_group(fs_window
, accel_group
);
427 widgets
= GTK_VBOX(gtk_vbox_new(FALSE
, 0));
429 /* The menu bar on the top. */
430 gtk_box_pack_start(GTK_BOX(widgets
), GTK_WIDGET(menu_bar
), FALSE
, FALSE
, 0);
432 /* The OpenGL widget and the vertical scrollbar under the menu bar. */
433 hbox
= GTK_HBOX(gtk_hbox_new(FALSE
, 0));
434 gtk_box_pack_start_defaults(GTK_BOX(hbox
), gl_widget
);
435 gtk_box_pack_start(GTK_BOX(hbox
), get_new_scrollbar(FALSE
), FALSE
, FALSE
,
437 gtk_box_pack_start_defaults(GTK_BOX(widgets
), GTK_WIDGET(hbox
));
439 /* The horizontal scrollbar under them. */
440 gtk_box_pack_start(GTK_BOX(widgets
), get_new_scrollbar(TRUE
), FALSE
, FALSE
,
443 /* The status bar in the bottom. */
444 gtk_box_pack_end(GTK_BOX(widgets
), GTK_WIDGET(status_bar
), FALSE
, FALSE
, 0);
446 first_window
= (options
->fullscreen
&& !has_net_wm())?
447 fs_window
: main_window
;
449 gtk_container_add(GTK_CONTAINER(first_window
), GTK_WIDGET(widgets
));
450 g_signal_connect_after(first_window
, "map", G_CALLBACK(first_map
), NULL
);
451 gtk_widget_show_all(GTK_WIDGET(widgets
));
452 set_initial_geometry();
453 gtk_widget_show_all(GTK_WIDGET(first_window
));
457 * Called when going from fullscreen mode to window mode, or when
458 * the displayed image changed in window mode.
460 void goto_window(GlivImage
* im
, gboolean first_time
)
462 gint new_width
, new_height
;
471 new_width
= im
->width
;
472 new_height
= im
->height
;
475 if (new_width
>= rt
->scr_width
)
476 new_width
= 3 * rt
->scr_width
/ 4;
478 if (new_height
>= rt
->scr_height
)
479 new_height
= 3 * rt
->scr_height
/ 4;
481 resize_window(new_width
, new_height
, first_time
);
485 * Return TRUE if we managed to toggle the fullscreen mode using the
488 static gboolean
net_wm_toggle_fullscreen(gboolean enable
)
490 if (get_current_window() != main_window
|| has_net_wm() == FALSE
)
494 gtk_window_fullscreen(main_window
);
496 gtk_window_unfullscreen(main_window
);
501 void toggle_fullscreen(gboolean enable
)
503 /* We first try using the NET WM protocol, then with the old method. */
504 if (net_wm_toggle_fullscreen(enable
) == FALSE
) {
505 GtkWindow
*new, *old
;
508 /* Go to fullscreen mode. */
512 /* Go to window mode. */
517 gtk_widget_show(GTK_WIDGET(new));
518 gtk_widget_hide(GTK_WIDGET(old
));
520 gtk_widget_reparent(GTK_WIDGET(widgets
), GTK_WIDGET(new));
525 goto_window(NULL
, FALSE
);
527 options
->fullscreen
= enable
;
528 schedule_hide_cursor();
531 void update_window_title(void)
534 gchar
*basename
, *title
;
536 if (current_image
->node
->data
== NULL
)
539 path
= filename_to_utf8(current_image
->node
->data
);
540 basename
= g_path_get_basename(path
);
541 title
= g_strdup_printf("GLiv - %s", basename
);
542 gtk_window_set_title(main_window
, title
);
547 GtkWindow
*get_current_window(void)
549 if (GTK_IS_WIDGET(gl_widget
) && GTK_WIDGET_VISIBLE(gl_widget
))
550 return GTK_WINDOW(gtk_widget_get_toplevel(gl_widget
));
555 GtkWindow
*new_window(const gchar
* title
)
557 GtkWindow
*win
= GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL
));
558 GtkWindow
*parent
= get_current_window();
560 gtk_window_set_title(win
, title
);
561 gtk_window_set_transient_for(win
, parent
);
562 gtk_window_set_destroy_with_parent(win
, TRUE
);
563 gtk_window_set_type_hint(win
, GDK_WINDOW_TYPE_HINT_DIALOG
);
568 gboolean
toggle_floating_windows(void)
570 static GList
*hidden_windows
;
573 if (hidden_windows
) {
574 for (ptr
= hidden_windows
; ptr
; ptr
= ptr
->next
) {
575 gtk_widget_show(GTK_WIDGET(ptr
->data
));
576 g_object_unref(ptr
->data
);
579 g_list_free(hidden_windows
);
580 hidden_windows
= NULL
;
582 GList
*toplevels
= gtk_window_list_toplevels();
584 /* First two windows are the main window and the full screen one */
585 for (ptr
= toplevels
->next
->next
; ptr
; ptr
= ptr
->next
) {
586 GtkWidget
*widget
= GTK_WIDGET(ptr
->data
);
587 if (GTK_WIDGET_VISIBLE(widget
)) {
588 gtk_widget_hide(widget
);
589 hidden_windows
= g_list_prepend(hidden_windows
, widget
);
590 g_object_ref(widget
);
593 g_list_free(toplevels
);