1/*
2* Copyright (c) 2009-2013 Yorba Foundation
3*               2017-2018 elementary, Inc. (https://elementary.io)
4*
5* This program is free software; you can redistribute it and/or
6* modify it under the terms of the GNU Lesser General Public
7* License as published by the Free Software Foundation; either
8* version 2.1 of the License, or (at your option) any later version.
9*
10* This program is distributed in the hope that it will be useful,
11* but WITHOUT ANY WARRANTY; without even the implied warranty of
12* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13* General Public License for more details.
14*
15* You should have received a copy of the GNU General Public
16* License along with this program; if not, write to the
17* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18* Boston, MA 02110-1301 USA
19*/
20
21public class AdjustDateTimeDialog : Granite.Dialog {
22    private const int64 SECONDS_IN_DAY = 60 * 60 * 24;
23    private const int64 SECONDS_IN_HOUR = 60 * 60;
24    private const int64 SECONDS_IN_MINUTE = 60;
25    private bool no_original_time = false;
26
27    private const int CALENDAR_THUMBNAIL_SCALE = 1;
28
29    private int64 original_time;
30    private Gtk.Label original_time_label;
31    private Gtk.Calendar calendar;
32    private Gtk.SpinButton hour;
33    private Gtk.SpinButton minute;
34    private Gtk.SpinButton second;
35    private Gtk.ComboBoxText system;
36    private Gtk.RadioButton relativity_radio_button;
37    private Gtk.RadioButton batch_radio_button;
38    private Gtk.CheckButton modify_originals_check_button;
39    private Gtk.Label notification;
40    private GLib.Settings ui_settings;
41    private GLib.Settings file_settings;
42
43    private enum TimeSystem {
44        AM,
45        PM,
46        24HR;
47    }
48
49    private TimeSystem previous_time_system;
50
51    construct {
52        ui_settings = new GLib.Settings (GSettingsConfigurationEngine.UI_PREFS_SCHEMA_NAME);
53        file_settings = new GLib.Settings (GSettingsConfigurationEngine.FILES_PREFS_SCHEMA_NAME);
54
55        add_buttons (
56            (_("_Cancel")), Gtk.ResponseType.CANCEL,
57            (_("_Apply")), Gtk.ResponseType.OK
58        );
59
60        deletable = false;
61        modal = true;
62        resizable = false;
63        title = _(Resources.ADJUST_DATE_TIME_LABEL);
64        transient_for = AppWindow.get_instance ();
65        set_default_response (Gtk.ResponseType.OK);
66    }
67
68    public AdjustDateTimeDialog (Dateable source, int photo_count, bool display_options = true,
69                                 bool contains_video = false, bool only_video = false) {
70        assert (source != null);
71
72        calendar = new Gtk.Calendar ();
73        calendar.day_selected.connect (on_time_changed);
74        calendar.month_changed.connect (on_time_changed);
75        calendar.next_year.connect (on_time_changed);
76        calendar.prev_year.connect (on_time_changed);
77
78        if (ui_settings.get_boolean ("use-24-hour-time"))
79            hour = new Gtk.SpinButton.with_range (0, 23, 1);
80        else
81            hour = new Gtk.SpinButton.with_range (1, 12, 1);
82
83        hour.output.connect (on_spin_button_output);
84        hour.set_width_chars (2);
85
86        minute = new Gtk.SpinButton.with_range (0, 59, 1);
87        minute.set_width_chars (2);
88        minute.output.connect (on_spin_button_output);
89
90        second = new Gtk.SpinButton.with_range (0, 59, 1);
91        second.set_width_chars (2);
92        second.output.connect (on_spin_button_output);
93
94        system = new Gtk.ComboBoxText ();
95        system.append_text (_ ("AM"));
96        system.append_text (_ ("PM"));
97        system.append_text (_ ("24 Hr"));
98        system.changed.connect (on_time_system_changed);
99
100        relativity_radio_button = new Gtk.RadioButton.with_mnemonic (null,
101                _ ("_Shift photos/videos by the same amount"));
102        relativity_radio_button.set_active (ui_settings.get_boolean ("keep-relativity"));
103        relativity_radio_button.sensitive = display_options && photo_count > 1;
104
105        batch_radio_button = new Gtk.RadioButton.with_mnemonic (relativity_radio_button.get_group (),
106                _ ("Set _all photos/videos to this time"));
107        batch_radio_button.set_active (!ui_settings.get_boolean ("keep-relativity"));
108        batch_radio_button.sensitive = display_options && photo_count > 1;
109        batch_radio_button.toggled.connect (on_time_changed);
110
111        if (contains_video) {
112            modify_originals_check_button = new Gtk.CheckButton.with_mnemonic (ngettext (
113                    "_Modify original photo file", "_Modify original photo files", photo_count));
114        } else {
115            modify_originals_check_button = new Gtk.CheckButton.with_mnemonic (ngettext (
116                    "_Modify original file", "_Modify original files", photo_count));
117        }
118
119        modify_originals_check_button.set_active (file_settings.get_boolean ("commit-metadata") && display_options);
120        modify_originals_check_button.sensitive = (!only_video) &&
121                (!file_settings.get_boolean ("commit-metadata") && display_options);
122
123        Gdk.Pixbuf preview = null;
124        try {
125            // Instead of calling get_pixbuf () here, we use the thumbnail instead;
126            // this was needed for Videos, since they don't support get_pixbuf ().
127            preview = source.get_thumbnail (CALENDAR_THUMBNAIL_SCALE);
128        } catch (Error err) {
129            warning ("Unable to fetch preview for %s", source.to_string ());
130        }
131
132        var image = (preview != null) ? new Gtk.Image.from_pixbuf (preview) : new Gtk.Image ();
133        original_time_label = new Gtk.Label (null);
134
135        notification = new Gtk.Label ("");
136        notification.set_line_wrap (true);
137        notification.set_justify (Gtk.Justification.CENTER);
138        notification.set_size_request (-1, -1);
139        notification.set_padding (12, 6);
140
141        var clock_grid = new Gtk.Grid ();
142        clock_grid.column_spacing = 3;
143        clock_grid.add (hour);
144        clock_grid.add (new Gtk.Label (":")); // internationalize?
145        clock_grid.add (minute);
146        clock_grid.add (new Gtk.Label (":"));
147        clock_grid.add (second);
148        clock_grid.add (system);
149
150        var grid = new Gtk.Grid ();
151        grid.column_spacing = 12;
152        grid.row_spacing = 12;
153        grid.margin = 6;
154        grid.attach (image, 0, 0);
155        grid.attach (original_time_label, 0, 1);
156        grid.attach (calendar, 1, 0);
157        grid.attach (clock_grid, 1, 1);
158        grid.attach (notification, 0, 5, 2, 1);
159
160        if (display_options) {
161            grid.attach (relativity_radio_button, 1, 2);
162            grid.attach (batch_radio_button, 1, 3);
163            grid.attach (modify_originals_check_button, 1, 4);
164        }
165
166        get_content_area ().add (grid);
167
168        original_time = source.get_exposure_time ();
169
170        if (original_time == 0) {
171            original_time = time_t ();
172            no_original_time = true;
173        }
174
175        set_time (new DateTime.from_unix_local (original_time));
176        set_original_time_label (ui_settings.get_boolean ("use-24-hour-time"));
177    }
178
179    private void set_time (DateTime time) {
180        calendar.select_month (time.get_month (), time.get_year ());
181        calendar.select_day (time.get_day_of_month ());
182
183        if (ui_settings.get_boolean ("use-24-hour-time")) {
184            hour.set_value (time.get_hour ());
185            system.set_active (TimeSystem.24HR);
186        } else {
187            int ampm_hour = time.get_hour () % 12;
188            hour.set_value ((ampm_hour == 0) ? 12 : ampm_hour);
189            system.set_active ((time.get_hour () >= 12) ? TimeSystem.PM : TimeSystem.AM);
190        }
191
192        minute.set_value (time.get_minute ());
193        second.set_value (time.get_second ());
194
195        previous_time_system = (TimeSystem) system.get_active ();
196    }
197
198    private void set_original_time_label (bool use_24_hr_format) {
199        if (no_original_time)
200            return;
201
202        original_time_label.set_text (
203            _ ("Original: %s").printf (
204                (new DateTime.from_unix_local (original_time)).format (
205                    use_24_hr_format ? _ ("%m/%d/%Y, %H:%M:%S") : _ ("%m/%d/%Y, %I:%M:%S %p")
206                )
207            )
208        );
209    }
210
211    private int64 get_time () {
212        // convert to 24 hr
213        int hour = (int) hour.get_value ();
214        hour = (hour == 12 && system.get_active () != TimeSystem.24HR) ? 0 : hour;
215        hour += ((system.get_active () == TimeSystem.PM) ? 12 : 0);
216
217        uint year, month, day;
218        calendar.get_date (out year, out month, out day);
219        var date_time = new DateTime.local (
220            (int) year, (int) month, (int) day, hour, (int) minute.get_value (), second.get_value ()
221        );
222        return date_time.to_unix ();
223    }
224
225    public bool execute (out int64 time_shift, out bool keep_relativity,
226                         out bool modify_originals) {
227        show_all ();
228
229        bool response = false;
230
231        if (run () == Gtk.ResponseType.OK) {
232            if (no_original_time) {
233                time_shift = get_time ();
234            } else {
235                time_shift = get_time () - original_time;
236            }
237
238            keep_relativity = relativity_radio_button.get_active ();
239
240            if (relativity_radio_button.sensitive)
241                ui_settings.set_boolean ("keep-relativity", keep_relativity);
242
243            modify_originals = modify_originals_check_button.get_active ();
244
245            if (modify_originals_check_button.sensitive)
246                ui_settings.set_boolean ("modify-originals", modify_originals);
247
248            response = true;
249        } else {
250            time_shift = 0;
251            keep_relativity = true;
252            modify_originals = false;
253        }
254
255        destroy ();
256
257        return response;
258    }
259
260    private bool on_spin_button_output (Gtk.SpinButton button) {
261        button.set_text ("%02d".printf ((int) button.get_value ()));
262
263        on_time_changed ();
264
265        return true;
266    }
267
268    private void on_time_changed () {
269        int64 time_shift = get_time () - original_time;
270
271        previous_time_system = (TimeSystem) system.get_active ();
272
273        if (time_shift == 0 || no_original_time || (batch_radio_button.get_active () &&
274                batch_radio_button.sensitive)) {
275            notification.hide ();
276        } else {
277            bool forward = time_shift > 0;
278            int days, hours, minutes, seconds;
279
280            time_shift = time_shift.abs ();
281
282            days = (int) (time_shift / SECONDS_IN_DAY);
283            time_shift = time_shift % SECONDS_IN_DAY;
284            hours = (int) (time_shift / SECONDS_IN_HOUR);
285            time_shift = time_shift % SECONDS_IN_HOUR;
286            minutes = (int) (time_shift / SECONDS_IN_MINUTE);
287            seconds = (int) (time_shift % SECONDS_IN_MINUTE);
288
289            string shift_status = (forward) ?
290                                  _ ("Exposure time will be shifted forward by\n%d %s, %d %s, %d %s, and %d %s.") :
291                                  _ ("Exposure time will be shifted backward by\n%d %s, %d %s, %d %s, and %d %s.");
292
293            notification.set_text (shift_status.printf (days, ngettext ("day", "days", days),
294                                   hours, ngettext ("hour", "hours", hours), minutes,
295                                   ngettext ("minute", "minutes", minutes), seconds,
296                                   ngettext ("second", "seconds", seconds)));
297
298            notification.show ();
299        }
300    }
301
302    private void on_time_system_changed () {
303        if (previous_time_system == system.get_active ())
304            return;
305
306        ui_settings.set_boolean ("use-24-hour-time", system.get_active () == TimeSystem.24HR);
307
308        if (system.get_active () == TimeSystem.24HR) {
309            int time = (hour.get_value () == 12.0) ? 0 : (int) hour.get_value ();
310            time = time + ((previous_time_system == TimeSystem.PM) ? 12 : 0);
311
312            hour.set_range (0, 23);
313            set_original_time_label (true);
314
315            hour.set_value (time);
316        } else {
317            int ampm_hour = ((int) hour.get_value ()) % 12;
318
319            hour.set_range (1, 12);
320            set_original_time_label (false);
321
322            hour.set_value ((ampm_hour == 0) ? 12 : ampm_hour);
323        }
324
325        on_time_changed ();
326    }
327}
328