Commit 5d2301e5 authored by Jim Nelson's avatar Jim Nelson

#282: Cancel in crop tool window doesn't remove the crop. #283: Standard...

#282: Cancel in crop tool window doesn't remove the crop.  #283: Standard icons in crop tool window buttons.  #64: Revert to original.
parent 02c4e07d
......@@ -413,21 +413,16 @@ public class CollectionLayout : Gtk.Layout {
}
private bool on_expose(CollectionLayout cl, Gdk.EventExpose event) {
Gdk.Rectangle visibleRect = Gdk.Rectangle();
visibleRect.x = (int) get_hadjustment().get_value();
visibleRect.y = (int) get_vadjustment().get_value();
visibleRect.width = allocation.width;
visibleRect.height = allocation.height;
Gdk.Rectangle visible_rect = Gdk.Rectangle();
visible_rect.x = (int) get_hadjustment().get_value();
visible_rect.y = (int) get_vadjustment().get_value();
visible_rect.width = allocation.width;
visible_rect.height = allocation.height;
/*
debug("on_client_exposed x:%d y:%d w:%d h:%d", visibleRect.x, visibleRect.y,
visibleRect.width, visibleRect.height);
*/
Gdk.Rectangle bitbucket = Gdk.Rectangle();
foreach (LayoutItem item in items) {
if (visibleRect.intersect((Gdk.Rectangle) item.allocation, bitbucket)) {
if (visible_rect.intersect((Gdk.Rectangle) item.allocation, bitbucket)) {
item.exposed();
} else {
item.unexposed();
......
......@@ -77,6 +77,7 @@ public class CollectionPage : CheckerboardPage {
{ "RotateClockwise", STOCK_CLOCKWISE, "Rotate c_lockwise", "<Ctrl>R", "Rotate the selected photos clockwise", on_rotate_clockwise },
{ "RotateCounterclockwise", STOCK_COUNTERCLOCKWISE, "Rotate c_ounterclockwise", "<Ctrl><Shift>R", "Rotate the selected photos counterclockwise", on_rotate_counterclockwise },
{ "Mirror", null, "_Mirror", "<Ctrl>M", "Make mirror images of the selected photos", on_mirror },
{ "Revert", Gtk.STOCK_REVERT_TO_SAVED, "_Revert to Original", null, "Revert to original photo", on_revert },
{ "ViewMenu", null, "_View", null, null, on_view_menu },
{ "SortPhotos", null, "_Sort Photos", null, null, null },
......@@ -202,6 +203,19 @@ public class CollectionPage : CheckerboardPage {
AppWindow.get_instance().switch_to_photo_page(this, thumbnail);
}
protected override bool on_context_invoked(Gtk.Menu context_menu) {
bool selected = (get_selected_count() > 0);
bool revert_possible = can_revert_selected();
set_item_sensitive("/CollectionContextMenu/ContextRemove", selected);
set_item_sensitive("/CollectionContextMenu/ContextRotateClockwise", selected);
set_item_sensitive("/CollectionContextMenu/ContextRotateCounterclockwise", selected);
set_item_sensitive("/CollectionContextMenu/ContextMirror", selected);
set_item_sensitive("/CollectionContextMenu/ContextRevert", selected && revert_possible);
return true;
}
public override LayoutItem? get_fullscreen_photo() {
Gee.Iterable<LayoutItem> iter = null;
......@@ -358,14 +372,26 @@ public class CollectionPage : CheckerboardPage {
select_all();
}
private bool can_revert_selected() {
foreach (LayoutItem item in get_selected()) {
Photo photo = ((Thumbnail) item).get_photo();
if (photo.has_transformations())
return true;
}
return false;
}
protected virtual void on_photos_menu() {
bool selected = (get_selected_count() > 0);
bool revert_possible = can_revert_selected();
set_item_sensitive("/CollectionMenuBar/PhotosMenu/IncreaseSize", scale < Thumbnail.MAX_SCALE);
set_item_sensitive("/CollectionMenuBar/PhotosMenu/DecreaseSize", scale > Thumbnail.MIN_SCALE);
set_item_sensitive("/CollectionMenuBar/PhotosMenu/RotateClockwise", selected);
set_item_sensitive("/CollectionMenuBar/PhotosMenu/RotateCounterclockwise", selected);
set_item_sensitive("/CollectionMenuBar/PhotosMenu/Mirror", selected);
set_item_sensitive("/CollectionMenuBar/PhotosMenu/Revert", selected && revert_possible);
}
private void on_increase_size() {
......@@ -422,6 +448,20 @@ public class CollectionPage : CheckerboardPage {
do_rotations(get_selected(), Rotation.MIRROR);
}
private void on_revert() {
bool revert_performed = false;
foreach (LayoutItem item in get_selected()) {
Photo photo = ((Thumbnail) item).get_photo();
photo.remove_all_transformations();
revert_performed = true;
}
// geometry could change
if (revert_performed)
refresh();
}
private void on_view_menu() {
set_item_sensitive("/CollectionMenuBar/ViewMenu/Fullscreen", get_count() > 0);
}
......
......@@ -92,6 +92,7 @@ public class PhotoTable : DatabaseTable {
+ "timestamp INTEGER, "
+ "exposure_time INTEGER, "
+ "orientation INTEGER, "
+ "original_orientation INTEGER, "
+ "import_id INTEGER, "
+ "event_id INTEGER, "
+ "transformations TEXT"
......@@ -115,10 +116,10 @@ public class PhotoTable : DatabaseTable {
}
public PhotoID add(File file, Dimensions dim, int64 filesize, long timestamp, time_t exposure_time,
Orientation orientation, ImportID importID) {
Orientation orientation, ImportID import_id) {
Sqlite.Statement stmt;
int res = db.prepare_v2(
"INSERT INTO PhotoTable (filename, width, height, filesize, timestamp, exposure_time, orientation, import_id, event_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
"INSERT INTO PhotoTable (filename, width, height, filesize, timestamp, exposure_time, orientation, original_orientation, import_id, event_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
-1, out stmt);
assert(res == Sqlite.OK);
......@@ -137,11 +138,13 @@ public class PhotoTable : DatabaseTable {
assert(res == Sqlite.OK);
res = stmt.bind_int64(6, exposure_time);
assert(res == Sqlite.OK);
res = stmt.bind_int64(7, orientation);
res = stmt.bind_int(7, orientation);
assert(res == Sqlite.OK);
res = stmt.bind_int64(8, importID.id);
res = stmt.bind_int(8, orientation);
assert(res == Sqlite.OK);
res = stmt.bind_int64(9, PhotoID.INVALID);
res = stmt.bind_int64(9, import_id.id);
assert(res == Sqlite.OK);
res = stmt.bind_int64(10, PhotoID.INVALID);
assert(res == Sqlite.OK);
res = stmt.step();
......@@ -161,7 +164,7 @@ public class PhotoTable : DatabaseTable {
Sqlite.Statement stmt;
int res = db.prepare_v2(
"UPDATE PhotoTable SET width = ?, height = ?, filesize = ?, timestamp = ?, "
+ "exposure_time = ?, orientation = ? WHERE id = ?", -1, out stmt);
+ "exposure_time = ?, orientation = ?, original_orientation = ? WHERE id = ?", -1, out stmt);
assert(res == Sqlite.OK);
debug("Update [%lld] %dx%d size=%lld mod=%ld exp=%ld or=%d", photoID.id, dim.width,
......@@ -177,7 +180,9 @@ public class PhotoTable : DatabaseTable {
assert(res == Sqlite.OK);
res = stmt.bind_int64(5, exposure_time);
assert(res == Sqlite.OK);
res = stmt.bind_int64(6, orientation);
res = stmt.bind_int(6, orientation);
assert(res == Sqlite.OK);
res = stmt.bind_int(7, orientation);
assert(res == Sqlite.OK);
res = stmt.bind_int64(7, photoID.id);
assert(res == Sqlite.OK);
......@@ -208,7 +213,7 @@ public class PhotoTable : DatabaseTable {
public bool get_photo(PhotoID photoID, out PhotoRow row) {
Sqlite.Statement stmt;
int res = db.prepare_v2("SELECT filename, width, height, filesize, timestamp, exposure_time, orientation, import_id, event_id FROM PhotoTable WHERE id=?", -1, out stmt);
int res = db.prepare_v2("SELECT filename, width, height, filesize, timestamp, exposure_time, orientation, original_orientation, import_id, event_id FROM PhotoTable WHERE id=?", -1, out stmt);
assert(res == Sqlite.OK);
res = stmt.bind_int64(1, photoID.id);
......@@ -225,8 +230,9 @@ public class PhotoTable : DatabaseTable {
row.timestamp = (long) stmt.column_int64(4);
row.exposure_time = (long) stmt.column_int64(5);
row.orientation = (Orientation) stmt.column_int(6);
row.import_id = ImportID(stmt.column_int64(7));
row.event_id = EventID(stmt.column_int64(8));
row.original_orientation = (Orientation) stmt.column_int(7);
row.import_id = ImportID(stmt.column_int64(8));
row.event_id = EventID(stmt.column_int64(9));
return true;
}
......@@ -361,6 +367,26 @@ public class PhotoTable : DatabaseTable {
return Dimensions(stmt.column_int(0), stmt.column_int(1));
}
public Orientation get_original_orientation(PhotoID photo_id) {
Sqlite.Statement stmt;
int res = db.prepare_v2("SELECT original_orientation FROM PhotoTable WHERE id=?", -1, out stmt);
assert(res == Sqlite.OK);
res = stmt.bind_int64(1, photo_id.id);
assert(res == Sqlite.OK);
res = stmt.step();
if (res != Sqlite.ROW) {
if (res != Sqlite.DONE) {
fatal("get_original_orientation", res);
}
return Orientation.TOP_LEFT;
}
return (Orientation) stmt.column_int(0);
}
public Orientation get_orientation(PhotoID photo_id) {
Sqlite.Statement stmt;
int res = db.prepare_v2("SELECT orientation FROM PhotoTable WHERE id=?", -1, out stmt);
......@@ -510,6 +536,10 @@ public class PhotoTable : DatabaseTable {
return true;
}
public bool has_transformations(PhotoID photo_id) {
return get_raw_transformations(photo_id) != null;
}
public KeyValueMap? get_transformation(PhotoID photo_id, string object) {
string trans = get_raw_transformations(photo_id);
if (trans == null)
......@@ -590,6 +620,27 @@ public class PhotoTable : DatabaseTable {
return set_raw_transformations(photo_id, trans);
}
public bool remove_all_transformations(PhotoID photo_id) {
if (get_raw_transformations(photo_id) == null)
return false;
Sqlite.Statement stmt;
int res = db.prepare_v2("UPDATE PhotoTable SET transformations='' WHERE id=?", -1, out stmt);
assert(res == Sqlite.OK);
res = stmt.bind_int64(1, photo_id.id);
assert(res == Sqlite.OK);
res = stmt.step();
if (res != Sqlite.DONE) {
fatal("remove_all_transformations", res);
return false;
}
return true;
}
}
public class ThumbnailCacheTable : DatabaseTable {
......@@ -968,6 +1019,7 @@ public struct PhotoRow {
public long timestamp;
public long exposure_time;
public Orientation orientation;
public Orientation original_orientation;
public ImportID import_id;
public EventID event_id;
}
......
......@@ -242,18 +242,22 @@ public abstract class CheckerboardPage : Page {
return page_name;
}
protected void init_context_menu(string path) {
public void init_context_menu(string path) {
context_menu = (Gtk.Menu) ui.get_widget(path);
}
protected virtual void on_selection_changed(int count) {
}
public virtual Gtk.Menu get_context_menu(LayoutItem item) {
return context_menu;
public virtual Gtk.Menu? get_context_menu(LayoutItem? item) {
return (item != null) ? context_menu : null;
}
public virtual void on_item_activated(LayoutItem item) {
protected virtual void on_item_activated(LayoutItem item) {
}
protected virtual bool on_context_invoked(Gtk.Menu context_menu) {
return true;
}
public abstract LayoutItem? get_fullscreen_photo();
......@@ -434,6 +438,9 @@ public abstract class CheckerboardPage : Page {
Gtk.Menu context_menu = get_context_menu(item);
if (context_menu != null) {
if (!on_context_invoked(context_menu))
return false;
context_menu.popup(null, null, null, event.button, event.time);
return true;
......
......@@ -135,6 +135,25 @@ public class Photo : Object {
return false;
}
public bool has_transformations() {
return photo_table.has_transformations(photo_id)
|| (photo_table.get_orientation(photo_id) != photo_table.get_original_orientation(photo_id));
}
public void remove_all_transformations() {
bool altered = photo_table.remove_all_transformations(photo_id);
Orientation orientation = photo_table.get_orientation(photo_id);
Orientation original_orientation = photo_table.get_original_orientation(photo_id);
if (orientation != original_orientation) {
photo_table.set_orientation(photo_id, original_orientation);
altered = true;
}
if (altered)
photo_altered();
}
public void rotate(Rotation rotation) {
Orientation orientation = photo_table.get_orientation(photo_id);
......
......@@ -3,8 +3,8 @@ public class CropToolWindow : Gtk.Window {
public static const int CONTROL_SPACING = 8;
public static const int WINDOW_BORDER = 8;
public Gtk.Button apply_button = new Gtk.Button.with_label("Apply");
public Gtk.Button cancel_button = new Gtk.Button.with_label("Cancel");
public Gtk.Button apply_button = new Gtk.Button.from_stock(Gtk.STOCK_APPLY);
public Gtk.Button cancel_button = new Gtk.Button.from_stock(Gtk.STOCK_CANCEL);
public bool user_moved = false;
private Gtk.Window owner;
......@@ -17,6 +17,9 @@ public class CropToolWindow : Gtk.Window {
type_hint = Gdk.WindowTypeHint.TOOLBAR;
set_transient_for(owner);
apply_button.set_image_position(Gtk.PositionType.LEFT);
cancel_button.set_image_position(Gtk.PositionType.LEFT);
layout.set_border_width(WINDOW_BORDER);
layout.add(apply_button);
layout.add(cancel_button);
......@@ -73,6 +76,7 @@ public class PhotoPage : Page {
private CheckerboardPage controller = null;
private Photo photo = null;
private Thumbnail thumbnail = null;
private Gtk.Menu context_menu;
private Gtk.Viewport viewport = new Gtk.Viewport(null, null);
private Gtk.Toolbar toolbar = new Gtk.Toolbar();
private Gtk.ToolButton rotate_button = null;
......@@ -112,12 +116,13 @@ public class PhotoPage : Page {
{ "ViewMenu", null, "_View", null, null, null },
{ "ReturnToPage", null, "_Return to collection", "Escape", null, on_return_to_collection },
{ "PhotoMenu", null, "_Photo", null, null, null },
{ "PhotoMenu", null, "_Photo", null, null, on_photo_menu },
{ "PrevPhoto", Gtk.STOCK_GO_BACK, "_Previous Photo", null, "Previous Photo", on_previous_photo },
{ "NextPhoto", Gtk.STOCK_GO_FORWARD, "_Next Photo", null, "Next Photo", on_next_photo },
{ "RotateClockwise", STOCK_CLOCKWISE, "Rotate c_lockwise", "<Ctrl>R", "Rotate the selected photos clockwise", on_rotate_clockwise },
{ "RotateCounterclockwise", STOCK_COUNTERCLOCKWISE, "Rotate c_ounterclockwise", "<Ctrl><Shift>R", "Rotate the selected photos counterclockwise", on_rotate_counterclockwise },
{ "Mirror", null, "_Mirror", "<Ctrl>M", "Make mirror images of the selected photos", on_mirror },
{ "Revert", Gtk.STOCK_REVERT_TO_SAVED, "_Revert to Original", null, "Revert to the original photo", on_revert },
{ "HelpMenu", null, "_Help", null, null, null }
};
......@@ -127,6 +132,8 @@ public class PhotoPage : Page {
init_ui("photo.ui", "/PhotoMenuBar", "PhotoActionGroup", ACTIONS);
context_menu = (Gtk.Menu) ui.get_widget("/PhotoContextMenu");
// set up page's toolbar (used by AppWindow for layout and FullscreenWindow as a popup)
//
// rotate tool
......@@ -292,18 +299,23 @@ public class PhotoPage : Page {
}
private bool on_canvas_button_pressed(Gtk.DrawingArea da, Gdk.EventButton event) {
// only interested in LMB
if (event.button != 1)
return false;
if (!show_crop)
// only interested in LMB & RMB
if (event.button != 1 && event.button != 3)
return false;
int x = (int) event.x;
int y = (int) event.y;
// only concerned about mouse-downs on the pixbuf
if (x < pixbuf_rect.x || y < pixbuf_rect.y)
if (!coord_in_rectangle(x, y, pixbuf_rect))
return false;
// RMB
if (event.button == 3)
return on_context_menu(event);
// only interested in LMB in regards to crop tool
if (!show_crop)
return false;
// scaled_crop is not maintained relative to photo's position on canvas
......@@ -319,6 +331,17 @@ public class PhotoPage : Page {
return false;
}
private bool on_context_menu(Gdk.EventButton event) {
if (photo == null)
return false;
set_item_sensitive("/PhotoContextMenu/ContextRevert", photo.has_transformations());
context_menu.popup(null, null, null, event.button, event.time);
return true;
}
private bool on_canvas_button_released(Gtk.DrawingArea da, Gdk.EventButton event) {
// only interested in LMB
if (event.button != 1)
......@@ -759,6 +782,10 @@ public class PhotoPage : Page {
private void on_mirror() {
rotate(Rotation.MIRROR);
}
private void on_revert() {
photo.remove_all_transformations();
}
private override bool on_ctrl_pressed(Gdk.EventKey event) {
rotate_button.set_stock_id(STOCK_COUNTERCLOCKWISE);
......@@ -1120,9 +1147,6 @@ public class PhotoPage : Page {
private void on_crop_cancel() {
deactivate_crop();
// let the signal generate a repaint
photo.remove_crop();
}
private void on_next_photo() {
......@@ -1132,6 +1156,20 @@ public class PhotoPage : Page {
update_display();
}
private void on_photo_menu() {
bool multiple = false;
if (controller != null)
multiple = controller.get_count() > 1;
bool revert_possible = false;
if (photo != null)
revert_possible = photo.has_transformations();
set_item_sensitive("/PhotoMenuBar/PhotoMenu/PrevPhoto", multiple);
set_item_sensitive("/PhotoMenuBar/PhotoMenu/NextPhoto", multiple);
set_item_sensitive("/PhotoMenuBar/PhotoMenu/Revert", revert_possible);
}
private void on_previous_photo() {
deactivate_crop();
......@@ -1142,13 +1180,10 @@ public class PhotoPage : Page {
private void update_sensitivity() {
assert(controller != null);
bool multiple = (controller.get_count() > 1);
bool multiple = controller.get_count() > 1;
prev_button.sensitive = multiple;
next_button.sensitive = multiple;
set_item_sensitive("/PhotoMenuBar/PhotoMenu/PrevPhoto", multiple);
set_item_sensitive("/PhotoMenuBar/PhotoMenu/NextPhoto", multiple);
}
}
......@@ -17,6 +17,8 @@
<menuitem name="RotateClockwise" action="RotateClockwise" />
<menuitem name="RotateCounterclockwise" action="RotateCounterclockwise" />
<menuitem name="Mirror" action="Mirror" />
<separator />
<menuitem name="Revert" action="Revert" />
</menu>
<menu name="ViewMenu" action="ViewMenu">
......@@ -43,6 +45,8 @@
<menuitem name="ContextRotateClockwise" action="RotateClockwise" />
<menuitem name="ContextRotateCounterclockwise" action="RotateCounterclockwise" />
<menuitem name="ContextMirror" action="Mirror" />
<separator />
<menuitem name="ContextRevert" action="Revert" />
</popup>
</ui>
......
......@@ -63,3 +63,7 @@ Gdk.Pixbuf scale_pixbuf(Gdk.Pixbuf pixbuf, int scale, Gdk.InterpType interp) {
return pixbuf.scale_simple(scaled.width, scaled.height, interp);
}
bool coord_in_rectangle(int x, int y, Gdk.Rectangle rect) {
return (x >= rect.x && x < (rect.x + rect.width) && y >= rect.y && y <= (rect.y + rect.height));
}
......@@ -17,6 +17,8 @@
<menuitem name="RotateClockwise" action="RotateClockwise" />
<menuitem name="RotateCounterclockwise" action="RotateCounterclockwise" />
<menuitem name="Mirror" action="Mirror" />
<separator />
<menuitem name="Revert" action="Revert" />
</menu>
<menu name="HelpMenu" action="HelpMenu">
......@@ -24,5 +26,13 @@
</menu>
</menubar>
<popup name="PhotoContextMenu">
<menuitem name="ContextRotateClockwise" action="RotateClockwise" />
<menuitem name="ContextRotateCounterclockwise" action="RotateCounterclockwise" />
<menuitem name="ContextMirror" action="Mirror" />
<separator />
<menuitem name="ContextRevert" action="Revert" />
</popup>
</ui>
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