Commit 95a7ce9f authored by Allison Barlow's avatar Allison Barlow

added extended information window

parent 5c29a2e0
......@@ -15,6 +15,20 @@
</schema>
<schema>
<key>/schemas/apps/shotwell/preferences/ui/display_extended_properties</key>
<applyto>/apps/shotwell/preferences/ui/display_extended_properties</applyto>
<type>bool</type>
<owner>shotwell</owner>
<default>false</default>
<locale name="en">
<short>extended properties display bool</short>
<long>Extended properties display bool. True if the window is displayed, false otherwise.</long>
</locale>
</schema>
<schema>
<key>/schemas/apps/shotwell/preferences/ui/display_photo_titles</key>
<applyto>/apps/shotwell/preferences/ui/display_photo_titles</applyto>
......
......@@ -7,6 +7,7 @@
public class Config {
#if NO_GCONF
bool display_basic_properties;
bool display_extended_properties;
bool display_photo_titles;
double slideshow_delay = SLIDESHOW_DELAY_DEFAULT;
#else
......@@ -62,6 +63,34 @@ public class Config {
#endif
}
public bool set_display_extended_properties(bool display) {
#if NO_GCONF
display_extended_properties = display;
return true;
#else
try {
client.set_bool("/apps/shotwell/preferences/ui/display_extended_properties", display);
return true;
} catch (GLib.Error err) {
message("Unable to set GConf value. Error message: %s", err.message);
return false;
}
#endif
}
public bool get_display_extended_properties() {
#if NO_GCONF
return display_extended_properties;
#else
try {
return client.get_bool("/apps/shotwell/preferences/ui/display_extended_properties");
} catch (GLib.Error err) {
message("Unable to get GConf value. Error message: %s", err.message);
return false;
}
#endif
}
public bool set_display_photo_titles(bool display) {
#if NO_GCONF
display_photo_titles = display;
......
......@@ -73,7 +73,7 @@ namespace Exif {
return false;
}
private Exif.Entry? find_entry(Exif.Data exif, Exif.Ifd ifd, Exif.Tag tag, Exif.Format format) {
private Exif.Entry? find_entry(Exif.Data exif, Exif.Ifd ifd, Exif.Tag tag, Exif.Format format, int size = 1) {
assert(exif != null);
Exif.Content content = exif.ifd[(int) ifd];
......@@ -85,7 +85,7 @@ namespace Exif {
assert(entry.format == format);
if ((format != Exif.Format.ASCII) && (format != Exif.Format.UNDEFINED))
assert(entry.size == format.get_size());
assert(entry.size == format.get_size() * size);
return entry;
}
......@@ -108,6 +108,10 @@ namespace Exif {
return entry;
}
private double rational_to_double(Exif.Rational rational) {
return (((double) rational.numerator) / ((double) rational.denominator));
}
public Orientation get_orientation(Exif.Data exif) {
Exif.Entry entry = find_entry(exif, Exif.Ifd.ZERO, Exif.Tag.ORIENTATION, Exif.Format.SHORT);
if (entry == null)
......@@ -181,6 +185,190 @@ namespace Exif {
Exif.Convert.set_long(height.data, exif.get_byte_order(), dim.height);
}
}
public string get_exposure(Exif.Data exif) {
Exif.Entry entry = find_entry(exif, Exif.Ifd.EXIF, Exif.Tag.EXPOSURE_TIME, Exif.Format.RATIONAL);
if (entry == null)
return "";
Exif.Rational exposure = Exif.Convert.get_rational(entry.data, exif.get_byte_order());
if (rational_to_double(exposure) >= 1) {
return "%f s".printf(rational_to_double(exposure));
} else {
// round to the nearest five
int denominator = (int) exposure.denominator;
if (denominator > 10) {
int off = denominator % 5;
denominator += (off >= 3) ? 5 - off : -1 * off;
}
return "%d/%d s".printf((int) exposure.numerator, denominator);
}
}
public string get_aperture(Exif.Data exif) {
Exif.Entry entry = find_entry(exif, Exif.Ifd.EXIF, Exif.Tag.FNUMBER, Exif.Format.RATIONAL);
if (entry == null)
return "";
return entry.get_value();
}
public string get_iso(Exif.Data exif) {
Exif.Entry entry = find_entry(exif, Exif.Ifd.EXIF, Exif.Tag.ISO_SPEED_RATINGS, Exif.Format.SHORT);
if (entry == null)
return "";
return entry.get_value();
}
public string get_camera_make(Exif.Data exif) {
Exif.Entry entry = find_entry(exif, Exif.Ifd.ZERO, Exif.Tag.MAKE, Exif.Format.ASCII);
if (entry == null)
return "";
return entry.get_value();
}
public string get_camera_model(Exif.Data exif) {
Exif.Entry entry = find_entry(exif, Exif.Ifd.ZERO, Exif.Tag.MODEL, Exif.Format.ASCII);
if (entry == null)
return "";
return entry.get_value();
}
public string get_flash(Exif.Data exif) {
Exif.Entry entry = find_entry(exif, Exif.Ifd.EXIF, Exif.Tag.FLASH, Exif.Format.SHORT);
if (entry == null)
return "";
return entry.get_value();
}
public string get_focal_length(Exif.Data exif) {
Exif.Entry entry = find_entry(exif, Exif.Ifd.EXIF, Exif.Tag.FOCAL_LENGTH,
Exif.Format.RATIONAL);
if (entry == null)
return "";
return entry.get_value();
}
private Exif.Rational[] get_gps_rationals(Exif.Entry entry, Exif.ByteOrder byte_order) {
Exif.Rational[] rationals = new Exif.Rational[3];
uchar* data = entry.data;
rationals[0] = Exif.Convert.get_rational(data, byte_order);
data += entry.format.get_size();
rationals[1] = Exif.Convert.get_rational(data, byte_order);
data += entry.format.get_size();
rationals[2] = Exif.Convert.get_rational(data, byte_order);
return rationals;
}
public Exif.Rational[]? get_raw_gps_lat(Exif.Data exif) {
Exif.Entry entry = find_entry(exif, Exif.Ifd.GPS, Exif.Tag.GPS_LATITUDE,
Exif.Format.RATIONAL, 3);
if (entry == null)
return null;
return get_gps_rationals(entry, exif.get_byte_order());
}
public Exif.Rational[]? get_raw_gps_long(Exif.Data exif) {
Exif.Entry entry = find_entry(exif, Exif.Ifd.GPS, Exif.Tag.GPS_LONGITUDE,
Exif.Format.RATIONAL, 3);
if (entry == null)
return null;
return get_gps_rationals(entry, exif.get_byte_order());
}
private double get_angle(Exif.Rational[] rationals) {
return rational_to_double(rationals[0]) + ((1.0 / 60.0) * rational_to_double(rationals[1])) +
((1.0 / 360.0) * rational_to_double(rationals[2]));
}
public double get_gps_lat(Exif.Data exif) {
Exif.Rational[] rationals = get_raw_gps_lat(exif);
if (rationals == null)
return -1;
return get_angle(rationals);
}
public double get_gps_long(Exif.Data exif) {
Exif.Rational[] rationals = get_raw_gps_long(exif);
if (rationals == null)
return -1;
return get_angle(rationals);
}
public string get_gps_lat_ref(Exif.Data exif) {
Exif.Entry entry = find_entry(exif, Exif.Ifd.GPS, Exif.Tag.GPS_LATITUDE_REF,
Exif.Format.ASCII);
if (entry == null)
return "";
return entry.get_value();
}
public string get_gps_long_ref(Exif.Data exif) {
Exif.Entry entry = find_entry(exif, Exif.Ifd.GPS, Exif.Tag.GPS_LONGITUDE_REF,
Exif.Format.ASCII);
if (entry == null)
return "";
return entry.get_value();
}
public string get_artist(Exif.Data exif) {
Exif.Entry entry = find_entry(exif, Exif.Ifd.ZERO, Exif.Tag.ARTIST, Exif.Format.ASCII);
if (entry == null)
return "";
return entry.get_value();
}
public string get_copyright(Exif.Data exif) {
Exif.Entry entry = find_entry(exif, Exif.Ifd.ZERO, Exif.Tag.COPYRIGHT, Exif.Format.ASCII);
if (entry == null)
return "";
return entry.get_value();
}
public string get_software(Exif.Data exif) {
Exif.Entry entry = find_entry(exif, Exif.Ifd.ZERO, Exif.Tag.SOFTWARE, Exif.Format.ASCII);
if (entry == null)
return "";
return entry.get_value();
}
}
public errordomain ExifError {
......
......@@ -5,7 +5,7 @@
*/
public class LibraryWindow : AppWindow {
public const int SIDEBAR_MIN_WIDTH = 160;
public const int SIDEBAR_MIN_WIDTH = 180;
public const int SIDEBAR_MAX_WIDTH = 320;
public const int PAGE_MIN_WIDTH =
Thumbnail.MAX_SCALE + (CheckerboardLayout.COLUMN_GUTTER_PADDING * 2);
......@@ -240,6 +240,7 @@ public class LibraryWindow : AppWindow {
private SidebarMarker cameras_marker = null;
private BasicProperties basic_properties = new BasicProperties();
private ExtendedPropertiesWindow extended_properties = new ExtendedPropertiesWindow();
private Gtk.Notebook notebook = new Gtk.Notebook();
private Gtk.Box layout = new Gtk.VBox(false, 0);
......@@ -296,6 +297,9 @@ public class LibraryWindow : AppWindow {
// start with only most recent month directory open
sidebar.expand_first_branch_only(events_directory_page.get_marker());
extended_properties.hide += hide_extended_properties;
extended_properties.show += show_extended_properties;
}
~LibraryWindow() {
......@@ -309,6 +313,9 @@ public class LibraryWindow : AppWindow {
#endif
unsubscribe_from_basic_information(get_current_page());
extended_properties.hide -= hide_extended_properties;
extended_properties.show -= show_extended_properties;
}
private Gtk.ActionEntry[] create_actions() {
......@@ -334,9 +341,15 @@ public class LibraryWindow : AppWindow {
Gtk.ToggleActionEntry basic_props = { "CommonDisplayBasicProperties", null,
TRANSLATABLE, "<Ctrl><Shift>I", TRANSLATABLE, on_display_basic_properties, false };
basic_props.label = _("Basic _Information");
basic_props.tooltip = _("Display basic information of the selection");
basic_props.tooltip = _("Display basic information for the selection");
actions += basic_props;
Gtk.ToggleActionEntry extended_props = { "CommonDisplayExtendedProperties", null,
TRANSLATABLE, "<Ctrl><Shift>X", TRANSLATABLE, on_display_extended_properties, false };
extended_props.label = _("Extended _Information");
extended_props.tooltip = _("Display extended information for the selection");
actions += extended_props;
return actions;
}
......@@ -512,6 +525,7 @@ public class LibraryWindow : AppWindow {
bool display = ((Gtk.ToggleAction) action).get_active();
if (display) {
basic_properties.update_properties(get_current_page());
bottom_frame.show();
} else {
if (sidebar_paned.child2 != null) {
......@@ -522,7 +536,37 @@ public class LibraryWindow : AppWindow {
// sync the setting so it will persist
Config.get_instance().set_display_basic_properties(display);
}
private void on_display_extended_properties(Gtk.Action action) {
bool display = ((Gtk.ToggleAction) action).get_active();
if (display) {
extended_properties.update_properties(get_current_page());
extended_properties.show_all();
} else {
extended_properties.hide();
}
}
private void show_extended_properties() {
sync_extended_properties(true);
}
private void hide_extended_properties() {
sync_extended_properties(false);
}
private void sync_extended_properties(bool show) {
Gtk.ToggleAction extended_display_action =
(Gtk.ToggleAction) get_current_page().common_action_group.get_action(
"CommonDisplayExtendedProperties");
assert(extended_display_action != null);
extended_display_action.set_active(show);
// sync the setting so it will persist
Config.get_instance().set_display_extended_properties(show);
}
public void enqueue_batch_import(BatchImport batch_import) {
if (!displaying_import_queue_page) {
insert_page_after(events_directory_page.get_marker(), import_queue_page);
......@@ -704,8 +748,7 @@ public class LibraryWindow : AppWindow {
}
}
// refresh basic properties
basic_properties.update_properties(get_current_page());
on_selection_changed();
}
private SubEventsDirectoryPageStub? get_dir_parent(SubEventsDirectoryPageStub dir) {
......@@ -935,11 +978,17 @@ public class LibraryWindow : AppWindow {
// check for settings that should persist between instances
private void load_configuration() {
Gtk.ToggleAction action =
Gtk.ToggleAction basic_display_action =
(Gtk.ToggleAction) get_current_page().common_action_group.get_action(
"CommonDisplayBasicProperties");
assert(action != null);
action.set_active(Config.get_instance().get_display_basic_properties());
assert(basic_display_action != null);
basic_display_action.set_active(Config.get_instance().get_display_basic_properties());
Gtk.ToggleAction extended_display_action =
(Gtk.ToggleAction) get_current_page().common_action_group.get_action(
"CommonDisplayExtendedProperties");
assert(extended_display_action != null);
extended_display_action.set_active(Config.get_instance().get_display_extended_properties());
}
private void create_layout(Page start_page) {
......@@ -1009,17 +1058,29 @@ public class LibraryWindow : AppWindow {
remove_accel_group(get_current_page().ui.get_accel_group());
// carry over menubar toggle activity between pages
Gtk.ToggleAction old_action =
Gtk.ToggleAction old_basic_display_action =
(Gtk.ToggleAction) get_current_page().common_action_group.get_action(
"CommonDisplayBasicProperties");
assert(old_action != null);
assert(old_basic_display_action != null);
Gtk.ToggleAction new_action =
Gtk.ToggleAction new_basic_display_action =
(Gtk.ToggleAction) page.common_action_group.get_action(
"CommonDisplayBasicProperties");
assert(new_action != null);
assert(new_basic_display_action != null);
new_action.set_active(old_action.get_active());
new_basic_display_action.set_active(old_basic_display_action.get_active());
Gtk.ToggleAction old_extended_display_action =
(Gtk.ToggleAction) get_current_page().common_action_group.get_action(
"CommonDisplayExtendedProperties");
assert(old_basic_display_action != null);
Gtk.ToggleAction new_extended_display_action =
(Gtk.ToggleAction) page.common_action_group.get_action(
"CommonDisplayExtendedProperties");
assert(new_basic_display_action != null);
new_extended_display_action.set_active(old_extended_display_action.get_active());
// old page unsubscribes to these signals (new page subscribes below)
unsubscribe_from_basic_information(get_current_page());
......@@ -1148,7 +1209,11 @@ public class LibraryWindow : AppWindow {
}
private void on_selection_changed() {
basic_properties.update_properties(get_current_page());
if (bottom_frame.visible)
basic_properties.update_properties(get_current_page());
if (extended_properties.visible)
extended_properties.update_properties(get_current_page());
}
#if !NO_CAMERA
......
......@@ -4,21 +4,14 @@
* See the COPYING file in this distribution.
*/
private class BasicProperties : Gtk.HBox {
private Gtk.Label label = new Gtk.Label("");
private Gtk.Label info = new Gtk.Label("");
private string title;
private time_t start_time = time_t();
private time_t end_time = time_t();
private Dimensions dimensions;
private uint64 filesize;
private int photo_count;
private int event_count;
private string basic_properties_labels;
private string basic_properties_info;
private bool first_line;
public BasicProperties() {
private abstract class Properties : Gtk.HBox {
protected Gtk.Label label = new Gtk.Label("");
protected Gtk.Label info = new Gtk.Label("");
protected string basic_properties_labels;
protected string basic_properties_info;
protected bool first_line;
public Properties() {
label.set_justify(Gtk.Justification.RIGHT);
label.set_alignment(0, (float) 5e-1);
info.set_alignment(0, (float) 5e-1);
......@@ -28,7 +21,7 @@ private class BasicProperties : Gtk.HBox {
info.set_ellipsize(Pango.EllipsizeMode.END);
}
private void add_line(string label, string info) {
protected void add_line(string label, string info) {
if (!first_line) {
basic_properties_labels += "\n";
basic_properties_info += "\n";
......@@ -38,7 +31,7 @@ private class BasicProperties : Gtk.HBox {
first_line = false;
}
private string get_prettyprint_time(Time time) {
protected string get_prettyprint_time(Time time) {
string timestring = time.format(_("%I:%M %p"));
if (timestring[0] == '0')
......@@ -47,7 +40,7 @@ private class BasicProperties : Gtk.HBox {
return timestring;
}
private string get_prettyprint_date(Time date) {
protected string get_prettyprint_date(Time date) {
string date_string = null;
Time today = Time.local(time_t());
if (date.day_of_year == today.day_of_year && date.year == today.year) {
......@@ -61,25 +54,85 @@ private class BasicProperties : Gtk.HBox {
return date_string;
}
private void set_text() {
label.set_text(basic_properties_labels);
protected void set_text() {
label.set_markup(GLib.Markup.printf_escaped("<span font_weight=\"bold\">%s</span>", basic_properties_labels));
info.set_text(basic_properties_info);
}
public void clear_properties() {
protected virtual void get_single_properties(DataView view) {
}
protected virtual void get_multiple_properties(Gee.Iterable<DataView>? iter) {
}
protected virtual void get_properties(Page current_page) {
ViewCollection view = current_page.get_view();
if (view == null)
return;
// summarize selected items, if none selected, summarize all
int count = view.get_selected_count();
Gee.Iterable<DataView> iter = null;
if (count != 0) {
iter = view.get_selected();
} else {
count = view.get_count();
iter = (Gee.Iterable<DataView>) view.get_all();
}
if (iter == null || count == 0)
return;
if (count == 1) {
foreach (DataView item in iter) {
get_single_properties(item);
break;
}
} else {
get_multiple_properties(iter);
}
}
protected virtual void clear_properties() {
basic_properties_labels = "";
basic_properties_info = "";
first_line = true;
}
public virtual void update_properties(Page page) {
clear_properties();
get_properties(page);
}
}
private class BasicProperties : Properties {
private string title;
private time_t start_time = time_t();
private time_t end_time = time_t();
private Dimensions dimensions;
private int photo_count;
private int event_count;
private string exposure;
private string aperture;
private string iso;
private override void clear_properties() {
base.clear_properties();
title = "";
start_time = 0;
end_time = 0;
dimensions = Dimensions(0,0);
filesize = 0;
photo_count = -1;
event_count = -1;
basic_properties_labels = "";
basic_properties_info = "";
first_line = true;
exposure = "";
aperture = "";
iso = "";
}
private void get_single_properties(DataView view) {
private override void get_single_properties(DataView view) {
base.get_single_properties(view);
DataSource source = view.get_source();
title = source.get_name();
......@@ -91,20 +144,25 @@ private class BasicProperties : Gtk.HBox {
end_time = start_time;
dimensions = photo_source.get_dimensions();
filesize = photo_source.get_filesize();
Exif.Data exif = photo_source.get_exif();
exposure = Exif.get_exposure(exif);
aperture = Exif.get_aperture(exif);
iso = Exif.get_iso(exif);
} else if (source is EventSource) {
EventSource event_source = (EventSource) source;
start_time = event_source.get_start_time();
end_time = event_source.get_end_time();
filesize = event_source.get_total_filesize();
photo_count = event_source.get_photo_count();
}
}
private void get_multiple_properties(Gee.Iterable<DataView>? iter) {
private override void get_multiple_properties(Gee.Iterable<DataView>? iter) {
base.get_multiple_properties(iter);
photo_count = 0;
foreach (DataView view in iter) {
DataSource source = view.get_source();
......@@ -122,7 +180,6 @@ private class BasicProperties : Gtk.HBox {
end_time = exposure_time;
}
filesize += photo_source.get_filesize();
photo_count++;
} else if (source is EventSource) {
EventSource event_source = (EventSource) source;
......@@ -141,40 +198,14 @@ private class BasicProperties : Gtk.HBox {
end_time = event_source.get_start_time();
}
filesize += event_source.get_total_filesize();
photo_count += event_source.get_photo_count();
event_count++;
}
}
}
private void get_properties(Page current_page) {
ViewCollection view = current_page.get_view();
if (view == null)
return;
// summarize selected items, if none selected, summarize all
int count = view.get_selected_count();
Gee.Iterable<DataView> iter = null;
if (count != 0) {
iter = view.get_selected();
} else {
count = view.get_count();
iter = (Gee.Iterable<DataView>) view.get_all();
}
if (iter == null || count == 0)
return;
if (count == 1) {
foreach (DataView item in iter) {
get_single_properties(item);
break;
}
} else {
get_multiple_properties(iter);
}
private override void get_properties(Page current_page) {
base.get_properties(current_page);
if (end_time == 0)
end_time = start_time;
......@@ -182,9 +213,8 @@ private class BasicProperties : Gtk.HBox {
start_time = end_time;
}
public void update_properties(Page current_page) {
clear_properties();
get_properties(current_page);
public override void update_properties(Page page) {
base.update_properties(page);
if (title != "")
add_line(_("Title:"), title);
......@@ -237,18 +267,167 @@ private class BasicProperties : Gtk.HBox {
}
}
if (filesize > 0 || dimensions.has_area()) {
if (dimensions.has_area()) {
string label = _("Size:");
if (dimensions.has_area()) {
add_line(label, "%d x %d".printf(dimensions.width, dimensions.height));
label = "";
}
}
if (filesize > 0)
add_line(label, format_size_for_display((int64) filesize));
if (exposure != "" && aperture != "" && iso != "") {
add_line(_("Exposure:"), exposure + ", " + aperture);
add_line("","ISO " + iso);
}
set_text();
}
}
private class ExtendedPropertiesWindow : Gtk.Window {
private ExtendedProperties properties = null;
private const int FRAME_BORDER = 6;
private class ExtendedProperties : Properties {
private const string NO_VALUE = " --";
private string file_path;
private uint64 filesize;
private Dimensions original_dim;
private string camera_make;
private string camera_model;
private string flash;
private string focal_length;
private double gps_lat;
private string gps_lat_ref;