Update selection widget with latest version

svn: r23426
This commit is contained in:
Nick Hall 2013-10-27 12:59:41 +00:00
parent 09388cf0aa
commit 046f89a2a7
2 changed files with 324 additions and 155 deletions

View File

@ -189,7 +189,7 @@ GRABBERS_SWITCH = [
[INSIDE, INSIDE, INSIDE],
[GRABBER_UPPER_RIGHT, GRABBER_LOWER_RIGHT, GRABBER_LOWER_LEFT],
[GRABBER_UPPER, GRABBER_LOWER, GRABBER_LOWER],
[GRABBER_UPPER_LEFT, GRABBER_LOWER_LEFT, GRABBER_UPPER_RIGHT],
[GRABBER_UPPER_LEFT, GRABBER_LOWER_LEFT, GRABBER_LOWER_RIGHT],
[GRABBER_LEFT, GRABBER_LEFT, GRABBER_RIGHT],
[GRABBER_LOWER_LEFT, GRABBER_UPPER_LEFT, GRABBER_UPPER_RIGHT],
[GRABBER_LOWER, GRABBER_UPPER, GRABBER_UPPER],

View File

@ -68,6 +68,10 @@ SHADING_OPACITY = 0.7
MIN_SELECTION_SIZE = 10
def scale_to_fit(orig_x, orig_y, target_x, target_y):
"""
Calculates the scale factor to fit the rectangle
orig_x * orig_y by scaling keeping the aspect ratio.
"""
orig_aspect = orig_x / orig_y
target_aspect = target_x / target_y
if orig_aspect > target_aspect:
@ -76,6 +80,11 @@ def scale_to_fit(orig_x, orig_y, target_x, target_y):
return target_y / orig_y
def resize_keep_aspect(orig_x, orig_y, target_x, target_y):
"""
Calculates the dimensions of the rectangle obtained from
the rectangle orig_x * orig_y by scaling to fit
target_x * target_y keeping the aspect ratio.
"""
orig_aspect = orig_x / orig_y
target_aspect = target_x / target_y
if orig_aspect > target_aspect:
@ -94,6 +103,14 @@ def order_coordinates(point1, point2):
y2 = max(point1[1], point2[1])
return (x1, y1, x2, y2)
def minimum_region(point1, point2):
"""
Returns whether the rectangle defined by the corner points point1
and point2 exceeds the minimum dimensions.
"""
return (abs(point1[0] - point2[0]) >= MIN_SELECTION_SIZE and
abs(point1[1] - point2[1]) >= MIN_SELECTION_SIZE)
class Region(object):
"""
@ -102,35 +119,65 @@ class Region(object):
"""
def __init__(self, x1, y1, x2, y2):
self.set_coords(x1, y1, x2, y2)
"""
Creates a new region with the specified coordinates.
"""
self.x1 = x1
self.y1 = y1
self.x2 = x2
self.y2 = y2
self.person = None
self.mediaref = None
def coords(self):
"""
Returns the coordinates of the region as a 4-tuple in the
format (x1, y1, x2, y2).
"""
return (self.x1, self.y1, self.x2, self.y2)
def set_coords(self, x1, y1, x2, y2):
"""
Sets the coordinates of this region.
"""
self.x1 = x1
self.y1 = y1
self.x2 = x2
self.y2 = y2
def contains(self, x, y):
"""
Returns whether the point with coordinates (x, y) lies insided
this region.
"""
return self.x1 <= x <= self.x2 and self.y1 <= y <= self.y2
def contains_rect(self, other):
"""
Returns whether this region fully contains the region other.
"""
return (self.contains(other.x1, other.y1) and
self.contains(other.x2, other.y2))
def area(self):
"""
Returns the area of this region.
"""
return abs(self.x1 - self.x2) * abs(self.y1 - self.y2)
def intersects(self, other):
"""
Returns whether the current region intersects other.
"""
# assumes that x1 <= x2 and y1 <= y2
return not (self.x2 < other.x1 or self.x1 > other.x2 or
self.y2 < other.y1 or self.y1 > other.y2)
class SelectionWidget(Gtk.ScrolledWindow):
"""
A widget that displays an image and permits GIMP-like selection of regions
within the image. The widget derives from gtk.ScrolledWindow.
"""
__gsignals__ = {
"region-modified": (GObject.SIGNAL_RUN_FIRST, None, ()),
@ -143,6 +190,9 @@ class SelectionWidget(Gtk.ScrolledWindow):
}
def __init__(self):
"""
Creates a new selection widget.
"""
self.multiple_selection = True
self.loaded = False
@ -158,24 +208,27 @@ class SelectionWidget(Gtk.ScrolledWindow):
self.scale = 1.0
Gtk.ScrolledWindow.__init__(self)
self.add(self.build_gui())
self.add(self._build_gui())
self.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
def build_gui(self):
def _build_gui(self):
"""
Builds and lays out the GUI of the widget.
"""
self.image = Gtk.Image()
self.image.set_has_tooltip(True)
self.image.connect_after("draw", self.expose_handler)
self.image.connect("query-tooltip", self.show_tooltip)
self.image.connect_after("draw", self._expose_handler)
self.image.connect("query-tooltip", self._show_tooltip)
self.event_box = Gtk.EventBox()
self.event_box.connect('button-press-event',
self.button_press_event)
self._button_press_event)
self.event_box.connect('button-release-event',
self.button_release_event)
self._button_release_event)
self.event_box.connect('motion-notify-event',
self.motion_notify_event)
self._motion_notify_event)
self.event_box.connect('scroll-event',
self.motion_scroll_event)
self._motion_scroll_event)
self.event_box.add_events(Gdk.EventMask.BUTTON_PRESS_MASK)
self.event_box.add_events(Gdk.EventMask.BUTTON_RELEASE_MASK)
self.event_box.add_events(Gdk.EventMask.POINTER_MOTION_MASK)
@ -187,7 +240,7 @@ class SelectionWidget(Gtk.ScrolledWindow):
return self.viewport
# ======================================================
# field accessors
# public field accessors
# ======================================================
def get_multiple_selection(self):
@ -202,16 +255,34 @@ class SelectionWidget(Gtk.ScrolledWindow):
"""
self.multiple_selection = enable
def is_image_loaded(self):
"""
Returns whether an image has been loaded into this selection widget.
"""
return self.loaded
def set_regions(self, regions):
"""
Sets the list of regions to be displayed in the widget.
"""
self.regions = regions
def get_current(self):
"""
Returns the currently active region.
"""
return self.current
def set_current(self, region):
"""
Activates the given region in the widget.
"""
self.current = region
def get_selection(self):
"""
Returns the coordinates of the current selection.
"""
return self.selection
# ======================================================
@ -219,6 +290,9 @@ class SelectionWidget(Gtk.ScrolledWindow):
# ======================================================
def load_image(self, image_path):
"""
Loads an image from a given path into this selection widget.
"""
self.start_point_screen = None
self.selection = None
self.in_region = None
@ -235,73 +309,160 @@ class SelectionWidget(Gtk.ScrolledWindow):
self.pixbuf.get_height(),
viewport_size.width,
viewport_size.height)
self.rescale()
self._rescale()
self.loaded = True
except (GObject.GError, OSError):
self.show_missing()
def show_missing(self):
"""
Displays a 'missing image' icon in the widget.
"""
self.pixbuf = None
self.image.set_from_stock(Gtk.STOCK_MISSING_IMAGE, Gtk.IconSize.DIALOG)
self.image.queue_draw()
# ======================================================
# coordinate transformations (public methods)
# ======================================================
def proportional_to_real_rect(self, rect):
"""
Translates proportional (ranging from 0 to 100) coordinates to image
coordinates (in pixels).
"""
x1, y1, x2, y2 = rect
return (self._proportional_to_real((x1, y1)) +
self._proportional_to_real((x2, y2)))
def real_to_proportional_rect(self, rect):
"""
Translates image coordinates (in pixels) to proportional (ranging
from 0 to 100).
"""
x1, y1, x2, y2 = rect
return (self._real_to_proportional((x1, y1)) +
self._real_to_proportional((x2, y2)))
# ======================================================
# widget manipulation
# ======================================================
def refresh(self):
"""
Schedules a redraw of the image.
"""
self.image.queue_draw()
def can_zoom_in(self):
"""
Returns whether it is possible to zoom in the image.
"""
scaled_size = (self.original_image_size[0] * self.scale * RESIZE_RATIO,
self.original_image_size[1] * self.scale * RESIZE_RATIO)
return scaled_size[0] < MAX_SIZE and scaled_size[1] < MAX_SIZE
def can_zoom_out(self):
"""
Returns whether it is possible to zoom out the image.
"""
scaled_size = (self.original_image_size[0] * self.scale * RESIZE_RATIO,
self.original_image_size[1] * self.scale * RESIZE_RATIO)
return scaled_size[0] >= MIN_SIZE and scaled_size[1] >= MIN_SIZE
def zoom_in(self):
"""
Zooms in the image. The zoom factor is defined by RESIZE_RATIO.
"""
if self.can_zoom_in():
self.scale *= RESIZE_RATIO
self._rescale()
self.emit("zoomed-in")
def zoom_out(self):
"""
Zooms out the image. The zoom factor is defined by RESIZE_RATIO.
"""
if self.can_zoom_out():
self.scale /= RESIZE_RATIO
self._rescale()
self.emit("zoomed-out")
def select(self, region):
"""
Highlights the given region in the image.
"""
self.current = region
if self.current is not None:
self.selection = self.current.coords()
self.image.queue_draw()
def clear_selection(self):
"""
Clears the selection.
"""
self.current = None
self.selection = None
self.image.queue_draw()
# ======================================================
# thumbnails
# ======================================================
def get_thumbnail(self, region, thumbnail_size):
"""
Returns the thumbnail of the given region.
"""
w = region.x2 - region.x1
h = region.y2 - region.y1
if w >= 1 and h >= 1:
subpixbuf = self.pixbuf.new_subpixbuf(region.x1, region.y1, w, h)
size = resize_keep_aspect(w, h, *thumbnail_size)
return subpixbuf.scale_simple(size[0], size[1],
GdkPixbuf.InterpType.BILINEAR)
else:
return None
# ======================================================
# utility functions for retrieving properties
# ======================================================
def is_image_loaded(self):
return self.loaded
def get_original_image_size(self):
def _get_original_image_size(self):
"""
Returns the size of the image before scaling.
"""
return self.original_image_size
def get_scaled_image_size(self):
unscaled_size = self.get_original_image_size()
def _get_scaled_image_size(self):
"""
Returns the size of images scaled with the current scaled.
"""
unscaled_size = self._get_original_image_size()
return (unscaled_size[0] * self.scale, unscaled_size[1] * self.scale)
def get_viewport_size(self):
rect = self.viewport.get_allocation()
return (rect.width, rect.height)
def get_used_screen_size(self):
scaled_image_size = self.get_scaled_image_size()
viewport_size = self.get_viewport_size()
return (min(scaled_image_size[0], viewport_size[0]),
min(scaled_image_size[1], viewport_size[1]))
# ======================================================
# coordinate transformations
# ======================================================
def proportional_to_real(self, coord):
def _proportional_to_real(self, coord):
"""
Translate proportional (ranging from 0 to 100) coordinates to image
Translates proportional (ranging from 0 to 100) coordinates to image
coordinates (in pixels).
"""
w, h = self.original_image_size
return (int(round(coord[0] * w / 100)), int(round(coord[1] * h / 100)))
def real_to_proportional(self, coord):
def _real_to_proportional(self, coord):
"""
Translate image coordinates (in pixels) to proportional (ranging
Translates image coordinates (in pixels) to proportional (ranging
from 0 to 100).
"""
w, h = self.original_image_size
return (int(round(coord[0] * 100 / w)), int(round(coord[1] * 100 / h)))
def proportional_to_real_rect(self, rect):
x1, y1, x2, y2 = rect
return (self.proportional_to_real((x1, y1)) +
self.proportional_to_real((x2, y2)))
def real_to_proportional_rect(self, rect):
x1, y1, x2, y2 = rect
return (self.real_to_proportional((x1, y1)) +
self.real_to_proportional((x2, y2)))
def image_to_screen(self, coords):
def _image_to_screen(self, coords):
"""
Translate image coordinates to viewport coordinates using the current
Translates image coordinates to viewport coordinates using the current
scale and viewport size.
"""
viewport_rect = self.viewport.get_allocation()
@ -317,9 +478,9 @@ class SelectionWidget(Gtk.ScrolledWindow):
return (int(coords[0] * self.scale - offset_x),
int(coords[1] * self.scale - offset_y))
def screen_to_image(self, coords):
def _screen_to_image(self, coords):
"""
Translate viewport coordinates to original (unscaled) image coordinates
Translates viewport coordinates to original (unscaled) image coordinates
using the current scale and viewport size.
"""
viewport_rect = self.viewport.get_allocation()
@ -335,59 +496,85 @@ class SelectionWidget(Gtk.ScrolledWindow):
return (int((coords[0] + offset_x) / self.scale),
int((coords[1] + offset_y) / self.scale))
def truncate_to_image_size(self, coords):
def _truncate_to_image_size(self, coords):
"""
Modifies the coordinates of the given point to ensure that it lies
within the image. Negative values are replaced with 0, positive values
exceeding the image dimensions - with those corresponding dimensions.
"""
x, y = coords
(image_width, image_height) = self.get_original_image_size()
(image_width, image_height) = self._get_original_image_size()
x = max(x, 0)
x = min(x, image_width)
y = max(y, 0)
y = min(y, image_height)
return self.proportional_to_real(self.real_to_proportional((x, y)))
return self._proportional_to_real(self._real_to_proportional((x, y)))
def screen_to_truncated(self, coords):
return self.truncate_to_image_size(self.screen_to_image(coords))
def _screen_to_truncated(self, coords):
"""
Transforms the screen coordinates to image coordinates and truncate to
the image size.
"""
return self._truncate_to_image_size(self._screen_to_image(coords))
def rect_image_to_screen(self, rect):
def _rect_image_to_screen(self, rect):
"""
Translates the coordinates of the rectangle from image to screen.
"""
x1, y1, x2, y2 = rect
x1, y1 = self.image_to_screen((x1, y1))
x2, y2 = self.image_to_screen((x2, y2))
x1, y1 = self._image_to_screen((x1, y1))
x2, y2 = self._image_to_screen((x2, y2))
return (x1, y1, x2, y2)
# ======================================================
# drawing, refreshing and zooming the image
# drawing and scaling the image
# ======================================================
def draw_selection(self):
def _expose_handler(self, widget, event):
"""
Handles the expose-event signal of the underlying widget.
"""
if self.pixbuf:
self._draw_selection()
def _draw_selection(self):
"""
Draws the image, the selection boxes and does the necessary
shading.
"""
if not self.scaled_size:
return
w, h = self.scaled_size
offset_x, offset_y = self.image_to_screen((0, 0))
offset_x, offset_y = self._image_to_screen((0, 0))
offset_x -= 1
offset_y -= 1
cr = self.image.get_window().cairo_create()
if self.selection:
x1, y1, x2, y2 = self.rect_image_to_screen(self.selection)
x1, y1, x2, y2 = self._rect_image_to_screen(self.selection)
# transparent shading
self.draw_transparent_shading(cr, x1, y1, x2, y2, w, h,
self._draw_transparent_shading(cr, x1, y1, x2, y2, w, h,
offset_x, offset_y)
# selection frame
self.draw_selection_frame(cr, x1, y1, x2, y2)
self._draw_selection_frame(cr, x1, y1, x2, y2)
# draw grabber
self.draw_grabber(cr)
self._draw_grabber(cr)
else:
# selection frame
for region in self.regions:
x1, y1, x2, y2 = self.rect_image_to_screen(region.coords())
self.draw_region_frame(cr, x1, y1, x2, y2)
x1, y1, x2, y2 = self._rect_image_to_screen(region.coords())
self._draw_region_frame(cr, x1, y1, x2, y2)
def draw_transparent_shading(self, cr, x1, y1, x2, y2, w, h,
def _draw_transparent_shading(self, cr, x1, y1, x2, y2, w, h,
offset_x, offset_y):
"""
Draws the shading for a selection box.
"""
cr.set_source_rgba(1.0, 1.0, 1.0, SHADING_OPACITY)
cr.rectangle(offset_x, offset_y, x1 - offset_x, y1 - offset_y)
cr.rectangle(offset_x, y1, x1 - offset_x, y2 - y1)
@ -399,10 +586,16 @@ class SelectionWidget(Gtk.ScrolledWindow):
cr.rectangle(x1, offset_y, x2 - x1 + 1, y1 - offset_y)
cr.fill()
def draw_selection_frame(self, cr, x1, y1, x2, y2):
self.draw_region_frame(cr, x1, y1, x2, y2)
def _draw_selection_frame(self, cr, x1, y1, x2, y2):
"""
Draws the frame during selection.
"""
self._draw_region_frame(cr, x1, y1, x2, y2)
def draw_region_frame(self, cr, x1, y1, x2, y2):
def _draw_region_frame(self, cr, x1, y1, x2, y2):
"""
Draws a region frame.
"""
cr.set_source_rgb(1.0, 1.0, 1.0) # white
cr.rectangle(x1, y1, x2 - x1, y2 - y1)
cr.stroke()
@ -410,9 +603,12 @@ class SelectionWidget(Gtk.ScrolledWindow):
cr.rectangle(x1 - 2, y1 - 2, x2 - x1 + 4, y2 - y1 + 4)
cr.stroke()
def draw_grabber(self, cr):
def _draw_grabber(self, cr):
"""
Draws a grabber.
"""
if self.selection is not None and self.grabber is not None:
selection_rect = self.rect_image_to_screen(self.selection)
selection_rect = self._rect_image_to_screen(self.selection)
cr.set_source_rgb(1.0, 0, 0)
if self.grabber_position is None:
generators = grabber_generators(selection_rect)
@ -429,10 +625,11 @@ class SelectionWidget(Gtk.ScrolledWindow):
cr.rectangle(x1, y1, x2 - x1, y2 - y1)
cr.stroke()
def refresh(self):
self.image.queue_draw()
def rescale(self):
def _rescale(self):
"""
Recalculates the sizes using the current scale and updates
the buffers.
"""
self.scaled_size = (int(self.original_image_size[0] * self.scale),
int(self.original_image_size[1] * self.scale))
self.scaled_image = self.pixbuf.scale_simple(self.scaled_size[0],
@ -442,48 +639,14 @@ class SelectionWidget(Gtk.ScrolledWindow):
self.image.set_size_request(*self.scaled_size)
self.event_box.set_size_request(*self.scaled_size)
def can_zoom_in(self):
scaled_size = (self.original_image_size[0] * self.scale * RESIZE_RATIO,
self.original_image_size[1] * self.scale * RESIZE_RATIO)
return scaled_size[0] < MAX_SIZE and scaled_size[1] < MAX_SIZE
def can_zoom_out(self):
scaled_size = (self.original_image_size[0] * self.scale * RESIZE_RATIO,
self.original_image_size[1] * self.scale * RESIZE_RATIO)
return scaled_size[0] >= MIN_SIZE and scaled_size[1] >= MIN_SIZE
def zoom_in(self):
if self.can_zoom_in():
self.scale *= RESIZE_RATIO
self.rescale()
self.emit("zoomed-in")
def zoom_out(self):
if self.can_zoom_out():
self.scale /= RESIZE_RATIO
self.rescale()
self.emit("zoomed-out")
def expose_handler(self, widget, event):
if self.pixbuf:
self.draw_selection()
def select(self, region):
self.current = region
if self.current is not None:
self.selection = self.current.coords()
self.image.queue_draw()
def clear_selection(self):
self.current = None
self.selection = None
self.image.queue_draw()
# ======================================================
# managing regions
# ======================================================
def find_region(self, x, y):
def _find_region(self, x, y):
"""
Finds the smallest region containing point (x, y).
"""
result = None
for region in self.regions:
if region.contains(x, y):
@ -491,26 +654,14 @@ class SelectionWidget(Gtk.ScrolledWindow):
result = region
return result
# ======================================================
# thumbnails
# ======================================================
def get_thumbnail(self, region, thumbnail_size):
w = region.x2 - region.x1
h = region.y2 - region.y1
if w >= 1 and h >= 1:
subpixbuf = self.pixbuf.new_subpixbuf(region.x1, region.y1, w, h)
size = resize_keep_aspect(w, h, *thumbnail_size)
return subpixbuf.scale_simple(size[0], size[1],
GdkPixbuf.InterpType.BILINEAR)
else:
return None
# ======================================================
# mouse event handlers
# ======================================================
def button_press_event(self, obj, event):
def _button_press_event(self, obj, event):
"""
Handles the button-press-event signal.
"""
if not self.is_image_loaded():
return
if event.button == 1: # left button
@ -522,8 +673,8 @@ class SelectionWidget(Gtk.ScrolledWindow):
self.emit("selection-cleared")
elif event.button == 3: # right button
# select a region, if clicked inside one
click_point = self.screen_to_image((event.x, event.y))
self.current = self.find_region(*click_point)
click_point = self._screen_to_image((event.x, event.y))
self.current = self._find_region(*click_point)
self.selection = \
self.current.coords() if self.current is not None else None
self.start_point_screen = None
@ -535,7 +686,10 @@ class SelectionWidget(Gtk.ScrolledWindow):
self.emit("selection-cleared")
return True # don't propagate the event further
def button_release_event(self, obj, event):
def _button_release_event(self, obj, event):
"""
Handles the button-release-event signal.
"""
if not self.is_image_loaded():
return
if event.button == 1:
@ -551,14 +705,14 @@ class SelectionWidget(Gtk.ScrolledWindow):
# clicked on one of the grabbers
dx, dy = (event.x - self.start_point_screen[0],
event.y - self.start_point_screen[1])
self.grabber_to_draw = self.modify_selection(dx, dy)
self.grabber_to_draw = self._modify_selection(dx, dy)
self.current.set_coords(*self.selection)
self.emit("region-modified")
else:
# nothing is currently selected
if (self.minimum_region(self.start_point_screen,
(event.x, event.y)) and
self.can_select()):
if (minimum_region(self.start_point_screen,
(event.x, event.y)) and
self._can_select()):
# region selection
region = Region(*self.selection)
self.regions.append(region)
@ -567,8 +721,8 @@ class SelectionWidget(Gtk.ScrolledWindow):
else:
# nothing selected, just a click
click_point = \
self.screen_to_image(self.start_point_screen)
self.current = self.find_region(*click_point)
self._screen_to_image(self.start_point_screen)
self.current = self._find_region(*click_point)
self.selection = \
self.current.coords() if self.current is not None \
else None
@ -577,28 +731,31 @@ class SelectionWidget(Gtk.ScrolledWindow):
self.start_point_screen = None
self.refresh()
def motion_notify_event(self, widget, event):
def _motion_notify_event(self, widget, event):
"""
Handles the motion-notify-event signal.
"""
if not self.is_image_loaded():
return
end_point_orig = self.screen_to_image((event.x, event.y))
end_point = self.truncate_to_image_size(end_point_orig)
end_point_orig = self._screen_to_image((event.x, event.y))
end_point = self._truncate_to_image_size(end_point_orig)
if self.start_point_screen:
# selection or dragging (mouse button pressed)
if self.grabber is not None and self.grabber != INSIDE:
# dragging the grabber
dx, dy = (event.x - self.start_point_screen[0],
event.y - self.start_point_screen[1])
self.grabber_to_draw = self.modify_selection(dx, dy)
elif self.can_select():
self.grabber_to_draw = self._modify_selection(dx, dy)
elif self._can_select():
# making new selection
start_point = self.screen_to_truncated(self.start_point_screen)
start_point = self._screen_to_truncated(self.start_point_screen)
self.selection = order_coordinates(start_point, end_point)
else:
# motion (mouse button is not pressed)
self.in_region = self.find_region(*end_point_orig)
self.in_region = self._find_region(*end_point_orig)
if self.current is not None:
# a box is active, so check if the pointer is inside a grabber
rect = self.rect_image_to_screen(self.current.coords())
rect = self._rect_image_to_screen(self.current.coords())
self.grabber = can_grab(rect, event.x, event.y)
if self.grabber is not None:
self.grabber_to_draw = self.grabber
@ -616,7 +773,10 @@ class SelectionWidget(Gtk.ScrolledWindow):
self.event_box.get_window().set_cursor(None)
self.image.queue_draw()
def motion_scroll_event(self, widget, event):
def _motion_scroll_event(self, widget, event):
"""
Handles the motion-scroll-event signal.
"""
if not self.is_image_loaded():
return
if event.direction == Gdk.ScrollDirection.UP:
@ -628,18 +788,24 @@ class SelectionWidget(Gtk.ScrolledWindow):
# helpers for mouse event handlers
# ======================================================
def minimum_region(self, point1, point2):
return (abs(point1[0] - point2[0]) >= MIN_SELECTION_SIZE and
abs(point1[1] - point2[1]) >= MIN_SELECTION_SIZE)
def can_select(self):
def _can_select(self):
"""
Returns whether selection is currently possible, which is when
multiple selection is enabled or otherwise when no region is
currently selected.
"""
return self.multiple_selection or len(self.regions) < 1
def modify_selection(self, dx, dy):
x1, y1, x2, y2 = self.rect_image_to_screen(self.current.coords())
def _modify_selection(self, dx, dy):
"""
Changes the selection when a grabber is dragged, returns the new
grabber if a grabber switch has happened, and the current grabber
otherwise.
"""
x1, y1, x2, y2 = self._rect_image_to_screen(self.current.coords())
x1, y1, x2, y2 = MOTION_FUNCTIONS[self.grabber](x1, y1, x2, y2, dx, dy)
(x1, y1) = self.screen_to_truncated((x1, y1))
(x2, y2) = self.screen_to_truncated((x2, y2))
(x1, y1) = self._screen_to_truncated((x1, y1))
(x2, y2) = self._screen_to_truncated((x2, y2))
grabber = switch_grabber(self.grabber, x1, y1, x2, y2)
self.selection = order_coordinates((x1, y1), (x2, y2))
return grabber
@ -648,7 +814,10 @@ class SelectionWidget(Gtk.ScrolledWindow):
# tooltips
# ======================================================
def show_tooltip(self, widget, x, y, keyboard_mode, tooltip):
def _show_tooltip(self, widget, x, y, keyboard_mode, tooltip):
"""
Handles the query-tooltip signal.
"""
if self.in_region:
person = self.in_region.person
if person: