diff --git a/ChangeLog b/ChangeLog index 673108fad..389633efe 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,14 @@ +2007-09-28 Tobias Gehrig + * src/DataViews/_PedigreeView.py: use subsection thumbnails + * src/DataViews/_RelationView.py: use subsection thumbnails + * src/Editors/_EditPerson.py: use subsection thumbnails + * src/Editors/_EditMediaRef.py: added subsection thumbnail preview and signal handlers + to update subsection thumbnail on spinbutton changes + * src/DisplayTabs/_GalleryTab.py: use subsection thumbnails + * src/ThumbNails.py: added rectangle parameter to thumbnail functions to specify the subsection + * src/GrampsWidgets.py: added class MonitoredSpinButton + * src/glade/gramps.glade: added preview pixmap of subsection thumbnail to change_description + 2007-09-28 Zsolt Foldvari * src/docgen/CairoDoc.py (fontstyle_to_fontdescription): Fix deprecated warning. diff --git a/src/DataViews/_PedigreeView.py b/src/DataViews/_PedigreeView.py index 93059e162..3797b8875 100644 --- a/src/DataViews/_PedigreeView.py +++ b/src/DataViews/_PedigreeView.py @@ -853,7 +853,8 @@ class PedigreeView(PageView.PersonNavView): if obj: mtype = obj.get_mime_type() if mtype and mtype[0:5] == "image": - image = ThumbNails.get_thumbnail_path(obj.get_path()) + image = ThumbNails.get_thumbnail_path(obj.get_path(), + rectangle=ph.get_rectangle()) if cairo_available: pw = PersonBoxWidget_cairo( self.format_helper, lst[i][0], lst[i][3], positions[i][0][3], image); else: diff --git a/src/DataViews/_RelationView.py b/src/DataViews/_RelationView.py index 9ab94beae..fe3a64da3 100644 --- a/src/DataViews/_RelationView.py +++ b/src/DataViews/_RelationView.py @@ -559,7 +559,8 @@ class RelationshipView(PageView.PersonNavView): if image_list: mobj = self.dbstate.db.get_object_from_handle(image_list[0].ref) if mobj.get_mime_type()[0:5] == "image": - pixbuf = ThumbNails.get_thumbnail_image(mobj.get_path()) + pixbuf = ThumbNails.get_thumbnail_image(mobj.get_path(), + rectangle=image_list[0].get_rectangle()) image = gtk.Image() image.set_from_pixbuf(pixbuf) image.show() diff --git a/src/DisplayTabs/_GalleryTab.py b/src/DisplayTabs/_GalleryTab.py index 1d210704d..1114736fb 100644 --- a/src/DisplayTabs/_GalleryTab.py +++ b/src/DisplayTabs/_GalleryTab.py @@ -191,7 +191,8 @@ class GalleryTab(ButtonTab): handle = ref.get_reference_handle() obj = self.dbstate.db.get_object_from_handle(handle) pixbuf = ThumbNails.get_thumbnail_image(obj.get_path(), - obj.get_mime_type()) + obj.get_mime_type(), + ref.get_rectangle()) self.iconmodel.append(row=[pixbuf, obj.get_description(), ref]) self._connect_icon_model() self._set_label() diff --git a/src/Editors/_EditMediaRef.py b/src/Editors/_EditMediaRef.py index 6f6c06f2a..4c9053b51 100644 --- a/src/Editors/_EditMediaRef.py +++ b/src/Editors/_EditMediaRef.py @@ -78,7 +78,7 @@ class EditMediaRef(EditReference): def _setup_fields(self): mtype = self.source.get_mime_type() - + self.mtype = mtype self.pix = ThumbNails.get_thumbnail_image(self.source.get_path(),mtype) self.pixmap = self.top.get_widget("pixmap") ebox = self.top.get_widget('eventbox') @@ -88,6 +88,14 @@ class EditMediaRef(EditReference): coord = self.source_ref.get_rectangle() + self.rectangle = coord + + self.subpixmap = self.top.get_widget("subpixmap") + self.subpix = ThumbNails.get_thumbnail_image(self.source.get_path(), + mtype, + coord) + self.subpixmap.set_from_pixbuf(self.subpix) + if coord and type(coord) == tuple: self.top.get_widget("upperx").set_value(coord[0]) self.top.get_widget("uppery").set_value(coord[1]) @@ -99,6 +107,30 @@ class EditMediaRef(EditReference): self.top.get_widget("lowerx").set_sensitive(False) self.top.get_widget("lowery").set_sensitive(False) + self.upperx_spinbutton = MonitoredSpinButton( + self.top.get_widget("upperx"), + self.set_upperx, + self.get_upperx, + self.db.readonly) + + self.uppery_spinbutton = MonitoredSpinButton( + self.top.get_widget("uppery"), + self.set_uppery, + self.get_uppery, + self.db.readonly) + + self.lowerx_spinbutton = MonitoredSpinButton( + self.top.get_widget("lowerx"), + self.set_lowerx, + self.get_lowerx, + self.db.readonly) + + self.lowery_spinbutton = MonitoredSpinButton( + self.top.get_widget("lowery"), + self.set_lowery, + self.get_lowery, + self.db.readonly) + self.descr_window = MonitoredEntry( self.top.get_widget("description"), self.source.set_description, @@ -133,6 +165,153 @@ class EditMediaRef(EditReference): else: self.top.get_widget("type").set_text("") + def set_upperx(self, value): + """ + Callback for the signal handling of the spinbutton for the left x coordinate of the subsection. + Updates the subsection thumbnail using the given value for the left x coordinate. + + @param value: the left x coordinate of the subsection + """ + + if self.rectangle == None: + self.rectangle = (0,0,0,0) + self.rectangle = (value, + self.rectangle[1], + self.rectangle[2], + self.rectangle[3]) + self.update_subpixmap() + + def set_uppery(self, value): + """ + Callback for the signal handling of the spinbutton for the upper y coordinate of the subsection. + Updates the subsection thumbnail using the given value for the upper y coordinate. + + @param value: the upper y coordinate of the subsection + """ + + if self.rectangle == None: + self.rectangle = (0,0,0,0) + self.rectangle = (self.rectangle[0], + value, + self.rectangle[2], + self.rectangle[3]) + self.update_subpixmap() + + def set_lowerx(self, value): + """ + Callback for the signal handling of the spinbutton for the right x coordinate of the subsection. + Updates the subsection thumbnail using the given value for the right x coordinate. + + @param value: the right x coordinate of the subsection + """ + + if self.rectangle == None: + self.rectangle = (0,0,0,0) + self.rectangle = (self.rectangle[0], + self.rectangle[1], + value, + self.rectangle[3]) + self.update_subpixmap() + + def set_lowery(self, value): + """ + Callback for the signal handling of the spinbutton for the lower y coordinate of the subsection. + Updates the subsection thumbnail using the given value for the lower y coordinate. + + @param value: the lower y coordinate of the subsection + """ + + if self.rectangle == None: + self.rectangle = (0,0,0,0) + self.rectangle = (self.rectangle[0], + self.rectangle[1], + self.rectangle[2], + value) + self.update_subpixmap() + + def get_upperx(self): + """ + Callback for the signal handling of the spinbutton for the left x coordinate of the subsection. + + @returns: the left x coordinate of the subsection or 0 if there is no selection + """ + + if self.rectangle != None: + return self.rectangle[0] + else: + return 0 + + def get_uppery(self): + """ + Callback for the signal handling of the spinbutton for the uppper y coordinate of the subsection. + + @returns: the upper y coordinate of the subsection or 0 if there is no selection + """ + + if self.rectangle != None: + return self.rectangle[1] + else: + return 0 + + def get_lowerx(self): + """ + Callback for the signal handling of the spinbutton for the right x coordinate of the subsection. + + @returns: the right x coordinate of the subsection or 0 if there is no selection + """ + + if self.rectangle != None: + return self.rectangle[2] + else: + return 0 + + def get_lowery(self): + """ + Callback for the signal handling of the spinbutton for the lower y coordinate of the subsection. + + @returns: the lower y coordinate of the subsection or 0 if there is no selection + """ + + if self.rectangle != None: + return self.rectangle[3] + else: + return 0 + + def update_subpixmap(self): + """ + Updates the thumbnail of the specified subsection + """ + + path = self.source.get_path() + if path == None: + self.subpixmap.hide() + else: + try: + pixbuf = gtk.gdk.pixbuf_new_from_file(path) + width = pixbuf.get_width() + height = pixbuf.get_height() + upper_x = min(self.rectangle[0], self.rectangle[2])/100. + lower_x = max(self.rectangle[0], self.rectangle[2])/100. + upper_y = min(self.rectangle[1], self.rectangle[3])/100. + lower_y = max(self.rectangle[1], self.rectangle[3])/100. + sub_x = int(upper_x * width) + sub_y = int(upper_y * height) + sub_width = int((lower_x - upper_x) * width) + sub_height = int((lower_y - upper_y) * height) + if sub_width > 0 and sub_height > 0: + pixbuf = pixbuf.subpixbuf(sub_x, sub_y, sub_width, sub_height) + width = sub_width + height = sub_height + ratio = float(max(height, width)) + scale = const.THUMBSCALE / ratio + x = int(scale * width) + y = int(scale * height) + pixbuf = pixbuf.scale_simple(x, y, gtk.gdk.INTERP_BILINEAR) + self.subpixmap.set_from_pixbuf(pixbuf) + self.subpixmap.show() + except: + self.subpixmap.hide() + def build_menu_names(self, person): """ Provides the information needed by the base class to define the diff --git a/src/Editors/_EditPerson.py b/src/Editors/_EditPerson.py index cf4ee0fde..e95c978a6 100644 --- a/src/Editors/_EditPerson.py +++ b/src/Editors/_EditPerson.py @@ -390,7 +390,7 @@ class EditPerson(EditPrimary): Called when a media reference had been edited. This allows fot the updating image on the main form which has just been modified. """ - self.load_photo(obj.get_path()) + self.load_photo(obj.get_path(), ref.get_rectangle()) self.gallery_tab.edit_callback(ref, obj) def _image_button_press(self, obj, event): @@ -525,7 +525,7 @@ class EditPerson(EditPrimary): return False return False - def load_photo(self, path): + def load_photo(self, path, rectangle=None): """loads, scales, and displays the person's main photo from the path""" self.load_obj = path if path == None: @@ -533,6 +533,21 @@ class EditPerson(EditPrimary): else: try: i = gtk.gdk.pixbuf_new_from_file(path) + width = i.get_width() + height = i.get_height() + + if rectangle != None: + upper_x = min(rectangle[0], rectangle[2])/100. + lower_x = max(rectangle[0], rectangle[2])/100. + upper_y = min(rectangle[1], rectangle[3])/100. + lower_y = max(rectangle[1], rectangle[3])/100. + sub_x = int(upper_x * width) + sub_y = int(upper_y * height) + sub_width = int((lower_x - upper_x) * width) + sub_height = int((lower_y - upper_y) * height) + if sub_width > 0 and sub_height > 0: + i = i.subpixbuf(sub_x, sub_y, sub_width, sub_height) + ratio = float(max(i.get_height(), i.get_width())) scale = float(100.0)/ratio x = int(scale*(i.get_width())) @@ -696,7 +711,7 @@ class EditPerson(EditPrimary): if self.load_obj != obj.get_path(): mime_type = obj.get_mime_type() if mime_type and mime_type.startswith("image"): - self.load_photo(obj.get_path()) + self.load_photo(obj.get_path(), photo.get_rectangle()) else: self.load_photo(None) else: diff --git a/src/GrampsWidgets.py b/src/GrampsWidgets.py index 335e6a9a7..a65d2b2e5 100644 --- a/src/GrampsWidgets.py +++ b/src/GrampsWidgets.py @@ -414,6 +414,122 @@ class MonitoredEntry: if self.get_val(): self.obj.set_text(self.get_val()) +class MonitoredSpinButton: + """ + Class for signal handling of spinbuttons. + (Code is a modified copy of MonitoredEntry) + """ + + def __init__(self, obj, set_val, get_val, read_only=False, + autolist=None, changed=None): + """ + @param obj: widget to be monitored + @type obj: gtk.SpinButton + @param set_val: callback to be called when obj is changed + @param get_val: callback to be called to retrieve value for obj + @param read_only: If SpinButton is read only. + """ + + self.obj = obj + self.set_val = set_val + self.get_val = get_val + self.changed = changed + + if get_val(): + self.obj.set_value(get_val()) + self.obj.connect('changed', self._on_change) + self.obj.set_editable(not read_only) + + if autolist: + AutoComp.fill_entry(obj,autolist) + + def reinit(self, set_val, get_val): + """ + Reinitialize class with the specified callback functions. + + @param set_val: callback to be called when SpinButton is changed + @param get_val: callback to be called to retrieve value for SpinButton + """ + + self.set_val = set_val + self.get_val = get_val + self.update() + + def set_value(self, value): + """ + Sets the value of the monitored widget to the specified value. + + @param value: Value to be set. + """ + + self.obj.set_value(value) + + def connect(self, signal, callback): + """ + Connect the signal of monitored widget to the specified callback. + + @param signal: Signal prototype for which a connection should be set up. + @param callback: Callback function to be called when signal is emitted. + """ + + self.obj.connect(signal, callback) + + def _on_change(self, obj): + """ + Event handler to be called when the monitored widget is changed. + + @param obj: Widget that has been changed. + @type obj: gtk.SpinButton + """ + + self.set_val(obj.get_value()) + if self.changed: + self.changed(obj) + + def force_value(self, value): + """ + Sets the value of the monitored widget to the specified value. + + @param value: Value to be set. + """ + + self.obj.set_value(value) + + def get_value(self): + """ + Gets the current value of the monitored widget. + + @returns: Current value of monitored widget. + """ + + return self.obj.get_value() + + def enable(self, value): + """ + Changes the property editable and sensitive of the monitored widget to value. + + @param value: If widget should be editable or deactivated. + @type value: bool + """ + + self.obj.set_sensitive(value) + self.obj.set_editable(value) + + def grab_focus(self): + """ + Assign the keyboard focus to the monitored widget. + """ + + self.obj.grab_focus() + + def update(self): + """ + Updates value of monitored SpinButton with the value returned by the get_val callback. + """ + + if self.get_val(): + self.obj.set_value(self.get_val()) + class MonitoredText: def __init__(self, obj, set_val, get_val, read_only=False): diff --git a/src/ThumbNails.py b/src/ThumbNails.py index 140d21542..5f00bdf70 100644 --- a/src/ThumbNails.py +++ b/src/ThumbNails.py @@ -106,7 +106,7 @@ def __get_gconf_bool(key): # __build_thumb_path # #------------------------------------------------------------------------- -def __build_thumb_path(path): +def __build_thumb_path(path, rectangle=None): """ Converts the specified path into a corresponding path for the thumbnail image. We do this by converting the original path into an MD5SUM value @@ -115,10 +115,15 @@ def __build_thumb_path(path): @type path: unicode @param path: filename of the source file + @type rectangle: tuple + @param rectangle: subsection rectangle @rtype: unicode @returns: full path name to the corresponding thumbnail file. """ - md5_hash = md5.md5(path) + extra = "" + if rectangle != None: + extra = "?" + str(rectangle) + md5_hash = md5.md5(path+extra) return os.path.join(const.THUMB_DIR, md5_hash.hexdigest()+'.png') #------------------------------------------------------------------------- @@ -126,7 +131,7 @@ def __build_thumb_path(path): # __create_thumbnail_image # #------------------------------------------------------------------------- -def __create_thumbnail_image(src_file, mtype=None): +def __create_thumbnail_image(src_file, mtype=None, rectangle=None): """ Generates the thumbnail image for a file. If the mime type is specified, and is not an 'image', then we attempt to find and run a thumbnailer @@ -137,8 +142,10 @@ def __create_thumbnail_image(src_file, mtype=None): @type src_file: unicode @param mtype: mime type of the specified file (optional) @type mtype: unicode + @param rectangle: subsection rectangle + @type rectangle: tuple """ - filename = __build_thumb_path(src_file) + filename = __build_thumb_path(src_file, rectangle) if mtype and not mtype.startswith('image/'): # Not an image, so run the thumbnailer @@ -150,6 +157,21 @@ def __create_thumbnail_image(src_file, mtype=None): pixbuf = gtk.gdk.pixbuf_new_from_file(src_file) width = pixbuf.get_width() height = pixbuf.get_height() + + if rectangle != None: + upper_x = min(rectangle[0], rectangle[2])/100. + lower_x = max(rectangle[0], rectangle[2])/100. + upper_y = min(rectangle[1], rectangle[3])/100. + lower_y = max(rectangle[1], rectangle[3])/100. + sub_x = int(upper_x * width) + sub_y = int(upper_y * height) + sub_width = int((lower_x - upper_x) * width) + sub_height = int((lower_y - upper_y) * height) + if sub_width > 0 and sub_height > 0: + pixbuf = pixbuf.subpixbuf(sub_x, sub_y, sub_width, sub_height) + width = sub_width + height = sub_height + scale = const.THUMBSCALE / (float(max(width, height))) scaled_width = int(width * scale) @@ -214,7 +236,7 @@ def run_thumbnailer(mime_type, src_file, dest_file, size=const.THUMBSCALE): # get_thumbnail_image # #------------------------------------------------------------------------- -def get_thumbnail_image(src_file, mtype=None): +def get_thumbnail_image(src_file, mtype=None, rectangle=None): """ Returns the thumbnail image (in GTK Pixbuf format) associated with the source file passed to the function. If no thumbnail could be found, @@ -228,11 +250,13 @@ def get_thumbnail_image(src_file, mtype=None): @type src_file: unicode @param mime_type: mime type of the source file @type mime_type: unicode + @param rectangle: subsection rectangle + @type rectangle: tuple @returns: thumbnail representing the source file @rtype: gtk.gdk.Pixbuf """ try: - filename = get_thumbnail_path(src_file, mtype) + filename = get_thumbnail_path(src_file, mtype, rectangle) return gtk.gdk.pixbuf_new_from_file(filename) except (gobject.GError, OSError): if mtype: @@ -246,7 +270,7 @@ def get_thumbnail_image(src_file, mtype=None): # get_thumbnail_path # #------------------------------------------------------------------------- -def get_thumbnail_path(src_file, mtype=None): +def get_thumbnail_path(src_file, mtype=None, rectangle=None): """ Returns the path to the thumbnail image associated with the source file passed to the function. If the thumbnail does not exist, @@ -256,16 +280,18 @@ def get_thumbnail_path(src_file, mtype=None): @type src_file: unicode @param mime_type: mime type of the source file @type mime_type: unicode + @param rectangle: subsection rectangle + @type rectangle: tuple @returns: thumbnail representing the source file @rtype: gtk.gdk.Pixbuf """ - filename = __build_thumb_path(src_file) + filename = __build_thumb_path(src_file, rectangle) if not os.path.isfile(src_file): return os.path.join(const.IMAGE_DIR, "image-missing.png") else: if not os.path.isfile(filename): - __create_thumbnail_image(src_file, mtype) + __create_thumbnail_image(src_file, mtype, rectangle) elif os.path.getmtime(src_file) > os.path.getmtime(filename): - __create_thumbnail_image(src_file, mtype) + __create_thumbnail_image(src_file, mtype, rectangle) return os.path.abspath(filename) diff --git a/src/glade/gramps.glade b/src/glade/gramps.glade index d078b9680..11112e870 100644 --- a/src/glade/gramps.glade +++ b/src/glade/gramps.glade @@ -4870,6 +4870,66 @@ + + + + True + 0 + 0.5 + GTK_SHADOW_ETCHED_IN + + + + True + True + False + + + + 100 + 100 + True + 0.5 + 0.5 + 0 + 0 + + + + + + + + True + <b>Preview</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + label_item + + + + + 0 + 1 + 2 + 4 + fill + fill + + False