Commit 8aae77d4 authored by Jim Nelson's avatar Jim Nelson

#1479: Open with external editor. Currently the editor is hardcoded as GIMP...

#1479: Open with external editor.  Currently the editor is hardcoded as GIMP (/usr/bin/gimp), but this will 
change with #1974 (Select external editor in Preferences dialog).
parent fcd27a0f
......@@ -89,6 +89,7 @@ public abstract class CollectionPage : CheckerboardPage {
get_view().contents_altered += on_contents_altered;
get_view().items_state_changed += on_selection_changed;
get_view().items_visibility_changed += on_contents_altered;
get_view().items_altered += on_photos_altered;
get_view().freeze_notifications();
get_view().set_property(CheckerboardItem.PROP_SHOW_TITLES,
......@@ -290,8 +291,14 @@ public abstract class CollectionPage : CheckerboardPage {
revert.label = Resources.REVERT_MENU;
revert.tooltip = Resources.REVERT_TOOLTIP;
actions += revert;
#if !NO_SET_BACKGROUND
Gtk.ActionEntry revert_editable = { "RevertEditable", null, TRANSLATABLE, null,
TRANSLATABLE, on_revert_editable };
revert_editable.label = Resources.REVERT_EDITABLE_MENU;
revert_editable.tooltip = Resources.REVERT_EDITABLE_TOOLTIP;
actions += revert_editable;
#if !NO_SET_BACKGROUND
Gtk.ActionEntry set_background = { "SetBackground", null, TRANSLATABLE, "<Ctrl>B",
TRANSLATABLE, on_set_background };
set_background.label = Resources.SET_BACKGROUND_MENU;
......@@ -328,7 +335,19 @@ public abstract class CollectionPage : CheckerboardPage {
adjust_date_time.label = Resources.ADJUST_DATE_TIME_MENU;
adjust_date_time.tooltip = Resources.ADJUST_DATE_TIME_TOOLTIP;
actions += adjust_date_time;
Gtk.ActionEntry external_edit = { "ExternalEdit", Gtk.STOCK_EDIT, TRANSLATABLE, null,
TRANSLATABLE, on_external_edit };
external_edit.label = Resources.EXTERNAL_EDIT_MENU;
external_edit.tooltip = Resources.EXTERNAL_EDIT_TOOLTIP;
actions += external_edit;
Gtk.ActionEntry edit_raw = { "ExternalEditRAW", null, TRANSLATABLE, null, TRANSLATABLE,
on_external_edit_raw };
edit_raw.label = Resources.EXTERNAL_EDIT_RAW_MENU;
edit_raw.tooltip = Resources.EXTERNAL_EDIT_RAW_TOOLTIP;
actions += edit_raw;
Gtk.ActionEntry slideshow = { "Slideshow", Gtk.STOCK_MEDIA_PLAY, TRANSLATABLE, "F5",
TRANSLATABLE, on_slideshow };
slideshow.label = _("_Slideshow");
......@@ -471,10 +490,24 @@ public abstract class CollectionPage : CheckerboardPage {
set_thumb_size(current_scale);
}
protected override void init_actions(int selected_count, int count) {
set_action_sensitive("ExternalEdit", selected_count > 0);
set_action_hidden("ExternalEditRAW");
set_action_sensitive("RevertEditable", can_revert_editable_selected());
base.init_actions(selected_count, count);
}
private void on_contents_altered() {
slideshow_button.sensitive = get_view().get_count() > 0;
}
private void on_photos_altered() {
// since the photo can be altered externally to Shotwell now, need to make the revert
// command available appropriately, even if the selection doesn't change
set_action_sensitive("RevertEditable", can_revert_editable_selected());
}
#if !NO_PRINTING
private void on_print() {
if (get_view().get_selected_count() != 1)
......@@ -489,13 +522,25 @@ public abstract class CollectionPage : CheckerboardPage {
PrintManager.get_instance().do_page_setup();
}
#endif
private void on_selection_changed(Gee.Iterable<DataView> items) {
rotate_button.sensitive = get_view().get_selected_count() > 0;
int selected_count = get_view().get_selected_count();
bool has_selected = selected_count > 0;
bool is_single_raw = selected_count == 1
&& ((Photo) get_view().get_selected_at(0).get_source()).get_master_file_format() == PhotoFileFormat.RAW;
rotate_button.sensitive = has_selected;
#if !NO_PUBLISHING
publish_button.set_sensitive(get_view().get_selected_count() > 0);
publish_button.set_sensitive(has_selected);
#endif
enhance_button.sensitive = get_view().get_selected_count() > 0;
enhance_button.sensitive = has_selected;
set_action_sensitive("ExternalEdit", selected_count == 1);
if (is_single_raw)
set_action_visible("ExternalEditRAW", true);
else
set_action_hidden("ExternalEditRAW");
set_action_sensitive("RevertEditable", can_revert_editable_selected());
}
protected override void on_item_activated(CheckerboardItem item) {
......@@ -546,7 +591,6 @@ public abstract class CollectionPage : CheckerboardPage {
bool selected = get_view().get_selected_count() > 0;
bool revert_possible = can_revert_selected();
set_item_sensitive("/CollectionContextMenu/ContextDuplicate", selected);
set_item_sensitive("/CollectionContextMenu/ContextMoveToTrash", selected);
set_item_sensitive("/CollectionContextMenu/ContextRotateClockwise", selected);
set_item_sensitive("/CollectionContextMenu/ContextRotateCounterclockwise", selected);
......@@ -735,6 +779,15 @@ public abstract class CollectionPage : CheckerboardPage {
return false;
}
private bool can_revert_editable_selected() {
foreach (DataView view in get_view().get_selected()) {
if (((Photo) view.get_source()).has_editable())
return true;
}
return false;
}
private bool can_favorite_selected() {
foreach (DataView view in get_view().get_selected()) {
if (!((Thumbnail) view).get_photo().is_favorite())
......@@ -851,6 +904,19 @@ public abstract class CollectionPage : CheckerboardPage {
get_command_manager().execute(command);
}
private void on_revert_editable() {
if (get_view().get_selected_count() == 0 || !can_revert_editable_selected())
return;
if (!revert_editable_dialog(AppWindow.get_instance(),
(Gee.Collection<Photo>) get_view().get_selected_sources())) {
return;
}
foreach (DataObject object in get_view().get_selected_sources())
((Photo) object).revert_to_master();
}
private void on_enhance() {
if (get_view().get_selected_count() == 0)
return;
......@@ -948,6 +1014,39 @@ public abstract class CollectionPage : CheckerboardPage {
}
}
private void on_external_edit() {
if (get_view().get_selected_count() != 1)
return;
Photo photo = (Photo) get_view().get_selected_at(0).get_source();
try {
AppWindow.get_instance().set_busy_cursor();
photo.open_with_external_editor();
AppWindow.get_instance().set_normal_cursor();
} catch (Error err) {
AppWindow.get_instance().set_normal_cursor();
AppWindow.error_message(Resources.launch_editor_failed(err));
}
}
private void on_external_edit_raw() {
if (get_view().get_selected_count() != 1)
return;
Photo photo = (Photo) get_view().get_selected_at(0).get_source();
if (photo.get_master_file_format() != PhotoFileFormat.RAW)
return;
try {
AppWindow.get_instance().set_busy_cursor();
photo.open_master_with_external_editor();
AppWindow.get_instance().set_normal_cursor();
} catch (Error err) {
AppWindow.get_instance().set_normal_cursor();
AppWindow.error_message(Resources.launch_editor_failed(err));
}
}
#if !NO_SET_BACKGROUND
public void on_set_background() {
if (get_view().get_selected_count() != 1)
......
......@@ -109,11 +109,20 @@ public abstract class SinglePhotoTransformationCommand : SingleDataSourceCommand
base(photo, name, explanation);
state = photo.save_transformation_state();
state.broken += on_state_broken;
}
~SinglePhotoTransformationCommand() {
state.broken -= on_state_broken;
}
public override void undo() {
((TransformablePhoto) source).load_transformation_state(state);
}
private void on_state_broken() {
get_command_manager().reset();
}
}
public abstract class GenericPhotoTransformationCommand : SingleDataSourceCommand {
......@@ -124,14 +133,24 @@ public abstract class GenericPhotoTransformationCommand : SingleDataSourceComman
base(photo, name, explanation);
}
~GenericPhotoTransformationState() {
if (original_state != null)
original_state.broken -= on_state_broken;
if (transformed_state != null)
transformed_state.broken -= on_state_broken;
}
public override void execute() {
TransformablePhoto photo = (TransformablePhoto) source;
original_state = photo.save_transformation_state();
original_state.broken += on_state_broken;
execute_on_photo(photo);
transformed_state = photo.save_transformation_state();
transformed_state.broken += on_state_broken;
}
public abstract void execute_on_photo(TransformablePhoto photo);
......@@ -169,6 +188,10 @@ public abstract class GenericPhotoTransformationCommand : SingleDataSourceComman
return true;
}
private void on_state_broken() {
get_command_manager().reset();
}
}
public abstract class MultipleDataSourceCommand : PageCommand {
......@@ -292,10 +315,18 @@ public abstract class MultiplePhotoTransformationCommand : MultipleDataSourceCom
foreach (DataSource source in source_list) {
TransformablePhoto photo = (TransformablePhoto) source;
map.set(photo, photo.save_transformation_state());
PhotoTransformationState state = photo.save_transformation_state();
state.broken += on_state_broken;
map.set(photo, state);
}
}
~MultiplePhotoTransformationCommand() {
foreach (PhotoTransformationState state in map.values)
state.broken -= on_state_broken;
}
public override void undo_on_source(DataSource source) {
TransformablePhoto photo = (TransformablePhoto) source;
......@@ -304,6 +335,10 @@ public abstract class MultiplePhotoTransformationCommand : MultipleDataSourceCom
photo.load_transformation_state(state);
}
private void on_state_broken() {
get_command_manager().reset();
}
}
public class RotateSingleCommand : SingleDataSourceCommand {
......
This diff is collapsed.
......@@ -633,6 +633,35 @@ public Gtk.ResponseType empty_trash_dialog(Gtk.Window owner, int count) {
return result;
}
public bool revert_editable_dialog(Gtk.Window owner, Gee.Collection<Photo> photos) {
int count = 0;
foreach (Photo photo in photos) {
if (photo.has_editable())
count++;
}
if (count == 0)
return false;
string msg = ngettext(
"This will destroy all changes made to the external file. Continue?",
"This will destroy all changes made to %d external files. Continue?",
count).printf(count);
string action = ngettext("Re_vert External Edit", "Re_vert External Edits", count);
Gtk.MessageDialog dialog = new Gtk.MessageDialog(owner, Gtk.DialogFlags.MODAL,
Gtk.MessageType.WARNING, Gtk.ButtonsType.NONE, "%s", msg);
dialog.add_button(_("_No"), Gtk.ResponseType.NO);
dialog.add_button(action, Gtk.ResponseType.YES);
dialog.title = ngettext("Revert External Edit", "Revert External Edits", count);
Gtk.ResponseType result = (Gtk.ResponseType) dialog.run();
dialog.destroy();
return result == Gtk.ResponseType.YES;
}
public class ProgressDialog : Gtk.Window {
private Gtk.ProgressBar progress_bar = new Gtk.ProgressBar();
private Gtk.Button cancel_button = null;
......
......@@ -61,6 +61,16 @@ public File? generate_unique_file(string basename, PhotoMetadata? metadata, time
// silently ignore not creating a directory that already exists
}
return generate_unique_file_at(dir, basename, out collision);
}
// Like generate_unique_file(), this function "claims" a file on the filesystem in the directory
// specified with a basename the same or similar as what has been requested. The file may exist
// when this function returns, and it should be overwritten. It does *not* attempt to create the
// parent directory, however.
//
// This function is thread-safe.
public File? generate_unique_file_at(File dir, string basename, out bool collision) throws Error {
// create the file to atomically "claim" it
File file = dir.get_child(basename);
if (claim_file(file)) {
......@@ -84,7 +94,7 @@ public File? generate_unique_file(string basename, PhotoMetadata? metadata, time
return file;
}
debug("generate_unique_filename for %s: unable to claim file", basename);
debug("generate_unique_filename_at %s for %s: unable to claim file", dir.get_path(), basename);
return null;
}
......
......@@ -117,7 +117,7 @@ public class MimicManager : Object {
return;
}
job.photo.set_mimic(job.writer.create_reader());
job.photo.set_mimic_reader(job.writer.create_reader());
}
private string generate_impersonator_filepath(Photo photo) {
......
......@@ -241,6 +241,30 @@ public abstract class Page : Gtk.ScrolledWindow, SidebarPage {
action.sensitive = sensitive;
}
public void set_action_visible(string name, bool sensitive) {
Gtk.Action action = action_group.get_action(name);
if (action == null) {
warning("Page %s: Unable to locate action %s", get_page_name(), name);
return;
}
action.visible = true;
action.sensitive = sensitive;
}
public void set_action_hidden(string name) {
Gtk.Action action = action_group.get_action(name);
if (action == null) {
warning("Page %s: Unable to locate action %s", get_page_name(), name);
return;
}
action.visible = false;
action.sensitive = false;
}
private void get_modifiers(out bool ctrl, out bool alt, out bool shift) {
int x, y;
Gdk.ModifierType mask;
......
This diff is collapsed.
......@@ -299,17 +299,14 @@ public abstract class EditingHostPage : SinglePhotoPage {
return get_view().get_count() == 1;
}
public TransformablePhoto? get_photo() {
public Photo? get_photo() {
// use the photo stored in our ViewCollection ... should either be zero or one in the
// collection at all times
assert(get_view().get_count() <= 1);
if (get_view().get_count() == 0)
return null;
PhotoView photo_view = (PhotoView) get_view().get_at(0);
return (TransformablePhoto) photo_view.get_source();
return (get_view().get_count() > 0)
? (Photo?) ((PhotoView) get_view().get_at(0)).get_source()
: null;
}
private void set_photo(TransformablePhoto photo) {
......@@ -1117,7 +1114,27 @@ public abstract class EditingHostPage : SinglePhotoPage {
RevertSingleCommand command = new RevertSingleCommand(get_photo());
get_command_manager().execute(command);
}
public void on_revert_editable() {
deactivate_tool();
if (!has_photo())
return;
if (!get_photo().has_editable())
return;
if (!revert_editable_dialog(AppWindow.get_instance(),
(Gee.Collection<Photo>) get_view().get_sources())) {
return;
}
cancel_zoom();
set_photo_missing(false);
get_photo().revert_to_master();
}
public void on_rename() {
LibraryPhoto item;
if (get_photo() is LibraryPhoto)
......@@ -1150,7 +1167,7 @@ public abstract class EditingHostPage : SinglePhotoPage {
get_command_manager().execute(command);
}
}
#if !NO_SET_BACKGROUND
public void on_set_background() {
if (!has_photo())
......@@ -1426,6 +1443,10 @@ public class LibraryPhotoPage : EditingHostPage {
context_menu = (Gtk.Menu) ui.get_widget("/PhotoContextMenu");
// monitor view to update UI elements
get_view().contents_altered += on_contents_altered;
get_view().items_altered += on_photos_altered;
// watch for photos being destroyed or removed or altered, either here or in other pages
LibraryPhoto.global.items_removed += on_photos_removed;
LibraryPhoto.global.item_destroyed += on_photo_destroyed;
......@@ -1560,7 +1581,13 @@ public class LibraryPhotoPage : EditingHostPage {
revert.label = Resources.REVERT_MENU;
revert.tooltip = Resources.REVERT_TOOLTIP;
actions += revert;
Gtk.ActionEntry revert_editable = { "RevertEditable", null, TRANSLATABLE, null,
TRANSLATABLE, on_revert_editable };
revert_editable.label = Resources.REVERT_EDITABLE_MENU;
revert_editable.tooltip = Resources.REVERT_EDITABLE_TOOLTIP;
actions += revert_editable;
Gtk.ActionEntry rename = { "PhotoRename", null, TRANSLATABLE, "F2", TRANSLATABLE,
on_rename };
rename.label = Resources.RENAME_PHOTO_MENU;
......@@ -1572,7 +1599,19 @@ public class LibraryPhotoPage : EditingHostPage {
adjust_date_time.label = Resources.ADJUST_DATE_TIME_MENU;
adjust_date_time.tooltip = Resources.ADJUST_DATE_TIME_TOOLTIP;
actions += adjust_date_time;
Gtk.ActionEntry external_edit = { "ExternalEdit", Gtk.STOCK_EDIT, TRANSLATABLE, null,
TRANSLATABLE, on_external_edit };
external_edit.label = Resources.EXTERNAL_EDIT_MENU;
external_edit.tooltip = Resources.EXTERNAL_EDIT_TOOLTIP;
actions += external_edit;
Gtk.ActionEntry edit_raw = { "ExternalEditRAW", null, TRANSLATABLE, null, TRANSLATABLE,
on_external_edit_raw };
edit_raw.label = Resources.EXTERNAL_EDIT_RAW_MENU;
edit_raw.tooltip = Resources.EXTERNAL_EDIT_RAW_TOOLTIP;
actions += edit_raw;
#if !NO_SET_BACKGROUND
Gtk.ActionEntry set_background = { "SetBackground", null, TRANSLATABLE, "<Ctrl>B",
TRANSLATABLE, on_set_background };
......@@ -1612,6 +1651,29 @@ public class LibraryPhotoPage : EditingHostPage {
return actions;
}
protected override void init_actions(int selected_count, int count) {
set_action_sensitive("ExternalEdit", count > 0);
set_action_hidden("ExternalEditRAW");
set_action_sensitive("RevertEditable", has_photo() && get_photo().has_editable());
base.init_actions(selected_count, count);
}
private void on_contents_altered() {
bool is_raw = has_photo() && get_photo().get_master_file_format() == PhotoFileFormat.RAW;
set_action_sensitive("ExternalEdit", has_photo());
if (is_raw)
set_action_visible("ExternalEditRAW", true);
else
set_action_hidden("ExternalEditRAW");
set_action_sensitive("RevertEditable", has_photo() && get_photo().has_editable());
}
private void on_photos_altered() {
set_action_sensitive("RevertEditable", has_photo() && get_photo().has_editable());
}
public void display_for_collection(CollectionPage return_page, Thumbnail thumbnail) {
this.return_page = return_page;
......@@ -1857,7 +1919,38 @@ public class LibraryPhotoPage : EditingHostPage {
PrintManager.get_instance().do_page_setup();
}
#endif
private void on_external_edit() {
if (!has_photo())
return;
try {
AppWindow.get_instance().set_busy_cursor();
get_photo().open_with_external_editor();
AppWindow.get_instance().set_normal_cursor();
} catch (Error err) {
AppWindow.get_instance().set_normal_cursor();
AppWindow.error_message(Resources.launch_editor_failed(err));
}
}
private void on_external_edit_raw() {
if (!has_photo())
return;
if (get_photo().get_master_file_format() != PhotoFileFormat.RAW)
return;
try {
AppWindow.get_instance().set_busy_cursor();
get_photo().open_master_with_external_editor();
AppWindow.get_instance().set_normal_cursor();
} catch (Error err) {
AppWindow.get_instance().set_normal_cursor();
AppWindow.error_message(Resources.launch_editor_failed(err));
}
}
private void on_export() {
if (!has_photo())
return;
......
......@@ -116,7 +116,10 @@ along with Shotwell; if not, write to the Free Software Foundation, Inc.,
public const string REVERT_MENU = _("Re_vert to Original");
public const string REVERT_LABEL = _("Revert to Original");
public const string REVERT_TOOLTIP = _("Revert to the original photo");
public const string REVERT_EDITABLE_MENU = _("Revert External E_dits");
public const string REVERT_EDITABLE_TOOLTIP = _("Revert to the master photo");
public const string SET_BACKGROUND_MENU = _("Set as Desktop _Background");
public const string SET_BACKGROUND_TOOLTIP = _("Set selected image to be the new desktop background");
......@@ -195,6 +198,16 @@ along with Shotwell; if not, write to the Free Software Foundation, Inc.,
public const string PREFERENCES_MENU = _("_Preferences");
public const string PREFERENCES_TOOLTIP = _("Edit preferences");
public const string EXTERNAL_EDIT_MENU = _("_Open With External Editor");
public const string EXTERNAL_EDIT_TOOLTIP = _("Open the selected photo with an external image editor");
public const string EXTERNAL_EDIT_RAW_MENU = _("Open With RA_W Editor");
public const string EXTERNAL_EDIT_RAW_TOOLTIP = _("Open the selected photo with a RAW image editor");
public string launch_editor_failed(Error err) {
return _("Unable to launch editor: %s").printf(err.message);
}
public string add_tags_label(string[] names) {
if (names.length == 1)
return _("Add Tag \"%s\"").printf(names[0]);
......
......@@ -66,7 +66,7 @@ void library_exec(string[] mounts) {
DatabaseTable.init(AppDirs.get_data_subdir("data").get_child("photo.db"));
// validate the databases prior to using them
message("Verifying databases ...");
message("Verifying database ...");
string errormsg = null;
string app_version;
DatabaseVerifyResult result = verify_database(out app_version);
......
......@@ -62,12 +62,15 @@
<separator />
<menuitem name="Enhance" action="Enhance" />
<menuitem name="Revert" action="Revert" />
<menuitem name="RevertEditable" action="RevertEditable" />
<separator />
<menuitem name="FavoriteUnfavorite" action="FavoriteUnfavorite" />
<menuitem name="HideUnhide" action="HideUnhide" />
<separator />
<menuitem name="PhotoRename" action="PhotoRename" />
<menuitem name="AdjustDateTime" action="AdjustDateTime" />
<menuitem name="ExternalEdit" action="ExternalEdit" />
<menuitem name="ExternalEditRAW" action="ExternalEditRAW" />
<separator />
<placeholder name="SetBackgroundPlaceholder"/>
<separator />
......@@ -96,6 +99,7 @@
<separator />
<menuitem name="ContextEnhance" action="Enhance" />
<menuitem name="ContextRevert" action="Revert" />
<menuitem name="ContextRevertEditable" action="RevertEditable" />
<separator />
<menuitem name="ContextAddTags" action="AddTags" />
<menuitem name="ContextModifyTags" action="ModifyTags" />
......@@ -105,8 +109,8 @@
<menuitem name="ContextHideUnhide" action="HideUnhide" />
<separator />
<menuitem name="ContextPhotoRename" action="PhotoRename" />
<menuitem name="ContextExternalEdit" action="ExternalEdit" />
<separator />
<menuitem name="ContextDuplicate" action="Duplicate" />
<menuitem name="ContextMoveToTrash" action="MoveToTrash" />
<separator />
<placeholder name="ContextSetBackgroundPlaceholder"/>
......
......@@ -54,12 +54,15 @@
<menuitem name="Adjust" action="Adjust" />
</menu>
<menuitem name="Revert" action="Revert" />
<menuitem name="RevertEditable" action="RevertEditable" />
<separator />
<menuitem name="FavoriteUnfavorite" action="FavoriteUnfavorite" />
<menuitem name="HideUnhide" action="HideUnhide" />
<separator />
<menuitem name="PhotoRename" action="PhotoRename" />
<menuitem name="AdjustDateTime" action="AdjustDateTime" />
<menuitem name="ExternalEdit" action="ExternalEdit" />
<menuitem name="ExternalEditRAW" action="ExternalEditRAW" />
<separator />
<placeholder name="SetBackgroundPlaceholder"/>
</menu>
......@@ -79,12 +82,14 @@
<separator />
<menuitem name="ContextEnhance" action="Enhance" />
<menuitem name="ContextRevert" action="Revert" />
<menuitem name="ContextRevertEditable" action="RevertEditable" />
<separator />
<menuitem name="ContextFavoriteUnfavorite" action="FavoriteUnfavorite" />
<menuitem name="ContextHideUnhide" action="HideUnhide" />
<separator />
<menuitem name="ContextPhotoRename" action="PhotoRename" />
<menuitem name="ContextAdjustDateTime" action="AdjustDateTime" />
<menuitem name="ContextExternalEdit" action="ExternalEdit" />
<separator />
<placeholder name="ContextSetBackgroundPlaceholder"/>
</popup>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment