window.vala 13 KB
Newer Older
Paolo Borelli's avatar
Paolo Borelli committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
 * Copyright (C) 2013  Paolo Borelli <pborelli@gnome.org>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

namespace Clocks {

21
[GtkTemplate (ui = "/org/gnome/clocks/ui/window.ui")]
22
public class Window : Hdy.ApplicationWindow {
23
    private const GLib.ActionEntry[] ACTION_ENTRIES = {
Paolo Borelli's avatar
Paolo Borelli committed
24
25
        // primary menu
        { "show-primary-menu", on_show_primary_menu_activate, null, "false", null },
Paolo Borelli's avatar
Paolo Borelli committed
26
        { "new", on_new_activate },
27
        { "back", on_back_activate },
Paolo Borelli's avatar
Paolo Borelli committed
28
        { "help", on_help_activate },
Paolo Borelli's avatar
Paolo Borelli committed
29
30
31
        { "about", on_about_activate },
    };

32
    [GtkChild]
Stefano Facchini's avatar
Stefano Facchini committed
33
    private HeaderBar header_bar;
34
    [GtkChild]
35
36
    private Hdy.Deck alarm_deck;
    [GtkChild]
37
38
39
40
    private Hdy.Deck world_deck;
    [GtkChild]
    private Gtk.Box main_view;
    [GtkChild]
41
    private Gtk.Stack stack;
42
    [GtkChild]
43
    private World.Face world;
44
    [GtkChild]
45
    private Alarm.Face alarm;
46
    [GtkChild]
47
48
    private World.Standalone world_standalone;
    [GtkChild]
49
50
    private Alarm.RingingPanel alarm_ringing_panel;
    [GtkChild]
51
    private Stopwatch.Face stopwatch;
52
    [GtkChild]
53
    private Timer.Face timer;
54

55
    private GLib.Settings settings;
Paolo Borelli's avatar
Paolo Borelli committed
56

57
    // DIY DzlBindingGroup
58
59
    private Binding? bind_button_mode = null;
    private Binding? bind_new_label = null;
60
61

    private bool inited = false;
62

Paolo Borelli's avatar
Paolo Borelli committed
63
    public Window (Application app) {
64
        Object (application: app);
Paolo Borelli's avatar
Paolo Borelli committed
65

66
        add_action_entries (ACTION_ENTRIES, this);
Paolo Borelli's avatar
Paolo Borelli committed
67

68
        settings = new Settings ("org.gnome.clocks.state.window");
69
        settings.delay ();
70

71
72
73
74
75
        // We need to set this manually, otherwise it fails in the devel version
        var builder = new Gtk.Builder.from_resource ("/org/gnome/clocks/gtk/help-overlay.ui");
        var dialog = (Gtk.ShortcutsWindow)builder.get_object ("help_overlay");
        set_help_overlay (dialog);

76
77
78
79
80
81
82
83
84
        // GSettings gives us the nick, which matches the stack page name
        stack.visible_child_name = settings.get_string ("panel-id");

        inited = true;

        header_bar.bind_property ("title", this, "title", SYNC_CREATE);

        pane_changed ();

85
        // Setup window geometry saving
Zander Brown's avatar
Zander Brown committed
86
        var window_state = (Gdk.WindowState) settings.get_int ("state");
87
88
        if (Gdk.WindowState.MAXIMIZED in window_state) {
            maximize ();
Zander Brown's avatar
Zander Brown committed
89
90
91
92
        } else {
            int width, height;
            settings.get ("size", "(ii)", out width, out height);
            resize (width, height);
93
94
        }

95
96
97
98
99
100
        world.show_standalone.connect ((w, l) => {
            stack.visible_child = w;
            world_standalone.location = l;
            world_deck.navigate (Hdy.NavigationDirection.FORWARD);
        });

101
        alarm.ring.connect ((w, a) => {
102
            close_standalone ();
Paolo Borelli's avatar
Paolo Borelli committed
103
            stack.visible_child = w;
104
105
            alarm_ringing_panel.alarm = a;
            alarm_deck.visible_child = alarm_ringing_panel;
Paolo Borelli's avatar
Paolo Borelli committed
106
107
        });

108
        stopwatch.notify["state"].connect ((w) => {
109
            stack.child_set_property (stopwatch, "needs-attention", stopwatch.state == Stopwatch.Face.State.RUNNING);
110
111
112
        });

        timer.ring.connect ((w) => {
113
            close_standalone ();
Paolo Borelli's avatar
Paolo Borelli committed
114
115
116
            stack.visible_child = w;
        });

117
118
119
120
121

        timer.notify["is-running"].connect ((w) => {
            stack.child_set_property (timer, "needs-attention", timer.is_running);
        });

122
123
124
125
126
127
128
        unowned Gtk.BindingSet binding_set = Gtk.BindingSet.by_class (get_class ());

        // plain ctrl+page_up/down is easten by the scrolled window...
        Gtk.BindingEntry.add_signal (binding_set,
                                     Gdk.Key.Page_Up,
                                     Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.MOD1_MASK,
                                     "change-page", 1,
129
                                     typeof (int), 0);
130
131
132
133
        Gtk.BindingEntry.add_signal (binding_set,
                                     Gdk.Key.Page_Down,
                                     Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.MOD1_MASK,
                                     "change-page", 1,
134
                                     typeof (int), 1);
135

136
        Gtk.BindingEntry.add_signal (binding_set,
Radush Niemczyk's avatar
Radush Niemczyk committed
137
138
139
                                     Gdk.Key.@1,
                                     Gdk.ModifierType.MOD1_MASK,
                                     "set-page", 1,
140
                                     typeof (string), "world");
141
        Gtk.BindingEntry.add_signal (binding_set,
Radush Niemczyk's avatar
Radush Niemczyk committed
142
143
144
                                     Gdk.Key.@2,
                                     Gdk.ModifierType.MOD1_MASK,
                                     "set-page", 1,
145
                                     typeof (string), "alarm");
146
        Gtk.BindingEntry.add_signal (binding_set,
Radush Niemczyk's avatar
Radush Niemczyk committed
147
148
149
                                     Gdk.Key.@3,
                                     Gdk.ModifierType.MOD1_MASK,
                                     "set-page", 1,
150
                                     typeof (string), "stopwatch");
151
        Gtk.BindingEntry.add_signal (binding_set,
Radush Niemczyk's avatar
Radush Niemczyk committed
152
153
154
                                     Gdk.Key.@4,
                                     Gdk.ModifierType.MOD1_MASK,
                                     "set-page", 1,
155
                                     typeof (string), "timer");
156

157
158
159
160
        Gtk.StyleContext style = get_style_context ();
        if (Config.PROFILE == "Devel") {
            style.add_class ("devel");
        }
Paolo Borelli's avatar
Paolo Borelli committed
161
162
    }

163
    [Signal (action = true)]
164
    public virtual signal void change_page (int offset) {
165
        var dir = false;
166

167
168
        if (get_direction () == RTL) {
            dir = offset == 0 ? false : true;
169
        } else {
170
            dir = offset == 1 ? false : true;
171
172
        }

173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
        switch (stack.visible_child_name) {
            case "world":
                if (dir) {
                    stack.error_bell ();
                } else {
                    stack.visible_child = alarm;
                }
                break;
            case "alarm":
                if (dir) {
                    stack.visible_child = world;
                } else {
                    stack.visible_child = stopwatch;
                }
                break;
            case "stopwatch":
                if (dir) {
                    stack.visible_child = alarm;
                } else {
                    stack.visible_child = timer;
                }
                break;
            case "timer":
                if (dir) {
                    stack.visible_child = stopwatch;
                } else {
                    stack.error_bell ();
                }
                break;
        }
203
204
    }

205
    [Signal (action = true)]
206
207
    public virtual signal void set_page (string page) {
        stack.visible_child_name = page;
208
209
    }

Paolo Borelli's avatar
Paolo Borelli committed
210
    private void on_show_primary_menu_activate (SimpleAction action) {
211
        var state = ((!) action.get_state ()).get_boolean ();
Paolo Borelli's avatar
Paolo Borelli committed
212
213
214
        action.set_state (new Variant.boolean (!state));
    }

Paolo Borelli's avatar
Paolo Borelli committed
215
216
217
218
    private void on_new_activate () {
        ((Clock) stack.visible_child).activate_new ();
    }

219
    private void on_back_activate () {
220
        world_deck.navigate (Hdy.NavigationDirection.BACK);
221
222
    }

223
    public void show_world () {
224
        close_standalone ();
225
        stack.visible_child = world;
226
227
    }

Jonas Danielsson's avatar
Jonas Danielsson committed
228
    public void add_world_location (GWeather.Location location) {
229
        world.add_location (location);
Jonas Danielsson's avatar
Jonas Danielsson committed
230
231
    }

Zander Brown's avatar
Zander Brown committed
232
233
234
235
236
237
238
239
240
241
    public override void destroy () {
        settings.apply ();
    }

    public override bool delete_event (Gdk.EventAny event) {
        settings.apply ();

        return hide_on_delete ();
    }

Paolo Borelli's avatar
Paolo Borelli committed
242
243
    public override bool key_press_event (Gdk.EventKey event) {
        uint keyval;
Paolo Borelli's avatar
Paolo Borelli committed
244
245
246
        bool handled = false;

        if (((Gdk.Event)(event)).get_keyval (out keyval) && keyval == Gdk.Key.Escape) {
247
248
249
250
251
            if (world_deck.visible_child == main_view) {
                handled = ((Clock) stack.visible_child).escape_pressed ();
            } else {
                world_deck.navigate (Hdy.NavigationDirection.BACK);
            }
Paolo Borelli's avatar
Paolo Borelli committed
252
253
254
255
        }

        if (handled) {
            return true;
Paolo Borelli's avatar
Paolo Borelli committed
256
257
        }

Paolo Borelli's avatar
Paolo Borelli committed
258
        return base.key_press_event (event);
Paolo Borelli's avatar
Paolo Borelli committed
259
260
    }

Paolo Borelli's avatar
Paolo Borelli committed
261
262
263
264
    public override bool button_release_event (Gdk.EventButton event) {
        const uint BUTTON_BACK = 8;
        uint button;

265
        if (((Gdk.Event) (event)).get_button (out button) && button == BUTTON_BACK) {
266
            on_back_activate ();
Paolo Borelli's avatar
Paolo Borelli committed
267
268
269
270
271
272
            return true;
        }

        return base.button_release_event (event);
    }

273
    protected override bool configure_event (Gdk.EventConfigure event) {
274
        if (get_realized () && !(Gdk.WindowState.MAXIMIZED in ((Gdk.Window) get_window ()).get_state ())) {
Matthias Clasen's avatar
Matthias Clasen committed
275
276
277
278
            int width, height;

            get_size (out width, out height);
            settings.set ("size", "(ii)", width, height);
279
280
281
282
283
284
285
        }

        return base.configure_event (event);
    }

    protected override bool window_state_event (Gdk.EventWindowState event) {
        settings.set_int ("state", event.new_window_state);
Zander Brown's avatar
Zander Brown committed
286

287
288
289
        return base.window_state_event (event);
    }

Paolo Borelli's avatar
Paolo Borelli committed
290
291
    private void on_help_activate () {
        try {
Andrei Lișiță's avatar
Andrei Lișiță committed
292
            Gtk.show_uri_on_window (this, "help:gnome-clocks", Gtk.get_current_event_time ());
Paolo Borelli's avatar
Paolo Borelli committed
293
294
295
296
297
        } catch (Error e) {
            warning (_("Failed to show help: %s"), e.message);
        }
    }

Paolo Borelli's avatar
Paolo Borelli committed
298
    private void on_about_activate () {
299
        const string COPYRIGHT = "Copyright \xc2\xa9 2011 Collabora Ltd.\n" +
Paolo Borelli's avatar
Paolo Borelli committed
300
                                 "Copyright \xc2\xa9 2012-2013 Collabora Ltd., Seif Lotfy, Emily Gonyer\n" +
301
                                 "Eslam Mostafa, Paolo Borelli, Volker Sobek\n" +
Zander Brown's avatar
Zander Brown committed
302
                                 "Copyright \xc2\xa9 2019-2020 Bilal Elmoussaoui & Zander Brown et al";
Paolo Borelli's avatar
Paolo Borelli committed
303

304
        const string? AUTHORS[] = {
Paolo Borelli's avatar
Paolo Borelli committed
305
306
307
308
309
            "Alex Anthony",
            "Paolo Borelli",
            "Allan Day",
            "Piotr Drąg",
            "Emily Gonyer",
Paolo Borelli's avatar
Paolo Borelli committed
310
            "Evgeny Bobkin",
Paolo Borelli's avatar
Paolo Borelli committed
311
312
313
314
315
316
317
            "Maël Lavault",
            "Seif Lotfy",
            "William Jon McCann",
            "Eslam Mostafa",
            "Bastien Nocera",
            "Volker Sobek",
            "Jakub Steiner",
Zander Brown's avatar
Zander Brown committed
318
319
            "Bilal Elmoussaoui",
            "Zander Brown",
Paolo Borelli's avatar
Paolo Borelli committed
320
321
322
            null
        };

323
        var program_name = Config.NAME_PREFIX + _("Clocks");
Paolo Borelli's avatar
Paolo Borelli committed
324
        Gtk.show_about_dialog (this,
325
                               "program-name", program_name,
326
                               "logo-icon-name", Config.APP_ID,
Paolo Borelli's avatar
Paolo Borelli committed
327
328
                               "version", Config.VERSION,
                               "comments", _("Utilities to help you with the time."),
329
330
                               "copyright", COPYRIGHT,
                               "authors", AUTHORS,
Paolo Borelli's avatar
Paolo Borelli committed
331
332
333
334
335
336
                               "license-type", Gtk.License.GPL_2_0,
                               "wrap-license", false,
                               "translator-credits", _("translator-credits"),
                               null);
    }

337
338
339
340
341
342
343
344
345
    [GtkCallback]
    private void pane_changed () {
        var help_overlay = get_help_overlay ();
        var panel = (Clock) stack.visible_child;

        if (stack.in_destruction ()) {
            return;
        }

346
        ((Gtk.ShortcutsWindow) help_overlay).view_name = Type.from_instance (panel).name ();
347
348
349
350
351
352

        if (inited) {
            settings.set_enum ("panel-id", panel.panel_id);
        }

        if (bind_button_mode != null) {
353
            ((Binding) bind_button_mode).unbind ();
354
355
356
357
358
        }
        bind_button_mode = panel.bind_property ("button-mode",
                                                header_bar,
                                                "button-mode",
                                                SYNC_CREATE);
359

360
        if (bind_new_label != null) {
361
            ((Binding) bind_new_label).unbind ();
362
363
364
365
366
        }
        bind_new_label = panel.bind_property ("new-label",
                                              header_bar,
                                              "new-label",
                                              SYNC_CREATE);
367

368
        stack.visible_child.grab_focus ();
Paolo Borelli's avatar
Paolo Borelli committed
369
    }
370

371
372
373
374
375
376
377
378
379
    [GtkCallback]
    private void visible_child_changed () {
        if (alarm_deck.visible_child == alarm_ringing_panel) {
            title = _("Alarm");
        } else if (world_deck.visible_child == world_standalone) {
            title = world_standalone.title;
        } else {
            title = _("Clocks");
        }
380
381

        deletable = (alarm_deck.visible_child != alarm_ringing_panel);
382
383
    }

384
385
386
387
388
    [GtkCallback]
    private void alarm_dismissed () {
        alarm_deck.visible_child = world_deck;
    }

389
390
391
    private void close_standalone () {
        world_deck.visible_child = main_view;
    }
Paolo Borelli's avatar
Paolo Borelli committed
392
393
394
}

} // namespace Clocks