summaryrefslogtreecommitdiff
path: root/src/Page.vala
diff options
context:
space:
mode:
Diffstat (limited to 'src/Page.vala')
-rw-r--r--src/Page.vala1557
1 files changed, 20 insertions, 1537 deletions
diff --git a/src/Page.vala b/src/Page.vala
index 65b263b..6b07568 100644
--- a/src/Page.vala
+++ b/src/Page.vala
@@ -13,13 +13,13 @@ public class InjectionGroup {
}
public string name;
public string action;
- public string? accellerator;
+ public string? accelerator;
public ItemType kind;
- public Element(string name, string? action, string? accellerator, ItemType kind) {
+ public Element(string name, string? action, string? accelerator, ItemType kind) {
this.name = name;
this.action = action != null ? action : name;
- this.accellerator = accellerator;
+ this.accelerator = accelerator;
this.kind = kind;
}
}
@@ -40,8 +40,8 @@ public class InjectionGroup {
return elements;
}
- public void add_menu_item(string name, string? action = null, string? accellerator = null) {
- elements.add(new Element(name, action, accellerator, Element.ItemType.MENUITEM));
+ public void add_menu_item(string name, string? action = null, string? accelerator = null) {
+ elements.add(new Element(name, action, accelerator, Element.ItemType.MENUITEM));
}
public void add_menu(string name, string? action = null) {
@@ -68,7 +68,6 @@ public abstract class Page : Gtk.ScrolledWindow {
private string toolbar_path;
private Gdk.Rectangle last_position = Gdk.Rectangle();
private Gtk.Widget event_source = null;
- private bool dnd_enabled = false;
private ulong last_configure_ms = 0;
private bool report_move_finished = false;
private bool report_resize_finished = false;
@@ -85,6 +84,9 @@ public abstract class Page : Gtk.ScrolledWindow {
private int cursor_hide_time_cached = 0;
private bool are_actions_attached = false;
private OneShotScheduler? update_actions_scheduler = null;
+
+ protected double wheel_factor = 0.0;
+ protected double modified_wheel_factor = 1.0;
protected Page(string page_name) {
this.page_name = page_name;
@@ -193,8 +195,6 @@ public abstract class Page : Gtk.ScrolledWindow {
event_source.leave_notify_event.disconnect(on_leave_notify_event);
event_source.scroll_event.disconnect(on_mousewheel_internal);
- disable_drag_source();
-
event_source = null;
}
@@ -230,10 +230,10 @@ public abstract class Page : Gtk.ScrolledWindow {
case InjectionGroup.Element.ItemType.MENUITEM:
var item = new GLib.MenuItem (element.name,
"win." + element.action);
- if (element.accellerator != null) {
+ if (element.accelerator != null) {
item.set_attribute ("accel",
"s",
- element.accellerator);
+ element.accelerator);
}
menu.append_item (item);
@@ -592,76 +592,6 @@ public abstract class Page : Gtk.ScrolledWindow {
protected virtual void update_actions(int selected_count, int count) {
}
- // This method enables drag-and-drop on the event source and routes its events through this
- // object
- public void enable_drag_source(Gdk.DragAction actions, Gtk.TargetEntry[] source_target_entries) {
- if (dnd_enabled)
- return;
-
- assert(event_source != null);
-
- Gtk.drag_source_set(event_source, Gdk.ModifierType.BUTTON1_MASK, source_target_entries, actions);
-
- // hook up handlers which route the event_source's DnD signals to the Page's (necessary
- // because Page is a NO_WINDOW widget and cannot support DnD on its own).
- event_source.drag_begin.connect(on_drag_begin);
- event_source.drag_data_get.connect(on_drag_data_get);
- event_source.drag_data_delete.connect(on_drag_data_delete);
- event_source.drag_end.connect(on_drag_end);
- event_source.drag_failed.connect(on_drag_failed);
-
- dnd_enabled = true;
- }
-
- public void disable_drag_source() {
- if (!dnd_enabled)
- return;
-
- assert(event_source != null);
-
- event_source.drag_begin.disconnect(on_drag_begin);
- event_source.drag_data_get.disconnect(on_drag_data_get);
- event_source.drag_data_delete.disconnect(on_drag_data_delete);
- event_source.drag_end.disconnect(on_drag_end);
- event_source.drag_failed.disconnect(on_drag_failed);
- Gtk.drag_source_unset(event_source);
-
- dnd_enabled = false;
- }
-
- public bool is_dnd_enabled() {
- return dnd_enabled;
- }
-
- private void on_drag_begin(Gdk.DragContext context) {
- drag_begin(context);
- }
-
- private void on_drag_data_get(Gdk.DragContext context, Gtk.SelectionData selection_data,
- uint info, uint time) {
- drag_data_get(context, selection_data, info, time);
- }
-
- private void on_drag_data_delete(Gdk.DragContext context) {
- drag_data_delete(context);
- }
-
- private void on_drag_end(Gdk.DragContext context) {
- drag_end(context);
- }
-
- // wierdly, Gtk 2.16.1 doesn't supply a drag_failed virtual method in the GtkWidget impl ...
- // Vala binds to it, but it's not available in gtkwidget.h, and so gcc complains. Have to
- // makeshift one for now.
- // https://bugzilla.gnome.org/show_bug.cgi?id=584247
- public virtual bool source_drag_failed(Gdk.DragContext context, Gtk.DragResult drag_result) {
- return false;
- }
-
- private bool on_drag_failed(Gdk.DragContext context, Gtk.DragResult drag_result) {
- return source_drag_failed(context, drag_result);
- }
-
// Use this function rather than GDK or GTK's get_pointer, especially if called during a
// button-down mouse drag (i.e. a window grab).
//
@@ -1032,13 +962,13 @@ public abstract class Page : Gtk.ScrolledWindow {
double dx, dy;
event.get_scroll_deltas(out dx, out dy);
- if (dy < 0)
+ if (dy < -1.0 * this.wheel_factor)
return on_mousewheel_up(event);
- else if (dy > 0)
+ else if (dy > this.wheel_factor)
return on_mousewheel_down(event);
- else if (dx < 0)
+ else if (dx < -1.0 * this.wheel_factor)
return on_mousewheel_left(event);
- else if (dx > 0)
+ else if (dx > this.wheel_factor)
return on_mousewheel_right(event);
else
return false;
@@ -1115,15 +1045,19 @@ public abstract class Page : Gtk.ScrolledWindow {
}
public void stop_cursor_hiding() {
- if (last_timeout_id != 0)
+ if (last_timeout_id != 0) {
Source.remove(last_timeout_id);
+ last_timeout_id = 0;
+ }
}
public void suspend_cursor_hiding() {
cursor_hide_time_cached = cursor_hide_msec;
- if (last_timeout_id != 0)
+ if (last_timeout_id != 0) {
Source.remove(last_timeout_id);
+ last_timeout_id = 0;
+ }
cursor_hide_msec = 0;
}
@@ -1209,1454 +1143,3 @@ public abstract class Page : Gtk.ScrolledWindow {
}
-public abstract class CheckerboardPage : Page {
- private const int AUTOSCROLL_PIXELS = 50;
- private const int AUTOSCROLL_TICKS_MSEC = 50;
-
- private CheckerboardLayout layout;
- private string item_context_menu_path = null;
- private string page_context_menu_path = null;
- private Gtk.Viewport viewport = new Gtk.Viewport(null, null);
- protected CheckerboardItem anchor = null;
- protected CheckerboardItem cursor = null;
- private CheckerboardItem current_hovered_item = null;
- private bool autoscroll_scheduled = false;
- private CheckerboardItem activated_item = null;
- private Gee.ArrayList<CheckerboardItem> previously_selected = null;
-
- public enum Activator {
- KEYBOARD,
- MOUSE
- }
-
- public struct KeyboardModifiers {
- public KeyboardModifiers(Page page) {
- ctrl_pressed = page.get_ctrl_pressed();
- alt_pressed = page.get_alt_pressed();
- shift_pressed = page.get_shift_pressed();
- super_pressed = page.get_super_pressed();
- }
-
- public bool ctrl_pressed;
- public bool alt_pressed;
- public bool shift_pressed;
- public bool super_pressed;
- }
-
- protected CheckerboardPage(string page_name) {
- base (page_name);
-
- layout = new CheckerboardLayout(get_view());
- layout.set_name(page_name);
-
- set_event_source(layout);
-
- set_border_width(0);
- set_shadow_type(Gtk.ShadowType.NONE);
-
- viewport.set_border_width(0);
- viewport.set_shadow_type(Gtk.ShadowType.NONE);
-
- viewport.add(layout);
-
- // want to set_adjustments before adding to ScrolledWindow to let our signal handlers
- // run first ... otherwise, the thumbnails draw late
- layout.set_adjustments(get_hadjustment(), get_vadjustment());
-
- add(viewport);
-
- // need to monitor items going hidden when dealing with anchor/cursor/highlighted items
- get_view().items_hidden.connect(on_items_hidden);
- get_view().contents_altered.connect(on_contents_altered);
- get_view().items_state_changed.connect(on_items_state_changed);
- get_view().items_visibility_changed.connect(on_items_visibility_changed);
-
- // scrollbar policy
- set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC);
- }
-
- public void init_item_context_menu(string path) {
- item_context_menu_path = path;
- }
-
- public void init_page_context_menu(string path) {
- page_context_menu_path = path;
- }
-
- public Gtk.Menu? get_context_menu() {
- // show page context menu if nothing is selected
- return (get_view().get_selected_count() != 0) ? get_item_context_menu() :
- get_page_context_menu();
- }
-
- private Gtk.Menu item_context_menu;
- public virtual Gtk.Menu? get_item_context_menu() {
- if (item_context_menu == null) {
- var model = this.builder.get_object (item_context_menu_path)
- as GLib.MenuModel;
- item_context_menu = new Gtk.Menu.from_model (model);
- item_context_menu.attach_to_widget (this, null);
- }
-
- return item_context_menu;
- }
-
- private Gtk.Menu page_context_menu;
- public override Gtk.Menu? get_page_context_menu() {
- if (page_context_menu_path == null)
- return null;
-
- if (page_context_menu == null) {
- var model = this.builder.get_object (page_context_menu_path)
- as GLib.MenuModel;
- page_context_menu = new Gtk.Menu.from_model (model);
- page_context_menu.attach_to_widget (this, null);
- }
-
- return page_context_menu;
- }
-
- protected override bool on_context_keypress() {
- return popup_context_menu(get_context_menu());
- }
-
- protected virtual string get_view_empty_message() {
- return _("No photos/videos");
- }
-
- protected virtual string get_filter_no_match_message() {
- return _("No photos/videos found which match the current filter");
- }
-
- protected virtual void on_item_activated(CheckerboardItem item, Activator activator,
- KeyboardModifiers modifiers) {
- }
-
- public CheckerboardLayout get_checkerboard_layout() {
- return layout;
- }
-
- // Gets the search view filter for this page.
- public abstract SearchViewFilter get_search_view_filter();
-
- public virtual Core.ViewTracker? get_view_tracker() {
- return null;
- }
-
- public override void switching_from() {
- layout.set_in_view(false);
- get_search_view_filter().refresh.disconnect(on_view_filter_refresh);
-
- // unselect everything so selection won't persist after page loses focus
- get_view().unselect_all();
-
- base.switching_from();
- }
-
- public override void switched_to() {
- layout.set_in_view(true);
- get_search_view_filter().refresh.connect(on_view_filter_refresh);
- on_view_filter_refresh();
-
- if (get_view().get_selected_count() > 0) {
- CheckerboardItem? item = (CheckerboardItem?) get_view().get_selected_at(0);
-
- // if item is in any way out of view, scroll to it
- Gtk.Adjustment vadj = get_vadjustment();
- if (!(get_adjustment_relation(vadj, item.allocation.y) == AdjustmentRelation.IN_RANGE
- && (get_adjustment_relation(vadj, item.allocation.y + item.allocation.height) == AdjustmentRelation.IN_RANGE))) {
-
- // scroll to see the new item
- int top = 0;
- if (item.allocation.y < vadj.get_value()) {
- top = item.allocation.y;
- top -= CheckerboardLayout.ROW_GUTTER_PADDING / 2;
- } else {
- top = item.allocation.y + item.allocation.height - (int) vadj.get_page_size();
- top += CheckerboardLayout.ROW_GUTTER_PADDING / 2;
- }
-
- vadj.set_value(top);
-
- }
- }
-
- base.switched_to();
- }
-
- private void on_view_filter_refresh() {
- update_view_filter_message();
- }
-
- private void on_contents_altered(Gee.Iterable<DataObject>? added,
- Gee.Iterable<DataObject>? removed) {
- update_view_filter_message();
- }
-
- private void on_items_state_changed(Gee.Iterable<DataView> changed) {
- update_view_filter_message();
- }
-
- private void on_items_visibility_changed(Gee.Collection<DataView> changed) {
- update_view_filter_message();
- }
-
- private void update_view_filter_message() {
- if (get_view().are_items_filtered_out() && get_view().get_count() == 0) {
- set_page_message(get_filter_no_match_message());
- } else if (get_view().get_count() == 0) {
- set_page_message(get_view_empty_message());
- } else {
- unset_page_message();
- }
- }
-
- public void set_page_message(string message) {
- layout.set_message(message);
- if (is_in_view())
- layout.queue_draw();
- }
-
- public void unset_page_message() {
- layout.unset_message();
- if (is_in_view())
- layout.queue_draw();
- }
-
- public override void set_page_name(string name) {
- base.set_page_name(name);
-
- layout.set_name(name);
- }
-
- public CheckerboardItem? get_item_at_pixel(double x, double y) {
- return layout.get_item_at_pixel(x, y);
- }
-
- private void on_items_hidden(Gee.Iterable<DataView> hidden) {
- foreach (DataView view in hidden) {
- CheckerboardItem item = (CheckerboardItem) view;
-
- if (anchor == item)
- anchor = null;
-
- if (cursor == item)
- cursor = null;
-
- if (current_hovered_item == item)
- current_hovered_item = null;
- }
- }
-
- protected override bool key_press_event(Gdk.EventKey event) {
- bool handled = true;
-
- // mask out the modifiers we're interested in
- uint state = event.state & Gdk.ModifierType.SHIFT_MASK;
-
- switch (Gdk.keyval_name(event.keyval)) {
- case "Up":
- case "KP_Up":
- move_cursor(CompassPoint.NORTH);
- select_anchor_to_cursor(state);
- break;
-
- case "Down":
- case "KP_Down":
- move_cursor(CompassPoint.SOUTH);
- select_anchor_to_cursor(state);
- break;
-
- case "Left":
- case "KP_Left":
- move_cursor(CompassPoint.WEST);
- select_anchor_to_cursor(state);
- break;
-
- case "Right":
- case "KP_Right":
- move_cursor(CompassPoint.EAST);
- select_anchor_to_cursor(state);
- break;
-
- case "Home":
- case "KP_Home":
- CheckerboardItem? first = (CheckerboardItem?) get_view().get_first();
- if (first != null)
- cursor_to_item(first);
- select_anchor_to_cursor(state);
- break;
-
- case "End":
- case "KP_End":
- CheckerboardItem? last = (CheckerboardItem?) get_view().get_last();
- if (last != null)
- cursor_to_item(last);
- select_anchor_to_cursor(state);
- break;
-
- case "Return":
- case "KP_Enter":
- if (get_view().get_selected_count() == 1)
- on_item_activated((CheckerboardItem) get_view().get_selected_at(0),
- Activator.KEYBOARD, KeyboardModifiers(this));
- else
- handled = false;
- break;
-
- case "space":
- Marker marker = get_view().mark(layout.get_cursor());
- get_view().toggle_marked(marker);
- break;
-
- default:
- handled = false;
- break;
- }
-
- if (handled)
- return true;
-
- return (base.key_press_event != null) ? base.key_press_event(event) : true;
- }
-
- protected override bool on_left_click(Gdk.EventButton event) {
- // only interested in single-click and double-clicks for now
- if ((event.type != Gdk.EventType.BUTTON_PRESS) && (event.type != Gdk.EventType.2BUTTON_PRESS))
- return false;
-
- // mask out the modifiers we're interested in
- uint state = event.state & (Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK);
-
- // use clicks for multiple selection and activation only; single selects are handled by
- // button release, to allow for multiple items to be selected then dragged ...
- CheckerboardItem item = get_item_at_pixel(event.x, event.y);
- if (item != null) {
- // ... however, there is no dragging if the user clicks on an interactive part of the
- // CheckerboardItem (e.g. a tag)
- if (layout.handle_left_click(item, event.x, event.y, event.state))
- return true;
-
- switch (state) {
- case Gdk.ModifierType.CONTROL_MASK:
- // with only Ctrl pressed, multiple selections are possible ... chosen item
- // is toggled
- Marker marker = get_view().mark(item);
- get_view().toggle_marked(marker);
-
- if (item.is_selected()) {
- anchor = item;
- cursor = item;
- }
- break;
-
- case Gdk.ModifierType.SHIFT_MASK:
- get_view().unselect_all();
-
- if (anchor == null)
- anchor = item;
-
- select_between_items(anchor, item);
-
- cursor = item;
- break;
-
- case Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK:
- // Ticket #853 - Make Ctrl + Shift + Mouse Button 1 able to start a new run
- // of contiguous selected items without unselecting previously-selected items
- // a la Nautilus.
- // Same as the case for SHIFT_MASK, but don't unselect anything first.
- if (anchor == null)
- anchor = item;
-
- select_between_items(anchor, item);
-
- cursor = item;
- break;
-
- default:
- if (event.type == Gdk.EventType.2BUTTON_PRESS) {
- activated_item = item;
- } else {
- // if the user has selected one or more items and is preparing for a drag,
- // don't want to blindly unselect: if they've clicked on an unselected item
- // unselect all and select that one; if they've clicked on a previously
- // selected item, do nothing
- if (!item.is_selected()) {
- Marker all = get_view().start_marking();
- all.mark_many(get_view().get_selected());
-
- get_view().unselect_and_select_marked(all, get_view().mark(item));
- }
- }
-
- anchor = item;
- cursor = item;
- break;
- }
- layout.set_cursor(item);
- } else {
- // user clicked on "dead" area; only unselect if control is not pressed
- // do we want similar behavior for shift as well?
- if (state != Gdk.ModifierType.CONTROL_MASK)
- get_view().unselect_all();
-
- // grab previously marked items
- previously_selected = new Gee.ArrayList<CheckerboardItem>();
- foreach (DataView view in get_view().get_selected())
- previously_selected.add((CheckerboardItem) view);
-
- layout.set_drag_select_origin((int) event.x, (int) event.y);
-
- return true;
- }
-
- // need to determine if the signal should be passed to the DnD handlers
- // Return true to block the DnD handler, false otherwise
-
- return get_view().get_selected_count() == 0;
- }
-
- protected override bool on_left_released(Gdk.EventButton event) {
- previously_selected = null;
-
- // if drag-selecting, stop here and do nothing else
- if (layout.is_drag_select_active()) {
- layout.clear_drag_select();
- anchor = cursor;
-
- return true;
- }
-
- // only interested in non-modified button releases
- if ((event.state & (Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK)) != 0)
- return false;
-
- // if the item was activated in the double-click, report it now
- if (activated_item != null) {
- on_item_activated(activated_item, Activator.MOUSE, KeyboardModifiers(this));
- activated_item = null;
-
- return true;
- }
-
- CheckerboardItem item = get_item_at_pixel(event.x, event.y);
- if (item == null) {
- // released button on "dead" area
- return true;
- }
-
- if (cursor != item) {
- // user released mouse button after moving it off the initial item, or moved from dead
- // space onto one. either way, unselect everything
- get_view().unselect_all();
- } else {
- // the idea is, if a user single-clicks on an item with no modifiers, then all other items
- // should be deselected, however, if they single-click in order to drag one or more items,
- // they should remain selected, hence performing this here rather than on_left_click
- // (item may not be selected if an unimplemented modifier key was used)
- if (item.is_selected())
- get_view().unselect_all_but(item);
- }
-
- return true;
- }
-
- protected override bool on_right_click(Gdk.EventButton event) {
- // only interested in single-clicks for now
- if (event.type != Gdk.EventType.BUTTON_PRESS)
- return false;
-
- // get what's right-clicked upon
- CheckerboardItem item = get_item_at_pixel(event.x, event.y);
- if (item != null) {
- // mask out the modifiers we're interested in
- switch (event.state & (Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK)) {
- case Gdk.ModifierType.CONTROL_MASK:
- // chosen item is toggled
- Marker marker = get_view().mark(item);
- get_view().toggle_marked(marker);
- break;
-
- case Gdk.ModifierType.SHIFT_MASK:
- // TODO
- break;
-
- case Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK:
- // TODO
- break;
-
- default:
- // if the item is already selected, proceed; if item is not selected, a bare right
- // click unselects everything else but it
- if (!item.is_selected()) {
- Marker all = get_view().start_marking();
- all.mark_many(get_view().get_selected());
-
- get_view().unselect_and_select_marked(all, get_view().mark(item));
- }
- break;
- }
- } else {
- // clicked in "dead" space, unselect everything
- get_view().unselect_all();
- }
-
- Gtk.Menu context_menu = get_context_menu();
- return popup_context_menu(context_menu, event);
- }
-
- protected virtual bool on_mouse_over(CheckerboardItem? item, int x, int y, Gdk.ModifierType mask) {
- if (item != null)
- layout.handle_mouse_motion(item, x, y, mask);
-
- // if hovering over the last hovered item, or both are null (nothing highlighted and
- // hovering over empty space), do nothing
- if (item == current_hovered_item)
- return true;
-
- // either something new is highlighted or now hovering over empty space, so dim old item
- if (current_hovered_item != null) {
- current_hovered_item.handle_mouse_leave();
- current_hovered_item = null;
- }
-
- // if over empty space, done
- if (item == null)
- return true;
-
- // brighten the new item
- current_hovered_item = item;
- current_hovered_item.handle_mouse_enter();
-
- return true;
- }
-
- protected override bool on_motion(Gdk.EventMotion event, int x, int y, Gdk.ModifierType mask) {
- // report what item the mouse is hovering over
- if (!on_mouse_over(get_item_at_pixel(x, y), x, y, mask))
- return false;
-
- // go no further if not drag-selecting
- if (!layout.is_drag_select_active())
- return false;
-
- // set the new endpoint of the drag selection
- layout.set_drag_select_endpoint(x, y);
-
- updated_selection_band();
-
- // if out of bounds, schedule a check to auto-scroll the viewport
- if (!autoscroll_scheduled
- && get_adjustment_relation(get_vadjustment(), y) != AdjustmentRelation.IN_RANGE) {
- Timeout.add(AUTOSCROLL_TICKS_MSEC, selection_autoscroll);
- autoscroll_scheduled = true;
- }
-
- // return true to stop a potential drag-and-drop operation
- return true;
- }
-
- private void updated_selection_band() {
- assert(layout.is_drag_select_active());
-
- // get all items inside the selection
- Gee.List<CheckerboardItem>? intersection = layout.items_in_selection_band();
- if (intersection == null)
- return;
-
- Marker to_unselect = get_view().start_marking();
- Marker to_select = get_view().start_marking();
-
- // mark all selected items to be unselected
- to_unselect.mark_many(get_view().get_selected());
-
- // except for the items that were selected before the drag began
- assert(previously_selected != null);
- to_unselect.unmark_many(previously_selected);
- to_select.mark_many(previously_selected);
-
- // toggle selection on everything in the intersection and update the cursor
- cursor = null;
-
- foreach (CheckerboardItem item in intersection) {
- if (to_select.toggle(item))
- to_unselect.unmark(item);
- else
- to_unselect.mark(item);
-
- if (cursor == null)
- cursor = item;
- }
-
- get_view().select_marked(to_select);
- get_view().unselect_marked(to_unselect);
- }
-
- private bool selection_autoscroll() {
- if (!layout.is_drag_select_active()) {
- autoscroll_scheduled = false;
-
- return false;
- }
-
- // as the viewport never scrolls horizontally, only interested in vertical
- Gtk.Adjustment vadj = get_vadjustment();
-
- int x, y;
- Gdk.ModifierType mask;
- get_event_source_pointer(out x, out y, out mask);
-
- int new_value = (int) vadj.get_value();
- switch (get_adjustment_relation(vadj, y)) {
- case AdjustmentRelation.BELOW:
- // pointer above window, scroll up
- new_value -= AUTOSCROLL_PIXELS;
- layout.set_drag_select_endpoint(x, new_value);
- break;
-
- case AdjustmentRelation.ABOVE:
- // pointer below window, scroll down, extend selection to bottom of page
- new_value += AUTOSCROLL_PIXELS;
- layout.set_drag_select_endpoint(x, new_value + (int) vadj.get_page_size());
- break;
-
- case AdjustmentRelation.IN_RANGE:
- autoscroll_scheduled = false;
-
- return false;
-
- default:
- warn_if_reached();
- break;
- }
-
- // It appears that in GTK+ 2.18, the adjustment is not clamped the way it was in 2.16.
- // This may have to do with how adjustments are different w/ scrollbars, that they're upper
- // clamp is upper - page_size ... either way, enforce these limits here
- vadj.set_value(new_value.clamp((int) vadj.get_lower(),
- (int) vadj.get_upper() - (int) vadj.get_page_size()));
-
- updated_selection_band();
-
- return true;
- }
-
- public void cursor_to_item(CheckerboardItem item) {
- assert(get_view().contains(item));
-
- cursor = item;
-
- if (!get_ctrl_pressed()) {
- get_view().unselect_all();
- Marker marker = get_view().mark(item);
- get_view().select_marked(marker);
- }
- layout.set_cursor(item);
-
- // if item is in any way out of view, scroll to it
- Gtk.Adjustment vadj = get_vadjustment();
- if (get_adjustment_relation(vadj, item.allocation.y) == AdjustmentRelation.IN_RANGE
- && (get_adjustment_relation(vadj, item.allocation.y + item.allocation.height) == AdjustmentRelation.IN_RANGE))
- return;
-
- // scroll to see the new item
- int top = 0;
- if (item.allocation.y < vadj.get_value()) {
- top = item.allocation.y;
- top -= CheckerboardLayout.ROW_GUTTER_PADDING / 2;
- } else {
- top = item.allocation.y + item.allocation.height - (int) vadj.get_page_size();
- top += CheckerboardLayout.ROW_GUTTER_PADDING / 2;
- }
-
- vadj.set_value(top);
- }
-
- public void move_cursor(CompassPoint point) {
- // if no items, nothing to do
- if (get_view().get_count() == 0)
- return;
-
- // if there is no better starting point, simply select the first and exit
- // The right half of the or is related to Bug #732334, the cursor might be non-null and still not contained in
- // the view, if the user dragged a full screen Photo off screen
- if (cursor == null && layout.get_cursor() == null || cursor != null && !get_view().contains(cursor)) {
- CheckerboardItem item = layout.get_item_at_coordinate(0, 0);
- cursor_to_item(item);
- anchor = item;
-
- return;
- }
-
- if (cursor == null) {
- cursor = layout.get_cursor() as CheckerboardItem;
- }
-
- // move the cursor relative to the "first" item
- CheckerboardItem? item = layout.get_item_relative_to(cursor, point);
- if (item != null)
- cursor_to_item(item);
- }
-
- public void set_cursor(CheckerboardItem item) {
- Marker marker = get_view().mark(item);
- get_view().select_marked(marker);
-
- cursor = item;
- anchor = item;
- }
-
- public void select_between_items(CheckerboardItem item_start, CheckerboardItem item_end) {
- Marker marker = get_view().start_marking();
-
- bool passed_start = false;
- bool passed_end = false;
-
- foreach (DataObject object in get_view().get_all()) {
- CheckerboardItem item = (CheckerboardItem) object;
-
- if (item_start == item)
- passed_start = true;
-
- if (item_end == item)
- passed_end = true;
-
- if (passed_start || passed_end)
- marker.mark((DataView) object);
-
- if (passed_start && passed_end)
- break;
- }
-
- get_view().select_marked(marker);
- }
-
- public void select_anchor_to_cursor(uint state) {
- if (cursor == null || anchor == null)
- return;
-
- if (state == Gdk.ModifierType.SHIFT_MASK) {
- get_view().unselect_all();
- select_between_items(anchor, cursor);
- } else {
- anchor = cursor;
- }
- }
-
- protected virtual void set_display_titles(bool display) {
- get_view().freeze_notifications();
- get_view().set_property(CheckerboardItem.PROP_SHOW_TITLES, display);
- get_view().thaw_notifications();
- }
-
- protected virtual void set_display_comments(bool display) {
- get_view().freeze_notifications();
- get_view().set_property(CheckerboardItem.PROP_SHOW_COMMENTS, display);
- get_view().thaw_notifications();
- }
-}
-
-public abstract class SinglePhotoPage : Page {
- public const Gdk.InterpType FAST_INTERP = Gdk.InterpType.NEAREST;
- public const Gdk.InterpType QUALITY_INTERP = Gdk.InterpType.BILINEAR;
- public const int KEY_REPEAT_INTERVAL_MSEC = 200;
-
- public enum UpdateReason {
- NEW_PIXBUF,
- QUALITY_IMPROVEMENT,
- RESIZED_CANVAS
- }
-
- protected Gtk.DrawingArea canvas = new Gtk.DrawingArea();
- protected Gtk.Viewport viewport = new Gtk.Viewport(null, null);
-
- private bool scale_up_to_viewport;
- private TransitionClock transition_clock;
- private int transition_duration_msec = 0;
- private Cairo.Surface pixmap = null;
- private Cairo.Context pixmap_ctx = null;
- private Cairo.Context text_ctx = null;
- private Dimensions pixmap_dim = Dimensions();
- private Gdk.Pixbuf unscaled = null;
- private Dimensions max_dim = Dimensions();
- private Gdk.Pixbuf scaled = null;
- private Gdk.Pixbuf old_scaled = null; // previous scaled image
- private Gdk.Rectangle scaled_pos = Gdk.Rectangle();
- private ZoomState static_zoom_state;
- private bool zoom_high_quality = true;
- private ZoomState saved_zoom_state;
- private bool has_saved_zoom_state = false;
- private uint32 last_nav_key = 0;
-
- protected SinglePhotoPage(string page_name, bool scale_up_to_viewport) {
- base(page_name);
-
- this.scale_up_to_viewport = scale_up_to_viewport;
-
- transition_clock = TransitionEffectsManager.get_instance().create_null_transition_clock();
-
- // With the current code automatically resizing the image to the viewport, scrollbars
- // should never be shown, but this may change if/when zooming is supported
- set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC);
-
- set_border_width(0);
- set_shadow_type(Gtk.ShadowType.NONE);
-
- viewport.set_shadow_type(Gtk.ShadowType.NONE);
- viewport.set_border_width(0);
- viewport.add(canvas);
-
- add(viewport);
-
- canvas.add_events(Gdk.EventMask.EXPOSURE_MASK | Gdk.EventMask.STRUCTURE_MASK
- | Gdk.EventMask.SUBSTRUCTURE_MASK);
-
- viewport.size_allocate.connect(on_viewport_resize);
- canvas.draw.connect(on_canvas_exposed);
-
- set_event_source(canvas);
- Config.Facade.get_instance().colors_changed.connect(on_colors_changed);
- }
-
- ~SinglePhotoPage() {
- Config.Facade.get_instance().colors_changed.disconnect(on_colors_changed);
- }
-
- public bool is_transition_in_progress() {
- return transition_clock.is_in_progress();
- }
-
- public void cancel_transition() {
- if (transition_clock.is_in_progress())
- transition_clock.cancel();
- }
-
- public void set_transition(string effect_id, int duration_msec) {
- cancel_transition();
-
- transition_clock = TransitionEffectsManager.get_instance().create_transition_clock(effect_id);
- if (transition_clock == null)
- transition_clock = TransitionEffectsManager.get_instance().create_null_transition_clock();
-
- transition_duration_msec = duration_msec;
- }
-
- // This method includes a call to pixmap_ctx.paint().
- private void render_zoomed_to_pixmap(ZoomState zoom_state) {
- assert(is_zoom_supported());
-
- Gdk.Rectangle view_rect = zoom_state.get_viewing_rectangle_wrt_content();
-
- Gdk.Pixbuf zoomed;
- if (get_zoom_buffer() != null) {
- zoomed = (zoom_high_quality) ? get_zoom_buffer().get_zoomed_image(zoom_state) :
- get_zoom_buffer().get_zoom_preview_image(zoom_state);
- } else {
- Gdk.Rectangle view_rect_proj = zoom_state.get_viewing_rectangle_projection(unscaled);
-
- Gdk.Pixbuf proj_subpixbuf = new Gdk.Pixbuf.subpixbuf(unscaled, view_rect_proj.x,
- view_rect_proj.y, view_rect_proj.width, view_rect_proj.height);
-
- zoomed = proj_subpixbuf.scale_simple(view_rect.width, view_rect.height,
- Gdk.InterpType.BILINEAR);
- }
-
- if (zoomed == null) {
- return;
- }
-
- int draw_x = (pixmap_dim.width - view_rect.width) / 2;
- draw_x = draw_x.clamp(0, int.MAX);
-
- int draw_y = (pixmap_dim.height - view_rect.height) / 2;
- draw_y = draw_y.clamp(0, int.MAX);
- paint_pixmap_with_background(pixmap_ctx, zoomed, draw_x, draw_y);
- }
-
- protected void on_interactive_zoom(ZoomState interactive_zoom_state) {
- assert(is_zoom_supported());
-
- set_source_color_from_string(pixmap_ctx, "#000");
- pixmap_ctx.paint();
-
- bool old_quality_setting = zoom_high_quality;
- zoom_high_quality = false;
- render_zoomed_to_pixmap(interactive_zoom_state);
- zoom_high_quality = old_quality_setting;
-
- canvas.queue_draw();
- }
-
- protected void on_interactive_pan(ZoomState interactive_zoom_state) {
- assert(is_zoom_supported());
-
- set_source_color_from_string(pixmap_ctx, "#000");
- pixmap_ctx.paint();
-
- bool old_quality_setting = zoom_high_quality;
- zoom_high_quality = true;
- render_zoomed_to_pixmap(interactive_zoom_state);
- zoom_high_quality = old_quality_setting;
-
- canvas.queue_draw();
- }
-
- protected virtual bool is_zoom_supported() {
- return false;
- }
-
- protected virtual void cancel_zoom() {
- if (pixmap != null) {
- set_source_color_from_string(pixmap_ctx, "#000");
- pixmap_ctx.paint();
- }
- }
-
- protected virtual void save_zoom_state() {
- saved_zoom_state = static_zoom_state;
- has_saved_zoom_state = true;
- }
-
- protected virtual void restore_zoom_state() {
- if (!has_saved_zoom_state)
- return;
-
- static_zoom_state = saved_zoom_state;
- repaint();
- has_saved_zoom_state = false;
- }
-
- protected virtual ZoomBuffer? get_zoom_buffer() {
- return null;
- }
-
- protected ZoomState get_saved_zoom_state() {
- return saved_zoom_state;
- }
-
- protected void set_zoom_state(ZoomState zoom_state) {
- assert(is_zoom_supported());
-
- static_zoom_state = zoom_state;
- }
-
- protected ZoomState get_zoom_state() {
- assert(is_zoom_supported());
-
- return static_zoom_state;
- }
-
- public override void switched_to() {
- base.switched_to();
-
- if (unscaled != null)
- repaint();
- }
-
- public override void set_container(Gtk.Window container) {
- base.set_container(container);
-
- // scrollbar policy in fullscreen mode needs to be auto/auto, else the pixbuf will shift
- // off the screen
- if (container is FullscreenWindow)
- set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC);
- }
-
- // max_dim represents the maximum size of the original pixbuf (i.e. pixbuf may be scaled and
- // the caller capable of producing larger ones depending on the viewport size). max_dim
- // is used when scale_up_to_viewport is set to true. Pass a Dimensions with no area if
- // max_dim should be ignored (i.e. scale_up_to_viewport is false).
- public void set_pixbuf(Gdk.Pixbuf unscaled, Dimensions max_dim, Direction? direction = null) {
- static_zoom_state = ZoomState(max_dim, pixmap_dim,
- static_zoom_state.get_interpolation_factor(),
- static_zoom_state.get_viewport_center());
-
- cancel_transition();
-
- this.unscaled = unscaled;
- this.max_dim = max_dim;
- this.old_scaled = scaled;
- scaled = null;
-
- // need to make sure this has happened
- canvas.realize();
-
- repaint(direction);
- }
-
- public void blank_display() {
- unscaled = null;
- max_dim = Dimensions();
- scaled = null;
- pixmap = null;
-
- // this has to have happened
- canvas.realize();
-
- // force a redraw
- invalidate_all();
- }
-
- public Cairo.Surface? get_surface() {
- return pixmap;
- }
-
- public Dimensions get_surface_dim() {
- return pixmap_dim;
- }
-
- public Cairo.Context get_cairo_context() {
- return pixmap_ctx;
- }
-
- public void paint_text(Pango.Layout pango_layout, int x, int y) {
- text_ctx.move_to(x, y);
- Pango.cairo_show_layout(text_ctx, pango_layout);
- }
-
- public Scaling get_canvas_scaling() {
- return (get_container() is FullscreenWindow) ? Scaling.for_screen(AppWindow.get_instance(), scale_up_to_viewport)
- : Scaling.for_widget(viewport, scale_up_to_viewport);
- }
-
- public Gdk.Pixbuf? get_unscaled_pixbuf() {
- return unscaled;
- }
-
- public Gdk.Pixbuf? get_scaled_pixbuf() {
- return scaled;
- }
-
- // Returns a rectangle describing the pixbuf in relation to the canvas
- public Gdk.Rectangle get_scaled_pixbuf_position() {
- return scaled_pos;
- }
-
- public bool is_inside_pixbuf(int x, int y) {
- return coord_in_rectangle(x, y, scaled_pos);
- }
-
- public void invalidate(Gdk.Rectangle rect) {
- if (canvas.get_window() != null)
- canvas.get_window().invalidate_rect(rect, false);
- }
-
- public void invalidate_all() {
- if (canvas.get_window() != null)
- canvas.get_window().invalidate_rect(null, false);
- }
-
- private void on_viewport_resize() {
- // do fast repaints while resizing
- internal_repaint(true, null);
- }
-
- protected override void on_resize_finished(Gdk.Rectangle rect) {
- base.on_resize_finished(rect);
-
- // when the resize is completed, do a high-quality repaint
- repaint();
- }
-
- private bool on_canvas_exposed(Cairo.Context exposed_ctx) {
- // draw pixmap onto canvas unless it's not been instantiated, in which case draw black
- // (so either old image or contents of another page is not left on screen)
- if (pixmap != null)
- exposed_ctx.set_source_surface(pixmap, 0, 0);
- else
- set_source_color_from_string(exposed_ctx, "#000");
-
- exposed_ctx.rectangle(0, 0, get_allocated_width(), get_allocated_height());
- exposed_ctx.paint();
-
- return true;
- }
-
- protected virtual void new_surface(Cairo.Context ctx, Dimensions ctx_dim) {
- }
-
- protected virtual void updated_pixbuf(Gdk.Pixbuf pixbuf, UpdateReason reason, Dimensions old_dim) {
- }
-
- protected virtual void paint(Cairo.Context ctx, Dimensions ctx_dim) {
- if (is_zoom_supported() && (!static_zoom_state.is_default())) {
- set_source_color_from_string(ctx, "#000");
- ctx.rectangle(0, 0, pixmap_dim.width, pixmap_dim.height);
- ctx.fill();
-
- render_zoomed_to_pixmap(static_zoom_state);
- } else if (!transition_clock.paint(ctx, ctx_dim.width, ctx_dim.height)) {
- // transition is not running, so paint the full image on a black background
- set_source_color_from_string(ctx, "#000");
-
- ctx.rectangle(0, 0, pixmap_dim.width, pixmap_dim.height);
- ctx.fill();
-
- paint_pixmap_with_background(ctx, scaled, scaled_pos.x, scaled_pos.y);
- }
- }
-
- private void repaint_pixmap() {
- if (pixmap_ctx == null)
- return;
-
- paint(pixmap_ctx, pixmap_dim);
- invalidate_all();
- }
-
- public void repaint(Direction? direction = null) {
- internal_repaint(false, direction);
- }
-
- private void internal_repaint(bool fast, Direction? direction) {
- // if not in view, assume a full repaint needed in future but do nothing more
- if (!is_in_view()) {
- pixmap = null;
- scaled = null;
-
- return;
- }
-
- // no image or window, no painting
- if (unscaled == null || canvas.get_window() == null)
- return;
-
- Gtk.Allocation allocation;
- viewport.get_allocation(out allocation);
-
- int width = allocation.width;
- int height = allocation.height;
-
- if (width <= 0 || height <= 0)
- return;
-
- bool new_pixbuf = (scaled == null);
-
- // save if reporting an image being rescaled
- Dimensions old_scaled_dim = Dimensions.for_rectangle(scaled_pos);
- Gdk.Rectangle old_scaled_pos = scaled_pos;
-
- // attempt to reuse pixmap
- if (pixmap_dim.width != width || pixmap_dim.height != height)
- pixmap = null;
-
- // if necessary, create a pixmap as large as the entire viewport
- bool new_pixmap = false;
- if (pixmap == null) {
- init_pixmap(width, height);
- new_pixmap = true;
- }
-
- if (new_pixbuf || new_pixmap) {
- Dimensions unscaled_dim = Dimensions.for_pixbuf(unscaled);
-
- // determine scaled size of pixbuf ... if a max dimensions is set and not scaling up,
- // respect it
- Dimensions scaled_dim = Dimensions();
- if (!scale_up_to_viewport && max_dim.has_area() && max_dim.width < width && max_dim.height < height)
- scaled_dim = max_dim;
- else
- scaled_dim = unscaled_dim.get_scaled_proportional(pixmap_dim);
-
- assert(width >= scaled_dim.width);
- assert(height >= scaled_dim.height);
-
- // center pixbuf on the canvas
- scaled_pos.x = (width - scaled_dim.width) / 2;
- scaled_pos.y = (height - scaled_dim.height) / 2;
- scaled_pos.width = scaled_dim.width;
- scaled_pos.height = scaled_dim.height;
- }
-
- Gdk.InterpType interp = (fast) ? FAST_INTERP : QUALITY_INTERP;
-
- // rescale if canvas rescaled or better quality is requested
- if (scaled == null) {
- scaled = resize_pixbuf(unscaled, Dimensions.for_rectangle(scaled_pos), interp);
-
- UpdateReason reason = UpdateReason.RESIZED_CANVAS;
- if (new_pixbuf)
- reason = UpdateReason.NEW_PIXBUF;
- else if (!new_pixmap && interp == QUALITY_INTERP)
- reason = UpdateReason.QUALITY_IMPROVEMENT;
-
- static_zoom_state = ZoomState(max_dim, pixmap_dim,
- static_zoom_state.get_interpolation_factor(),
- static_zoom_state.get_viewport_center());
-
- updated_pixbuf(scaled, reason, old_scaled_dim);
- }
-
- zoom_high_quality = !fast;
-
- if (direction != null && !transition_clock.is_in_progress()) {
- Spit.Transitions.Visuals visuals = new Spit.Transitions.Visuals(old_scaled,
- old_scaled_pos, scaled, scaled_pos, parse_color("#000"));
-
- transition_clock.start(visuals, direction.to_transition_direction(), transition_duration_msec,
- repaint_pixmap);
- }
-
- if (!transition_clock.is_in_progress())
- repaint_pixmap();
- }
-
- private void init_pixmap(int width, int height) {
- assert(unscaled != null);
- assert(canvas.get_window() != null);
-
- // Cairo backing surface (manual double-buffering)
- pixmap = new Cairo.ImageSurface(Cairo.Format.ARGB32, width, height);
- pixmap_dim = Dimensions(width, height);
-
- // Cairo context for drawing on the pixmap
- pixmap_ctx = new Cairo.Context(pixmap);
-
- // need a new pixbuf to fit this scale
- scaled = null;
-
- // Cairo context for drawing text on the pixmap
- text_ctx = new Cairo.Context(pixmap);
- set_source_color_from_string(text_ctx, "#fff");
-
-
- // no need to resize canvas, viewport does that automatically
-
- new_surface(pixmap_ctx, pixmap_dim);
- }
-
- protected override bool on_context_keypress() {
- return popup_context_menu(get_page_context_menu());
- }
-
- protected virtual void on_previous_photo() {
- }
-
- protected virtual void on_next_photo() {
- }
-
- public override bool key_press_event(Gdk.EventKey event) {
- // if the user holds the arrow keys down, we will receive a steady stream of key press
- // events for an operation that isn't designed for a rapid succession of output ...
- // we staunch the supply of new photos to under a quarter second (#533)
- bool nav_ok = (event.time - last_nav_key) > KEY_REPEAT_INTERVAL_MSEC;
-
- bool handled = true;
- switch (Gdk.keyval_name(event.keyval)) {
- case "Left":
- case "KP_Left":
- case "BackSpace":
- if (nav_ok) {
- on_previous_photo();
- last_nav_key = event.time;
- }
- break;
-
- case "Right":
- case "KP_Right":
- case "space":
- if (nav_ok) {
- on_next_photo();
- last_nav_key = event.time;
- }
- break;
-
- default:
- handled = false;
- break;
- }
-
- if (handled)
- return true;
-
- return (base.key_press_event != null) ? base.key_press_event(event) : true;
- }
-
- private void on_colors_changed() {
- invalidate_transparent_background();
- repaint();
- }
-}
-
-//
-// DragAndDropHandler attaches signals to a Page to properly handle drag-and-drop requests for the
-// Page as a DnD Source. (DnD Destination handling is handled by the appropriate AppWindow, i.e.
-// LibraryWindow and DirectWindow). Assumes the Page's ViewCollection holds MediaSources.
-//
-public class DragAndDropHandler {
- private enum TargetType {
- XDS,
- MEDIA_LIST
- }
-
- private const Gtk.TargetEntry[] SOURCE_TARGET_ENTRIES = {
- { "XdndDirectSave0", Gtk.TargetFlags.OTHER_APP, TargetType.XDS },
- { "shotwell/media-id-atom", Gtk.TargetFlags.SAME_APP, TargetType.MEDIA_LIST }
- };
-
- private static Gdk.Atom? XDS_ATOM = null;
- private static Gdk.Atom? TEXT_ATOM = null;
- private static uint8[]? XDS_FAKE_TARGET = null;
-
- private weak Page page;
- private Gtk.Widget event_source;
- private File? drag_destination = null;
- private ExporterUI exporter = null;
-
- public DragAndDropHandler(Page page) {
- this.page = page;
- this.event_source = page.get_event_source();
- assert(event_source != null);
- assert(event_source.get_has_window());
-
- // Need to do this because static member variables are not properly handled
- if (XDS_ATOM == null)
- XDS_ATOM = Gdk.Atom.intern_static_string("XdndDirectSave0");
-
- if (TEXT_ATOM == null)
- TEXT_ATOM = Gdk.Atom.intern_static_string("text/plain");
-
- if (XDS_FAKE_TARGET == null)
- XDS_FAKE_TARGET = string_to_uchar_array("shotwell.txt");
-
- // register what's available on this DnD Source
- Gtk.drag_source_set(event_source, Gdk.ModifierType.BUTTON1_MASK, SOURCE_TARGET_ENTRIES,
- Gdk.DragAction.COPY);
-
- // attach to the event source's DnD signals, not the Page's, which is a NO_WINDOW widget
- // and does not emit them
- event_source.drag_begin.connect(on_drag_begin);
- event_source.drag_data_get.connect(on_drag_data_get);
- event_source.drag_end.connect(on_drag_end);
- event_source.drag_failed.connect(on_drag_failed);
- }
-
- ~DragAndDropHandler() {
- if (event_source != null) {
- event_source.drag_begin.disconnect(on_drag_begin);
- event_source.drag_data_get.disconnect(on_drag_data_get);
- event_source.drag_end.disconnect(on_drag_end);
- event_source.drag_failed.disconnect(on_drag_failed);
- }
-
- page = null;
- event_source = null;
- }
-
- private void on_drag_begin(Gdk.DragContext context) {
- debug("on_drag_begin (%s)", page.get_page_name());
-
- if (page == null || page.get_view().get_selected_count() == 0 || exporter != null)
- return;
-
- drag_destination = null;
-
- // use the first media item as the icon
- ThumbnailSource thumb = (ThumbnailSource) page.get_view().get_selected_at(0).get_source();
-
- try {
- Gdk.Pixbuf icon = thumb.get_thumbnail(AppWindow.DND_ICON_SCALE);
- Gtk.drag_source_set_icon_pixbuf(event_source, icon);
- } catch (Error err) {
- warning("Unable to fetch icon for drag-and-drop from %s: %s", thumb.to_string(),
- err.message);
- }
-
- // set the XDS property to indicate an XDS save is available
-#if VALA_0_20
- Gdk.property_change(context.get_source_window(), XDS_ATOM, TEXT_ATOM, 8, Gdk.PropMode.REPLACE,
- XDS_FAKE_TARGET, 1);
-#else
- Gdk.property_change(context.get_source_window(), XDS_ATOM, TEXT_ATOM, 8, Gdk.PropMode.REPLACE,
- XDS_FAKE_TARGET);
-#endif
- }
-
- private void on_drag_data_get(Gdk.DragContext context, Gtk.SelectionData selection_data,
- uint target_type, uint time) {
- debug("on_drag_data_get (%s)", page.get_page_name());
-
- if (page == null || page.get_view().get_selected_count() == 0)
- return;
-
- switch (target_type) {
- case TargetType.XDS:
- // Fetch the XDS property that has been set with the destination path
- uchar[] data = new uchar[4096];
- Gdk.Atom actual_type;
- int actual_format = 0;
- bool fetched = Gdk.property_get(context.get_source_window(), XDS_ATOM, TEXT_ATOM,
- 0, data.length, 0, out actual_type, out actual_format, out data);
-
- // the destination path is actually for our XDS_FAKE_TARGET, use its parent
- // to determine where the file(s) should go
- if (fetched && data != null && data.length > 0)
- drag_destination = File.new_for_uri(uchar_array_to_string(data)).get_parent();
-
- debug("on_drag_data_get (%s): %s", page.get_page_name(),
- (drag_destination != null) ? drag_destination.get_path() : "(no path)");
-
- // Set the property to "S" for Success or "E" for Error
- selection_data.set(XDS_ATOM, 8,
- string_to_uchar_array((drag_destination != null) ? "S" : "E"));
- break;
-
- case TargetType.MEDIA_LIST:
- Gee.Collection<MediaSource> sources =
- (Gee.Collection<MediaSource>) page.get_view().get_selected_sources();
-
- // convert the selected media sources to Gdk.Atom-encoded sourceID strings for
- // internal drag-and-drop
- selection_data.set(Gdk.Atom.intern_static_string("SourceIDAtom"), (int) sizeof(Gdk.Atom),
- serialize_media_sources(sources));
- break;
-
- default:
- warning("on_drag_data_get (%s): unknown target type %u", page.get_page_name(),
- target_type);
- break;
- }
- }
-
- private void on_drag_end() {
- debug("on_drag_end (%s)", page.get_page_name());
-
- if (page == null || page.get_view().get_selected_count() == 0 || drag_destination == null
- || exporter != null) {
- return;
- }
-
- debug("Exporting to %s", drag_destination.get_path());
-
- // drag-and-drop export doesn't pop up an export dialog, so use what are likely the
- // most common export settings (the current -- or "working" -- file format, with
- // all transformations applied, at the image's original size).
- if (drag_destination.get_path() != null) {
- exporter = new ExporterUI(new Exporter(
- (Gee.Collection<Photo>) page.get_view().get_selected_sources(),
- drag_destination, Scaling.for_original(), ExportFormatParameters.current()));
- exporter.export(on_export_completed);
- } else {
- AppWindow.error_message(_("Photos cannot be exported to this directory."));
- }
-
- drag_destination = null;
- }
-
- private bool on_drag_failed(Gdk.DragContext context, Gtk.DragResult drag_result) {
- debug("on_drag_failed (%s): %d", page.get_page_name(), (int) drag_result);
-
- if (page == null)
- return false;
-
- drag_destination = null;
-
- return false;
- }
-
- private void on_export_completed() {
- exporter = null;
- }
-
-}