[gnome-clocks/wip/exalm/split: 3/8] alarm: Split into multiple files



commit d9cd96a851698ef7f5676fcc0cae2b65d513d697
Author: Alexander Mikhaylenko <alexm gnome org>
Date:   Sat May 23 03:50:20 2020 +0500

    alarm: Split into multiple files
    
    Also fix ui file names.

 data/gnome-clocks.gresource.xml                    |  10 +-
 ...larmdaypickerrow.ui => alarm-day-picker-row.ui} |   0
 data/ui/{alarm.ui => alarm-face.ui}                |   0
 .../ui/{alarmringing.ui => alarm-ringing-panel.ui} |   0
 data/ui/{alarmrow.ui => alarm-row.ui}              |   0
 .../{alarmsetupdialog.ui => alarm-setup-dialog.ui} |   0
 src/alarm-day-picker-row.vala                      | 184 +++++
 src/alarm-face.vala                                | 149 ++++
 src/alarm-item.vala                                | 280 +++++++
 src/alarm-ringing-panel.vala                       |  90 ++
 src/alarm-row.vala                                 | 112 +++
 src/alarm-setup-dialog.vala                        | 215 +++++
 src/alarm.vala                                     | 911 ---------------------
 src/meson.build                                    |   7 +-
 14 files changed, 1041 insertions(+), 917 deletions(-)
---
diff --git a/data/gnome-clocks.gresource.xml b/data/gnome-clocks.gresource.xml
index 13b5937..89ad9c4 100644
--- a/data/gnome-clocks.gresource.xml
+++ b/data/gnome-clocks.gresource.xml
@@ -10,11 +10,11 @@
     <file preprocess="xml-stripblanks">ui/world.ui</file>
     <file preprocess="xml-stripblanks">ui/worldstandalone.ui</file>
     <file preprocess="xml-stripblanks">ui/worldtile.ui</file>
-    <file preprocess="xml-stripblanks">ui/alarmringing.ui</file>
-    <file preprocess="xml-stripblanks">ui/alarmrow.ui</file>
-    <file preprocess="xml-stripblanks">ui/alarmdaypickerrow.ui</file>
-    <file preprocess="xml-stripblanks">ui/alarmsetupdialog.ui</file>
-    <file preprocess="xml-stripblanks">ui/alarm.ui</file>
+    <file preprocess="xml-stripblanks">ui/alarm-day-picker-row.ui</file>
+    <file preprocess="xml-stripblanks">ui/alarm-face.ui</file>
+    <file preprocess="xml-stripblanks">ui/alarm-ringing-panel.ui</file>
+    <file preprocess="xml-stripblanks">ui/alarm-row.ui</file>
+    <file preprocess="xml-stripblanks">ui/alarm-setup-dialog.ui</file>
     <file preprocess="xml-stripblanks">ui/stopwatch.ui</file>
     <file preprocess="xml-stripblanks">ui/stopwatchlapsrow.ui</file>
     <file preprocess="xml-stripblanks">ui/timer.ui</file>
diff --git a/data/ui/alarmdaypickerrow.ui b/data/ui/alarm-day-picker-row.ui
similarity index 100%
rename from data/ui/alarmdaypickerrow.ui
rename to data/ui/alarm-day-picker-row.ui
diff --git a/data/ui/alarm.ui b/data/ui/alarm-face.ui
similarity index 100%
rename from data/ui/alarm.ui
rename to data/ui/alarm-face.ui
diff --git a/data/ui/alarmringing.ui b/data/ui/alarm-ringing-panel.ui
similarity index 100%
rename from data/ui/alarmringing.ui
rename to data/ui/alarm-ringing-panel.ui
diff --git a/data/ui/alarmrow.ui b/data/ui/alarm-row.ui
similarity index 100%
rename from data/ui/alarmrow.ui
rename to data/ui/alarm-row.ui
diff --git a/data/ui/alarmsetupdialog.ui b/data/ui/alarm-setup-dialog.ui
similarity index 100%
rename from data/ui/alarmsetupdialog.ui
rename to data/ui/alarm-setup-dialog.ui
diff --git a/src/alarm-day-picker-row.vala b/src/alarm-day-picker-row.vala
new file mode 100644
index 0000000..1fa93fa
--- /dev/null
+++ b/src/alarm-day-picker-row.vala
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2013  Paolo Borelli <pborelli gnome org>
+ * Copyright (C) 2020  Zander Brown <zbrown 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 {
+namespace Alarm {
+
+[GtkTemplate (ui = "/org/gnome/clocks/ui/alarm-day-picker-row.ui")]
+public class DayPickerRow : Hdy.ActionRow {
+    public bool monday {
+        get {
+            return days[Utils.Weekdays.Day.MON];
+        }
+
+        set {
+            days[Utils.Weekdays.Day.MON] = value;
+            update ();
+        }
+    }
+
+    public bool tuesday {
+        get {
+            return days[Utils.Weekdays.Day.TUE];
+        }
+
+        set {
+            days[Utils.Weekdays.Day.TUE] = value;
+            update ();
+        }
+    }
+
+    public bool wednesday {
+        get {
+            return days[Utils.Weekdays.Day.WED];
+        }
+
+        set {
+            days[Utils.Weekdays.Day.WED] = value;
+            update ();
+        }
+    }
+
+    public bool thursday {
+        get {
+            return days[Utils.Weekdays.Day.THU];
+        }
+
+        set {
+            days[Utils.Weekdays.Day.THU] = value;
+            update ();
+        }
+    }
+
+    public bool friday {
+        get {
+            return days[Utils.Weekdays.Day.FRI];
+        }
+
+        set {
+            days[Utils.Weekdays.Day.FRI] = value;
+            update ();
+        }
+    }
+
+    public bool saturday {
+        get {
+            return days[Utils.Weekdays.Day.SAT];
+        }
+
+        set {
+            days[Utils.Weekdays.Day.SAT] = value;
+            update ();
+        }
+    }
+
+    public bool sunday {
+        get {
+            return days[Utils.Weekdays.Day.SUN];
+        }
+
+        set {
+            days[Utils.Weekdays.Day.SUN] = value;
+            update ();
+        }
+    }
+
+    public signal void days_changed ();
+
+    private Utils.Weekdays days = new Utils.Weekdays ();
+
+    [GtkChild]
+    private Gtk.FlowBox flow;
+
+    construct {
+        // Create actions to control propeties from menu items
+        var group = new SimpleActionGroup ();
+        group.add_action (new PropertyAction ("day-0", this, "monday"));
+        group.add_action (new PropertyAction ("day-1", this, "tuesday"));
+        group.add_action (new PropertyAction ("day-2", this, "wednesday"));
+        group.add_action (new PropertyAction ("day-3", this, "thursday"));
+        group.add_action (new PropertyAction ("day-4", this, "friday"));
+        group.add_action (new PropertyAction ("day-5", this, "saturday"));
+        group.add_action (new PropertyAction ("day-6", this, "sunday"));
+        insert_action_group ("repeats", group);
+
+        // Create an array with the weekday items with
+        // buttons[0] referencing the button for Monday, and so on.
+        var buttons = new Gtk.ToggleButton[7];
+        for (int i = 0; i < 7; i++) {
+            var day = (Utils.Weekdays.Day) i;
+            buttons[i] = new Gtk.ToggleButton.with_label (day.symbol ());
+            buttons[i].action_name = "repeats.day-%i".printf (i);
+            buttons[i].tooltip_text = day.name ();
+            buttons[i].get_style_context ().add_class ("circular");
+            buttons[i].show ();
+        }
+
+        // Add the items, starting with the first day of the week
+        // depending on the locale.
+        var first_weekday = Utils.Weekdays.Day.get_first_weekday ();
+        for (int i = 0; i < 7; i++) {
+            var day_number = (first_weekday + i) % 7;
+
+            var wrap = new Gtk.FlowBoxChild ();
+            wrap.can_focus = false;
+            wrap.add (buttons[day_number]);
+            wrap.show ();
+
+            flow.add (wrap);
+        }
+
+        update ();
+    }
+
+    public void load (Utils.Weekdays current_days) {
+        // Copy in the days
+        for (int i = 0; i < 7; i++) {
+            days[(Utils.Weekdays.Day) i] = current_days[(Utils.Weekdays.Day) i];
+        }
+
+        // Make sure the buttons update
+        notify_property ("monday");
+        notify_property ("tuesday");
+        notify_property ("wednesday");
+        notify_property ("thursday");
+        notify_property ("friday");
+        notify_property ("saturday");
+        notify_property ("sunday");
+
+        update ();
+    }
+
+    public Utils.Weekdays store () {
+        var new_days = new Utils.Weekdays ();
+
+        for (int i = 0; i < 7; i++) {
+            new_days[(Utils.Weekdays.Day) i] = days[(Utils.Weekdays.Day) i];
+        }
+
+        return new_days;
+    }
+
+    private void update () {
+        days_changed ();
+    }
+}
+
+} // namespace Alarm
+} // namespace Clocks
diff --git a/src/alarm-face.vala b/src/alarm-face.vala
new file mode 100644
index 0000000..71719af
--- /dev/null
+++ b/src/alarm-face.vala
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2013  Paolo Borelli <pborelli gnome org>
+ * Copyright (C) 2020  Zander Brown <zbrown 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 {
+namespace Alarm {
+
+[GtkTemplate (ui = "/org/gnome/clocks/ui/alarm-face.ui")]
+public class Face : Gtk.Stack, Clocks.Clock {
+    public PanelId panel_id { get; construct set; }
+    public ButtonMode button_mode { get; set; default = NEW; }
+    // Translators: Tooltip for the + button
+    public string? new_label { get; default = _("New Alarm"); }
+
+    private ContentStore alarms;
+    private GLib.Settings settings;
+    [GtkChild]
+    private Gtk.Widget empty_view;
+    [GtkChild]
+    private Gtk.ListBox listbox;
+    [GtkChild]
+    private Gtk.ScrolledWindow list_view;
+
+    construct {
+        panel_id = ALARM;
+
+        alarms = new ContentStore ();
+        settings = new GLib.Settings ("org.gnome.clocks");
+
+        var app = GLib.Application.get_default ();
+        var action = (GLib.SimpleAction) app.lookup_action ("stop-alarm");
+        action.activate.connect ((action, param) => {
+            var a = alarms.find ((a) => {
+                return ((Item) a).id == (string) param;
+            });
+
+            if (a != null) {
+                ((Item) a).stop ();
+            }
+        });
+
+        action = (GLib.SimpleAction) app.lookup_action ("snooze-alarm");
+        action.activate.connect ((action, param) => {
+            var a = alarms.find ((a) => {
+                return ((Item) a).id == (string) param;
+            });
+
+            if (a != null) {
+                ((Item) a).snooze ();
+            }
+        });
+
+        listbox.bind_model (alarms, (item) => {
+            return new Row ((Item) item, this);
+        });
+
+        load ();
+
+        alarms.items_changed.connect ((position, removed, added) => {
+            save ();
+            reset_view ();
+        });
+
+        reset_view ();
+
+        // Start ticking...
+        Utils.WallClock.get_default ().tick.connect (() => {
+            alarms.foreach ((i) => {
+                var a = (Item)i;
+                if (a.tick ()) {
+                    if (a.state == Item.State.RINGING) {
+                        ring (a);
+                    }
+                }
+            });
+        });
+    }
+
+    internal signal void ring (Item item);
+
+    private void load () {
+        alarms.deserialize (settings.get_value ("alarms"), Item.deserialize);
+    }
+
+    private void save () {
+        settings.set_value ("alarms", alarms.serialize ());
+    }
+
+    internal void edit (Item alarm) {
+        var dialog = new SetupDialog ((Gtk.Window) get_toplevel (), alarm, alarms);
+
+        // Disable alarm while editing it and remember the original active state.
+        alarm.editing = true;
+
+        dialog.response.connect ((dialog, response) => {
+            alarm.editing = false;
+            if (response == Gtk.ResponseType.OK) {
+                ((SetupDialog) dialog).apply_to_alarm (alarm);
+                save ();
+            } else if (response == DELETE_ALARM) {
+                alarms.delete_item (alarm);
+                save ();
+            }
+            dialog.destroy ();
+        });
+        dialog.show ();
+    }
+
+    internal void delete (Item alarm) {
+        alarms.delete_item (alarm);
+        save ();
+    }
+
+    private void reset_view () {
+        visible_child = alarms.get_n_items () == 0 ? empty_view : list_view;
+    }
+
+    public void activate_new () {
+        var dialog = new SetupDialog ((Gtk.Window) get_toplevel (), null, alarms);
+        dialog.response.connect ((dialog, response) => {
+            if (response == Gtk.ResponseType.OK) {
+                var alarm = new Item ();
+                ((SetupDialog) dialog).apply_to_alarm (alarm);
+                alarms.add (alarm);
+                save ();
+            }
+            dialog.destroy ();
+        });
+        dialog.show ();
+    }
+}
+
+} // namespace Alarm
+} // namespace Clocks
diff --git a/src/alarm-item.vala b/src/alarm-item.vala
new file mode 100644
index 0000000..8849f38
--- /dev/null
+++ b/src/alarm-item.vala
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2013  Paolo Borelli <pborelli gnome org>
+ * Copyright (C) 2020  Zander Brown <zbrown 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 {
+namespace Alarm {
+
+private struct AlarmTime {
+    public int hour;
+    public int minute;
+}
+
+private class Item : Object, ContentItem {
+    const int SNOOZE_MINUTES = 9;
+    const int RING_MINUTES = 3;
+
+    // FIXME: should we add a "MISSED" state where the alarm stopped
+    // ringing but we keep showing the ringing panel?
+    public enum State {
+        READY,
+        RINGING,
+        SNOOZING
+    }
+
+    public bool editing { get; set; default = false; }
+
+    public string id { get; construct set; }
+
+    public string? name {
+        get {
+            return _name;
+        }
+
+        set {
+            _name = (string) value;
+            setup_bell ();
+        }
+    }
+
+    public AlarmTime time { get; set; }
+
+    public Utils.Weekdays? days { get; set; }
+
+    public State state { get; private set; }
+
+    public string time_label {
+         owned get {
+            return Utils.WallClock.get_default ().format_time (alarm_time);
+         }
+    }
+
+    public string snooze_time_label {
+         owned get {
+            return Utils.WallClock.get_default ().format_time (snooze_time);
+         }
+    }
+
+    public string? days_label {
+         owned get {
+            return days != null ? (string?) ((Utils.Weekdays) days).get_label () : null;
+         }
+    }
+
+    [CCode (notify = false)]
+    public bool active {
+        get {
+            return _active && !this.editing;
+        }
+
+        set {
+            if (value != _active) {
+                _active = value;
+                if (_active) {
+                    reset ();
+                } else if (state == State.RINGING) {
+                    stop ();
+                }
+                notify_property ("active");
+            }
+        }
+    }
+
+    private string _name;
+    private bool _active = true;
+    private GLib.DateTime alarm_time;
+    private GLib.DateTime snooze_time;
+    private GLib.DateTime ring_end_time;
+    private Utils.Bell bell;
+    private GLib.Notification notification;
+
+    public Item (string? id = null) {
+        var guid = id != null ? (string) id : GLib.DBus.generate_guid ();
+        Object (id: guid);
+    }
+
+    private void setup_bell () {
+        bell = new Utils.Bell ("alarm-clock-elapsed");
+        notification = new GLib.Notification (_("Alarm"));
+        notification.set_body (name);
+        notification.add_button (_("Stop"), "app.stop-alarm::".concat (id));
+        notification.add_button (_("Snooze"), "app.snooze-alarm::".concat (id));
+    }
+
+    public void reset () {
+        update_alarm_time ();
+        update_snooze_time (alarm_time);
+        state = State.READY;
+    }
+
+    private void update_alarm_time () {
+        var wallclock = Utils.WallClock.get_default ();
+        var now = wallclock.date_time;
+        var dt = new GLib.DateTime (wallclock.timezone,
+                                    now.get_year (),
+                                    now.get_month (),
+                                    now.get_day_of_month (),
+                                    time.hour,
+                                    time.minute,
+                                    0);
+
+        if (days == null || ((Utils.Weekdays) days).empty) {
+            // Alarm without days.
+            if (dt.compare (now) <= 0) {
+                // Time already passed, ring tomorrow.
+                dt = dt.add_days (1);
+            }
+        } else {
+            // Alarm with at least one day set.
+            // Find the next possible day for ringing
+            while (dt.compare (now) <= 0 || ! ((Utils.Weekdays) days).get ((Utils.Weekdays.Day) 
(dt.get_day_of_week () - 1))) {
+                dt = dt.add_days (1);
+            }
+        }
+
+        alarm_time = dt;
+    }
+
+    private void update_snooze_time (GLib.DateTime start_time) {
+        snooze_time = start_time.add_minutes (SNOOZE_MINUTES);
+    }
+
+    public virtual signal void ring () {
+        var app = (Clocks.Application) GLib.Application.get_default ();
+        app.send_notification ("alarm-clock-elapsed", notification);
+        bell.ring ();
+    }
+
+    private void start_ringing (GLib.DateTime now) {
+        update_snooze_time (now);
+        ring_end_time = now.add_minutes (RING_MINUTES);
+        state = State.RINGING;
+        ring ();
+    }
+
+    public void snooze () {
+        bell.stop ();
+        state = State.SNOOZING;
+    }
+
+    public void stop () {
+        bell.stop ();
+        update_snooze_time (alarm_time);
+        state = State.READY;
+    }
+
+    private bool compare_with_item (Item i) {
+        return (this.alarm_time.compare (i.alarm_time) == 0 && (this.active || this.editing) && i.active);
+    }
+
+    public bool check_duplicate_alarm (List<Item> alarms) {
+        update_alarm_time ();
+
+        foreach (var item in alarms) {
+            if (this.compare_with_item (item)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    // Update the state and ringing time. Ring or stop
+    // depending on the current time.
+    // Returns true if the state changed, false otherwise.
+    public bool tick () {
+        if (!active) {
+            return false;
+        }
+
+        State last_state = state;
+
+        var wallclock = Utils.WallClock.get_default ();
+        var now = wallclock.date_time;
+
+        if (state == State.RINGING && now.compare (ring_end_time) > 0) {
+            stop ();
+        }
+
+        if (state == State.SNOOZING && now.compare (snooze_time) > 0) {
+            start_ringing (now);
+        }
+
+        if (state == State.READY && now.compare (alarm_time) > 0) {
+            start_ringing (now);
+            update_alarm_time (); // reschedule for the next repeat
+        }
+
+        return state != last_state;
+    }
+
+    public void serialize (GLib.VariantBuilder builder) {
+        builder.open (new GLib.VariantType ("a{sv}"));
+        builder.add ("{sv}", "name", new GLib.Variant.string ((string) name));
+        builder.add ("{sv}", "id", new GLib.Variant.string (id));
+        builder.add ("{sv}", "active", new GLib.Variant.boolean (active));
+        builder.add ("{sv}", "hour", new GLib.Variant.int32 (time.hour));
+        builder.add ("{sv}", "minute", new GLib.Variant.int32 (time.minute));
+        builder.add ("{sv}", "days", ((Utils.Weekdays) days).serialize ());
+        builder.close ();
+    }
+
+    public static ContentItem? deserialize (Variant alarm_variant) {
+        string key;
+        Variant val;
+        string? name = null;
+        string? id = null;
+        bool active = true;
+        int hour = -1;
+        int minute = -1;
+        Utils.Weekdays? days = null;
+
+        var iter = alarm_variant.iterator ();
+        while (iter.next ("{sv}", out key, out val)) {
+            if (key == "name") {
+                name = (string) val;
+            } else if (key == "id") {
+                id = (string) val;
+            } else if (key == "active") {
+                active = (bool) val;
+            } else if (key == "hour") {
+                hour = (int32) val;
+            } else if (key == "minute") {
+                minute = (int32) val;
+            } else if (key == "days") {
+                days = Utils.Weekdays.deserialize (val);
+            }
+        }
+
+        if (hour >= 0 && minute >= 0) {
+            Item alarm = new Item (id);
+            alarm.name = name;
+            alarm.active = active;
+            alarm.time = { hour, minute };
+            alarm.days = days;
+            alarm.reset ();
+            return alarm;
+        } else {
+            warning ("Invalid alarm %s", name != null ? (string) name : "[unnamed]");
+        }
+
+        return null;
+    }
+}
+
+} // namespace Alarm
+} // namespace Clocks
diff --git a/src/alarm-ringing-panel.vala b/src/alarm-ringing-panel.vala
new file mode 100644
index 0000000..820a5bb
--- /dev/null
+++ b/src/alarm-ringing-panel.vala
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2013  Paolo Borelli <pborelli gnome org>
+ * Copyright (C) 2020  Zander Brown <zbrown 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 {
+namespace Alarm {
+
+[GtkTemplate (ui = "/org/gnome/clocks/ui/alarm-ringing-panel.ui")]
+private class RingingPanel : Gtk.Bin {
+    public Item? alarm {
+        get {
+            return _alarm;
+        }
+        set {
+            if (_alarm != null) {
+                ((Item) _alarm).disconnect (alarm_state_handler);
+            }
+
+            _alarm = value;
+
+            if (_alarm != null) {
+                alarm_state_handler = ((Item) _alarm).notify["state"].connect (() => {
+                    if (((Item) _alarm).state != Item.State.RINGING) {
+                        dismiss ();
+                    }
+                });
+
+                stop_button.action_target = ((Item) _alarm).id;
+                stop_button.action_name = "app.stop-alarm";
+
+                snooze_button.action_target = ((Item) _alarm).id;
+                snooze_button.action_name = "app.snooze-alarm";
+            }
+
+            update ();
+        }
+    }
+
+    private Item? _alarm;
+    private ulong alarm_state_handler;
+    [GtkChild]
+    private Gtk.Label title_label;
+    [GtkChild]
+    private Gtk.Label time_label;
+    [GtkChild]
+    private Gtk.Button stop_button;
+    [GtkChild]
+    private Gtk.Button snooze_button;
+
+    construct {
+        // Start ticking...
+        Utils.WallClock.get_default ().tick.connect (update);
+    }
+
+    public virtual signal void dismiss () {
+        alarm = null;
+    }
+
+    private void update () {
+        if (alarm != null) {
+            title_label.label = (string) ((Item) alarm).name;
+            if (((Item) alarm).state == SNOOZING) {
+                time_label.label = ((Item) alarm).snooze_time_label;
+            } else {
+                time_label.label = ((Item) alarm).time_label;
+            }
+        } else {
+            title_label.label = "";
+            time_label.label = "";
+        }
+    }
+}
+
+} // namespace Alarm
+} // namespace Clocks
diff --git a/src/alarm-row.vala b/src/alarm-row.vala
new file mode 100644
index 0000000..c82f81e
--- /dev/null
+++ b/src/alarm-row.vala
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2013  Paolo Borelli <pborelli gnome org>
+ * Copyright (C) 2020  Zander Brown <zbrown 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 {
+namespace Alarm {
+
+[GtkTemplate (ui = "/org/gnome/clocks/ui/alarm-row.ui")]
+private class Row : Gtk.ListBoxRow {
+    public Item alarm { get; construct set; }
+    public Face face { get; construct set; }
+
+    [GtkChild]
+    private Gtk.Switch toggle;
+    [GtkChild]
+    private Gtk.Label title;
+    [GtkChild]
+    private Gtk.Revealer title_reveal;
+    [GtkChild]
+    private Gtk.Label time;
+    [GtkChild]
+    private Gtk.Label repeats;
+    [GtkChild]
+    private Gtk.Revealer repeats_reveal;
+
+    public Row (Item alarm, Face face) {
+        Object (alarm: alarm, face: face);
+
+        alarm.notify["days"].connect (update_repeats);
+
+        alarm.bind_property ("active", toggle, "active", SYNC_CREATE | BIDIRECTIONAL);
+
+        alarm.notify["name"].connect (update);
+        alarm.notify["active"].connect (update);
+        alarm.notify["state"].connect (update);
+        alarm.notify["time"].connect (update);
+
+        update_repeats ();
+        update ();
+    }
+
+    private void update_repeats () {
+        repeats_reveal.reveal_child = !((Utils.Weekdays) alarm.days).empty;
+        repeats.label = (string) alarm.days_label;
+    }
+
+    private void update () {
+        if (alarm.active) {
+            get_style_context ().add_class ("active");
+        } else {
+            get_style_context ().remove_class ("active");
+        }
+
+        if (alarm.state == Item.State.SNOOZING) {
+            get_style_context ().add_class ("snoozing");
+            time.label = alarm.snooze_time_label;
+        } else {
+            get_style_context ().remove_class ("snoozing");
+            time.label = alarm.time_label;
+        }
+
+        var label = alarm.name;
+
+        // Prior to 3.36 unamed alarms would just be called "Alarm",
+        // pretend alarms called "Alarm" don't have a name (of course
+        // this fails if the language/translation has since changed)
+        if (alarm.name == _("Alarm")) {
+            label = null;
+        }
+
+        if (alarm.state == Item.State.SNOOZING) {
+            if (label != null && ((string) label).length > 0) {
+                // Translators: The alarm for the time %s titled %s has been "snoozed"
+                label = _("Snoozed from %s: %s").printf (alarm.time_label, (string) label);
+            } else {
+                // Translators: %s is a time
+                label = _("Snoozed from %s").printf (alarm.time_label);
+            }
+        }
+
+        title_reveal.reveal_child = label != null && ((string) label).length > 0;
+        title.label = (string) label;
+    }
+
+    [GtkCallback]
+    private void edit () {
+        face.edit (alarm);
+    }
+
+    [GtkCallback]
+    private void delete () {
+        face.delete (alarm);
+    }
+}
+
+} // namespace Alarm
+} // namespace Clocks
diff --git a/src/alarm-setup-dialog.vala b/src/alarm-setup-dialog.vala
new file mode 100644
index 0000000..adee495
--- /dev/null
+++ b/src/alarm-setup-dialog.vala
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2013  Paolo Borelli <pborelli gnome org>
+ * Copyright (C) 2020  Zander Brown <zbrown 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 {
+namespace Alarm {
+
+// Response used for the "Delete Alarm" button in the edit dialogue
+const int DELETE_ALARM = 2;
+
+[GtkTemplate (ui = "/org/gnome/clocks/ui/alarm-setup-dialog.ui")]
+private class SetupDialog : Gtk.Dialog {
+    private Utils.WallClock.Format format;
+    [GtkChild]
+    private Gtk.Grid time_grid;
+    [GtkChild]
+    private Gtk.SpinButton h_spinbutton;
+    [GtkChild]
+    private Gtk.SpinButton m_spinbutton;
+    [GtkChild]
+    private Gtk.Entry name_entry;
+    private AmPmToggleButton am_pm_button;
+    [GtkChild]
+    private DayPickerRow repeats;
+    [GtkChild]
+    private Gtk.Stack am_pm_stack;
+    [GtkChild]
+    private Gtk.Revealer label_revealer;
+    [GtkChild]
+    private Gtk.Button delete_button;
+    private List<Item> other_alarms;
+
+    static construct {
+        typeof (DayPickerRow).ensure ();
+    }
+
+    public SetupDialog (Gtk.Window parent, Item? alarm, ListModel all_alarms) {
+        Object (transient_for: parent,
+                title: alarm != null ? _("Edit Alarm") : _("New Alarm"),
+                use_header_bar: 1);
+
+        add_button (_("Cancel"), Gtk.ResponseType.CANCEL);
+        if (alarm != null) {
+            add_button (_("Done"), Gtk.ResponseType.OK);
+        } else {
+            add_button (_("Add"), Gtk.ResponseType.OK);
+        }
+        set_default_response (Gtk.ResponseType.OK);
+
+        delete_button.visible = alarm != null;
+
+        other_alarms = new List<Item> ();
+        var n = all_alarms.get_n_items ();
+        for (int i = 0; i < n; i++) {
+            var item = (Item) all_alarms.get_object (i);
+            if (alarm != item) {
+                other_alarms.prepend ((Item) all_alarms.get_object (i));
+            }
+        }
+
+        // Force LTR since we do not want to reverse [hh] : [mm]
+        time_grid.set_direction (Gtk.TextDirection.LTR);
+
+        format = Utils.WallClock.get_default ().format;
+        am_pm_button = new AmPmToggleButton ();
+        am_pm_button.clicked.connect (() => {
+            avoid_duplicate_alarm ();
+        });
+
+        if (format == Utils.WallClock.Format.TWENTYFOUR) {
+            h_spinbutton.set_range (0, 23);
+        } else {
+            h_spinbutton.set_range (1, 12);
+            am_pm_button.hexpand = false;
+            am_pm_button.vexpand = false;
+            am_pm_button.halign = Gtk.Align.CENTER;
+            am_pm_button.valign = Gtk.Align.CENTER;
+            am_pm_stack.add (am_pm_button);
+            am_pm_stack.visible_child = am_pm_button;
+        }
+
+        set_from_alarm (alarm);
+    }
+
+    // Sets up the dialog to show the values of alarm.
+    public void set_from_alarm (Item? alarm) {
+        string? name;
+        bool active;
+        int hour;
+        int minute;
+        unowned Utils.Weekdays? days;
+
+        if (alarm == null) {
+            var wc = Utils.WallClock.get_default ();
+            // Not great but we can't null it
+            name = "";
+            hour = wc.date_time.get_hour ();
+            minute = wc.date_time.get_minute ();
+            days = null;
+            active = true;
+        } else {
+            name = ((Item) alarm).name;
+            hour = ((Item) alarm).time.hour;
+            minute = ((Item) alarm).time.minute;
+            days = ((Item) alarm).days;
+            active = ((Item) alarm).active;
+        }
+
+        // Set the time.
+        if (format == Utils.WallClock.Format.TWELVE) {
+            if (hour < 12) {
+                am_pm_button.choice = AmPmToggleButton.AmPm.AM;
+            } else {
+                am_pm_button.choice = AmPmToggleButton.AmPm.PM;
+                hour -= 12;
+            }
+
+            if (hour == 0) {
+                hour = 12;
+            }
+        }
+        h_spinbutton.set_value (hour);
+        m_spinbutton.set_value (minute);
+
+        // Set the name.
+        name_entry.set_text ((string) name);
+
+        if (days != null) {
+            repeats.load ((Utils.Weekdays) days);
+        }
+    }
+
+    // Sets alarm according to the current dialog settings.
+    public void apply_to_alarm (Item alarm) {
+        var name = name_entry.get_text ();
+        var hour = h_spinbutton.get_value_as_int ();
+        var minute = m_spinbutton.get_value_as_int ();
+        if (format == Utils.WallClock.Format.TWELVE) {
+            var choice = am_pm_button.choice;
+            if (choice == AmPmToggleButton.AmPm.AM && hour == 12) {
+                hour = 0;
+            } else if (choice == AmPmToggleButton.AmPm.PM && hour != 12) {
+                hour += 12;
+            }
+        }
+
+        AlarmTime time = { hour, minute };
+
+        var days = repeats.store ();
+
+        alarm.freeze_notify ();
+
+        alarm.name = name;
+        alarm.time = time;
+        alarm.days = days;
+
+        // Force update of alarm_time before notifying the changes
+        alarm.reset ();
+
+        alarm.thaw_notify ();
+    }
+
+    private void avoid_duplicate_alarm () {
+        var alarm = new Item ();
+        apply_to_alarm (alarm);
+
+        var duplicate = alarm.check_duplicate_alarm (other_alarms);
+        this.set_response_sensitive (Gtk.ResponseType.OK, !duplicate);
+        label_revealer.set_reveal_child (duplicate);
+    }
+
+    [GtkCallback]
+    private void days_changed () {
+        avoid_duplicate_alarm ();
+    }
+
+    [GtkCallback]
+    private void entry_changed (Gtk.Editable editable) {
+        avoid_duplicate_alarm ();
+    }
+
+    [GtkCallback]
+    private void spinbuttons_changed (Gtk.Editable editable) {
+        avoid_duplicate_alarm ();
+    }
+
+    [GtkCallback]
+    private bool show_leading_zeros (Gtk.SpinButton spin_button) {
+        spin_button.set_text ("%02i".printf (spin_button.get_value_as_int ()));
+        return true;
+    }
+
+    [GtkCallback]
+    private void delete () {
+        response (DELETE_ALARM);
+    }
+}
+
+} // namespace Alarm
+} // namespace Clocks
diff --git a/src/meson.build b/src/meson.build
index 4e782a4..8dd7a87 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -3,7 +3,12 @@ vapi_sources = [
 ]
 
 clocks_vala_sources = [
-  'alarm.vala',
+  'alarm-day-picker-row.vala',
+  'alarm-face.vala',
+  'alarm-item.vala',
+  'alarm-ringing-panel.vala',
+  'alarm-row.vala',
+  'alarm-setup-dialog.vala',
   'application.vala',
   'clock.vala',
   'geocoding.vala',


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]