Modify media reference editor to use new selection widget
svn: r23421
This commit is contained in:
parent
5b31f8ce60
commit
767430f93f
@ -6,6 +6,7 @@
|
||||
# 2009 Gary Burton
|
||||
# 2011 Robert Cheramy <robert@cheramy.net>
|
||||
# Copyright (C) 2011 Tim G L Lyons
|
||||
# Copyright (C) 2013 Nick Hall
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@ -58,7 +59,7 @@ from ..glade import Glade
|
||||
from .displaytabs import (CitationEmbedList, AttrEmbedList, MediaBackRefList,
|
||||
NoteTab)
|
||||
from ..widgets import (MonitoredSpinButton, MonitoredEntry, PrivacyButton,
|
||||
MonitoredDate, MonitoredTagList)
|
||||
MonitoredDate, MonitoredTagList, SelectionWidget, Region)
|
||||
from .editreference import RefTab, EditReference
|
||||
from .addmedia import AddMediaObject
|
||||
|
||||
@ -136,7 +137,7 @@ class EditMediaRef(EditReference):
|
||||
if not self.source.get_mime_type():
|
||||
self.mimetext.set_text(_('Note'))
|
||||
|
||||
def draw_preview(self, onlysub=False):
|
||||
def draw_preview(self):
|
||||
"""
|
||||
Draw the two preview images. This method can be called on eg change of
|
||||
the path.
|
||||
@ -144,33 +145,18 @@ class EditMediaRef(EditReference):
|
||||
mtype = self.source.get_mime_type()
|
||||
if mtype:
|
||||
fullpath = media_path_full(self.db, self.source.get_path())
|
||||
if not onlysub:
|
||||
pb = get_thumbnail_image(fullpath, mtype)
|
||||
self.pixmap.set_from_pixbuf(pb)
|
||||
subpix = get_thumbnail_image(fullpath, mtype,
|
||||
self.rectangle)
|
||||
self.subpixmap.set_from_pixbuf(subpix)
|
||||
pb = get_thumbnail_image(fullpath, mtype)
|
||||
self.pixmap.set_from_pixbuf(pb)
|
||||
self.selection.load_image(fullpath)
|
||||
else:
|
||||
pb = find_mime_type_pixbuf('text/plain')
|
||||
if not onlysub:
|
||||
self.pixmap.set_from_pixbuf(pb)
|
||||
self.subpixmap.set_from_pixbuf(pb)
|
||||
self.pixmap.set_from_pixbuf(pb)
|
||||
self.selection.load_image('')
|
||||
|
||||
def _setup_fields(self):
|
||||
|
||||
ebox_shared = self.top.get_object('eventbox')
|
||||
ebox_shared.connect('button-press-event', self.button_press_event)
|
||||
|
||||
if not self.dbstate.db.readonly:
|
||||
self.button_press_coords = (0, 0)
|
||||
ebox_ref = self.top.get_object('eventbox1')
|
||||
ebox_ref.connect('button-press-event', self.button_press_event_ref)
|
||||
ebox_ref.connect('button-release-event',
|
||||
self.button_release_event_ref)
|
||||
ebox_ref.connect('motion-notify-event', self.motion_notify_event_ref)
|
||||
ebox_ref.add_events(Gdk.EventMask.BUTTON_PRESS_MASK)
|
||||
ebox_ref.add_events(Gdk.EventMask.BUTTON_RELEASE_MASK)
|
||||
|
||||
self.pixmap = self.top.get_object("pixmap")
|
||||
self.mimetext = self.top.get_object("type")
|
||||
self.track_ref_for_deletion("mimetext")
|
||||
@ -185,13 +171,22 @@ class EditMediaRef(EditReference):
|
||||
):
|
||||
coord = None
|
||||
|
||||
self.rectangle = coord
|
||||
self.subpixmap = self.top.get_object("subpixmap")
|
||||
self.track_ref_for_deletion("subpixmap")
|
||||
if coord is not None:
|
||||
self.rectangle = coord
|
||||
else:
|
||||
self.rectangle = (0, 0, 100, 100)
|
||||
|
||||
self.selection = SelectionWidget()
|
||||
self.selection.set_multiple_selection(False)
|
||||
self.selection.connect("region-modified", self.region_modified)
|
||||
self.selection.connect("region-created", self.region_modified)
|
||||
frame = self.top.get_object("frame9")
|
||||
frame.add(self.selection)
|
||||
self.track_ref_for_deletion("selection")
|
||||
self.selection.show()
|
||||
|
||||
self.setup_filepath()
|
||||
self.determine_mime()
|
||||
self.draw_preview()
|
||||
|
||||
corners = ["corner1_x", "corner1_y", "corner2_x", "corner2_y"]
|
||||
|
||||
@ -278,6 +273,13 @@ class EditMediaRef(EditReference):
|
||||
self.uistate, self.track,
|
||||
self.db.readonly)
|
||||
|
||||
def _post_init(self):
|
||||
"""
|
||||
Initialization that must happen after the window is shown.
|
||||
"""
|
||||
self.draw_preview()
|
||||
self.update_region()
|
||||
|
||||
def set_corner1_x(self, value):
|
||||
"""
|
||||
Callback for the signal handling of the spinbutton for the first
|
||||
@ -287,10 +289,8 @@ class EditMediaRef(EditReference):
|
||||
@param value: the first corner x coordinate of the subsection in int
|
||||
"""
|
||||
|
||||
if self.rectangle is None:
|
||||
self.rectangle = (0,0,100,100)
|
||||
self.rectangle = (value,) + self.rectangle[1:]
|
||||
self.update_subpixmap()
|
||||
self.update_region()
|
||||
|
||||
def set_corner1_y(self, value):
|
||||
"""
|
||||
@ -301,10 +301,8 @@ class EditMediaRef(EditReference):
|
||||
@param value: the first corner y coordinate of the subsection in int
|
||||
"""
|
||||
|
||||
if self.rectangle is None:
|
||||
self.rectangle = (0,0,100,100)
|
||||
self.rectangle = self.rectangle[:1] + (value,) + self.rectangle[2:]
|
||||
self.update_subpixmap()
|
||||
self.update_region()
|
||||
|
||||
def set_corner2_x(self, value):
|
||||
"""
|
||||
@ -315,10 +313,8 @@ class EditMediaRef(EditReference):
|
||||
@param value: the second corner x coordinate of the subsection in int
|
||||
"""
|
||||
|
||||
if self.rectangle is None:
|
||||
self.rectangle = (0,0,100,100)
|
||||
self.rectangle = self.rectangle[:2] + (value,) + self.rectangle[3:]
|
||||
self.update_subpixmap()
|
||||
self.update_region()
|
||||
|
||||
def set_corner2_y(self, value):
|
||||
"""
|
||||
@ -329,10 +325,8 @@ class EditMediaRef(EditReference):
|
||||
@param value: the second corner y coordinate of the subsection in int
|
||||
"""
|
||||
|
||||
if self.rectangle is None:
|
||||
self.rectangle = (0,0,100,100)
|
||||
self.rectangle = self.rectangle[:3] + (value,)
|
||||
self.update_subpixmap()
|
||||
self.update_region()
|
||||
|
||||
def get_corner1_x(self):
|
||||
"""
|
||||
@ -342,11 +336,7 @@ class EditMediaRef(EditReference):
|
||||
@returns: the first corner x coordinate of the subsection or 0 if
|
||||
there is no selection
|
||||
"""
|
||||
|
||||
if self.rectangle is not None:
|
||||
return self.rectangle[0]
|
||||
else:
|
||||
return 0
|
||||
return self.rectangle[0]
|
||||
|
||||
def get_corner1_y(self):
|
||||
"""
|
||||
@ -356,11 +346,7 @@ class EditMediaRef(EditReference):
|
||||
@returns: the first corner y coordinate of the subsection or 0 if
|
||||
there is no selection
|
||||
"""
|
||||
|
||||
if self.rectangle is not None:
|
||||
return self.rectangle[1]
|
||||
else:
|
||||
return 0
|
||||
return self.rectangle[1]
|
||||
|
||||
def get_corner2_x(self):
|
||||
"""
|
||||
@ -370,11 +356,7 @@ class EditMediaRef(EditReference):
|
||||
@returns: the second corner x coordinate of the subsection or 100 if
|
||||
there is no selection
|
||||
"""
|
||||
|
||||
if self.rectangle is not None:
|
||||
return self.rectangle[2]
|
||||
else:
|
||||
return 100
|
||||
return self.rectangle[2]
|
||||
|
||||
def get_corner2_y(self):
|
||||
"""
|
||||
@ -384,17 +366,29 @@ class EditMediaRef(EditReference):
|
||||
@returns: the second corner x coordinate of the subsection or 100 if
|
||||
there is no selection
|
||||
"""
|
||||
|
||||
if self.rectangle is not None:
|
||||
return self.rectangle[3]
|
||||
else:
|
||||
return 100
|
||||
return self.rectangle[3]
|
||||
|
||||
def update_subpixmap(self):
|
||||
def update_region(self):
|
||||
"""
|
||||
Updates the thumbnail of the specified subsection
|
||||
Updates the thumbnail of the specified subsection.
|
||||
"""
|
||||
self.draw_preview(onlysub=True)
|
||||
if not self.selection.is_image_loaded():
|
||||
return
|
||||
real = self.selection.proportional_to_real_rect(self.rectangle)
|
||||
region = Region(real[0], real[1], real[2], real[3])
|
||||
self.selection.set_regions([region])
|
||||
self.selection.refresh()
|
||||
|
||||
def region_modified(self, widget):
|
||||
"""
|
||||
Update new values when the selection is changed.
|
||||
"""
|
||||
real = self.selection.get_selection()
|
||||
coords = self.selection.real_to_proportional_rect(real)
|
||||
self.corner1_x_spinbutton.set_value(coords[0])
|
||||
self.corner1_y_spinbutton.set_value(coords[1])
|
||||
self.corner2_x_spinbutton.set_value(coords[2])
|
||||
self.corner2_y_spinbutton.set_value(coords[3])
|
||||
|
||||
def build_menu_names(self, person):
|
||||
"""
|
||||
@ -412,139 +406,6 @@ class EditMediaRef(EditReference):
|
||||
photo_path = media_path_full(self.db, self.source.get_path())
|
||||
open_file_with_default_application(photo_path)
|
||||
|
||||
def button_press_event_ref(self, widget, event):
|
||||
"""
|
||||
Handle the button-press-event generated by the eventbox
|
||||
parent of the subpixmap. Remember these coordinates
|
||||
so we can crop the picture when button-release-event
|
||||
is received.
|
||||
"""
|
||||
self.button_press_coords = (event.x, event.y)
|
||||
# prepare drawing of a feedback rectangle
|
||||
self.orig_subpixbuf = self.subpixmap.get_pixbuf()
|
||||
gtkimg_win = self.subpixmap.get_window()
|
||||
self.rect_pixbuf = Gdk.pixbuf_get_from_window(
|
||||
gtkimg_win, 0, 0,
|
||||
gtkimg_win.get_width(),
|
||||
gtkimg_win.get_height())
|
||||
|
||||
def motion_notify_event_ref(self, widget, event):
|
||||
# get the image size and calculate the X and Y offsets
|
||||
# (image is centered *horizontally* when smaller than THUMBSCALE)
|
||||
w, h = self.orig_subpixbuf.get_width(), self.orig_subpixbuf.get_height()
|
||||
offset_x = (THUMBSCALE - w) / 2
|
||||
offset_y = 0
|
||||
|
||||
# get coordinates of the rectangle, so that x1 < x2 and y1 < y2
|
||||
x1 = min(self.button_press_coords[0], event.x)
|
||||
x2 = max(self.button_press_coords[0], event.x)
|
||||
y1 = min(self.button_press_coords[1], event.y)
|
||||
y2 = max(self.button_press_coords[1], event.y)
|
||||
|
||||
width = int(x2 - x1)
|
||||
height = int(y2 - y1)
|
||||
x1 = int(x1 - offset_x)
|
||||
y1 = int(y1 - offset_y)
|
||||
|
||||
cr_pixbuf = self.subpixmap.get_window().cairo_create()
|
||||
cr_pixbuf.reset_clip()
|
||||
#reset the pixbuf
|
||||
Gdk.cairo_set_source_pixbuf(cr_pixbuf, self.rect_pixbuf, 0, 0)
|
||||
cr_pixbuf.paint()
|
||||
cr_pixbuf.set_source_rgba(0, 0, 1, 0.5) #blue transparant
|
||||
cr_pixbuf.move_to(x1,y1)
|
||||
cr_pixbuf.line_to(x1+width, y1)
|
||||
cr_pixbuf.line_to(x1+width, y1+height)
|
||||
cr_pixbuf.line_to(x1, y1+height)
|
||||
cr_pixbuf.close_path()
|
||||
cr_pixbuf.fill()
|
||||
|
||||
def button_release_event_ref(self, widget, event):
|
||||
"""
|
||||
Handle the button-release-event generated by the eventbox
|
||||
parent of the subpixmap. Crop the picture accordingly.
|
||||
"""
|
||||
|
||||
# reset the crop on double-click or click+CTRL
|
||||
if (event.button==1 and event.type == Gdk.EventType._2BUTTON_PRESS) or \
|
||||
(event.button==1 and (event.get_state() & Gdk.ModifierType.CONTROL_MASK) ):
|
||||
self.corner1_x_spinbutton.set_value(0)
|
||||
self.corner1_y_spinbutton.set_value(0)
|
||||
self.corner2_x_spinbutton.set_value(100)
|
||||
self.corner2_y_spinbutton.set_value(100)
|
||||
|
||||
else:
|
||||
if (self.orig_subpixbuf == None):
|
||||
return
|
||||
self.subpixmap.set_from_pixbuf(self.orig_subpixbuf)
|
||||
|
||||
# ensure the clicks happened at least 5 pixels away from each other
|
||||
new_x1 = min(self.button_press_coords[0], event.x)
|
||||
new_y1 = min(self.button_press_coords[1], event.y)
|
||||
new_x2 = max(self.button_press_coords[0], event.x)
|
||||
new_y2 = max(self.button_press_coords[1], event.y)
|
||||
|
||||
if new_x2 - new_x1 >= 5 and new_y2 - new_y1 >= 5:
|
||||
|
||||
# get the image size and calculate the X and Y offsets
|
||||
# (image is centered *horizontally* when smaller than THUMBSCALE)
|
||||
w = self.orig_subpixbuf.get_width()
|
||||
h = self.orig_subpixbuf.get_height()
|
||||
x = (THUMBSCALE - w) / 2
|
||||
y = 0
|
||||
|
||||
# if the click was outside of the image,
|
||||
# bring it within the boundaries
|
||||
if new_x1 < x:
|
||||
new_x1 = x
|
||||
if new_y1 < y:
|
||||
new_y1 = y
|
||||
if new_x2 >= x + w:
|
||||
new_x2 = x + w - 1
|
||||
if new_y2 >= y + h:
|
||||
new_y2 = y + h - 1
|
||||
|
||||
# get the old spinbutton % values
|
||||
old_x1 = self.corner1_x_spinbutton.get_value()
|
||||
old_y1 = self.corner1_y_spinbutton.get_value()
|
||||
old_x2 = self.corner2_x_spinbutton.get_value()
|
||||
old_y2 = self.corner2_y_spinbutton.get_value()
|
||||
delta_x = old_x2 - old_x1 # horizontal scale
|
||||
delta_y = old_y2 - old_y1 # vertical scale
|
||||
|
||||
# Took a while to figure out the math here.
|
||||
#
|
||||
# 1) figure out the current crop % values
|
||||
# by doing the following:
|
||||
#
|
||||
# xp = click_location_x / width * 100
|
||||
# yp = click_location_y / height * 100
|
||||
#
|
||||
# but remember that click_location_x and _y
|
||||
# might not be zero-based for non-rectangular
|
||||
# images, so subtract the pixbuf "x" and "y"
|
||||
# to bring the values back to zero-based
|
||||
#
|
||||
# 2) the minimum value cannot be less than the
|
||||
# existing crop value, so add the current
|
||||
# minimum to the new values
|
||||
|
||||
new_x1 = old_x1 + delta_x * (new_x1 - x) / w
|
||||
new_y1 = old_y1 + delta_y * (new_y1 - y) / h
|
||||
new_x2 = old_x1 + delta_x * (new_x2 - x) / w
|
||||
new_y2 = old_y1 + delta_y * (new_y2 - y) / h
|
||||
|
||||
# set the new values
|
||||
self.corner1_x_spinbutton.set_value(new_x1)
|
||||
self.corner1_y_spinbutton.set_value(new_y1)
|
||||
self.corner2_x_spinbutton.set_value(new_x2)
|
||||
self.corner2_y_spinbutton.set_value(new_y2)
|
||||
|
||||
# Free the pixbuf as it is not needed anymore
|
||||
self.orig_subpixbuf = None
|
||||
self.rect_pixbuf = None
|
||||
self.draw_preview()
|
||||
|
||||
def _update_addmedia(self, obj):
|
||||
"""
|
||||
Called when the add media dialog has been called.
|
||||
|
@ -39,12 +39,10 @@
|
||||
<child>
|
||||
<object class="GtkButton" id="button84">
|
||||
<property name="label">gtk-cancel</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="can_default">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="use_stock">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
@ -56,13 +54,11 @@
|
||||
<child>
|
||||
<object class="GtkButton" id="button82">
|
||||
<property name="label">gtk-ok</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="can_default">True</property>
|
||||
<property name="has_default">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="use_stock">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
@ -74,12 +70,10 @@
|
||||
<child>
|
||||
<object class="GtkButton" id="button104">
|
||||
<property name="label">gtk-help</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="can_default">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="use_stock">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
@ -111,24 +105,15 @@
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="border_width">12</property>
|
||||
<property name="n_rows">5</property>
|
||||
<property name="n_columns">5</property>
|
||||
<property name="n_rows">6</property>
|
||||
<property name="n_columns">3</property>
|
||||
<property name="column_spacing">12</property>
|
||||
<property name="row_spacing">6</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label425">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="xalign">1</property>
|
||||
<property name="label" translatable="yes">_Corner 2: X</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="mnemonic_widget">corner2_x</property>
|
||||
@ -136,46 +121,28 @@
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="right_attach">2</property>
|
||||
<property name="top_attach">3</property>
|
||||
<property name="bottom_attach">4</property>
|
||||
<property name="x_options"></property>
|
||||
<property name="y_options"></property>
|
||||
<property name="top_attach">4</property>
|
||||
<property name="bottom_attach">5</property>
|
||||
<property name="x_options">GTK_FILL</property>
|
||||
<property name="y_options">GTK_FILL</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label428">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="xalign">1</property>
|
||||
<property name="label" translatable="yes">Y coordinate|Y</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="mnemonic_widget">corner2_y</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">3</property>
|
||||
<property name="right_attach">4</property>
|
||||
<property name="top_attach">3</property>
|
||||
<property name="bottom_attach">4</property>
|
||||
<property name="x_options"></property>
|
||||
<property name="y_options"></property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label430">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="label" translatable="yes">Referenced Region</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="right_attach">5</property>
|
||||
<property name="top_attach">1</property>
|
||||
<property name="bottom_attach">2</property>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="right_attach">2</property>
|
||||
<property name="top_attach">5</property>
|
||||
<property name="bottom_attach">6</property>
|
||||
<property name="x_options">GTK_FILL</property>
|
||||
<property name="y_options"></property>
|
||||
<property name="y_options">GTK_FILL</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@ -190,11 +157,12 @@ You can use the mouse on the picture to select a region, or use these spinbutton
|
||||
<property name="numeric">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">4</property>
|
||||
<property name="right_attach">5</property>
|
||||
<property name="top_attach">3</property>
|
||||
<property name="bottom_attach">4</property>
|
||||
<property name="y_options"></property>
|
||||
<property name="left_attach">2</property>
|
||||
<property name="right_attach">3</property>
|
||||
<property name="top_attach">5</property>
|
||||
<property name="bottom_attach">6</property>
|
||||
<property name="x_options">GTK_FILL</property>
|
||||
<property name="y_options"/>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@ -211,9 +179,10 @@ You can use the mouse on the picture to select a region, or use these spinbutton
|
||||
<packing>
|
||||
<property name="left_attach">2</property>
|
||||
<property name="right_attach">3</property>
|
||||
<property name="top_attach">3</property>
|
||||
<property name="bottom_attach">4</property>
|
||||
<property name="y_options"></property>
|
||||
<property name="top_attach">4</property>
|
||||
<property name="bottom_attach">5</property>
|
||||
<property name="x_options">GTK_FILL</property>
|
||||
<property name="y_options"/>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@ -224,19 +193,7 @@ You can use the mouse on the picture to select a region, or use these spinbutton
|
||||
Select a region with clicking and holding the mouse button on the top left corner of the region you want, dragging the mouse to the bottom right corner of the region, and then releasing the mouse button.</property>
|
||||
<property name="label_xalign">0</property>
|
||||
<child>
|
||||
<object class="GtkEventBox" id="eventbox1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="subpixmap">
|
||||
<property name="width_request">100</property>
|
||||
<property name="height_request">100</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="yalign">0</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child type="label">
|
||||
<object class="GtkLabel" id="label707">
|
||||
@ -250,17 +207,14 @@ Select a region with clicking and holding the mouse button on the top left corne
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="top_attach">2</property>
|
||||
<property name="bottom_attach">5</property>
|
||||
<property name="x_options"></property>
|
||||
<property name="y_options"></property>
|
||||
<property name="bottom_attach">6</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label426">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="xalign">1</property>
|
||||
<property name="label" translatable="yes">_Corner 1: X</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="mnemonic_widget">corner1_x</property>
|
||||
@ -270,13 +224,10 @@ Select a region with clicking and holding the mouse button on the top left corne
|
||||
<property name="right_attach">2</property>
|
||||
<property name="top_attach">2</property>
|
||||
<property name="bottom_attach">3</property>
|
||||
<property name="x_options"></property>
|
||||
<property name="y_options"></property>
|
||||
<property name="x_options">GTK_FILL</property>
|
||||
<property name="y_options">GTK_FILL</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSpinButton" id="corner1_x">
|
||||
<property name="visible">True</property>
|
||||
@ -285,16 +236,19 @@ Select a region with clicking and holding the mouse button on the top left corne
|
||||
You can use the mouse on the picture to select a region, or use these spinbuttons to set the top left, and bottom right corner of the referenced region. Point (0,0) is the top left corner of the picture, and (100,100) the bottom right corner.
|
||||
</property>
|
||||
<property name="invisible_char">●</property>
|
||||
<property name="shadow_type">none</property>
|
||||
<property name="adjustment">adjustment2</property>
|
||||
<property name="climb_rate">1</property>
|
||||
<property name="numeric">True</property>
|
||||
<property name="update_policy">if-valid</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">2</property>
|
||||
<property name="right_attach">3</property>
|
||||
<property name="top_attach">2</property>
|
||||
<property name="bottom_attach">3</property>
|
||||
<property name="y_options"></property>
|
||||
<property name="x_options">GTK_FILL</property>
|
||||
<property name="y_options"/>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@ -309,67 +263,52 @@ You can use the mouse on the picture to select a region, or use these spinbutton
|
||||
<property name="numeric">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">4</property>
|
||||
<property name="right_attach">5</property>
|
||||
<property name="top_attach">2</property>
|
||||
<property name="bottom_attach">3</property>
|
||||
<property name="y_options"></property>
|
||||
<property name="left_attach">2</property>
|
||||
<property name="right_attach">3</property>
|
||||
<property name="top_attach">3</property>
|
||||
<property name="bottom_attach">4</property>
|
||||
<property name="x_options">GTK_FILL</property>
|
||||
<property name="y_options"/>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label427">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="xalign">1</property>
|
||||
<property name="label" translatable="yes">Y coordinate|Y</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="mnemonic_widget">corner1_y</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">3</property>
|
||||
<property name="right_attach">4</property>
|
||||
<property name="top_attach">2</property>
|
||||
<property name="bottom_attach">3</property>
|
||||
<property name="x_options"></property>
|
||||
<property name="y_options"></property>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="right_attach">2</property>
|
||||
<property name="top_attach">3</property>
|
||||
<property name="bottom_attach">4</property>
|
||||
<property name="x_options">GTK_FILL</property>
|
||||
<property name="y_options">GTK_FILL</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">2</property>
|
||||
<property name="right_attach">3</property>
|
||||
<property name="top_attach">4</property>
|
||||
<property name="bottom_attach">5</property>
|
||||
<property name="y_options"></property>
|
||||
<property name="y_options"/>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkHBox" id="hbox1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="private">
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="relief">none</property>
|
||||
<accelerator key="p" signal="activate" modifiers="GDK_CONTROL_MASK"/>
|
||||
<child internal-child="accessible">
|
||||
@ -400,10 +339,10 @@ You can use the mouse on the picture to select a region, or use these spinbutton
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">4</property>
|
||||
<property name="right_attach">5</property>
|
||||
<property name="left_attach">2</property>
|
||||
<property name="right_attach">3</property>
|
||||
<property name="x_options">GTK_FILL</property>
|
||||
<property name="y_options"></property>
|
||||
<property name="y_options"/>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@ -413,22 +352,17 @@ You can use the mouse on the picture to select a region, or use these spinbutton
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
<object class="GtkLabel" id="dummy">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">2</property>
|
||||
<property name="right_attach">3</property>
|
||||
<property name="top_attach">1</property>
|
||||
<property name="bottom_attach">2</property>
|
||||
<property name="x_options"/>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
@ -447,7 +381,7 @@ You can use the mouse on the picture to select a region, or use these spinbutton
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="padding">10</property>
|
||||
<property name="position">0</property>
|
||||
@ -499,7 +433,7 @@ You can use the mouse on the picture to select a region, or use these spinbutton
|
||||
<property name="top_attach">3</property>
|
||||
<property name="bottom_attach">4</property>
|
||||
<property name="x_options">GTK_FILL</property>
|
||||
<property name="y_options"></property>
|
||||
<property name="y_options"/>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@ -516,7 +450,7 @@ You can use the mouse on the picture to select a region, or use these spinbutton
|
||||
<property name="left_attach">1</property>
|
||||
<property name="right_attach">2</property>
|
||||
<property name="x_options">GTK_FILL</property>
|
||||
<property name="y_options"></property>
|
||||
<property name="y_options"/>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@ -550,7 +484,7 @@ You can use the mouse on the picture to select a region, or use these spinbutton
|
||||
<property name="right_attach">3</property>
|
||||
<property name="top_attach">1</property>
|
||||
<property name="bottom_attach">2</property>
|
||||
<property name="y_options"></property>
|
||||
<property name="y_options"/>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@ -595,6 +529,7 @@ You can use the mouse on the picture to select a region, or use these spinbutton
|
||||
<property name="top_attach">6</property>
|
||||
<property name="bottom_attach">7</property>
|
||||
<property name="x_options">GTK_FILL</property>
|
||||
<property name="y_options">GTK_FILL</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@ -650,7 +585,7 @@ You can use the mouse on the picture to select a region, or use these spinbutton
|
||||
<property name="top_attach">5</property>
|
||||
<property name="bottom_attach">6</property>
|
||||
<property name="x_options">GTK_FILL</property>
|
||||
<property name="y_options"></property>
|
||||
<property name="y_options"/>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@ -666,7 +601,7 @@ You can use the mouse on the picture to select a region, or use these spinbutton
|
||||
<property name="top_attach">5</property>
|
||||
<property name="bottom_attach">6</property>
|
||||
<property name="x_options">GTK_FILL</property>
|
||||
<property name="y_options"></property>
|
||||
<property name="y_options"/>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@ -684,7 +619,7 @@ You can use the mouse on the picture to select a region, or use these spinbutton
|
||||
<property name="top_attach">2</property>
|
||||
<property name="bottom_attach">3</property>
|
||||
<property name="x_options">GTK_FILL</property>
|
||||
<property name="y_options"></property>
|
||||
<property name="y_options"/>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@ -698,18 +633,16 @@ You can use the mouse on the picture to select a region, or use these spinbutton
|
||||
<property name="right_attach">3</property>
|
||||
<property name="top_attach">2</property>
|
||||
<property name="bottom_attach">3</property>
|
||||
<property name="y_options"></property>
|
||||
<property name="y_options"/>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="date_edit">
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="has_tooltip">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Invoke date editor</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="relief">none</property>
|
||||
<accelerator key="d" signal="activate" modifiers="GDK_CONTROL_MASK"/>
|
||||
<accessibility>
|
||||
@ -739,16 +672,14 @@ You can use the mouse on the picture to select a region, or use these spinbutton
|
||||
<property name="top_attach">2</property>
|
||||
<property name="bottom_attach">3</property>
|
||||
<property name="x_options">GTK_FILL</property>
|
||||
<property name="y_options"></property>
|
||||
<property name="y_options"/>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="privacy">
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="relief">none</property>
|
||||
<child internal-child="accessible">
|
||||
<object class="AtkObject" id="privacy-atkobject">
|
||||
@ -772,6 +703,8 @@ You can use the mouse on the picture to select a region, or use these spinbutton
|
||||
<packing>
|
||||
<property name="left_attach">3</property>
|
||||
<property name="right_attach">4</property>
|
||||
<property name="x_options">GTK_FILL</property>
|
||||
<property name="y_options">GTK_FILL</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@ -783,17 +716,16 @@ You can use the mouse on the picture to select a region, or use these spinbutton
|
||||
<packing>
|
||||
<property name="left_attach">2</property>
|
||||
<property name="right_attach">3</property>
|
||||
<property name="y_options">GTK_FILL</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="file_select">
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="has_tooltip">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Select a file</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="relief">none</property>
|
||||
<accessibility>
|
||||
<relation type="labelled-by" target="label129"/>
|
||||
@ -822,6 +754,8 @@ You can use the mouse on the picture to select a region, or use these spinbutton
|
||||
<property name="right_attach">4</property>
|
||||
<property name="top_attach">3</property>
|
||||
<property name="bottom_attach">4</property>
|
||||
<property name="x_options">GTK_FILL</property>
|
||||
<property name="y_options">GTK_FILL</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@ -835,6 +769,7 @@ You can use the mouse on the picture to select a region, or use these spinbutton
|
||||
<property name="right_attach">3</property>
|
||||
<property name="top_attach">3</property>
|
||||
<property name="bottom_attach">4</property>
|
||||
<property name="y_options">GTK_FILL</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@ -852,7 +787,7 @@ You can use the mouse on the picture to select a region, or use these spinbutton
|
||||
<property name="top_attach">4</property>
|
||||
<property name="bottom_attach">5</property>
|
||||
<property name="x_options">GTK_FILL</property>
|
||||
<property name="y_options"></property>
|
||||
<property name="y_options"/>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@ -865,15 +800,14 @@ You can use the mouse on the picture to select a region, or use these spinbutton
|
||||
<property name="right_attach">3</property>
|
||||
<property name="top_attach">4</property>
|
||||
<property name="bottom_attach">5</property>
|
||||
<property name="y_options">GTK_FILL</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="tag_button">
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<accessibility>
|
||||
<relation type="labelled-by" target="label1"/>
|
||||
</accessibility>
|
||||
@ -888,9 +822,14 @@ You can use the mouse on the picture to select a region, or use these spinbutton
|
||||
<property name="right_attach">4</property>
|
||||
<property name="top_attach">4</property>
|
||||
<property name="bottom_attach">5</property>
|
||||
<property name="x_options">GTK_FILL</property>
|
||||
<property name="y_options">GTK_FILL</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="tab_fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child type="tab">
|
||||
<object class="GtkLabel" id="label617">
|
||||
@ -919,14 +858,14 @@ You can use the mouse on the picture to select a region, or use these spinbutton
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
|
@ -31,6 +31,7 @@ from .labels import *
|
||||
from .linkbox import *
|
||||
from .photo import *
|
||||
from .monitoredwidgets import *
|
||||
from .selectionwidget import SelectionWidget, Region
|
||||
from .shortlistcomboentry import *
|
||||
from .springseparator import *
|
||||
from .statusbar import Statusbar
|
||||
|
277
gramps/gui/widgets/grabbers.py
Normal file
277
gramps/gui/widgets/grabbers.py
Normal file
@ -0,0 +1,277 @@
|
||||
#
|
||||
# Gramps - a GTK+/GNOME based genealogy program
|
||||
#
|
||||
# Copyright (C) 2013 Artem Glebov <artem.glebov@gmail.com>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
|
||||
# $Id$
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# GTK/Gnome modules
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
from gi.repository import Gdk
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# grabbers constants and routines
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
GRABBER_INSIDE = 0
|
||||
GRABBER_OUTSIDE = 1
|
||||
|
||||
MIN_CORNER_GRABBER = 20
|
||||
MIN_SIDE_GRABBER = 20
|
||||
MIN_GRABBER_PADDING = 10
|
||||
MIN_SIDE_FOR_INSIDE_GRABBERS = (2 * (MIN_CORNER_GRABBER + MIN_GRABBER_PADDING) +
|
||||
MIN_SIDE_GRABBER)
|
||||
|
||||
INSIDE = 0
|
||||
GRABBER_UPPER_LEFT = 1
|
||||
GRABBER_UPPER = 2
|
||||
GRABBER_UPPER_RIGHT = 3
|
||||
GRABBER_RIGHT = 4
|
||||
GRABBER_LOWER_RIGHT = 5
|
||||
GRABBER_LOWER = 6
|
||||
GRABBER_LOWER_LEFT = 7
|
||||
GRABBER_LEFT = 8
|
||||
|
||||
def upper_left_grabber_inner(x1, y1, x2, y2):
|
||||
return (x1, y1, x1 + MIN_CORNER_GRABBER, y1 + MIN_CORNER_GRABBER)
|
||||
|
||||
def upper_grabber_inner(x1, y1, x2, y2):
|
||||
return (x1 + MIN_CORNER_GRABBER + MIN_GRABBER_PADDING,
|
||||
y1,
|
||||
x2 - MIN_CORNER_GRABBER - MIN_GRABBER_PADDING,
|
||||
y1 + MIN_CORNER_GRABBER)
|
||||
|
||||
def upper_right_grabber_inner(x1, y1, x2, y2):
|
||||
return (x2 - MIN_CORNER_GRABBER, y1, x2, y1 + MIN_CORNER_GRABBER)
|
||||
|
||||
def right_grabber_inner(x1, y1, x2, y2):
|
||||
return (x2 - MIN_CORNER_GRABBER,
|
||||
y1 + MIN_CORNER_GRABBER + MIN_GRABBER_PADDING,
|
||||
x2,
|
||||
y2 - MIN_CORNER_GRABBER - MIN_GRABBER_PADDING)
|
||||
|
||||
def lower_right_grabber_inner(x1, y1, x2, y2):
|
||||
return (x2 - MIN_CORNER_GRABBER, y2 - MIN_CORNER_GRABBER, x2, y2)
|
||||
|
||||
def lower_grabber_inner(x1, y1, x2, y2):
|
||||
return (x1 + MIN_CORNER_GRABBER + MIN_GRABBER_PADDING,
|
||||
y2 - MIN_CORNER_GRABBER,
|
||||
x2 - MIN_CORNER_GRABBER - MIN_GRABBER_PADDING,
|
||||
y2)
|
||||
|
||||
def lower_left_grabber_inner(x1, y1, x2, y2):
|
||||
return (x1, y2 - MIN_CORNER_GRABBER, x1 + MIN_CORNER_GRABBER, y2)
|
||||
|
||||
def left_grabber_inner(x1, y1, x2, y2):
|
||||
return (x1,
|
||||
y1 + MIN_CORNER_GRABBER + MIN_GRABBER_PADDING,
|
||||
x1 + MIN_CORNER_GRABBER,
|
||||
y2 - MIN_CORNER_GRABBER - MIN_GRABBER_PADDING)
|
||||
|
||||
# outer
|
||||
|
||||
def upper_left_grabber_outer(x1, y1, x2, y2):
|
||||
return (x1 - MIN_CORNER_GRABBER, y1 - MIN_CORNER_GRABBER, x1, y1)
|
||||
|
||||
def upper_grabber_outer(x1, y1, x2, y2):
|
||||
return (x1, y1 - MIN_CORNER_GRABBER, x2, y1)
|
||||
|
||||
def upper_right_grabber_outer(x1, y1, x2, y2):
|
||||
return (x2, y1 - MIN_CORNER_GRABBER, x2 + MIN_CORNER_GRABBER, y1)
|
||||
|
||||
def right_grabber_outer(x1, y1, x2, y2):
|
||||
return (x2, y1, x2 + MIN_CORNER_GRABBER, y2)
|
||||
|
||||
def lower_right_grabber_outer(x1, y1, x2, y2):
|
||||
return (x2, y2, x2 + MIN_CORNER_GRABBER, y2 + MIN_CORNER_GRABBER)
|
||||
|
||||
def lower_grabber_outer(x1, y1, x2, y2):
|
||||
return (x1, y2, x2, y2 + MIN_CORNER_GRABBER)
|
||||
|
||||
def lower_left_grabber_outer(x1, y1, x2, y2):
|
||||
return (x1 - MIN_CORNER_GRABBER, y2, x1, y2 + MIN_CORNER_GRABBER)
|
||||
|
||||
def left_grabber_outer(x1, y1, x2, y2):
|
||||
return (x1 - MIN_CORNER_GRABBER, y1, x1, y2)
|
||||
|
||||
# motion
|
||||
|
||||
def inside_moved(x1, y1, x2, y2, dx, dy):
|
||||
return (x1, y1, x2, y2)
|
||||
|
||||
def upper_left_moved(x1, y1, x2, y2, dx, dy):
|
||||
return (x1 + dx, y1 + dy, x2, y2)
|
||||
|
||||
def upper_moved(x1, y1, x2, y2, dx, dy):
|
||||
return (x1, y1 + dy, x2, y2)
|
||||
|
||||
def upper_right_moved(x1, y1, x2, y2, dx, dy):
|
||||
return (x1, y1 + dy, x2 + dx, y2)
|
||||
|
||||
def right_moved(x1, y1, x2, y2, dx, dy):
|
||||
return (x1, y1, x2 + dx, y2)
|
||||
|
||||
def lower_right_moved(x1, y1, x2, y2, dx, dy):
|
||||
return (x1, y1, x2 + dx, y2 + dy)
|
||||
|
||||
def lower_moved(x1, y1, x2, y2, dx, dy):
|
||||
return (x1, y1, x2, y2 + dy)
|
||||
|
||||
def lower_left_moved(x1, y1, x2, y2, dx, dy):
|
||||
return (x1 + dx, y1, x2, y2 + dy)
|
||||
|
||||
def left_moved(x1, y1, x2, y2, dx, dy):
|
||||
return (x1 + dx, y1, x2, y2)
|
||||
|
||||
# switching
|
||||
|
||||
GRABBERS = [INSIDE,
|
||||
GRABBER_UPPER_LEFT,
|
||||
GRABBER_UPPER,
|
||||
GRABBER_UPPER_RIGHT,
|
||||
GRABBER_RIGHT,
|
||||
GRABBER_LOWER_RIGHT,
|
||||
GRABBER_LOWER,
|
||||
GRABBER_LOWER_LEFT,
|
||||
GRABBER_LEFT]
|
||||
|
||||
INNER_GRABBERS = [None,
|
||||
upper_left_grabber_inner,
|
||||
upper_grabber_inner,
|
||||
upper_right_grabber_inner,
|
||||
right_grabber_inner,
|
||||
lower_right_grabber_inner,
|
||||
lower_grabber_inner,
|
||||
lower_left_grabber_inner,
|
||||
left_grabber_inner]
|
||||
|
||||
OUTER_GRABBERS = [None,
|
||||
upper_left_grabber_outer,
|
||||
upper_grabber_outer,
|
||||
upper_right_grabber_outer,
|
||||
right_grabber_outer,
|
||||
lower_right_grabber_outer,
|
||||
lower_grabber_outer,
|
||||
lower_left_grabber_outer,
|
||||
left_grabber_outer]
|
||||
|
||||
MOTION_FUNCTIONS = [inside_moved,
|
||||
upper_left_moved,
|
||||
upper_moved,
|
||||
upper_right_moved,
|
||||
right_moved,
|
||||
lower_right_moved,
|
||||
lower_moved,
|
||||
lower_left_moved,
|
||||
left_moved]
|
||||
|
||||
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_LEFT, GRABBER_LEFT, GRABBER_RIGHT],
|
||||
[GRABBER_LOWER_LEFT, GRABBER_UPPER_LEFT, GRABBER_UPPER_RIGHT],
|
||||
[GRABBER_LOWER, GRABBER_UPPER, GRABBER_UPPER],
|
||||
[GRABBER_LOWER_RIGHT, GRABBER_UPPER_RIGHT, GRABBER_UPPER_LEFT],
|
||||
[GRABBER_RIGHT, GRABBER_RIGHT, GRABBER_LEFT]
|
||||
]
|
||||
|
||||
# cursors
|
||||
|
||||
CURSOR_UPPER = Gdk.Cursor.new(Gdk.CursorType.TOP_SIDE)
|
||||
CURSOR_LOWER = Gdk.Cursor.new(Gdk.CursorType.BOTTOM_SIDE)
|
||||
CURSOR_LEFT = Gdk.Cursor.new(Gdk.CursorType.LEFT_SIDE)
|
||||
CURSOR_RIGHT = Gdk.Cursor.new(Gdk.CursorType.RIGHT_SIDE)
|
||||
CURSOR_UPPER_LEFT = Gdk.Cursor.new(Gdk.CursorType.TOP_LEFT_CORNER)
|
||||
CURSOR_UPPER_RIGHT = Gdk.Cursor.new(Gdk.CursorType.TOP_RIGHT_CORNER)
|
||||
CURSOR_LOWER_LEFT = Gdk.Cursor.new(Gdk.CursorType.BOTTOM_LEFT_CORNER)
|
||||
CURSOR_LOWER_RIGHT = Gdk.Cursor.new(Gdk.CursorType.BOTTOM_RIGHT_CORNER)
|
||||
|
||||
CURSORS = [None,
|
||||
CURSOR_UPPER_LEFT,
|
||||
CURSOR_UPPER,
|
||||
CURSOR_UPPER_RIGHT,
|
||||
CURSOR_RIGHT,
|
||||
CURSOR_LOWER_RIGHT,
|
||||
CURSOR_LOWER,
|
||||
CURSOR_LOWER_LEFT,
|
||||
CURSOR_LEFT]
|
||||
|
||||
# helper functions
|
||||
|
||||
def grabber_position(rect):
|
||||
x1, y1, x2, y2 = rect
|
||||
if (x2 - x1 >= MIN_SIDE_FOR_INSIDE_GRABBERS and
|
||||
y2 - y1 >= MIN_SIDE_FOR_INSIDE_GRABBERS):
|
||||
return GRABBER_INSIDE
|
||||
else:
|
||||
return GRABBER_OUTSIDE
|
||||
|
||||
def grabber_generators(rect):
|
||||
if grabber_position(rect) == GRABBER_INSIDE:
|
||||
return INNER_GRABBERS
|
||||
else:
|
||||
return OUTER_GRABBERS
|
||||
|
||||
def switch_grabber(grabber, x1, y1, x2, y2):
|
||||
switch_row = GRABBERS_SWITCH[grabber]
|
||||
if x1 > x2:
|
||||
if y1 > y2:
|
||||
return switch_row[1]
|
||||
else:
|
||||
return switch_row[0]
|
||||
else:
|
||||
if y1 > y2:
|
||||
return switch_row[2]
|
||||
else:
|
||||
return grabber
|
||||
|
||||
def inside_rect(rect, x, y):
|
||||
x1, y1, x2, y2 = rect
|
||||
return x1 <= x <= x2 and y1 <= y <= y2
|
||||
|
||||
def can_grab(rect, x, y):
|
||||
"""
|
||||
Checks if (x,y) lies within one of the grabbers of rect.
|
||||
"""
|
||||
(x1, y1, x2, y2) = rect
|
||||
if (x2 - x1 >= MIN_SIDE_FOR_INSIDE_GRABBERS and
|
||||
y2 - y1 >= MIN_SIDE_FOR_INSIDE_GRABBERS):
|
||||
# grabbers are inside
|
||||
if x < x1 or x > x2 or y < y1 or y > y2:
|
||||
return None
|
||||
for grabber in GRABBERS[1:]:
|
||||
grabber_area = INNER_GRABBERS[grabber](x1, y1, x2, y2)
|
||||
if inside_rect(grabber_area, x, y):
|
||||
return grabber
|
||||
return INSIDE
|
||||
else:
|
||||
# grabbers are outside
|
||||
if x1 <= x <= x2 and y1 <= y <= y2:
|
||||
return INSIDE
|
||||
for grabber in GRABBERS[1:]:
|
||||
grabber_area = OUTER_GRABBERS[grabber](x1, y1, x2, y2)
|
||||
if inside_rect(grabber_area, x, y):
|
||||
return grabber
|
||||
return None
|
661
gramps/gui/widgets/selectionwidget.py
Normal file
661
gramps/gui/widgets/selectionwidget.py
Normal file
@ -0,0 +1,661 @@
|
||||
#
|
||||
# Gramps - a GTK+/GNOME based genealogy program
|
||||
#
|
||||
# Copyright (C) 2013 Artem Glebov <artem.glebov@gmail.com>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
|
||||
# $Id$
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Standard python modules
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
from __future__ import division
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# GTK/Gnome modules
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Gdk
|
||||
from gi.repository import GdkPixbuf
|
||||
from gi.repository import GObject
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# gramps modules
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
from gramps.gen.display.name import displayer as name_displayer
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# grabbers constants and routines
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
from .grabbers import (grabber_generators, can_grab, grabber_position,
|
||||
switch_grabber, CURSORS, GRABBER_INSIDE, INSIDE,
|
||||
INNER_GRABBERS, OUTER_GRABBERS, MOTION_FUNCTIONS)
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# PhotoTaggingGramplet
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
RESIZE_RATIO = 1.5
|
||||
MAX_ZOOM = 10
|
||||
MIN_ZOOM = 0.05
|
||||
MAX_SIZE = 2000
|
||||
MIN_SIZE = 50
|
||||
SHADING_OPACITY = 0.7
|
||||
MIN_SELECTION_SIZE = 10
|
||||
|
||||
def scale_to_fit(orig_x, orig_y, target_x, target_y):
|
||||
orig_aspect = orig_x / orig_y
|
||||
target_aspect = target_x / target_y
|
||||
if orig_aspect > target_aspect:
|
||||
return target_x / orig_x
|
||||
else:
|
||||
return target_y / orig_y
|
||||
|
||||
def resize_keep_aspect(orig_x, orig_y, target_x, target_y):
|
||||
orig_aspect = orig_x / orig_y
|
||||
target_aspect = target_x / target_y
|
||||
if orig_aspect > target_aspect:
|
||||
return (target_x, target_x * orig_y // orig_x)
|
||||
else:
|
||||
return (target_y * orig_x // orig_y, target_y)
|
||||
|
||||
def order_coordinates(point1, point2):
|
||||
"""
|
||||
Returns the rectangle (x1, y1, x2, y2) based on point1 and point2,
|
||||
such that x1 <= x2 and y1 <= y2.
|
||||
"""
|
||||
x1 = min(point1[0], point2[0])
|
||||
x2 = max(point1[0], point2[0])
|
||||
y1 = min(point1[1], point2[1])
|
||||
y2 = max(point1[1], point2[1])
|
||||
return (x1, y1, x2, y2)
|
||||
|
||||
|
||||
class Region(object):
|
||||
"""
|
||||
Representation of a region of image that can be associated with
|
||||
a person.
|
||||
"""
|
||||
|
||||
def __init__(self, x1, y1, x2, y2):
|
||||
self.set_coords(x1, y1, x2, y2)
|
||||
self.person = None
|
||||
self.mediaref = None
|
||||
|
||||
def coords(self):
|
||||
return (self.x1, self.y1, self.x2, self.y2)
|
||||
|
||||
def set_coords(self, x1, y1, x2, y2):
|
||||
self.x1 = x1
|
||||
self.y1 = y1
|
||||
self.x2 = x2
|
||||
self.y2 = y2
|
||||
|
||||
def contains(self, x, y):
|
||||
return self.x1 <= x <= self.x2 and self.y1 <= y <= self.y2
|
||||
|
||||
def contains_rect(self, other):
|
||||
return (self.contains(other.x1, other.y1) and
|
||||
self.contains(other.x2, other.y2))
|
||||
|
||||
def area(self):
|
||||
return abs(self.x1 - self.x2) * abs(self.y1 - self.y2)
|
||||
|
||||
def intersects(self, 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):
|
||||
|
||||
__gsignals__ = {
|
||||
"region-modified": (GObject.SIGNAL_RUN_FIRST, None, ()),
|
||||
"region-created": (GObject.SIGNAL_RUN_FIRST, None, ()),
|
||||
"region-selected": (GObject.SIGNAL_RUN_FIRST, None, ()),
|
||||
"selection-cleared": (GObject.SIGNAL_RUN_FIRST, None, ()),
|
||||
"right-button-clicked": (GObject.SIGNAL_RUN_FIRST, None, ()),
|
||||
"zoomed-in": (GObject.SIGNAL_RUN_FIRST, None, ()),
|
||||
"zoomed-out": (GObject.SIGNAL_RUN_FIRST, None, ())
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.multiple_selection = True
|
||||
|
||||
self.loaded = False
|
||||
self.start_point_screen = None
|
||||
self.selection = None
|
||||
self.current = None
|
||||
self.in_region = None
|
||||
self.grabber = None
|
||||
self.regions = []
|
||||
self.translation = None
|
||||
self.pixbuf = None
|
||||
self.scaled_pixbuf = None
|
||||
self.scale = 1.0
|
||||
|
||||
Gtk.ScrolledWindow.__init__(self)
|
||||
self.add(self.build_gui())
|
||||
self.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
|
||||
|
||||
def build_gui(self):
|
||||
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.event_box = Gtk.EventBox()
|
||||
self.event_box.connect('button-press-event',
|
||||
self.button_press_event)
|
||||
self.event_box.connect('button-release-event',
|
||||
self.button_release_event)
|
||||
self.event_box.connect('motion-notify-event',
|
||||
self.motion_notify_event)
|
||||
self.event_box.connect('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)
|
||||
|
||||
self.event_box.add(self.image)
|
||||
|
||||
self.viewport = Gtk.Viewport()
|
||||
self.viewport.add(self.event_box)
|
||||
return self.viewport
|
||||
|
||||
# ======================================================
|
||||
# field accessors
|
||||
# ======================================================
|
||||
|
||||
def get_multiple_selection(self):
|
||||
"""
|
||||
Return whether multiple selection is enabled.
|
||||
"""
|
||||
return self.multiple_selection
|
||||
|
||||
def set_multiple_selection(self, enable):
|
||||
"""
|
||||
Enables or disables multiple selection.
|
||||
"""
|
||||
self.multiple_selection = enable
|
||||
|
||||
def set_regions(self, regions):
|
||||
self.regions = regions
|
||||
|
||||
def get_current(self):
|
||||
return self.current
|
||||
|
||||
def set_current(self, region):
|
||||
self.current = region
|
||||
|
||||
def get_selection(self):
|
||||
return self.selection
|
||||
|
||||
# ======================================================
|
||||
# loading the image
|
||||
# ======================================================
|
||||
|
||||
def load_image(self, image_path):
|
||||
self.start_point_screen = None
|
||||
self.selection = None
|
||||
self.in_region = None
|
||||
self.grabber_position = None
|
||||
self.grabber_to_draw = None
|
||||
|
||||
try:
|
||||
self.pixbuf = GdkPixbuf.Pixbuf.new_from_file(image_path)
|
||||
self.original_image_size = (self.pixbuf.get_width(),
|
||||
self.pixbuf.get_height())
|
||||
|
||||
viewport_size = self.viewport.get_allocation()
|
||||
self.scale = scale_to_fit(self.pixbuf.get_width(),
|
||||
self.pixbuf.get_height(),
|
||||
viewport_size.width,
|
||||
viewport_size.height)
|
||||
self.rescale()
|
||||
self.loaded = True
|
||||
except (GObject.GError, OSError):
|
||||
self.show_missing()
|
||||
|
||||
def show_missing(self):
|
||||
self.pixbuf = None
|
||||
self.image.set_from_stock(Gtk.STOCK_MISSING_IMAGE, Gtk.IconSize.DIALOG)
|
||||
self.image.queue_draw()
|
||||
|
||||
# ======================================================
|
||||
# utility functions for retrieving properties
|
||||
# ======================================================
|
||||
|
||||
def is_image_loaded(self):
|
||||
return self.loaded
|
||||
|
||||
def get_original_image_size(self):
|
||||
return self.original_image_size
|
||||
|
||||
def get_scaled_image_size(self):
|
||||
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):
|
||||
"""
|
||||
Translate 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):
|
||||
"""
|
||||
Translate 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):
|
||||
"""
|
||||
Translate image coordinates to viewport coordinates using the current
|
||||
scale and viewport size.
|
||||
"""
|
||||
viewport_rect = self.viewport.get_allocation()
|
||||
image_rect = self.scaled_size
|
||||
if image_rect[0] < viewport_rect.width:
|
||||
offset_x = (image_rect[0] - viewport_rect.width) / 2
|
||||
else:
|
||||
offset_x = 0.0
|
||||
if image_rect[1] < viewport_rect.height:
|
||||
offset_y = (image_rect[1] - viewport_rect.height) / 2
|
||||
else:
|
||||
offset_y = 0.0
|
||||
return (int(coords[0] * self.scale - offset_x),
|
||||
int(coords[1] * self.scale - offset_y))
|
||||
|
||||
def screen_to_image(self, coords):
|
||||
"""
|
||||
Translate viewport coordinates to original (unscaled) image coordinates
|
||||
using the current scale and viewport size.
|
||||
"""
|
||||
viewport_rect = self.viewport.get_allocation()
|
||||
image_rect = self.scaled_size
|
||||
if image_rect[0] < viewport_rect.width:
|
||||
offset_x = (image_rect[0] - viewport_rect.width) / 2
|
||||
else:
|
||||
offset_x = 0.0
|
||||
if image_rect[1] < viewport_rect.height:
|
||||
offset_y = (image_rect[1] - viewport_rect.height) / 2
|
||||
else:
|
||||
offset_y = 0.0
|
||||
return (int((coords[0] + offset_x) / self.scale),
|
||||
int((coords[1] + offset_y) / self.scale))
|
||||
|
||||
def truncate_to_image_size(self, coords):
|
||||
x, y = coords
|
||||
(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)))
|
||||
|
||||
def screen_to_truncated(self, coords):
|
||||
return self.truncate_to_image_size(self.screen_to_image(coords))
|
||||
|
||||
def rect_image_to_screen(self, rect):
|
||||
x1, y1, x2, y2 = rect
|
||||
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
|
||||
# ======================================================
|
||||
|
||||
def draw_selection(self):
|
||||
if not self.scaled_size:
|
||||
return
|
||||
|
||||
w, h = self.scaled_size
|
||||
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)
|
||||
|
||||
# transparent shading
|
||||
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)
|
||||
|
||||
# draw grabber
|
||||
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)
|
||||
|
||||
def draw_transparent_shading(self, cr, x1, y1, x2, y2, w, h,
|
||||
offset_x, offset_y):
|
||||
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)
|
||||
cr.rectangle(offset_x, y2, x1 - offset_x, h - y2 + offset_y)
|
||||
cr.rectangle(x1, y2 + 1, x2 - x1 + 1, h - y2 + offset_y)
|
||||
cr.rectangle(x2 + 1, y2 + 1, w - x2 + offset_x, h - y2 + offset_y)
|
||||
cr.rectangle(x2 + 1, y1, w - x2 + offset_x, y2 - y1 + 1)
|
||||
cr.rectangle(x2 + 1, offset_y, w - x2 + offset_x, y2 - offset_y)
|
||||
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_region_frame(self, cr, x1, y1, x2, y2):
|
||||
cr.set_source_rgb(1.0, 1.0, 1.0) # white
|
||||
cr.rectangle(x1, y1, x2 - x1, y2 - y1)
|
||||
cr.stroke()
|
||||
cr.set_source_rgb(0.0, 0.0, 1.0) # blue
|
||||
cr.rectangle(x1 - 2, y1 - 2, x2 - x1 + 4, y2 - y1 + 4)
|
||||
cr.stroke()
|
||||
|
||||
def draw_grabber(self, cr):
|
||||
if self.selection is not None and self.grabber is not None:
|
||||
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)
|
||||
elif self.grabber_position == GRABBER_INSIDE:
|
||||
generators = INNER_GRABBERS
|
||||
else:
|
||||
generators = OUTER_GRABBERS
|
||||
if self.grabber_to_draw is not None:
|
||||
generator = generators[self.grabber_to_draw]
|
||||
else:
|
||||
generator = generators[self.grabber]
|
||||
if generator is not None:
|
||||
x1, y1, x2, y2 = generator(*selection_rect)
|
||||
cr.rectangle(x1, y1, x2 - x1, y2 - y1)
|
||||
cr.stroke()
|
||||
|
||||
def refresh(self):
|
||||
self.image.queue_draw()
|
||||
|
||||
def rescale(self):
|
||||
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],
|
||||
self.scaled_size[1],
|
||||
GdkPixbuf.InterpType.BILINEAR)
|
||||
self.image.set_from_pixbuf(self.scaled_image)
|
||||
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):
|
||||
result = None
|
||||
for region in self.regions:
|
||||
if region.contains(x, y):
|
||||
if result is None or result.area() > region.area():
|
||||
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):
|
||||
if not self.is_image_loaded():
|
||||
return
|
||||
if event.button == 1: # left button
|
||||
self.start_point_screen = (event.x, event.y)
|
||||
if self.current is not None and self.grabber is None:
|
||||
self.current = None
|
||||
self.selection = None
|
||||
self.refresh()
|
||||
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)
|
||||
self.selection = \
|
||||
self.current.coords() if self.current is not None else None
|
||||
self.start_point_screen = None
|
||||
self.refresh()
|
||||
if self.current is not None:
|
||||
self.emit("region-selected")
|
||||
self.emit("right-button-clicked")
|
||||
else:
|
||||
self.emit("selection-cleared")
|
||||
return True # don't propagate the event further
|
||||
|
||||
def button_release_event(self, obj, event):
|
||||
if not self.is_image_loaded():
|
||||
return
|
||||
if event.button == 1:
|
||||
if self.start_point_screen:
|
||||
if self.current is not None:
|
||||
# a box is currently selected
|
||||
if self.grabber is None:
|
||||
# clicked outside of the grabbing area
|
||||
self.current = None
|
||||
self.selection = None
|
||||
self.emit("selection-cleared")
|
||||
elif self.grabber != INSIDE:
|
||||
# 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.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()):
|
||||
# region selection
|
||||
region = Region(*self.selection)
|
||||
self.regions.append(region)
|
||||
self.current = region
|
||||
self.emit("region-created")
|
||||
else:
|
||||
# nothing selected, just a click
|
||||
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
|
||||
self.emit("region-selected")
|
||||
|
||||
self.start_point_screen = None
|
||||
self.refresh()
|
||||
|
||||
def motion_notify_event(self, widget, event):
|
||||
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)
|
||||
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():
|
||||
# making new selection
|
||||
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)
|
||||
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())
|
||||
self.grabber = can_grab(rect, event.x, event.y)
|
||||
if self.grabber is not None:
|
||||
self.grabber_to_draw = self.grabber
|
||||
self.grabber_position = grabber_position(rect)
|
||||
self.event_box.get_window().set_cursor(CURSORS[self.grabber])
|
||||
else:
|
||||
self.grabber_to_draw = None
|
||||
self.grabber_position = None
|
||||
self.event_box.get_window().set_cursor(None)
|
||||
else:
|
||||
# nothing is active
|
||||
self.grabber = None
|
||||
self.grabber_to_draw = None
|
||||
self.grabber_position = None
|
||||
self.event_box.get_window().set_cursor(None)
|
||||
self.image.queue_draw()
|
||||
|
||||
def motion_scroll_event(self, widget, event):
|
||||
if not self.is_image_loaded():
|
||||
return
|
||||
if event.direction == Gdk.ScrollDirection.UP:
|
||||
self.zoom_in()
|
||||
elif event.direction == Gdk.ScrollDirection.DOWN:
|
||||
self.zoom_out()
|
||||
|
||||
# ======================================================
|
||||
# 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):
|
||||
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())
|
||||
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))
|
||||
grabber = switch_grabber(self.grabber, x1, y1, x2, y2)
|
||||
self.selection = order_coordinates((x1, y1), (x2, y2))
|
||||
return grabber
|
||||
|
||||
# ======================================================
|
||||
# tooltips
|
||||
# ======================================================
|
||||
|
||||
def show_tooltip(self, widget, x, y, keyboard_mode, tooltip):
|
||||
if self.in_region:
|
||||
person = self.in_region.person
|
||||
if person:
|
||||
name = name_displayer.display(person)
|
||||
else:
|
||||
return False
|
||||
tooltip.set_text(name)
|
||||
return True
|
||||
else:
|
||||
return False
|
Loading…
Reference in New Issue
Block a user