1/*
2* Copyright (c) 2011-2014 Yorba Foundation
3*
4* This program is free software; you can redistribute it and/or
5* modify it under the terms of the GNU Lesser General Public
6* License as published by the Free Software Foundation; either
7* version 2.1 of the License, or (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 GNU
12* General Public License for more details.
13*
14* You should have received a copy of the GNU General Public
15* License along with this program; if not, write to the
16* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17* Boston, MA 02110-1301 USA
18*/
19
20private class BasicProperties : Properties {
21    private int64 start_time = 0;
22    private int64 end_time = 0;
23    private Dimensions dimensions;
24    private EditableTitle title_entry;
25    private MediaSource? source;
26    private Properties.Label place_label;
27    private int photo_count;
28    private int event_count;
29    private int video_count;
30    private string camera_make;
31    private string camera_model;
32    private string exposure;
33    private string exposure_bias;
34    private string flash;
35    private string focal_length;
36    private double gps_lat;
37    private string gps_lat_ref;
38    private double gps_long;
39    private string gps_long_ref;
40    private double gps_alt;
41    private string title;
42    private string aperture;
43    private string iso;
44    private double clip_duration;
45    private string raw_developer;
46    private string raw_assoc;
47    private uint64 filesize;
48
49    public BasicProperties () {
50    }
51
52    protected override void clear_properties () {
53        base.clear_properties ();
54        camera_make = "";
55        camera_model = "";
56        title = "";
57        start_time = 0;
58        end_time = 0;
59        dimensions = Dimensions (0, 0);
60        flash = "";
61        filesize = 0;
62        focal_length = "";
63        gps_lat = 0.0;
64        gps_lat_ref = "";
65        gps_long = 0.0;
66        gps_long_ref = "";
67        photo_count = -1;
68        event_count = -1;
69        video_count = -1;
70        exposure = "";
71        exposure_bias = "";
72        aperture = "";
73        iso = "";
74        clip_duration = 0.0;
75        raw_developer = "";
76        raw_assoc = "";
77    }
78
79    protected override void get_single_properties (DataView view) {
80        base.get_single_properties (view);
81
82        source = view.source as MediaSource;
83
84        filesize = source.get_master_filesize ();
85        title = source.get_name ();
86
87        if (source is PhotoSource || source is PhotoImportSource) {
88            start_time = (source is PhotoSource) ? ((PhotoSource) source).get_exposure_time () :
89                         ((PhotoImportSource) source).get_exposure_time ();
90            end_time = start_time;
91
92            PhotoMetadata ? metadata = (source is PhotoSource) ? ((PhotoSource) source).get_metadata () :
93                                       ((PhotoImportSource) source).get_metadata ();
94
95            if (metadata != null) {
96                camera_make = metadata.get_camera_make ();
97                camera_model = metadata.get_camera_model ();
98
99                exposure = metadata.get_exposure_string ();
100                if (exposure == null)
101                    exposure = "";
102
103                exposure_bias = metadata.get_exposure_bias ();
104
105                flash = metadata.get_flash_string ();
106
107                aperture = metadata.get_aperture_string (true);
108                if (aperture == null)
109                    aperture = "";
110
111                iso = metadata.get_iso_string ();
112                if (iso == null)
113                    iso = "";
114
115                dimensions = (metadata.get_pixel_dimensions () != null) ?
116                             metadata.get_orientation ().rotate_dimensions (metadata.get_pixel_dimensions ()) :
117                             Dimensions (0, 0);
118
119                focal_length = metadata.get_focal_length_string ();
120
121                metadata.get_gps (out gps_long, out gps_long_ref, out gps_lat, out gps_lat_ref, out gps_alt);
122            }
123
124            if (source is PhotoSource)
125                dimensions = ((PhotoSource) source).get_dimensions ();
126
127            if (source is Photo && ((Photo) source).get_master_file_format () == PhotoFileFormat.RAW) {
128                Photo photo = source as Photo;
129                raw_developer = photo.get_raw_developer ().get_label ();
130                raw_assoc = photo.is_raw_developer_available (RawDeveloper.CAMERA) ? _ ("RAW+JPEG") : "";
131            }
132        } else if (source is EventSource) {
133            EventSource event_source = (EventSource) source;
134
135            start_time = event_source.get_start_time ();
136            end_time = event_source.get_end_time ();
137
138            int event_photo_count;
139            int event_video_count;
140            MediaSourceCollection.count_media (event_source.get_media (), out event_photo_count,
141                                               out event_video_count);
142
143            photo_count = event_photo_count;
144            video_count = event_video_count;
145        } else if (source is VideoSource || source is VideoImportSource) {
146            if (source is VideoSource) {
147                Video video = (Video) source;
148                clip_duration = video.get_clip_duration ();
149
150                if (video.get_is_interpretable ())
151                    dimensions = video.get_frame_dimensions ();
152
153                start_time = video.get_exposure_time ();
154            } else {
155                start_time = ((VideoImportSource) source).get_exposure_time ();
156            }
157            end_time = start_time;
158        }
159    }
160
161    protected override void get_multiple_properties (Gee.Iterable<DataView>? iter) {
162        base.get_multiple_properties (iter);
163
164        photo_count = 0;
165        video_count = 0;
166        foreach (DataView view in iter) {
167            DataSource source = view.source;
168
169            if (source is PhotoSource || source is PhotoImportSource) {
170                int64 exposure_time = (source is PhotoSource) ?
171                                       ((PhotoSource) source).get_exposure_time () :
172                                       ((PhotoImportSource) source).get_exposure_time ();
173
174                if (exposure_time != 0) {
175                    if (start_time == 0 || exposure_time < start_time)
176                        start_time = exposure_time;
177
178                    if (end_time == 0 || exposure_time > end_time)
179                        end_time = exposure_time;
180                }
181
182                photo_count++;
183            } else if (source is EventSource) {
184                EventSource event_source = (EventSource) source;
185
186                if (event_count == -1)
187                    event_count = 0;
188
189                if ((start_time == 0 || event_source.get_start_time () < start_time) &&
190                        event_source.get_start_time () != 0 ) {
191                    start_time = event_source.get_start_time ();
192                }
193                if ((end_time == 0 || event_source.get_end_time () > end_time) &&
194                        event_source.get_end_time () != 0 ) {
195                    end_time = event_source.get_end_time ();
196                } else if (end_time == 0 || event_source.get_start_time () > end_time) {
197                    end_time = event_source.get_start_time ();
198                }
199
200                int event_photo_count;
201                int event_video_count;
202                MediaSourceCollection.count_media (event_source.get_media (), out event_photo_count,
203                                                   out event_video_count);
204
205                photo_count += event_photo_count;
206                video_count += event_video_count;
207                event_count++;
208            } else if (source is VideoSource || source is VideoImportSource) {
209                int64 exposure_time = (source is VideoSource) ?
210                                       ((VideoSource) source).get_exposure_time () :
211                                       ((VideoImportSource) source).get_exposure_time ();
212
213                if (exposure_time != 0) {
214                    if (start_time == 0 || exposure_time < start_time)
215                        start_time = exposure_time;
216
217                    if (end_time == 0 || exposure_time > end_time)
218                        end_time = exposure_time;
219                }
220
221                video_count++;
222            }
223        }
224    }
225
226    protected override void get_properties (Page current_page) {
227        base.get_properties (current_page);
228
229        if (end_time == 0)
230            end_time = start_time;
231        if (start_time == 0)
232            start_time = end_time;
233    }
234
235    protected override void internal_update_properties (Page page) {
236        base.internal_update_properties (page);
237
238        if (title != null && title != "") {
239            title_entry = new EditableTitle (title);
240            title_entry.tooltip_text = _("Title");
241            title_entry.changed.connect (title_entry_changed);
242
243            attach (title_entry, 0, 0, 2, 1);
244            line_count++;
245        }
246
247        if (photo_count >= 0 || video_count >= 0) {
248            var label = new Properties.Header (_("Items:"));
249            attach (label, 0, (int) line_count, 1, 1);
250
251            if (event_count >= 0) {
252                attach_item_count_label ((ngettext ("%d Event", "%d Events", event_count)).printf (event_count));
253            }
254
255            if (photo_count > 0) {
256                attach_item_count_label ((ngettext ("%d Photo", "%d Photos", photo_count)).printf (photo_count));
257            }
258
259            if (video_count > 0) {
260                attach_item_count_label ((ngettext ("%d Video", "%d Videos", video_count)).printf (video_count));
261            }
262        }
263
264        if (start_time != 0) {
265            var start_dt = new DateTime.from_unix_local (start_time);
266            string start_date = get_prettyprint_date (start_dt);
267            string start_time = get_prettyprint_time (start_dt);
268            var end_dt = new DateTime.from_unix_local (end_time);
269            string end_date = get_prettyprint_date (end_dt);
270            string end_time = get_prettyprint_time (end_dt);
271
272            if (start_date == end_date) {
273                if (start_time == end_time) {
274                    var datetime_label = new Properties.Label ("%s at %s".printf (start_date, start_time));
275                    attach (datetime_label, 0, (int) line_count, 2, 1);
276                    line_count++;
277                } else {
278                    add_line (_ ("Date:"), start_date);
279                    // display time range
280                    add_line (_ ("From:"), start_time);
281                    add_line (_ ("To:"), end_time);
282                }
283            } else {
284                // display date range
285                add_line (_ ("From:"), start_date);
286                add_line (_ ("To:"), end_date);
287            }
288        }
289
290        if (gps_lat != 0.0 && gps_long != 0.0) {
291            place_label = new Properties.Label ("");
292            place_label.no_show_all = true;
293            place_label.visible = false;
294
295            create_place_label.begin (gps_lat, gps_long);
296
297            attach (place_label, 0, (int) line_count, 2, 1);
298
299            line_count++;
300        }
301
302        if (dimensions.has_area ()) {
303            var size_label = new Properties.Label ("%s — %d &#215; %d".printf (format_size ((int64) filesize), dimensions.width, dimensions.height));
304            attach (size_label, 0, (int) line_count, 2, 1);
305
306            line_count++;
307        }
308
309        if (clip_duration > 0.0) {
310            var duration_label = new Properties.Label (ngettext ("%.1f second", "%.1f seconds", (ulong) clip_duration).printf (clip_duration));
311            attach (duration_label, 0, (int) line_count, 2, 1);
312
313            line_count++;
314        }
315
316        if (raw_developer != null && raw_developer != "") {
317            add_line (_ ("Developer:"), raw_developer);
318        }
319
320        // RAW+JPEG flag.
321        if (raw_assoc != null && raw_assoc != "") {
322            add_line ("", raw_assoc);
323        }
324
325        if (camera_make != null && camera_make != "" && camera_model != null && camera_model != "") {
326            string camera_string;
327
328            if (camera_make in camera_model) {
329                camera_string = camera_model;
330            } else {
331                camera_string = camera_make + " " + camera_model;
332            }
333
334            var camera_label = new Properties.Label (camera_string);
335            camera_label.margin_top = 12;
336
337            attach (camera_label, 0, 8, 2, 1);
338        }
339
340        var flowbox = new Gtk.FlowBox ();
341        flowbox.column_spacing = 12;
342        flowbox.row_spacing = 12;
343        flowbox.hexpand = true;
344        flowbox.margin_top = 12;
345        flowbox.selection_mode = Gtk.SelectionMode.NONE;
346        attach (flowbox, 0, 9, 2, 1);
347
348        if (aperture != null && aperture != "") {
349            var aperture_item = new ExifItem ("aperture-symbolic", _("Aperture"), aperture);
350            flowbox.add (aperture_item);
351        }
352
353        if (focal_length != null && focal_length != "") {
354            var focal_length_item = new ExifItem ("focal-length-symbolic", _("Focal length"), focal_length);
355            flowbox.add (focal_length_item);
356        }
357
358        if (exposure != null && exposure != "") {
359            var exposure_item = new ExifItem ("exposure-symbolic", _("Exposure"), exposure);
360            flowbox.add (exposure_item);
361        }
362
363        if (iso != null && iso != "") {
364            var iso_item = new ExifItem ("iso-symbolic", _("ISO"), iso);
365            flowbox.add (iso_item);
366        }
367
368        if (exposure_bias != null && exposure_bias != "") {
369            var exposure_bias_item = new ExifItem ("exposure-bias-symbolic", _("Exposure bias"), exposure_bias);
370            flowbox.add (exposure_bias_item);
371        }
372
373        if (flash != null && flash != "") {
374            var flash_item = new ExifItem ("flash-symbolic", _("Flash"), flash);
375            flowbox.add (flash_item);
376        }
377    }
378
379    public override void save_changes_to_source () {
380        if (source != null && title != null && title != source.get_name ()) {
381            AppWindow.get_command_manager ().execute (new EditTitleCommand (source, title));
382        }
383    }
384
385    // Unit test: https://github.com/Philip-Scott/misc/blob/master/GeolocationTest.vala
386    private async void create_place_label (double lat, double long) {
387        var location = new Geocode.Location (lat, long);
388        var reverse = new Geocode.Reverse.for_location (location);
389
390        try {
391            Geocode.Place place = yield reverse.resolve_async ();
392
393            if (place.get_state () != null) {
394                if (place.get_town () != null) {
395                    place_label.label = place.get_town () + ", " + place.get_state ();
396                } else if (place.get_county () != null) {
397                    place_label.label = place.get_county () + ", " + place.get_state ();
398                } else {
399                    place_label.label = place.get_state () + ", " + place.get_country ();
400                }
401
402                place_label.no_show_all = false;
403                place_label.visible = true;
404            }
405        } catch (Error e) {
406            warning ("Failed to obtain place for %f, %f: %s", lat, long, e.message);
407        }
408    }
409
410    private void title_entry_changed () {
411        title = title_entry.text;
412    }
413
414    private void attach_item_count_label (string text) {
415        var label = new Properties.Label (text);
416        attach (label, 1, (int) line_count, 1, 1);
417        line_count++;
418    }
419
420    private class ExifItem : Gtk.FlowBoxChild {
421        public ExifItem (string icon_name, string tooltip_text, string data) {
422            can_focus = false;
423
424            var icon = new Gtk.Image.from_icon_name (icon_name, Gtk.IconSize.MENU);
425            var label = new Properties.Label (data);
426
427            var grid = new Gtk.Grid ();
428            grid.column_spacing = 6;
429            grid.tooltip_text = _(tooltip_text);
430            grid.add (icon);
431            grid.add (label);
432
433            add (grid);
434            show_all ();
435        }
436    }
437}
438