1/*
2 * Copyright (c) 2016 gnome-pomodoro contributors
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 *
17 * Authors: Kamil Prusko <kamilprusko@gmail.com>
18 *
19 */
20
21
22namespace Pomodoro
23{
24    [GtkTemplate (ui = "/org/gnome/pomodoro/window.ui")]
25    public class Window : Gtk.ApplicationWindow, Gtk.Buildable
26    {
27        private const int MIN_WIDTH = 500;
28        private const int MIN_HEIGHT = 650;
29
30        private const double FADED_IN = 1.0;
31        private const double FADED_OUT = 0.2;
32
33        private const double TIMER_LINE_WIDTH = 6.0;
34        private const double TIMER_RADIUS = 165.0;
35
36        private struct Name
37        {
38            public string name;
39            public string display_name;
40        }
41
42        private const Name[] STATE_NAMES = {
43            { "null", "" },
44            { "pomodoro", N_("Pomodoro") },
45            { "short-break", N_("Short Break") },
46            { "long-break", N_("Long Break") }
47        };
48
49        public string mode {
50            get {
51                return this.stack.visible_child_name;
52            }
53            set {
54                this.stack.visible_child_name = value;
55            }
56        }
57
58        public string default_mode {
59            get {
60                return this.default_page;
61            }
62        }
63
64        private unowned Pomodoro.Timer timer;
65
66        [GtkChild]
67        private unowned Gtk.Stack stack;
68        [GtkChild]
69        private unowned Gtk.Stack timer_stack;
70        [GtkChild]
71        private unowned Gtk.ToggleButton state_togglebutton;
72        [GtkChild]
73        private unowned Gtk.Label minutes_label;
74        [GtkChild]
75        private unowned Gtk.Label seconds_label;
76        [GtkChild]
77        private unowned Gtk.Widget timer_box;
78        [GtkChild]
79        private unowned Gtk.Button pause_button;
80        [GtkChild]
81        private unowned Gtk.Image pause_button_image;
82
83        private Pomodoro.Animation blink_animation;
84        private string default_page;
85
86        construct
87        {
88            var geometry = Gdk.Geometry () {
89                min_width = MIN_WIDTH,
90                max_width = -1,
91                min_height = MIN_HEIGHT,
92                max_height = -1
93            };
94            this.set_geometry_hints (this, geometry, Gdk.WindowHints.MIN_SIZE);
95
96            // this.stack.add_titled (this.timer_stack, "timer", _("Timer"));
97            this.stack.add_titled (new Pomodoro.StatsView (), "stats", _("Stats"));
98
99            // TODO: this.default_page should be set from application.vala
100            var application = Pomodoro.Application.get_default ();
101
102            // if (application.capabilities.has_enabled ("task-list")) {  // TODO
103            //     this.default_page = "task-list";
104            // }
105            if (application.capabilities.has_capability ("indicator")) {
106                this.default_page = "stats";
107            }
108            else {
109                this.default_page = "timer";
110            }
111
112            this.stack.visible_child_name = this.default_page;
113
114            this.on_timer_state_notify ();
115            this.on_timer_elapsed_notify ();
116            this.on_timer_is_paused_notify ();
117        }
118
119        public void parser_finished (Gtk.Builder builder)
120        {
121            this.timer = Pomodoro.Timer.get_default ();
122            this.insert_action_group ("timer", this.timer.get_action_group ());
123
124            base.parser_finished (builder);
125
126            var state_togglebutton = builder.get_object ("state_togglebutton");
127            state_togglebutton.bind_property ("active",
128                                              builder.get_object ("state_popover"),
129                                              "visible",
130                                              GLib.BindingFlags.BIDIRECTIONAL);
131
132            this.timer.notify["state"].connect_after (this.on_timer_state_notify);
133            this.timer.notify["elapsed"].connect_after (this.on_timer_elapsed_notify);
134            this.timer.notify["is-paused"].connect_after (this.on_timer_is_paused_notify);
135        }
136
137        private void on_blink_animation_complete ()
138        {
139            if (this.timer.is_paused) {
140                this.blink_animation.start_with_value (1.0);
141            }
142        }
143
144        private void on_timer_state_notify ()
145        {
146            this.timer_stack.visible_child_name =
147                    (this.timer.state is Pomodoro.DisabledState) ? "disabled" : "enabled";
148
149            foreach (var mapping in STATE_NAMES)
150            {
151                if (mapping.name == this.timer.state.name && mapping.display_name != "") {
152                    this.state_togglebutton.label = mapping.display_name;
153                    break;
154                }
155            }
156        }
157
158        private void on_timer_elapsed_notify ()
159        {
160            if (!(this.timer.state is Pomodoro.DisabledState))
161            {
162                var remaining = (uint) double.max (Math.ceil (this.timer.remaining), 0.0);
163                var minutes   = remaining / 60;
164                var seconds   = remaining % 60;
165
166                this.minutes_label.label = "%02u".printf (minutes);
167                this.seconds_label.label = "%02u".printf (seconds);
168
169                this.timer_box.queue_draw ();
170            }
171        }
172
173        private void on_timer_is_paused_notify ()
174        {
175            if (this.blink_animation != null) {
176                this.blink_animation.stop ();
177                this.blink_animation = null;
178            }
179
180            if (this.timer.is_paused) {
181                this.pause_button_image.icon_name = "media-playback-start-symbolic";
182                this.pause_button.action_name     = "timer.resume";
183                this.pause_button.tooltip_text    = _("Resume");
184
185                this.blink_animation = new Pomodoro.Animation (Pomodoro.AnimationMode.BLINK,
186                                                               2500,
187                                                               5);
188                this.blink_animation.add_property (this.timer_box,
189                                                   "opacity",
190                                                   FADED_OUT);
191                this.blink_animation.complete.connect (this.on_blink_animation_complete);
192                this.blink_animation.start_with_value (1.0);
193            }
194            else {
195                this.pause_button_image.icon_name = "media-playback-pause-symbolic";
196                this.pause_button.action_name     = "timer.pause";
197                this.pause_button.tooltip_text    = _("Pause");
198
199                this.blink_animation = new Pomodoro.Animation (Pomodoro.AnimationMode.EASE_OUT,
200                                                               200,
201                                                               50);
202                this.blink_animation.add_property (this.timer_box,
203                                                   "opacity",
204                                                   1.0);
205                this.blink_animation.start ();
206            }
207        }
208
209        [GtkCallback]
210        private bool on_timer_box_draw (Gtk.Widget    widget,
211                                        Cairo.Context context)
212        {
213            if (!(this.timer.state is Pomodoro.DisabledState))
214            {
215                var style_context = widget.get_style_context ();
216                var color         = style_context.get_color (widget.get_state_flags ());
217
218                var width  = widget.get_allocated_width ();
219                var height = widget.get_allocated_height ();
220                var x      = 0.5 * width;
221                var y      = 0.5 * height;
222                var progress = this.timer.state_duration > 0.0
223                        ? this.timer.elapsed / this.timer.state_duration : 0.0;
224
225                var angle1 = - 0.5 * Math.PI - 2.0 * Math.PI * progress.clamp (0.000001, 1.0);
226                var angle2 = - 0.5 * Math.PI;
227
228                context.set_line_width (TIMER_LINE_WIDTH);
229
230                context.set_source_rgba (color.red,
231                                         color.green,
232                                         color.blue,
233                                         color.alpha * 0.1);
234                context.arc (x, y, TIMER_RADIUS, 0.0, 2 * Math.PI);
235                context.stroke ();
236
237                context.set_line_cap (Cairo.LineCap.ROUND);
238                context.set_source_rgba (color.red,
239                                         color.green,
240                                         color.blue,
241                                         color.alpha * FADED_IN - (color.alpha * 0.1) * (1.0 - FADED_IN));
242
243                context.arc_negative (x, y, TIMER_RADIUS, angle1, angle2);
244                context.stroke ();
245            }
246
247            return false;
248        }
249
250        [GtkCallback]
251        private bool on_button_press (Gtk.Widget      widget,
252                                      Gdk.EventButton event)
253        {
254            if (event.button == 1) {
255                this.begin_move_drag ((int) event.button, (int) event.x_root, (int) event.y_root, event.time);
256
257                return true;
258            }
259
260            return false;
261        }
262    }
263}
264