1
/* Copyright 2009-2011 Yorba Foundation
3
* This software is licensed under the GNU LGPL (version 2.1 or later).
4
* See the COPYING file in this distribution.
7
public class FullscreenWindow : PageWindow {
8
public const int TOOLBAR_INVOCATION_MSEC = 250;
9
public const int TOOLBAR_DISMISSAL_SEC = 2;
10
public const int TOOLBAR_CHECK_DISMISSAL_MSEC = 500;
12
private Gtk.Window toolbar_window = new Gtk.Window(Gtk.WindowType.POPUP);
13
private Gtk.UIManager ui = new Gtk.UIManager();
14
private Gtk.ToolButton close_button = new Gtk.ToolButton.from_stock(Gtk.Stock.LEAVE_FULLSCREEN);
15
private Gtk.ToggleToolButton pin_button = new Gtk.ToggleToolButton.from_stock(Resources.PIN_TOOLBAR);
16
private bool is_toolbar_shown = false;
17
private bool waiting_for_invoke = false;
18
private time_t left_toolbar_time = 0;
19
private bool switched_to = false;
20
private bool is_toolbar_dismissal_enabled = true;
22
public FullscreenWindow(Page page) {
23
set_current_page(page);
25
File ui_file = Resources.get_ui("fullscreen.ui");
28
ui.add_ui_from_file(ui_file.get_path());
30
error("Error loading UI file %s: %s", ui_file.get_path(), err.message);
33
Gtk.ActionGroup action_group = new Gtk.ActionGroup("FullscreenActionGroup");
34
action_group.add_actions(create_actions(), this);
35
ui.insert_action_group(action_group, 0);
38
// add the accelerators for the hosted page as well
39
Gtk.AccelGroup hosted_accel_group = page.ui.get_accel_group();
40
if (hosted_accel_group != null)
41
add_accel_group(hosted_accel_group);
43
// the local accelerator group must come after host accelerator group so that they cover the
44
// old accelerator group bindings
45
Gtk.AccelGroup accel_group = ui.get_accel_group();
46
if (accel_group != null)
47
add_accel_group(accel_group);
49
set_screen(AppWindow.get_instance().get_screen());
51
// Needed so fullscreen will occur on correct monitor in multi-monitor setups
52
Gdk.Rectangle monitor = get_monitor_geometry();
53
move(monitor.x, monitor.y);
57
pin_button.set_label(_("Pin Toolbar"));
58
pin_button.set_tooltip_text(_("Pin the toolbar open"));
59
pin_button.clicked.connect(update_toolbar_dismissal);
61
close_button.set_tooltip_text(_("Leave fullscreen"));
62
close_button.clicked.connect(on_close);
64
Gtk.Toolbar toolbar = page.get_toolbar();
65
toolbar.set_show_arrow(false);
67
if (page is SlideshowPage) {
68
// slideshow page doesn't own toolbar to hide it, subscribe to signal instead
69
((SlideshowPage) page).hide_toolbar.connect(hide_toolbar);
71
// only non-slideshow pages should have pin button
72
toolbar.insert(pin_button, -1);
75
page.set_cursor_hide_time(TOOLBAR_DISMISSAL_SEC * 1000);
76
page.start_cursor_hiding();
78
toolbar.insert(close_button, -1);
80
// set up toolbar along bottom of screen
81
toolbar_window.set_screen(get_screen());
82
toolbar_window.set_border_width(0);
83
toolbar_window.add(toolbar);
85
toolbar_window.realize.connect(on_toolbar_realized);
89
// call to set_default_size() saves one repaint caused by changing
90
// size from default to full screen. In slideshow mode, this change
91
// also causes pixbuf cache updates, so it really saves some work.
92
set_default_size(monitor.width, monitor.height);
94
// need to create a Gdk.Window to set masks
98
// capture motion events to show the toolbar
99
add_events(Gdk.EventMask.POINTER_MOTION_MASK);
101
// start off with toolbar invoked, as a clue for the user
105
public void disable_toolbar_dismissal() {
106
is_toolbar_dismissal_enabled = false;
109
public void update_toolbar_dismissal() {
110
is_toolbar_dismissal_enabled = !pin_button.get_active();
113
private Gdk.Rectangle get_monitor_geometry() {
114
Gdk.Rectangle monitor;
116
get_screen().get_monitor_geometry(
117
get_screen().get_monitor_at_window(AppWindow.get_instance().get_window()), out monitor);
122
public override bool configure_event(Gdk.EventConfigure event) {
123
bool result = base.configure_event(event);
126
get_current_page().switched_to();
133
private Gtk.ActionEntry[] create_actions() {
134
Gtk.ActionEntry[] actions = new Gtk.ActionEntry[0];
136
Gtk.ActionEntry leave_fullscreen = { "LeaveFullscreen", Gtk.Stock.LEAVE_FULLSCREEN,
137
TRANSLATABLE, "F11", TRANSLATABLE, on_close };
138
leave_fullscreen.label = _("Leave _Fullscreen");
139
leave_fullscreen.tooltip = _("Leave fullscreen");
140
actions += leave_fullscreen;
145
public override bool key_press_event(Gdk.EventKey event) {
146
// check for an escape/abort
147
if (Gdk.keyval_name(event.keyval) == "Escape") {
153
// ...then let the base class take over
154
return (base.key_press_event != null) ? base.key_press_event(event) : false;
157
private void on_close() {
159
toolbar_window = null;
161
AppWindow.get_instance().end_fullscreen();
164
public void close() {
168
public override void destroy() {
169
Page? page = get_current_page();
170
clear_current_page();
173
page.stop_cursor_hiding();
174
page.switching_from();
180
public override bool delete_event(Gdk.Event event) {
182
AppWindow.get_instance().destroy();
187
public override bool motion_notify_event(Gdk.EventMotion event) {
188
if (!is_toolbar_shown) {
189
// if pointer is in toolbar height range without the mouse down (i.e. in the middle of
190
// an edit operation) and it stays there the necessary amount of time, invoke the
192
if (!waiting_for_invoke && is_pointer_in_toolbar()) {
193
Timeout.add(TOOLBAR_INVOCATION_MSEC, on_check_toolbar_invocation);
194
waiting_for_invoke = true;
198
return (base.motion_notify_event != null) ? base.motion_notify_event(event) : false;
201
private bool is_pointer_in_toolbar() {
203
get_display().get_pointer(null, null, out py, null);
204
toolbar_window.window.get_geometry(null, out wy, null, null, null);
208
private bool on_check_toolbar_invocation() {
209
waiting_for_invoke = false;
211
if (is_toolbar_shown)
214
if (!is_pointer_in_toolbar())
222
private void on_toolbar_realized() {
224
toolbar_window.size_request(out req);
226
// place the toolbar in the center of the monitor along the bottom edge
227
Gdk.Rectangle monitor = get_monitor_geometry();
228
int tx = monitor.x + (monitor.width - req.width) / 2;
232
int ty = monitor.y + monitor.height - req.height;
236
toolbar_window.move(tx, ty);
237
toolbar_window.set_opacity(Resources.TRANSIENT_WINDOW_OPACITY);
240
private void invoke_toolbar() {
241
toolbar_window.show_all();
243
is_toolbar_shown = true;
245
Timeout.add(TOOLBAR_CHECK_DISMISSAL_MSEC, on_check_toolbar_dismissal);
248
private bool on_check_toolbar_dismissal() {
249
if (!is_toolbar_shown)
252
if (toolbar_window == null)
255
// if dismissal is disabled, keep open but keep checking
256
if ((!is_toolbar_dismissal_enabled))
259
// if the pointer is in toolbar range, keep it alive, but keep checking
260
if (is_pointer_in_toolbar()) {
261
left_toolbar_time = 0;
266
// if this is the first time noticed, start the timer and keep checking
267
if (left_toolbar_time == 0) {
268
left_toolbar_time = time_t();
273
// see if enough time has elapsed
274
time_t now = time_t();
275
assert(now >= left_toolbar_time);
277
if (now - left_toolbar_time < TOOLBAR_DISMISSAL_SEC)
285
private void hide_toolbar() {
286
toolbar_window.hide();
287
is_toolbar_shown = false;
291
// PageWindow is a Gtk.Window with essential functions for hosting a Page. There may be more than
292
// one PageWindow in the system, and closing one does not imply exiting the application.
294
// PageWindow offers support for hosting a single Page; multiple Pages must be handled by the
295
// subclass. A subclass should set current_page to the user-visible Page for it to receive
296
// various notifications. It is the responsibility of the subclass to notify Pages when they're
297
// switched to and from, and other aspects of the Page interface.
298
public abstract class PageWindow : Gtk.Window {
299
private Page current_page = null;
300
private int busy_counter = 0;
302
protected virtual void switched_pages(Page? old_page, Page? new_page) {
305
public PageWindow() {
306
// the current page needs to know when modifier keys are pressed
307
add_events(Gdk.EventMask.KEY_PRESS_MASK | Gdk.EventMask.KEY_RELEASE_MASK
308
| Gdk.EventMask.STRUCTURE_MASK);
311
public Page? get_current_page() {
315
public virtual void set_current_page(Page page) {
316
if (current_page != null)
317
current_page.clear_container();
319
Page? old_page = current_page;
321
current_page.set_container(this);
323
switched_pages(old_page, page);
326
public virtual void clear_current_page() {
327
if (current_page != null)
328
current_page.clear_container();
330
Page? old_page = current_page;
333
switched_pages(old_page, null);
336
public override bool key_press_event(Gdk.EventKey event) {
337
if (get_focus() is Gtk.Entry && get_focus().key_press_event(event))
340
if (current_page != null && current_page.notify_app_key_pressed(event))
343
return (base.key_press_event != null) ? base.key_press_event(event) : false;
346
public override bool key_release_event(Gdk.EventKey event) {
347
if (get_focus() is Gtk.Entry && get_focus().key_release_event(event))
350
if (current_page != null && current_page.notify_app_key_released(event))
353
return (base.key_release_event != null) ? base.key_release_event(event) : false;
356
public override bool focus_in_event(Gdk.EventFocus event) {
357
if (current_page != null && current_page.notify_app_focus_in(event))
360
return (base.focus_in_event != null) ? base.focus_in_event(event) : false;
363
public override bool focus_out_event(Gdk.EventFocus event) {
364
if (current_page != null && current_page.notify_app_focus_out(event))
367
return (base.focus_out_event != null) ? base.focus_out_event(event) : false;
370
public override bool configure_event(Gdk.EventConfigure event) {
371
if (current_page != null) {
372
if (current_page.notify_configure_event(event))
376
return (base.configure_event != null) ? base.configure_event(event) : false;
379
public void set_busy_cursor() {
380
if (busy_counter++ > 0)
383
window.set_cursor(new Gdk.Cursor(Gdk.CursorType.WATCH));
387
public void set_normal_cursor() {
388
if (busy_counter <= 0) {
391
} else if (--busy_counter > 0) {
395
window.set_cursor(new Gdk.Cursor(Gdk.CursorType.LEFT_PTR));
401
// AppWindow is the parent window for most windows in Shotwell (FullscreenWindow is the exception).
402
// There are multiple types of AppWindows (LibraryWindow, DirectWindow) for different tasks, but only
403
// one AppWindow may exist per process. Thus, if the user closes an AppWindow, the program exits.
405
// AppWindow also offers support for going into fullscreen mode. It handles the interface
406
// notifications Page is expecting when switching back and forth.
407
public abstract class AppWindow : PageWindow {
408
public const int DND_ICON_SCALE = 128;
410
protected static AppWindow instance = null;
412
private static FullscreenWindow fullscreen_window = null;
413
private static CommandManager command_manager = null;
415
// the AppWindow maintains its own UI manager because the first UIManager an action group is
416
// added to is the one that claims its accelerators
417
protected Gtk.UIManager ui = new Gtk.UIManager();
418
protected Gtk.ActionGroup[] common_action_groups;
419
protected bool maximized = false;
420
protected Dimensions dimensions;
421
protected int pos_x = 0;
422
protected int pos_y = 0;
424
private Gtk.ActionGroup common_action_group = new Gtk.ActionGroup("AppWindowGlobalActionGroup");
427
// although there are multiple AppWindow types, only one may exist per-process
428
assert(instance == null);
431
title = Resources.APP_TITLE;
433
GLib.List<Gdk.Pixbuf> pixbuf_list = new GLib.List<Gdk.Pixbuf>();
434
foreach (string resource in Resources.APP_ICONS)
435
pixbuf_list.append(Resources.get_icon(resource, 0));
436
set_default_icon_list(pixbuf_list);
438
// restore previous size and maximization state
439
if (this is LibraryWindow) {
440
Config.Facade.get_instance().get_library_window_state(out maximized, out dimensions);
442
assert(this is DirectWindow);
443
Config.Facade.get_instance().get_direct_window_state(out maximized, out dimensions);
446
set_default_size(dimensions.width, dimensions.height);
451
assert(command_manager == null);
452
command_manager = new CommandManager();
453
command_manager.altered.connect(on_command_manager_altered);
455
// Because the first UIManager to associated with an ActionGroup claims the accelerators,
456
// need to create the AppWindow's ActionGroup early on and add it to an application-wide
457
// UIManager. In order to activate those accelerators, we need to create a dummy UI string
458
// that lists all the common actions. We build it on-the-fly from the actions associated
459
// with each ActionGroup while we're adding the groups to the UIManager.
460
common_action_groups = create_common_action_groups();
461
foreach (Gtk.ActionGroup group in common_action_groups)
462
ui.insert_action_group(group, 0);
465
ui.add_ui_from_string(build_dummy_ui_string(common_action_groups), -1);
466
} catch (Error err) {
467
error("Unable to add AppWindow UI: %s", err.message);
471
add_accel_group(ui.get_accel_group());
474
private Gtk.ActionEntry[] create_common_actions() {
475
Gtk.ActionEntry[] actions = new Gtk.ActionEntry[0];
477
Gtk.ActionEntry quit = { "CommonQuit", Gtk.Stock.QUIT, TRANSLATABLE, "<Ctrl>Q",
478
TRANSLATABLE, on_quit };
479
quit.label = _("_Quit");
482
Gtk.ActionEntry about = { "CommonAbout", Gtk.Stock.ABOUT, TRANSLATABLE, null,
483
TRANSLATABLE, on_about };
484
about.label = _("_About");
487
Gtk.ActionEntry fullscreen = { "CommonFullscreen", Gtk.Stock.FULLSCREEN,
488
TRANSLATABLE, "F11", TRANSLATABLE, on_fullscreen };
489
fullscreen.label = _("Fulls_creen");
490
actions += fullscreen;
492
Gtk.ActionEntry help_contents = { "CommonHelpContents", Gtk.Stock.HELP,
493
TRANSLATABLE, "F1", TRANSLATABLE, on_help_contents };
494
help_contents.label = _("_Contents");
495
actions += help_contents;
497
Gtk.ActionEntry help_faq = { "CommonHelpFAQ", null, TRANSLATABLE, null,
498
TRANSLATABLE, on_help_faq };
499
help_faq.label = _("_Frequently Asked Questions");
502
Gtk.ActionEntry undo = { "CommonUndo", Gtk.Stock.UNDO, TRANSLATABLE, "<Ctrl>Z",
503
TRANSLATABLE, on_undo };
504
undo.label = Resources.UNDO_MENU;
507
Gtk.ActionEntry redo = { "CommonRedo", Gtk.Stock.REDO, TRANSLATABLE, "<Ctrl><Shift>Z",
508
TRANSLATABLE, on_redo };
509
redo.label = Resources.REDO_MENU;
512
Gtk.ActionEntry jump_to_file = { "CommonJumpToFile", Gtk.Stock.JUMP_TO, TRANSLATABLE,
513
"<Ctrl><Shift>M", TRANSLATABLE, on_jump_to_file };
514
jump_to_file.label = Resources.JUMP_TO_FILE_MENU;
515
actions += jump_to_file;
517
Gtk.ActionEntry select_all = { "CommonSelectAll", Gtk.Stock.SELECT_ALL, TRANSLATABLE,
518
"<Ctrl>A", TRANSLATABLE, on_select_all };
519
select_all.label = Resources.SELECT_ALL_MENU;
520
actions += select_all;
522
Gtk.ActionEntry select_none = { "CommonSelectNone", null, null,
523
"<Ctrl><Shift>A", TRANSLATABLE, on_select_none };
524
actions += select_none;
529
protected abstract void on_fullscreen();
531
public static bool has_instance() {
532
return instance != null;
535
public static AppWindow get_instance() {
539
public static FullscreenWindow get_fullscreen() {
540
return fullscreen_window;
543
public static Gtk.Builder create_builder(string glade_filename = "shotwell.glade", void *user = null) {
544
Gtk.Builder builder = new Gtk.Builder();
546
builder.add_from_file(AppDirs.get_resources_dir().get_child("ui").get_child(
547
glade_filename).get_path());
548
} catch(GLib.Error error) {
549
warning("Unable to create Gtk.Builder: %s\n", error.message);
552
builder.connect_signals(user);
557
public static void error_message(string message, Gtk.Window? parent = null) {
558
error_message_with_title(Resources.APP_TITLE, message, parent);
561
public static void error_message_with_title(string title, string message, Gtk.Window? parent = null) {
562
// Per the Gnome HIG (http://library.gnome.org/devel/hig-book/2.32/windows-alert.html.en),
563
// alert-style dialogs mustn't have titles; we use the title as the primary text, and the
564
// existing message as the secondary text.
565
Gtk.MessageDialog dialog = new Gtk.MessageDialog.with_markup((parent != null) ? parent : get_instance(),
566
Gtk.DialogFlags.MODAL, Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, "%s", build_alert_body_text(title, message));
568
// Occasionally, with_markup doesn't actually do anything, but set_markup always works.
569
dialog.set_markup(build_alert_body_text(title, message));
571
dialog.use_markup = true;
576
public static bool negate_affirm_question(string message, string negative, string affirmative,
577
string? title = null, Gtk.Window? parent = null) {
578
Gtk.MessageDialog dialog = new Gtk.MessageDialog((parent != null) ? parent : get_instance(),
579
Gtk.DialogFlags.MODAL, Gtk.MessageType.QUESTION, Gtk.ButtonsType.NONE, "%s", build_alert_body_text(title, message));
581
dialog.set_markup(build_alert_body_text(title, message));
582
dialog.add_buttons(negative, Gtk.ResponseType.NO, affirmative, Gtk.ResponseType.YES);
584
bool response = (dialog.run() == Gtk.ResponseType.YES);
591
public static Gtk.ResponseType negate_affirm_cancel_question(string message, string negative,
592
string affirmative, string? title = null, Gtk.Window? parent = null) {
593
Gtk.MessageDialog dialog = new Gtk.MessageDialog.with_markup((parent != null) ? parent : get_instance(),
594
Gtk.DialogFlags.MODAL, Gtk.MessageType.QUESTION, Gtk.ButtonsType.NONE, "%s", build_alert_body_text(title, message));
596
dialog.add_buttons(negative, Gtk.ResponseType.NO, affirmative, Gtk.ResponseType.YES,
597
_("_Cancel"), Gtk.ResponseType.CANCEL);
599
// Occasionally, with_markup doesn't actually enable markup, but set_markup always works.
600
dialog.set_markup(build_alert_body_text(title, message));
601
dialog.use_markup = true;
603
int response = dialog.run();
607
return (Gtk.ResponseType) response;
610
public static Gtk.ResponseType affirm_cancel_question(string message, string affirmative,
611
string? title = null, Gtk.Window? parent = null) {
612
Gtk.MessageDialog dialog = new Gtk.MessageDialog.with_markup((parent != null) ? parent : get_instance(),
613
Gtk.DialogFlags.MODAL, Gtk.MessageType.QUESTION, Gtk.ButtonsType.NONE, message);
614
dialog.title = (title != null) ? title : Resources.APP_TITLE;
615
dialog.add_buttons(affirmative, Gtk.ResponseType.YES, _("_Cancel"),
616
Gtk.ResponseType.CANCEL);
618
int response = dialog.run();
622
return (Gtk.ResponseType) response;
625
public static Gtk.ResponseType negate_affirm_all_cancel_question(string message,
626
string negative, string affirmative, string affirmative_all, string? title = null,
627
Gtk.Window? parent = null) {
628
Gtk.MessageDialog dialog = new Gtk.MessageDialog((parent != null) ? parent : get_instance(),
629
Gtk.DialogFlags.MODAL, Gtk.MessageType.QUESTION, Gtk.ButtonsType.NONE, "%s", message);
630
dialog.title = (title != null) ? title : Resources.APP_TITLE;
631
dialog.add_buttons(negative, Gtk.ResponseType.NO, affirmative, Gtk.ResponseType.YES,
632
affirmative_all, Gtk.ResponseType.APPLY, _("_Cancel"), Gtk.ResponseType.CANCEL);
634
int response = dialog.run();
638
return (Gtk.ResponseType) response;
641
public static void database_error(DatabaseError err) {
642
panic(_("A fatal error occurred when accessing Shotwell's library. Shotwell cannot continue.\n\n%s").printf(
646
public static void panic(string msg) {
650
Application.get_instance().panic();
653
public abstract string get_app_role();
655
protected void on_about() {
656
Gtk.show_about_dialog(this,
657
"version", Resources.APP_VERSION,
658
"comments", get_app_role(),
659
"copyright", Resources.COPYRIGHT,
660
"website", Resources.YORBA_URL,
661
"license", Resources.LICENSE,
662
"website-label", _("Visit the Yorba web site"),
663
"authors", Resources.AUTHORS,
664
"logo", Resources.get_icon(Resources.ICON_ABOUT_LOGO, -1),
665
"translator-credits", _("translator-credits"),
670
private void on_help_contents() {
672
Resources.launch_help(get_screen());
673
} catch (Error err) {
674
error_message(_("Unable to display help: %s").printf(err.message));
678
private void on_help_faq() {
680
show_uri(Resources.FAQ_URL);
681
} catch (Error err) {
682
error_message(_("Unable to display FAQ: %s").printf(err.message));
686
protected virtual void on_quit() {
687
Application.get_instance().exit();
690
protected void on_jump_to_file() {
691
if (get_current_page().get_view().get_selected_count() != 1)
694
MediaSource? media = get_current_page().get_view().get_selected_at(0).get_source()
700
AppWindow.get_instance().show_file_uri(media.get_master_file().get_parent());
701
} catch (Error err) {
702
AppWindow.error_message(Resources.jump_to_file_failed(err));
706
protected override void destroy() {
710
public void show_file_uri(File file) throws Error {
711
show_uri(file.get_uri());
714
public void show_uri(string url) throws Error {
715
sys_show_uri(window.get_screen(), url);
718
protected virtual Gtk.ActionGroup[] create_common_action_groups() {
719
Gtk.ActionGroup[] groups = new Gtk.ActionGroup[0];
721
common_action_group.add_actions(create_common_actions(), this);
722
groups += common_action_group;
727
public Gtk.ActionGroup[] get_common_action_groups() {
728
return common_action_groups;
731
public virtual void replace_common_placeholders(Gtk.UIManager ui) {
734
public void go_fullscreen(Page page) {
735
// if already fullscreen, use that
736
if (fullscreen_window != null) {
737
fullscreen_window.present();
742
get_position(out pos_x, out pos_y);
745
FullscreenWindow fsw = new FullscreenWindow(page);
747
if (get_current_page() != null)
748
get_current_page().switching_to_fullscreen(fsw);
750
fullscreen_window = fsw;
751
fullscreen_window.present();
754
public void end_fullscreen() {
755
if (fullscreen_window == null)
762
if (get_current_page() != null)
763
get_current_page().returning_from_fullscreen(fullscreen_window);
765
fullscreen_window.hide();
766
fullscreen_window.destroy();
767
fullscreen_window = null;
772
public Gtk.Action? get_common_action(string name) {
773
foreach (Gtk.ActionGroup group in common_action_groups) {
774
Gtk.Action? action = group.get_action(name);
779
warning("No common action found: %s", name);
784
public void set_common_action_sensitive(string name, bool sensitive) {
785
Gtk.Action? action = get_common_action(name);
787
action.sensitive = sensitive;
790
public void set_common_action_important(string name, bool important) {
791
Gtk.Action? action = get_common_action(name);
793
action.is_important = important;
796
public void set_common_action_visible(string name, bool visible) {
797
Gtk.Action? action = get_common_action(name);
799
action.visible = visible;
802
protected override void switched_pages(Page? old_page, Page? new_page) {
803
update_common_action_availability(old_page, new_page);
805
if (old_page != null) {
806
old_page.get_view().contents_altered.disconnect(on_update_common_actions);
807
old_page.get_view().selection_group_altered.disconnect(on_update_common_actions);
808
old_page.get_view().items_state_changed.disconnect(on_update_common_actions);
811
if (new_page != null) {
812
new_page.get_view().contents_altered.connect(on_update_common_actions);
813
new_page.get_view().selection_group_altered.connect(on_update_common_actions);
814
new_page.get_view().items_state_changed.connect(on_update_common_actions);
816
update_common_actions(new_page, new_page.get_view().get_selected_count(),
817
new_page.get_view().get_count());
820
base.switched_pages(old_page, new_page);
823
// This is called when a Page is switched out and certain common actions are simply
824
// unavailable for the new one. This is different than update_common_actions() in that that
825
// call is made when state within the Page has changed.
826
protected virtual void update_common_action_availability(Page? old_page, Page? new_page) {
827
bool is_checkerboard = new_page is CheckerboardPage;
829
set_common_action_sensitive("CommonSelectAll", is_checkerboard);
830
set_common_action_sensitive("CommonSelectNone", is_checkerboard);
833
// This is a counterpart to Page.update_actions(), but for common Gtk.Actions
834
// NOTE: Although CommonFullscreen is declared here, it's implementation is up to the subclasses,
835
// therefore they need to update its action.
836
protected virtual void update_common_actions(Page page, int selected_count, int count) {
837
if (page is CheckerboardPage)
838
set_common_action_sensitive("CommonSelectAll", count > 0);
839
set_common_action_sensitive("CommonJumpToFile", selected_count == 1);
841
decorate_undo_action();
842
decorate_redo_action();
845
private void on_update_common_actions() {
846
Page? page = get_current_page();
848
update_common_actions(page, page.get_view().get_selected_count(), page.get_view().get_count());
851
public static CommandManager get_command_manager() {
852
return command_manager;
855
private void on_command_manager_altered() {
856
decorate_undo_action();
857
decorate_redo_action();
860
private void decorate_command_manager_action(string name, string prefix,
861
string default_explanation, CommandDescription? desc) {
862
Gtk.Action? action = get_common_action(name);
867
action.label = "%s %s".printf(prefix, desc.get_name());
868
action.tooltip = desc.get_explanation();
869
action.sensitive = true;
871
action.label = prefix;
872
action.tooltip = default_explanation;
873
action.sensitive = false;
877
public void decorate_undo_action() {
878
decorate_command_manager_action("CommonUndo", Resources.UNDO_MENU, "",
879
get_command_manager().get_undo_description());
882
public void decorate_redo_action() {
883
decorate_command_manager_action("CommonRedo", Resources.REDO_MENU, "",
884
get_command_manager().get_redo_description());
887
private void on_undo() {
888
command_manager.undo();
891
private void on_redo() {
892
command_manager.redo();
895
private void on_select_all() {
896
Page? page = get_current_page() as CheckerboardPage;
898
page.get_view().select_all();
901
private void on_select_none() {
902
Page? page = get_current_page() as CheckerboardPage;
904
page.get_view().unselect_all();
907
public override bool configure_event(Gdk.EventConfigure event) {
908
maximized = (window.get_state() == Gdk.WindowState.MAXIMIZED);
911
get_size(out dimensions.width, out dimensions.height);
913
return base.configure_event(event);