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 × %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