Page.vala 38.3 KB
Newer Older
1
/* Copyright 2016 Software Freedom Conservancy Inc.
2 3
 *
 * This software is licensed under the GNU LGPL (version 2.1 or later).
4
 * See the COPYING file in this distribution.
5
 */
6

7
public class InjectionGroup {
8
    public class Element {
9 10 11 12 13
        public enum ItemType {
            MENUITEM,
            MENU,
            SEPARATOR
        }
14 15
        public string name;
        public string action;
16 17 18 19
        public string? accellerator;
        public ItemType kind;

        public Element(string name, string? action, string? accellerator, ItemType kind) {
20
            this.name = name;
21
            this.action = action != null ? action : name;
22
            this.accellerator = accellerator;
23 24 25
            this.kind = kind;
        }
    }
26

27 28
    private string path;
    private Gee.ArrayList<Element?> elements = new Gee.ArrayList<Element?>();
29
    private int separator_id = 0;
30

31 32 33
    public InjectionGroup(string path) {
        this.path = path;
    }
34

35 36 37
    public string get_path() {
        return path;
    }
38

39 40 41
    public Gee.List<Element?> get_elements() {
        return elements;
    }
42

43
    public void add_menu_item(string name, string? action = null, string? accellerator = null) {
44
        elements.add(new Element(name, action, accellerator, Element.ItemType.MENUITEM));
45
    }
46

47
    public void add_menu(string name, string? action = null) {
48
        elements.add(new Element(name, action, null, Element.ItemType.MENU));
49
    }
50

51
    public void add_separator() {
52 53 54
        elements.add(new Element("%d-separator".printf(separator_id++), null,
                    null,
                    Element.ItemType.SEPARATOR));
55 56
    }
}
57

58
public abstract class Page : Gtk.ScrolledWindow {
59 60
    private const int CONSIDER_CONFIGURE_HALTED_MSEC = 400;
    
61
    protected Gtk.Builder builder = new Gtk.Builder ();
62 63
    protected Gtk.Toolbar toolbar;
    protected bool in_view = false;
64
    
65
    private string page_name;
66
    private ViewCollection view = null;
67
    private Gtk.Window container = null;
68
    private string toolbar_path;
69 70 71
    private Gdk.Rectangle last_position = Gdk.Rectangle();
    private Gtk.Widget event_source = null;
    private bool dnd_enabled = false;
72 73 74
    private ulong last_configure_ms = 0;
    private bool report_move_finished = false;
    private bool report_resize_finished = false;
75
    private Gdk.Point last_down = Gdk.Point();
76
    private bool is_destroyed = false;
77 78 79 80
    private bool ctrl_pressed = false;
    private bool alt_pressed = false;
    private bool shift_pressed = false;
    private bool super_pressed = false;
81
    private Gdk.CursorType last_cursor = Gdk.CursorType.LEFT_PTR;
82 83 84 85
    private bool cursor_hidden = false;
    private int cursor_hide_msec = 0;
    private uint last_timeout_id = 0;
    private int cursor_hide_time_cached = 0;
86 87
    private bool are_actions_attached = false;
    private OneShotScheduler? update_actions_scheduler = null;
88
    
89
    protected Page(string page_name) {
90
        this.page_name = page_name;
91 92
        
        view = new ViewCollection("ViewCollection for Page %s".printf(page_name));
93
        
94 95
        last_down = { -1, -1 };
        
Jim Nelson's avatar
Jim Nelson committed
96
        set_can_focus(true);
97

98
        popup_menu.connect(on_context_keypress);
99
        
100
        realize.connect(attach_view_signals);
101
    }
102 103
    
    ~Page() {
104
#if TRACE_DTORS
105
        debug("DTOR: Page %s", page_name);
106 107 108 109 110
#endif
    }
    
    // This is called by the page controller when it has removed this page ... pages should override
    // this (or the signal) to clean up
111
    public override void destroy() {
112 113 114
        if (is_destroyed)
            return;
        
115
        // untie signals
116
        detach_event_source();
117
        detach_view_signals();
118
        view.close();
119 120 121
        
        // remove refs to external objects which may be pointing to the Page
        clear_container();
122
        
123 124
        if (toolbar != null)
            toolbar.destroy();
125 126 127 128 129
        
        // halt any pending callbacks
        if (update_actions_scheduler != null)
            update_actions_scheduler.cancel();
        
130
        is_destroyed = true;
131 132
        
        base.destroy();
133 134
        
        debug("Page %s Destroyed", get_page_name());
135 136
    }
    
137 138 139 140
    public string get_page_name() {
        return page_name;
    }
    
141
    public virtual void set_page_name(string page_name) {
142 143 144
        this.page_name = page_name;
    }
    
145 146 147 148
    public string to_string() {
        return page_name;
    }
    
149 150 151 152
    public ViewCollection get_view() {
        return view;
    }
    
153 154 155 156 157 158 159 160 161 162 163 164 165 166
    public Gtk.Window? get_container() {
        return container;
    }
    
    public virtual void set_container(Gtk.Window container) {
        assert(this.container == null);
        
        this.container = container;
    }
    
    public virtual void clear_container() {
        container = null;
    }
    
167 168 169 170
    public void set_event_source(Gtk.Widget event_source) {
        assert(this.event_source == null);

        this.event_source = event_source;
Jim Nelson's avatar
Jim Nelson committed
171
        event_source.set_can_focus(true);
172

173 174 175
        // interested in mouse button and motion events on the event source
        event_source.add_events(Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK
            | Gdk.EventMask.POINTER_MOTION_MASK | Gdk.EventMask.POINTER_MOTION_HINT_MASK
176
            | Gdk.EventMask.BUTTON_MOTION_MASK | Gdk.EventMask.LEAVE_NOTIFY_MASK
177
            | Gdk.EventMask.SCROLL_MASK | Gdk.EventMask.SMOOTH_SCROLL_MASK);
178 179 180
        event_source.button_press_event.connect(on_button_pressed_internal);
        event_source.button_release_event.connect(on_button_released_internal);
        event_source.motion_notify_event.connect(on_motion_internal);
181
        event_source.leave_notify_event.connect(on_leave_notify_event);
182
        event_source.scroll_event.connect(on_mousewheel_internal);
183
        event_source.realize.connect(on_event_source_realize);
184 185
    }
    
186 187 188 189
    private void detach_event_source() {
        if (event_source == null)
            return;
        
190 191 192
        event_source.button_press_event.disconnect(on_button_pressed_internal);
        event_source.button_release_event.disconnect(on_button_released_internal);
        event_source.motion_notify_event.disconnect(on_motion_internal);
193
        event_source.leave_notify_event.disconnect(on_leave_notify_event);
194
        event_source.scroll_event.disconnect(on_mousewheel_internal);
195 196
        
        disable_drag_source();
197 198
        
        event_source = null;
199 200
    }
    
201 202
    public Gtk.Widget? get_event_source() {
        return event_source;
203 204
    }

205 206 207
    private bool menubar_injected = false;
    public GLib.MenuModel get_menubar() {
        var model = builder.get_object ("MenuBar") as GLib.Menu;
208 209 210
        if (model == null) {
            return new GLib.Menu();
        }
211

212
        if (!menubar_injected) {
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247
            // Collect injected UI elements and add them to the UI manager
            InjectionGroup[] injection_groups = init_collect_injection_groups();
            foreach (InjectionGroup group in injection_groups) {
                var items = model.get_n_items ();
                for (int i = 0; i < items; i++) {
                    var submenu = model.get_item_link (i, GLib.Menu.LINK_SUBMENU);

                    var section = this.find_extension_point (submenu,
                                                             group.get_path ());

                    if (section == null) {
                        continue;
                    }

                    foreach (var element in group.get_elements ()) {
                        var menu = section as GLib.Menu;
                        switch (element.kind) {
                            case InjectionGroup.Element.ItemType.MENUITEM:
                                var item = new GLib.MenuItem (element.name,
                                                              "win." + element.action);
                                if (element.accellerator != null) {
                                    item.set_attribute ("accel",
                                                        "s",
                                                        element.accellerator);
                                }

                                menu.append_item (item);
                                break;
                            default:
                                break;
                        }
                    }
                }
            }

248
            this.menubar_injected = true;
249 250
        }

251
        return model;
252 253
    }

254
    public virtual Gtk.Toolbar get_toolbar() {
255
        if (toolbar == null) {
256
            toolbar = toolbar_path == null ? new Gtk.Toolbar() :
257 258
                                             builder.get_object (toolbar_path)
                                             as Gtk.Toolbar;
259
            toolbar.get_style_context().add_class("bottom-toolbar");  // for elementary theme
260
            toolbar.set_icon_size(Gtk.IconSize.SMALL_TOOLBAR);
261
        }
262 263 264 265 266 267
        return toolbar;
    }
    
    public virtual Gtk.Menu? get_page_context_menu() {
        return null;
    }
268 269
    
    public virtual void switching_from() {
270
        in_view = false;
271 272 273 274 275
        //remove_actions(AppWindow.get_instance());
        var map = get_container() as GLib.ActionMap;
        if (map != null) {
            remove_actions(map);
        }
276 277
        if (toolbar_path != null)
            toolbar = null;
278 279 280
    }
    
    public virtual void switched_to() {
281
        in_view = true;
282
        add_ui();
283 284 285 286
        var map = get_container() as GLib.ActionMap;
        if (map != null) {
            add_actions(map);
        }
287 288 289 290
        int selected_count = get_view().get_selected_count();
        int count = get_view().get_count();
        init_actions(selected_count, count);
        update_actions(selected_count, count);
291
        update_modifiers();
292 293
    }
    
294 295 296
    public virtual void ready() {
    }
    
297 298
    public bool is_in_view() {
        return in_view;
299 300
    }
    
301
    public virtual void switching_to_fullscreen(FullscreenWindow fsw) {
Jens Georg's avatar
Jens Georg committed
302
        add_actions(fsw);
303 304
    }
    
305
    public virtual void returning_from_fullscreen(FullscreenWindow fsw) {
Jens Georg's avatar
Jens Georg committed
306 307
        remove_actions(fsw);
        switched_to();
308
    }
309 310

    public GLib.Action? get_action (string name) {
Jens Georg's avatar
Jens Georg committed
311 312 313 314 315 316
        GLib.ActionMap? map = null;
        if (container is FullscreenWindow) {
            map = container as GLib.ActionMap;
        } else {
            map = AppWindow.get_instance () as GLib.ActionMap;
        }
317

Jens Georg's avatar
Jens Georg committed
318 319
        if (map != null) {
            return map.lookup_action(name);
320 321 322
        }

        return null;
323
    }
324
    
325
    public void set_action_sensitive(string name, bool sensitive) {
326
        GLib.SimpleAction? action = get_action(name) as GLib.SimpleAction;
327
        if (action != null)
328
            action.set_enabled (sensitive);
329 330
    }
    
331
    public void set_action_details(string name, string? label, string? tooltip, bool sensitive) {
332 333
        GLib.SimpleAction? action = get_action(name) as GLib.SimpleAction;

334
        if (action == null)
335
            return;
336

337
        if (label != null)
338 339 340
            this.update_menu_item_label (name, label);

        action.set_enabled (sensitive);
341 342
    }
    
343
    public void activate_action(string name) {
344 345
        var action = get_action(name);

346
        if (action != null)
347
            action.activate (null);
348 349
    }
    
350 351 352 353 354
    public GLib.Action? get_common_action(string name, bool log_warning = true) {
        var action = get_action (name);

        if (action != null)
            return action;
355 356
        
        if (log_warning)
357
            warning("Page %s: Unable to locate common action %s", get_page_name(), name);
358
        
359
        return null;
360 361 362
    }
    
    public void set_common_action_sensitive(string name, bool sensitive) {
363
        var action = get_common_action(name) as GLib.SimpleAction;
364
        if (action != null)
365
            action.set_enabled (sensitive);
366
    }
367 368

    public void set_common_action_label(string name, string label) {
369
        debug ("Trying to set common action label for %s", name);
370
    }
371 372
    
    public void set_common_action_important(string name, bool important) {
373
        debug ("Setting action to important: %s", name);
374 375 376
    }
    
    public void activate_common_action(string name) {
377
        var action = get_common_action(name) as GLib.SimpleAction;
378
        if (action != null)
379
            action.activate(null);
380
    }
381
    
382 383 384 385 386 387 388 389 390 391 392 393 394 395 396
    public bool get_ctrl_pressed() {
        return ctrl_pressed;
    }
    
    public bool get_alt_pressed() {
        return alt_pressed;
    }
    
    public bool get_shift_pressed() {
        return shift_pressed;
    }
    
    public bool get_super_pressed() {
        return super_pressed;
    }
397 398 399 400 401 402 403 404

     protected void set_action_active (string name, bool active) {
        var action = get_action (name) as GLib.SimpleAction;
        if (action != null) {
            action.set_state (active);
        }
    }

Jim Nelson's avatar
Jim Nelson committed
405
    private bool get_modifiers(out bool ctrl, out bool alt, out bool shift, out bool super) {
406 407 408 409 410 411
        if (AppWindow.get_instance().get_window() == null) {
            ctrl = false;
            alt = false;
            shift = false;
            super = false;
            
Jim Nelson's avatar
Jim Nelson committed
412
            return false;
413
        }
Jim Nelson's avatar
Jim Nelson committed
414
        
415
        int x, y;
416
        Gdk.ModifierType mask;
417 418 419
        var seat = Gdk.Display.get_default().get_default_seat();
        AppWindow.get_instance().get_window().get_device_position(seat.get_pointer(), out x, out y, out mask);

420 421 422
        ctrl = (mask & Gdk.ModifierType.CONTROL_MASK) != 0;
        alt = (mask & Gdk.ModifierType.MOD1_MASK) != 0;
        shift = (mask & Gdk.ModifierType.SHIFT_MASK) != 0;
423
        super = (mask & Gdk.ModifierType.MOD4_MASK) != 0; // not SUPER_MASK
Jim Nelson's avatar
Jim Nelson committed
424 425
        
        return true;
426
    }
427

428
    private void update_modifiers() {
429 430
        bool ctrl_currently_pressed, alt_currently_pressed, shift_currently_pressed,
            super_currently_pressed;
Jim Nelson's avatar
Jim Nelson committed
431 432 433 434
        if (!get_modifiers(out ctrl_currently_pressed, out alt_currently_pressed,
            out shift_currently_pressed, out super_currently_pressed)) {
            return;
        }
435
        
436 437 438 439 440 441 442 443 444 445 446 447 448 449
        if (ctrl_pressed && !ctrl_currently_pressed)
            on_ctrl_released(null);
        else if (!ctrl_pressed && ctrl_currently_pressed)
            on_ctrl_pressed(null);

        if (alt_pressed && !alt_currently_pressed)
            on_alt_released(null);
        else if (!alt_pressed && alt_currently_pressed)
            on_alt_pressed(null);

        if (shift_pressed && !shift_currently_pressed)
            on_shift_released(null);
        else if (!shift_pressed && shift_currently_pressed)
            on_shift_pressed(null);
450 451 452 453 454

        if(super_pressed && !super_currently_pressed)
            on_super_released(null);
        else if (!super_pressed && super_currently_pressed)
            on_super_pressed(null);
455 456 457 458
        
        ctrl_pressed = ctrl_currently_pressed;
        alt_pressed = alt_currently_pressed;
        shift_pressed = shift_currently_pressed;
459
        super_pressed = super_currently_pressed;
460
    }
461
    
462 463 464 465 466 467 468 469 470 471 472 473
    public PageWindow? get_page_window() {
        Gtk.Widget p = parent;
        while (p != null) {
            if (p is PageWindow)
                return (PageWindow) p;
            
            p = p.parent;
        }
        
        return null;
    }
    
474 475 476
    public CommandManager get_command_manager() {
        return AppWindow.get_command_manager();
    }
477

Jens Georg's avatar
Jens Georg committed
478 479
    protected virtual void add_actions (GLib.ActionMap map) { }
    protected virtual void remove_actions (GLib.ActionMap map) { }
480 481 482 483 484 485 486 487 488 489

    protected void on_action_toggle (GLib.Action action, Variant? value) {
        Variant new_state = ! (bool) action.get_state ();
        action.change_state (new_state);
    }

    protected void on_action_radio (GLib.Action action, Variant? value) {
        action.change_state (value);
    }

490 491 492 493 494 495
    private void add_ui() {
        // Collect all UI filenames and load them into the UI manager
        Gee.List<string> ui_filenames = new Gee.ArrayList<string>();
        init_collect_ui_filenames(ui_filenames);
        if (ui_filenames.size == 0)
            message("No UI file specified for %s", get_page_name());
496
        
497 498
        foreach (string ui_filename in ui_filenames)
            init_load_ui(ui_filename);
499 500

        //ui.insert_action_group(action_group, 0);
501
    }
502

503
    public void init_toolbar(string path) {
504
        toolbar_path = path;
505 506
    }
   
507 508 509 510
    // Called from "realize"
    private void attach_view_signals() {
        if (are_actions_attached)
            return;
511 512 513 514 515 516 517 518
        
        // initialize the Gtk.Actions according to current state
        int selected_count = get_view().get_selected_count();
        int count = get_view().get_count();
        init_actions(selected_count, count);
        update_actions(selected_count, count);
        
        // monitor state changes to update actions
519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537
        get_view().items_state_changed.connect(on_update_actions);
        get_view().selection_group_altered.connect(on_update_actions);
        get_view().items_visibility_changed.connect(on_update_actions);
        get_view().contents_altered.connect(on_update_actions);
        
        are_actions_attached = true;
    }
    
    // Called from destroy()
    private void detach_view_signals() {
        if (!are_actions_attached)
            return;
        
        get_view().items_state_changed.disconnect(on_update_actions);
        get_view().selection_group_altered.disconnect(on_update_actions);
        get_view().items_visibility_changed.disconnect(on_update_actions);
        get_view().contents_altered.disconnect(on_update_actions);
        
        are_actions_attached = false;
538 539
    }
    
540
    private void on_update_actions() {
541 542 543 544 545 546 547 548 549 550 551 552
        if (update_actions_scheduler == null) {
            update_actions_scheduler = new OneShotScheduler(
                "Update actions scheduler for %s".printf(get_page_name()),
                on_update_actions_on_idle);
        }
        
        update_actions_scheduler.at_priority_idle(Priority.LOW);
    }
    
    private void on_update_actions_on_idle() {
        if (is_destroyed)
            return;
553 554 555

        if (!this.in_view)
            return;
556
        
557
        update_actions(get_view().get_selected_count(), get_view().get_count());
Jim Nelson's avatar
Jim Nelson committed
558 559
    }
    
560
    private void init_load_ui(string ui_filename) {
561
        var ui_resource = Resources.get_ui(ui_filename);
562
        try {
563
            builder.add_from_resource(ui_resource);
564
            this.menubar_injected = false;
565
        } catch (Error err) {
566 567
            AppWindow.error_message("Error loading UI resource %s: %s".printf(
                ui_resource, err.message));
568
            Application.get_instance().panic();
569
        }
570 571
    }
    
572
    // This is called during add_ui() to collect all the UI files to be loaded into the UI
573 574 575
    // manager.  Because order is important here, call the base method *first*, then add the
    // classes' filename.
    protected virtual void init_collect_ui_filenames(Gee.List<string> ui_filenames) {
576
    }
577

578
    // This is called during add_ui() to collect all Page.InjectedUIElements for the page.  They
579 580
    // should be added to the MultiSet using the injection path as the key.
    protected virtual InjectionGroup[] init_collect_injection_groups() {
581
        return new InjectionGroup[0];
582 583 584 585
    }
    
    // This is called during "map" allowing for Gtk.Actions to be updated at
    // initialization time.
Jim Nelson's avatar
Jim Nelson committed
586 587 588
    protected virtual void init_actions(int selected_count, int count) {
    }
    
589 590 591 592
    // This is called during "map" and during ViewCollection selection, visibility,
    // and collection content altered events.  This can be used to both initialize Gtk.Actions and
    // update them when selection or visibility has been altered.
    protected virtual void update_actions(int selected_count, int count) {
593 594
    }
    
595 596
    // This method enables drag-and-drop on the event source and routes its events through this
    // object
597
    public void enable_drag_source(Gdk.DragAction actions, Gtk.TargetEntry[] source_target_entries) {
598 599 600 601 602
        if (dnd_enabled)
            return;
            
        assert(event_source != null);
        
603 604 605 606
        Gtk.drag_source_set(event_source, Gdk.ModifierType.BUTTON1_MASK, source_target_entries, actions);
        
        // hook up handlers which route the event_source's DnD signals to the Page's (necessary
        // because Page is a NO_WINDOW widget and cannot support DnD on its own).
607 608 609 610 611
        event_source.drag_begin.connect(on_drag_begin);
        event_source.drag_data_get.connect(on_drag_data_get);
        event_source.drag_data_delete.connect(on_drag_data_delete);
        event_source.drag_end.connect(on_drag_end);
        event_source.drag_failed.connect(on_drag_failed);
612 613 614 615
        
        dnd_enabled = true;
    }
    
616 617 618 619 620 621
    public void disable_drag_source() {
        if (!dnd_enabled)
            return;

        assert(event_source != null);
        
622 623 624 625 626
        event_source.drag_begin.disconnect(on_drag_begin);
        event_source.drag_data_get.disconnect(on_drag_data_get);
        event_source.drag_data_delete.disconnect(on_drag_data_delete);
        event_source.drag_end.disconnect(on_drag_end);
        event_source.drag_failed.disconnect(on_drag_failed);
627 628 629 630 631
        Gtk.drag_source_unset(event_source);
        
        dnd_enabled = false;
    }
    
632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655
    public bool is_dnd_enabled() {
        return dnd_enabled;
    }
    
    private void on_drag_begin(Gdk.DragContext context) {
        drag_begin(context);
    }
    
    private void on_drag_data_get(Gdk.DragContext context, Gtk.SelectionData selection_data,
        uint info, uint time) {
        drag_data_get(context, selection_data, info, time);
    }
    
    private void on_drag_data_delete(Gdk.DragContext context) {
        drag_data_delete(context);
    }
    
    private void on_drag_end(Gdk.DragContext context) {
        drag_end(context);
    }
    
    // wierdly, Gtk 2.16.1 doesn't supply a drag_failed virtual method in the GtkWidget impl ...
    // Vala binds to it, but it's not available in gtkwidget.h, and so gcc complains.  Have to
    // makeshift one for now.
656
    // https://bugzilla.gnome.org/show_bug.cgi?id=584247
657 658 659 660 661 662 663 664
    public virtual bool source_drag_failed(Gdk.DragContext context, Gtk.DragResult drag_result) {
        return false;
    }
    
    private bool on_drag_failed(Gdk.DragContext context, Gtk.DragResult drag_result) {
        return source_drag_failed(context, drag_result);
    }
    
665 666 667 668 669
    // Use this function rather than GDK or GTK's get_pointer, especially if called during a 
    // button-down mouse drag (i.e. a window grab).
    //
    // For more information, see: https://bugzilla.gnome.org/show_bug.cgi?id=599937
    public bool get_event_source_pointer(out int x, out int y, out Gdk.ModifierType mask) {
670 671 672 673 674
        if (event_source == null) {
            x = 0;
            y = 0;
            mask = 0;
            
675
            return false;
676
        }
677
        
678 679
        var seat = Gdk.Display.get_default().get_default_seat();
        event_source.get_window().get_device_position(seat.get_pointer(), out x, out y, out mask);
680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696
        
        if (last_down.x < 0 || last_down.y < 0)
            return true;
            
        // check for bogus values inside a drag which goes outside the window
        // caused by (most likely) X windows signed 16-bit int overflow and fixup
        // (https://bugzilla.gnome.org/show_bug.cgi?id=599937)
        
        if ((x - last_down.x).abs() >= 0x7FFF)
            x += 0xFFFF;
        
        if ((y - last_down.y).abs() >= 0x7FFF)
            y += 0xFFFF;
        
        return true;
    }
    
697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721
    protected virtual bool on_left_click(Gdk.EventButton event) {
        return false;
    }
    
    protected virtual bool on_middle_click(Gdk.EventButton event) {
        return false;
    }
    
    protected virtual bool on_right_click(Gdk.EventButton event) {
        return false;
    }
    
    protected virtual bool on_left_released(Gdk.EventButton event) {
        return false;
    }
    
    protected virtual bool on_middle_released(Gdk.EventButton event) {
        return false;
    }
    
    protected virtual bool on_right_released(Gdk.EventButton event) {
        return false;
    }
    
    private bool on_button_pressed_internal(Gdk.EventButton event) {
722 723
        switch (event.button) {
            case 1:
724 725 726
                if (event_source != null)
                    event_source.grab_focus();
                
727 728 729 730
                // stash location of mouse down for drag fixups
                last_down.x = (int) event.x;
                last_down.y = (int) event.y;
                
731
                return on_left_click(event);
732

733 734 735 736 737 738 739 740 741 742
            case 2:
                return on_middle_click(event);
            
            case 3:
                return on_right_click(event);
            
            default:
                return false;
        }
    }
743 744 745 746
    
    private bool on_button_released_internal(Gdk.EventButton event) {
        switch (event.button) {
            case 1:
747 748 749
                // clear when button released, only for drag fixups
                last_down = { -1, -1 };
                
750 751 752 753 754 755 756 757 758 759 760 761
                return on_left_released(event);
            
            case 2:
                return on_middle_released(event);
            
            case 3:
                return on_right_released(event);
            
            default:
                return false;
        }
    }
762

763
    protected virtual bool on_ctrl_pressed(Gdk.EventKey? event) {
764
        return false;
765 766
    }
    
767
    protected virtual bool on_ctrl_released(Gdk.EventKey? event) {
768
        return false;
769 770
    }
    
771
    protected virtual bool on_alt_pressed(Gdk.EventKey? event) {
772
        return false;
773 774
    }
    
775
    protected virtual bool on_alt_released(Gdk.EventKey? event) {
776 777 778
        return false;
    }
    
779
    protected virtual bool on_shift_pressed(Gdk.EventKey? event) {
780
        return false;
781 782
    }
    
783
    protected virtual bool on_shift_released(Gdk.EventKey? event) {
784
        return false;
785
    }
786 787 788 789 790 791 792 793

    protected virtual bool on_super_pressed(Gdk.EventKey? event) {
        return false;
    }
    
    protected virtual bool on_super_released(Gdk.EventKey? event) {
        return false;
    }
794
    
795 796 797 798 799 800 801 802
    protected virtual bool on_app_key_pressed(Gdk.EventKey event) {
        return false;
    }
    
    protected virtual bool on_app_key_released(Gdk.EventKey event) {
        return false;
    }
    
803
    public bool notify_app_key_pressed(Gdk.EventKey event) {
804 805
        bool ctrl_currently_pressed, alt_currently_pressed, shift_currently_pressed,
            super_currently_pressed;
806
        get_modifiers(out ctrl_currently_pressed, out alt_currently_pressed,
807
            out shift_currently_pressed, out super_currently_pressed);
808

809 810 811
        switch (Gdk.keyval_name(event.keyval)) {
            case "Control_L":
            case "Control_R":
812 813 814
                if (!ctrl_currently_pressed || ctrl_pressed)
                    return false;

815 816 817
                ctrl_pressed = true;
                
                return on_ctrl_pressed(event);
818 819 820

            case "Meta_L":
            case "Meta_R":
821 822
            case "Alt_L":
            case "Alt_R":
823 824 825
                if (!alt_currently_pressed || alt_pressed)
                    return false;

826 827 828 829 830 831
                alt_pressed = true;
                
                return on_alt_pressed(event);
            
            case "Shift_L":
            case "Shift_R":
832 833 834
                if (!shift_currently_pressed || shift_pressed)
                    return false;

835 836 837
                shift_pressed = true;
                
                return on_shift_pressed(event);
838 839 840 841 842 843 844 845
            
            case "Super_L":
            case "Super_R":
                if (!super_currently_pressed || super_pressed)
                    return false;
                
                super_pressed = true;
                
846
                return on_super_pressed(event);
847
        }
848
        
849
        return on_app_key_pressed(event);
850
    }
851
    
852
    public bool notify_app_key_released(Gdk.EventKey event) {
853 854
        bool ctrl_currently_pressed, alt_currently_pressed, shift_currently_pressed,
            super_currently_pressed;
855
        get_modifiers(out ctrl_currently_pressed, out alt_currently_pressed,
856
            out shift_currently_pressed, out super_currently_pressed);
857

858 859 860
        switch (Gdk.keyval_name(event.keyval)) {
            case "Control_L":
            case "Control_R":
861 862 863
                if (ctrl_currently_pressed || !ctrl_pressed)
                    return false;

864 865 866 867
                ctrl_pressed = false;
                
                return on_ctrl_released(event);
            
868 869
            case "Meta_L":
            case "Meta_R":
870 871
            case "Alt_L":
            case "Alt_R":
872 873 874
                if (alt_currently_pressed || !alt_pressed)
                    return false;

875 876 877 878 879 880
                alt_pressed = false;
                
                return on_alt_released(event);
            
            case "Shift_L":
            case "Shift_R":
881 882 883
                if (shift_currently_pressed || !shift_pressed)
                    return false;

884 885 886
                shift_pressed = false;
                
                return on_shift_released(event);
887 888 889 890 891 892 893 894 895

            case "Super_L":
            case "Super_R":
                if (super_currently_pressed || !super_pressed)
                    return false;

                super_pressed = false;
                
                return on_super_released(event);
896
        }
897
        
898
        return on_app_key_released(event);
899
    }
900
    
901 902
    public bool notify_app_focus_in(Gdk.EventFocus event) {
        update_modifiers();
903
        
904 905 906 907 908 909
        return false;
    }

    public bool notify_app_focus_out(Gdk.EventFocus event) {
        return false;
    }
910 911 912 913
    
    protected virtual void on_move(Gdk.Rectangle rect) {
    }
    
914 915 916 917 918 919
    protected virtual void on_move_start(Gdk.Rectangle rect) {
    }
    
    protected virtual void on_move_finished(Gdk.Rectangle rect) {
    }
    
920
    protected virtual void on_resize(Gdk.Rectangle rect) {
921
    }
922
    
923 924 925 926 927 928
    protected virtual void on_resize_start(Gdk.Rectangle rect) {
    }
    
    protected virtual void on_resize_finished(Gdk.Rectangle rect) {
    }
    
929 930 931 932 933
    protected virtual bool on_configure(Gdk.EventConfigure event, Gdk.Rectangle rect) {
        return false;
    }
    
    public bool notify_configure_event(Gdk.EventConfigure event) {
934 935 936 937 938 939
        Gdk.Rectangle rect = Gdk.Rectangle();
        rect.x = event.x;
        rect.y = event.y;
        rect.width = event.width;
        rect.height = event.height;
        
940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955
        // special case events, to report when a configure first starts (and appears to end)
        if (last_configure_ms == 0) {
            if (last_position.x != rect.x || last_position.y != rect.y) {
                on_move_start(rect);
                report_move_finished = true;
            }
            
            if (last_position.width != rect.width || last_position.height != rect.height) {
                on_resize_start(rect);
                report_resize_finished = true;
            }

            // need to check more often then the timeout, otherwise it could be up to twice the
            // wait time before it's noticed
            Timeout.add(CONSIDER_CONFIGURE_HALTED_MSEC / 8, check_configure_halted);
        }
956 957 958 959 960 961 962
        
        if (last_position.x != rect.x || last_position.y != rect.y)
            on_move(rect);
        
        if (last_position.width != rect.width || last_position.height != rect.height)
            on_resize(rect);
        
963
        last_position = rect;
964 965
        last_configure_ms = now_ms();

966
        return on_configure(event, rect);
967
    }
968
    
969
    private bool check_configure_halted() {
970 971 972
        if (is_destroyed)
            return false;

973 974
        if ((now_ms() - last_configure_ms) < CONSIDER_CONFIGURE_HALTED_MSEC)
            return true;
975
        
Jim Nelson's avatar
Jim Nelson committed
976 977 978
        Gtk.Allocation allocation;
        get_allocation(out allocation);
        
979 980 981 982 983 984 985 986 987 988 989 990 991
        if (report_move_finished)
            on_move_finished((Gdk.Rectangle) allocation);
        
        if (report_resize_finished)
            on_resize_finished((Gdk.Rectangle) allocation);
        
        last_configure_ms = 0;
        report_move_finished = false;
        report_resize_finished = false;
        
        return false;
    }
    
992
    protected virtual bool on_motion(Gdk.EventMotion event, int x, int y, Gdk.ModifierType mask) {
993 994
        check_cursor_hiding();

995 996 997
        return false;
    }
    
998 999 1000 1001
    protected virtual bool on_leave_notify_event() {
        return false;
    }
    
1002 1003 1004
    private bool on_motion_internal(Gdk.EventMotion event) {
        int x, y;
        Gdk.ModifierType mask;
1005
        if (event.is_hint == 1) {
1006
            get_event_source_pointer(out x, out y, out mask);
1007 1008 1009 1010 1011 1012 1013 1014
        } else {
            x = (int) event.x;
            y = (int) event.y;
            mask = event.state;
        }
        
        return on_motion(event, x, y, mask);
    }
1015

1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028
    private bool on_mousewheel_internal(Gdk.EventScroll event) {
        switch (event.direction) {
            case Gdk.ScrollDirection.UP:
                return on_mousewheel_up(event);

            case Gdk.ScrollDirection.DOWN:
                return on_mousewheel_down(event);
            
            case Gdk.ScrollDirection.LEFT:
                return on_mousewheel_left(event);

            case Gdk.ScrollDirection.RIGHT:
                return on_mousewheel_right(event);
1029 1030 1031 1032 1033 1034

            case Gdk.ScrollDirection.SMOOTH:
                {
                    double dx, dy;
                    event.get_scroll_deltas(out dx, out dy);

1035
                    if (dy <= -1.0)
1036
                        return on_mousewheel_up(event);
1037
                    else if (dy >= 1.0)
1038
                        return on_mousewheel_down(event);
1039
                    else if (dx <= -1.0)
1040
                        return on_mousewheel_left(event);
1041
                    else if (dx >= 1.0)
1042 1043
                        return on_mousewheel_right(event);
                    else
1044
                        return true;
1045
                }
1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067
           
            default:
                return false;
        }
    }
    
    protected virtual bool on_mousewheel_up(Gdk.EventScroll event) {
        return false;
    }
    
    protected virtual bool on_mousewheel_down(Gdk.EventScroll event) {
        return false;
    }
    
    protected virtual bool on_mousewheel_left(Gdk.EventScroll event) {
        return false;
    }
    
    protected virtual bool on_mousewheel_right(Gdk.EventScroll event) {
        return false;
    }
    
1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082
    protected virtual bool on_context_keypress() {
        return false;
    }
    
    protected virtual bool on_context_buttonpress(Gdk.EventButton event) {
        return false;
    }
    
    protected virtual bool on_context_invoked() {
        return true;
    }

    protected bool popup_context_menu(Gtk.Menu? context_menu,
        Gdk.EventButton? event = null) {

1083
        if (context_menu == null || !on_context_invoked())
1084 1085
            return false;

1086
        context_menu.popup_at_pointer(event);
1087 1088 1089

        return true;
    }
1090 1091

    protected void on_event_source_realize() {
Jim Nelson's avatar
Jim Nelson committed
1092
        assert(event_source.get_window() != null); // the realize event means the Widget has a window
1093

Jim Nelson's avatar
Jim Nelson committed
1094 1095
        if (event_source.get_window().get_cursor() != null) {
            last_cursor = event_source.get_window().get_cursor().get_cursor_type();
1096 1097 1098 1099
            return;
        }

        // no custom cursor defined, check parents
Jim Nelson's avatar
Jim Nelson committed
1100
        Gdk.Window? parent_window = event_source.get_window();
1101 1102 1103 1104 1105
        do {
            parent_window = parent_window.get_parent();
        } while (parent_window != null && parent_window.get_cursor() == null);
        
        if (parent_window != null)
Jim Nelson's avatar
Jim Nelson committed
1106
            last_cursor = parent_window.get_cursor().get_cursor_type();
1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117
    }

    public void set_cursor_hide_time(int hide_time) {
        cursor_hide_msec = hide_time;
    }

    public void start_cursor_hiding() {
        check_cursor_hiding();
    }

    public void stop_cursor_hiding() {
1118
        if (last_timeout_id != 0) {
1119
            Source.remove(last_timeout_id);
1120 1121
            last_timeout_id = 0;
        }
1122 1123 1124 1125 1126
    }

    public void suspend_cursor_hiding() {
        cursor_hide_time_cached = cursor_hide_msec;

1127
        if (last_timeout_id != 0) {
1128
            Source.remove(last_timeout_id);
1129 1130
            last_timeout_id = 0;
        }
1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143

        cursor_hide_msec = 0;
    }

    public void restore_cursor_hiding() {
        cursor_hide_msec = cursor_hide_time_cached;
        check_cursor_hiding();
    }

    // Use this method to set the cursor for a page, NOT window.set_cursor(...)
    protected virtual void set_page_cursor(Gdk.CursorType cursor_type) {
        last_cursor = cursor_type;

Jens Georg's avatar
Jens Georg committed
1144 1145 1146 1147
        if (!cursor_hidden && event_source != null) {
            var display = event_source.get_window ().get_display ();
            event_source.get_window().set_cursor(new Gdk.Cursor.for_display(display, cursor_type));
        }
1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165
    }

    private void check_cursor_hiding() {
        if (cursor_hidden) {
            cursor_hidden = false;
            set_page_cursor(last_cursor);
        }

        if (cursor_hide_msec != 0) {
            if (last_timeout_id != 0)
                Source.remove(last_timeout_id);
            last_timeout_id = Timeout.add(cursor_hide_msec, on_hide_cursor);
        }
    }

    private bool on_hide_cursor() {
        cursor_hidden = true;

Jens Georg's avatar
Jens Georg committed
1166 1167 1168 1169
        if (event_source != null) {
            var display = event_source.get_window().get_display ();
            event_source.get_window().set_cursor(new Gdk.Cursor.for_display(display, Gdk.CursorType.BLANK_CURSOR));
        }
1170

1171 1172 1173
        // We remove the timeout so reset the id
        last_timeout_id = 0;

1174 1175
        return false;
    }
1176 1177

    protected void update_menu_item_label (string id,
1178 1179
                                         string new_label) {
        AppWindow.get_instance().update_menu_item_label (id, new_label);
1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213
    }

    protected GLib.MenuModel? find_extension_point (GLib.MenuModel model,
                                                    string extension_point) {
        var items = model.get_n_items ();
        GLib.MenuModel? section = null;

        for (int i = 0; i < items && section == null; i++) {
            string? name = null;
            model.get_item_attribute (i, "id", "s", out name);
            if (name == extension_point) {
                section = model.get_item_link (i, GLib.Menu.LINK_SECTION);
            } else {
                var subsection = model.get_item_link (i, GLib.Menu.LINK_SECTION);

                if (subsection == null)
                    continue;

                // Recurse into submenus
                var sub_items = subsection.get_n_items ();
                for (int j = 0; j < sub_items && section == null; j++) {
                    var submenu = subsection.get_item_link
                                                (j, GLib.Menu.LINK_SUBMENU);
                    if (submenu != null) {
                        section = this.find_extension_point (submenu,
                                                             extension_point);
                    }
                }
            }
        }

        return section;
    }

1214 1215
}