2007-09-28 Tobias Gehrig <tobias@gehrignet.de>

* 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



svn: r9027
This commit is contained in:
Benny Malengier 2007-09-28 18:33:22 +00:00
parent faaaf7e858
commit d502801584
9 changed files with 427 additions and 17 deletions

View File

@ -1,3 +1,14 @@
2007-09-28 Tobias Gehrig <tobias@gehrignet.de>
* 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 <zfoldvar@users.sourceforge.net> 2007-09-28 Zsolt Foldvari <zfoldvar@users.sourceforge.net>
* src/docgen/CairoDoc.py (fontstyle_to_fontdescription): Fix deprecated * src/docgen/CairoDoc.py (fontstyle_to_fontdescription): Fix deprecated
warning. warning.

View File

@ -853,7 +853,8 @@ class PedigreeView(PageView.PersonNavView):
if obj: if obj:
mtype = obj.get_mime_type() mtype = obj.get_mime_type()
if mtype and mtype[0:5] == "image": 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: if cairo_available:
pw = PersonBoxWidget_cairo( self.format_helper, lst[i][0], lst[i][3], positions[i][0][3], image); pw = PersonBoxWidget_cairo( self.format_helper, lst[i][0], lst[i][3], positions[i][0][3], image);
else: else:

View File

@ -559,7 +559,8 @@ class RelationshipView(PageView.PersonNavView):
if image_list: if image_list:
mobj = self.dbstate.db.get_object_from_handle(image_list[0].ref) mobj = self.dbstate.db.get_object_from_handle(image_list[0].ref)
if mobj.get_mime_type()[0:5] == "image": 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 = gtk.Image()
image.set_from_pixbuf(pixbuf) image.set_from_pixbuf(pixbuf)
image.show() image.show()

View File

@ -191,7 +191,8 @@ class GalleryTab(ButtonTab):
handle = ref.get_reference_handle() handle = ref.get_reference_handle()
obj = self.dbstate.db.get_object_from_handle(handle) obj = self.dbstate.db.get_object_from_handle(handle)
pixbuf = ThumbNails.get_thumbnail_image(obj.get_path(), 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.iconmodel.append(row=[pixbuf, obj.get_description(), ref])
self._connect_icon_model() self._connect_icon_model()
self._set_label() self._set_label()

View File

@ -78,7 +78,7 @@ class EditMediaRef(EditReference):
def _setup_fields(self): def _setup_fields(self):
mtype = self.source.get_mime_type() mtype = self.source.get_mime_type()
self.mtype = mtype
self.pix = ThumbNails.get_thumbnail_image(self.source.get_path(),mtype) self.pix = ThumbNails.get_thumbnail_image(self.source.get_path(),mtype)
self.pixmap = self.top.get_widget("pixmap") self.pixmap = self.top.get_widget("pixmap")
ebox = self.top.get_widget('eventbox') ebox = self.top.get_widget('eventbox')
@ -88,6 +88,14 @@ class EditMediaRef(EditReference):
coord = self.source_ref.get_rectangle() 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: if coord and type(coord) == tuple:
self.top.get_widget("upperx").set_value(coord[0]) self.top.get_widget("upperx").set_value(coord[0])
self.top.get_widget("uppery").set_value(coord[1]) 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("lowerx").set_sensitive(False)
self.top.get_widget("lowery").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.descr_window = MonitoredEntry(
self.top.get_widget("description"), self.top.get_widget("description"),
self.source.set_description, self.source.set_description,
@ -133,6 +165,153 @@ class EditMediaRef(EditReference):
else: else:
self.top.get_widget("type").set_text("") 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): def build_menu_names(self, person):
""" """
Provides the information needed by the base class to define the Provides the information needed by the base class to define the

View File

@ -390,7 +390,7 @@ class EditPerson(EditPrimary):
Called when a media reference had been edited. This allows fot Called when a media reference had been edited. This allows fot
the updating image on the main form which has just been modified. 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) self.gallery_tab.edit_callback(ref, obj)
def _image_button_press(self, obj, event): def _image_button_press(self, obj, event):
@ -525,7 +525,7 @@ class EditPerson(EditPrimary):
return False return False
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""" """loads, scales, and displays the person's main photo from the path"""
self.load_obj = path self.load_obj = path
if path == None: if path == None:
@ -533,6 +533,21 @@ class EditPerson(EditPrimary):
else: else:
try: try:
i = gtk.gdk.pixbuf_new_from_file(path) 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())) ratio = float(max(i.get_height(), i.get_width()))
scale = float(100.0)/ratio scale = float(100.0)/ratio
x = int(scale*(i.get_width())) x = int(scale*(i.get_width()))
@ -696,7 +711,7 @@ class EditPerson(EditPrimary):
if self.load_obj != obj.get_path(): if self.load_obj != obj.get_path():
mime_type = obj.get_mime_type() mime_type = obj.get_mime_type()
if mime_type and mime_type.startswith("image"): if mime_type and mime_type.startswith("image"):
self.load_photo(obj.get_path()) self.load_photo(obj.get_path(), photo.get_rectangle())
else: else:
self.load_photo(None) self.load_photo(None)
else: else:

View File

@ -414,6 +414,122 @@ class MonitoredEntry:
if self.get_val(): if self.get_val():
self.obj.set_text(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: class MonitoredText:
def __init__(self, obj, set_val, get_val, read_only=False): def __init__(self, obj, set_val, get_val, read_only=False):

View File

@ -106,7 +106,7 @@ def __get_gconf_bool(key):
# __build_thumb_path # __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 Converts the specified path into a corresponding path for the thumbnail
image. We do this by converting the original path into an MD5SUM value 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 @type path: unicode
@param path: filename of the source file @param path: filename of the source file
@type rectangle: tuple
@param rectangle: subsection rectangle
@rtype: unicode @rtype: unicode
@returns: full path name to the corresponding thumbnail file. @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') return os.path.join(const.THUMB_DIR, md5_hash.hexdigest()+'.png')
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
@ -126,7 +131,7 @@ def __build_thumb_path(path):
# __create_thumbnail_image # __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, 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 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 @type src_file: unicode
@param mtype: mime type of the specified file (optional) @param mtype: mime type of the specified file (optional)
@type mtype: unicode @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/'): if mtype and not mtype.startswith('image/'):
# Not an image, so run the thumbnailer # 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) pixbuf = gtk.gdk.pixbuf_new_from_file(src_file)
width = pixbuf.get_width() width = pixbuf.get_width()
height = pixbuf.get_height() 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))) scale = const.THUMBSCALE / (float(max(width, height)))
scaled_width = int(width * scale) scaled_width = int(width * scale)
@ -214,7 +236,7 @@ def run_thumbnailer(mime_type, src_file, dest_file, size=const.THUMBSCALE):
# get_thumbnail_image # 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 Returns the thumbnail image (in GTK Pixbuf format) associated with the
source file passed to the function. If no thumbnail could be found, 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 @type src_file: unicode
@param mime_type: mime type of the source file @param mime_type: mime type of the source file
@type mime_type: unicode @type mime_type: unicode
@param rectangle: subsection rectangle
@type rectangle: tuple
@returns: thumbnail representing the source file @returns: thumbnail representing the source file
@rtype: gtk.gdk.Pixbuf @rtype: gtk.gdk.Pixbuf
""" """
try: try:
filename = get_thumbnail_path(src_file, mtype) filename = get_thumbnail_path(src_file, mtype, rectangle)
return gtk.gdk.pixbuf_new_from_file(filename) return gtk.gdk.pixbuf_new_from_file(filename)
except (gobject.GError, OSError): except (gobject.GError, OSError):
if mtype: if mtype:
@ -246,7 +270,7 @@ def get_thumbnail_image(src_file, mtype=None):
# get_thumbnail_path # 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 Returns the path to the thumbnail image associated with the
source file passed to the function. If the thumbnail does not exist, 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 @type src_file: unicode
@param mime_type: mime type of the source file @param mime_type: mime type of the source file
@type mime_type: unicode @type mime_type: unicode
@param rectangle: subsection rectangle
@type rectangle: tuple
@returns: thumbnail representing the source file @returns: thumbnail representing the source file
@rtype: gtk.gdk.Pixbuf @rtype: gtk.gdk.Pixbuf
""" """
filename = __build_thumb_path(src_file) filename = __build_thumb_path(src_file, rectangle)
if not os.path.isfile(src_file): if not os.path.isfile(src_file):
return os.path.join(const.IMAGE_DIR, "image-missing.png") return os.path.join(const.IMAGE_DIR, "image-missing.png")
else: else:
if not os.path.isfile(filename): 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): 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) return os.path.abspath(filename)

View File

@ -4870,6 +4870,66 @@
<property name="y_options"></property> <property name="y_options"></property>
</packing> </packing>
</child> </child>
<child>
<widget class="GtkFrame" id="frame9">
<property name="visible">True</property>
<property name="label_xalign">0</property>
<property name="label_yalign">0.5</property>
<property name="shadow_type">GTK_SHADOW_ETCHED_IN</property>
<child>
<widget class="GtkEventBox" id="eventbox1">
<property name="visible">True</property>
<property name="visible_window">True</property>
<property name="above_child">False</property>
<child>
<widget class="GtkImage" id="subpixmap">
<property name="width_request">100</property>
<property name="height_request">100</property>
<property name="visible">True</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
</widget>
</child>
</widget>
</child>
<child>
<widget class="GtkLabel" id="label707">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;b&gt;Preview&lt;/b&gt;</property>
<property name="use_underline">False</property>
<property name="use_markup">True</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="type">label_item</property>
</packing>
</child>
</widget>
<packing>
<property name="left_attach">0</property>
<property name="right_attach">1</property>
<property name="top_attach">2</property>
<property name="bottom_attach">4</property>
<property name="x_options">fill</property>
<property name="y_options">fill</property>
</packing>
</child>
</widget> </widget>
<packing> <packing>
<property name="tab_expand">False</property> <property name="tab_expand">False</property>