Update selection widget with latest version
svn: r23426
This commit is contained in:
parent
09388cf0aa
commit
046f89a2a7
@ -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],
|
||||
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user