diff --git a/gramps2/ChangeLog b/gramps2/ChangeLog index 4105816b1..643d95b46 100644 --- a/gramps2/ChangeLog +++ b/gramps2/ChangeLog @@ -1,3 +1,11 @@ +2005-03-15 Richard Taylor + * src/Makefile.am : Added TreeTip.py to list of installed modules. + * src/TreeTip.py: New module that implements tooltips on TreeView rows. + * src/plugins/Makefile.am: Added ScratchPad.py and scratchpad.glade. + * src/plugins/ScratchPad.py: New module to implement a scratch pad for + temporary storage of drag and drop objects. + * src/plugins/scratchpad.glade: New glade file for ScratchPad window. + 2005-03-15 Martin Hawlisch * src/GenericFilter.py (FullTextSearch): Added new filter thad provides a full-text search thru (nearly) all attributes and linked objexts of a person. diff --git a/gramps2/src/Makefile.am b/gramps2/src/Makefile.am index 9b68176a7..30d606538 100644 --- a/gramps2/src/Makefile.am +++ b/gramps2/src/Makefile.am @@ -101,6 +101,7 @@ gdir_PYTHON = \ TarFile.py\ TipOfDay.py\ TransTable.py\ + TreeTips.py\ UrlEdit.py\ Utils.py\ Witness.py\ diff --git a/gramps2/src/TreeTips.py b/gramps2/src/TreeTips.py new file mode 100644 index 000000000..1566f5f53 --- /dev/null +++ b/gramps2/src/TreeTips.py @@ -0,0 +1,248 @@ +# File: treetips.py +# Author: Toshio Kuratomi +# Date: 6 April, 2004 +# Copyright: Toshio Kuratomi +# License: GPL +# Id: $Id$ +"""A tooltip class for TreeViews +""" +__revision__ = "$Rev$" + + +# +# Support for text markup added: March 05 - rjt-gramps thegrindstone.me.uk +# + +import gtk +import gobject + +class TreeTips(gtk.Widget): + ''' A tooltips widget specialized to work with gtk.TreeView's. + + TreeTips associates a column in a TreeStore with tooltips that will be + displayed when the mouse is over the row the column is for. Each row can + have one treetip. + ''' + __gproperties__ = { + 'tip_window' : (gobject.TYPE_PYOBJECT, + 'The window that the tooltip is displayed in.', + 'The window that the tooltip is displayed in.', + gobject.PARAM_READABLE), + 'tip_label' : (gobject.TYPE_PYOBJECT, + 'The label that displays the tooltip text.', + 'The label that displays the tooltip text.', + gobject.PARAM_READABLE), + 'active_tips_data' : (gobject.TYPE_PYOBJECT, + 'The data associated with the active tooltip.', + 'The data associated with the active tooltip.', + gobject.PARAM_READABLE), + 'delay' : (gobject.TYPE_INT, + 'MSecs before displaying the tooltip.', + 'The delay between the mouse pausing over the widget and the display of the tooltip in msec.', + 0, 60000, 500, + gobject.PARAM_READWRITE), + 'enabled' : (gobject.TYPE_BOOLEAN, + 'If TRUE the tooltips are enabled', + 'If TRUE the tooltips are enabled', + True, + gobject.PARAM_READABLE), + 'view' : (gobject.TYPE_PYOBJECT, + 'gtk.TreeView that we get our data from.', + 'The tip data comes from a column in a gtk.TreeView.', + gobject.PARAM_READWRITE), + 'column' : (gobject.TYPE_INT, + 'Column from the gtk.TreeView that holds tip data.', + 'The tip data for each row is held by a column in the row. This specifies which column that data is in.', + 0, 32000, 0, + gobject.PARAM_READWRITE), + 'markup_enabled' : (gobject.TYPE_BOOLEAN, + 'If TRUE the tooltips are in Pango Markup', + 'If TRUE the tooltips are in Pango Markup', + False, + gobject.PARAM_READWRITE), + + } + + def __init__(self, treeview=None, column=None, markup_enabled=False): + '''Create a new TreeTips Group. + + :Parameters: + treeview : gtk.TreeView === Treeview for which the tips display, + default is None. + column : integer === Column id in the Treemodel holding the treetip + text, default is None. + markup_enabled : bool === If True the tooltips are in Pango Markup, + if False the tooltips are in plain text. + ''' + if treeview: + try: + treeview.connect('leave-notify-event', self.__tree_leave_notify) + treeview.connect('motion-notify-event', self.__tree_motion_notify) + except (AttributeError, TypeError): + raise TypeError, ('The value of view must be an object that' + 'implements leave-notify-event and motion-notify-event ' + 'gsignals such as gtk.TreeStore.') + + gobject.GObject.__init__(self) + + self.view = treeview or None + self.delay = 500 + self.enabled = True + self.column = column or 0 + self.markup_enabled = markup_enabled + self.tip_window = gtk.Window(gtk.WINDOW_POPUP) + self.tip_window.set_app_paintable(True) + self.tip_window.set_border_width(4) + self.tip_window.connect('expose-event', self.__paint_window) + self.tip_label = gtk.Label('') + self.tip_label.set_line_wrap(True) + self.tip_label.set_alignment(0.5, 0.5) + self.active_tips_data = '' + self.tip_window.add(self.tip_label) + self.unique = 1 # Unique number used for timeouts + self.timeoutID = 0 + self.path = None + self.screenWidth = gtk.gdk.screen_width() + self.screenHeight = gtk.gdk.screen_height() + + def enable(self): + '''Enable showing of tooltips''' + self.enabled = True + + def disable(self): + '''Disable showing tooltips''' + self.enabled = False + + def do_get_property(self, prop): + '''Return the gproperty's value.''' + if prop.name == 'delay': + return self.delay + elif prop.name == 'enabled': + return self.enabled + elif prop.name == 'view': + return self.view + elif prop.name == 'column': + return self.column + elif prop.name == 'active-tips-data': + return self.active_tips_data + elif prop.name == 'tip-label': + return self.tip_label + elif prop.name == 'tip-window': + return self.tip_window + elif prop.name == 'markup_enabled': + return self.markup_enabled + else: + raise AttributeError, 'unknown property %s' % prop.name + + def do_set_property(self, prop, value): + '''Set the property of writable properties. + + ''' + if prop.name == 'delay': + self.delay = value + elif prop.name == 'view': + try: + value.connect('leave-notify-event', self.__tree_leave_notify) + value.connect('motion-notify-event', self.__tree_motion_notify) + except (AttributeError, TypeError): + raise TypeError, ('The value of view must be an object that' + 'implements leave-notify-event and motion-notify-event ' + 'gsignals') + self.view = value + elif prop.name == 'column': + self.column = value + elif prop.name == 'markup_enabled': + self.markup_enabled = value + else: + raise AttributeError, 'unknown or read only property %s' % prop.name + + def __paint_window(self, window, event): + window.style.paint_flat_box(window.window, gtk.STATE_NORMAL, + gtk.SHADOW_OUT, None, window, + 'tooltip', 0, 0, -1, -1) + + def __tree_leave_notify(self, tree, event): + '''Hide tooltips when we leave the tree.''' + + self.timeoutID = 0 + self.path = None + self.tip_window.hide() + + def __tree_motion_notify(self, tree, event): + '''Decide which tooltip to display when we move within the tree.''' + + if not self.enabled: + return + self.tip_window.hide() + self.path = None + self.unique += 1 + self.timeoutID = self.unique + gobject.timeout_add(self.delay, self.__treetip_show, tree, + int(event.x), int(event.y), self.timeoutID) + + def __treetip_show(self, tree, xEvent, yEvent, ID): + '''Show the treetip window.''' + if self.timeoutID != ID: + return False + pathReturn = tree.get_path_at_pos(xEvent, yEvent) + model = tree.get_model() + if pathReturn == None: + self.path = None + elif self.path != pathReturn[0]: + self.path = pathReturn[0] + rowIter = model.get_iter(self.path) + text = model.get_value(rowIter, self.column) + self.active_tips_data = text + if not text: + if self.markup_enabled: + self.tip_label.set_markup('') + else: + self.tip_label.set_text('') + return False + + if self.markup_enabled: + self.tip_label.set_markup(text) + else: + self.tip_label.set_text(text) + x, y = self.tip_label.size_request() + self.tip_window.resize(x, y) + windowWidth, windowHeight = self.tip_window.get_size() + cellInfo = tree.get_cell_area(self.path, pathReturn[1]) + x, y = self.__compute_tooltip_position(cellInfo, windowWidth, windowHeight) + self.tip_window.move(int(x), int(y)) + self.tip_window.show_all() + + return False + + def __compute_tooltip_position(self, cellInfo, popupWidth, popupHeight): + '''Figures out where the tooltip should be placed on the page:: + + [p] = pointer + x = [p] + +---------+ + (half on each side) + + y = [p] + +------------+ + |____________| + If it fits else: + +------------+ + |____________| + [p] + ''' + + xOrigin, yOrigin = self.view.get_bin_window().get_origin() + x = xOrigin + cellInfo.x + cellInfo.width/2 - popupWidth/2 + if x < 0: + x = 0 + elif x + popupWidth > self.screenWidth: + x = self.screenWidth - popupWidth + + y = yOrigin + cellInfo.y + cellInfo.height + 3 + if y + popupHeight > self.screenHeight: + y = yOrigin + cellInfo.y - 3 - popupHeight + if y < 0: + y = 0 + + return x, y +gobject.type_register(TreeTips) diff --git a/gramps2/src/plugins/Makefile.am b/gramps2/src/plugins/Makefile.am index e5a332bc5..a4d7ee0bf 100644 --- a/gramps2/src/plugins/Makefile.am +++ b/gramps2/src/plugins/Makefile.am @@ -35,6 +35,7 @@ pkgdata_PYTHON = \ ReadPkg.py\ RelCalc.py\ ReorderIds.py\ + ScratchPad.py\ SoundGen.py\ StatisticsChart.py\ Summary.py\ @@ -80,7 +81,8 @@ GLADEFILES = \ leak.glade\ book.glade\ writeftree.glade\ - genewebexport.glade + genewebexport.glade\ + scratchpad.glade dist_pkgdata_DATA = $(GLADEFILES) diff --git a/gramps2/src/plugins/ScratchPad.py b/gramps2/src/plugins/ScratchPad.py new file mode 100644 index 000000000..0faf467a3 --- /dev/null +++ b/gramps2/src/plugins/ScratchPad.py @@ -0,0 +1,489 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2004 Donald N. Allingham +# +# This program is free software; you can redistribute it and/or modiy +# 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$ + +import pickle +import os +from xml.sax.saxutils import escape + +#------------------------------------------------------------------------- +# +# GTK/Gnome modules +# +#------------------------------------------------------------------------- +import gtk +import gtk.gdk +import gtk.glade + +#------------------------------------------------------------------------- +# +# gramps modules +# +#------------------------------------------------------------------------- + +import const +import Utils +import ListModel +import TreeTips + +from gettext import gettext as _ + +from gtk.gdk import ACTION_COPY, BUTTON1_MASK + +#------------------------------------------------------------------------- +# +# Globals +# +#------------------------------------------------------------------------- + +text_targets = ['text/plain', + 'TEXT', + 'STRING', + 'COMPOUND_TEXT', + 'UTF8_STRING'] + +gramps_targets = ['url', + 'pevent', + 'pattr', + 'paddr', + 'srcref'] + +pycode_tgts = [('url', 0, 0), + ('pevent', 0, 1), + ('pattr', 0, 2), + ('paddr', 0, 3), + ('srcref', 0, 4), + ('text/plain',0,0), + ('TEXT', 0, 1), + ('STRING', 0, 2), + ('COMPOUND_TEXT', 0, 3), + ('UTF8_STRING', 0, 4)] + +TEXT_TARGET = 'TEXT' + +target_map = {'url':('url', 0, 0), + 'pevent': ('pevent', 0, 1), + 'pattr': ('pattr', 0, 2), + 'paddr': ('paddr', 0, 3), + 'srcref': ('srcref', 0, 4), + TEXT_TARGET: ('TEXT', 0, 1)} + + + +#------------------------------------------------------------------------- +# +# ScatchPadWindow class +# +#------------------------------------------------------------------------- +class ScratchPadWindow: + """ + The ScratchPad provides a temporary area to hold objects that can + be reused accross multiple Person records. The pad provides a window + onto which objects can be dropped and then dragged into new Person + dialogs. The objects are stored as the pickles that are built by the + origininating widget. The objects are only unpickled in order to + provide the text in the display. + + No attempt is made to ensure that any references contained within + the pickles are valid. Because the pad extends the life time of drag + and drop objects, it is possible that references that were valid + when an object is copied to the pad are invalid by the time they + are dragged to a new Person. For this reason, using the pad places + a responsibility on all '_drag_data_received' methods to check the + references of objects before attempting to use them. + """ + + def __init__(self,database,parent): + """Initializes the ScratchPad class, and displays the window""" + + self.db = database + self.parent = parent + self.win_key = self + + self.olist = [] + self.otitles = [(_('Type'),-1,150), + (_('Title'),-1,150), + (_('Value'),-1,150), + ('',-1,0)] # To hold the tooltip text + + base = os.path.dirname(__file__) + self.glade_file = "%s/%s" % (base,"scratchpad.glade") + + self.top = gtk.glade.XML(self.glade_file,"scratchPad","gramps") + self.window = self.top.get_widget("scratchPad") + self.window.set_icon(self.parent.topWindow.get_icon()) + + self.object_list = self.top.get_widget('objectlist') + + self.otree = ListModel.ListModel(self.object_list,self.otitles, + self.on_object_select_row, + self.on_update_object_clicked) + + self.treetips = TreeTips.TreeTips(self.object_list,3,True) + + self.top.signal_autoconnect({ + "on_close_scratchpad" : self.on_close_scratchpad, + "on_clear_clicked": self.on_clear_clicked, + "on_clear_all_clicked": self.on_clear_all_clicked, + "on_objectlist_delete_event": self.on_delete_event, + "on_scratchPad_delete_event": self.on_delete_event + }) + + self.object_list.drag_dest_set(gtk.DEST_DEFAULT_ALL, pycode_tgts, + ACTION_COPY) + + self.object_list.connect('drag_data_get', self.object_drag_data_get) + self.object_list.connect('drag_begin', self.object_drag_begin) + self.object_list.connect('drag_data_received', + self.object_drag_data_received) + + self.add_itself_to_menu() + self.window.show() + + + def on_delete_event(self,obj,b): + self.remove_itself_from_menu() + + def add_itself_to_menu(self): + """Add the ScratchPad window to the list of windows in the + main GRAMPS interface. If this is the first instance to be + created a submenu is created, if it is not the first instance + then an entry is created in the existing sub menu.""" + + sub_menu_label = _("Scratch Pad Tool") + instance_number_key = "scratch_pad_instance_number" + + self.parent.child_windows[self.win_key] = self + + # First check to see if the Scratch Pad sub menu already exists. + # The MenuItems contain a list of child widgets, the first one + # should be the AccelLabel so we can check the label text on + # that one. + sub_menu_list = [ menu for menu in self.parent.winsmenu.get_children() if \ + menu.get_children()[0].get_label() == sub_menu_label ] + + if len(sub_menu_list) > 0: + # This list should always be of length 0 or 1 but in the unlikely + # situation that it is greater than 1 it will still be safe to use + # the first. + self.parent_menu_item = sub_menu_list[0] + else: + # There is no existing instances so we must create the submenu. + self.parent_menu_item = gtk.MenuItem(sub_menu_label) + self.parent_menu_item.set_submenu(gtk.Menu()) + self.parent_menu_item.show() + self.parent.winsmenu.append(self.parent_menu_item) + + # Get a handle to the submenu and remember it so that + # remove_itself_from_menu can delete it later. + self.winsmenu = self.parent_menu_item.get_submenu() + + # Get the first available instance number. The instance number + # is stored in the data item store of the menu item so we can + # read it with get_data. + num = 1 + existing_instances = [ menu_item.get_data(instance_number_key) \ + for menu_item in self.winsmenu.get_children() ] + + if len(existing_instances) > 0: + # Calculate the first available instance number. + existing_instances.sort() + for instance_num in existing_instances: + if instance_num != num: + break + else: + num += 1 + + # Create the instance menuitem with the instance number in the + # label. + instance_title = _('Scratch Pad - %d') % (num,) + self.menu_item = gtk.MenuItem(instance_title) + self.menu_item.set_data(instance_number_key,num) + self.menu_item.connect("activate",self.present) + self.menu_item.show() + + # Set the window title to the same as the menu label. + self.window.set_title(instance_title) + + # Add the item to the submenu. + self.winsmenu.append(self.menu_item) + + def remove_itself_from_menu(self): + """Remove the instance of the pad from the Window menu in the + main GRAMPS window. If this is the last pad then remove the + ScratchPad sub menu as well.""" + + del self.parent.child_windows[self.win_key] + self.menu_item.destroy() + if len(self.winsmenu.get_children()) == 0: + self.winsmenu.destroy() + self.parent_menu_item.destroy() + + def present(self,obj): + self.window.present() + + + def on_close_scratchpad(self,obj): + self.remove_itself_from_menu() + self.window.destroy() + + def on_clear_clicked(self,obj): + """Deletes the selected object from the object list""" + store,node = self.otree.get_selected() + if node: + self.olist.remove(self.otree.get_object(node)) + self.redraw_object_list() + + def on_clear_all_clicked(self,obj): + self.olist = [] + self.redraw_object_list() + + def on_object_select_row(self,obj): + global target_map + global TEXT_TARGET + + o = self.otree.get_selected_objects() + + if len(o): + bits_per = 8; # we're going to pass a string + + obj_targets = o[0]['targets'] + + # union with gramps_types + if len([target for target \ + in obj_targets if target in gramps_targets]) > 0: + + exec 'data = %s' % o[0]['data'] + exec 'mytype = "%s"' % data[0] + target = target_map[mytype] + + # Union with text targets + elif len([target for target \ + in obj_targets if target in text_targets]) > 0: + target = target_map[TEXT_TARGET] + + self.object_list.drag_source_unset() + self.object_list.drag_source_set(BUTTON1_MASK, [target], ACTION_COPY) + + + def on_update_object_clicked(self, obj): + pass + + def object_drag_begin(self, context, a): + return + + def object_drag_data_get(self,widget, context, sel_data, info, time): + + global gramps_targets + global text_targets + + o = self.otree.get_selected_objects() + + if len(o): + bits_per = 8; # we're going to pass a string + + obj_targets = o[0]['targets'] + + # union with gramps_types + if len([target for target \ + in obj_targets if target in gramps_targets]) > 0: + + exec 'data = %s' % o[0]['data'] + exec 'mytype = "%s"' % data[0] + exec 'person = "%s"' % data[1] + + pickled = data[2] + send_data = str((mytype,person,pickled)); + + # Union with text targets + elif len([target for target \ + in obj_targets if target in text_targets]) > 0: + send_data = str(o[0]['data']) + + sel_data.set(sel_data.target, bits_per, send_data) + + + def object_drag_data_received(self,widget,context,x,y,sel_data,info,time): + row = self.otree.get_row_at(x,y) + + if sel_data and sel_data.data: + self.olist.insert(row,{'targets':context.targets, + 'data':sel_data.data}) + self.redraw_object_list() + + + def redraw_object_list(self): + """Redraws the address list""" + + global gramps_targets + global text_targets + + self.otree.clear() + + for obj in self.olist: + obj_targets = obj['targets'] + + # union with gramps_types + if len([target for target \ + in obj_targets if target in gramps_targets]) > 0: + + exec 'unpack_data = %s' % obj['data'] + exec 'mytype = "%s"' % unpack_data[0] + data = pickle.loads(unpack_data[2]); + + node = None + + if mytype == 'paddr': + location = "%s %s %s %s" % (data.get_street(),data.get_city(), + data.get_state(),data.get_country()) + node = self.otree.add([_("Address"), + data.get_date(), + location, + self.generate_addr_tooltip(data)],obj) + + elif mytype == 'pevent': + node = self.otree.add([_("Event"), + const.display_pevent(data.get_name()), + data.get_description(), + self.generate_event_tooltip(data)],obj) + + elif mytype == 'url': + node = self.otree.add([_("Url"), + data.get_path(), + data.get_description(), + self.generate_url_tooltip(data)],obj) + elif mytype == 'pattr': + node = self.otree.add([_("Attribute"), + const.display_pattr(data.get_type()), + data.get_value(), + self.generate_pattr_tooltip(data)],obj) + elif mytype == 'srcref': + base = self.db.get_source_from_handle(data.get_base_handle()) + node = self.otree.add([_("SourceRef"), + base.get_title(), + data.get_text(), + self.generate_srcref_tooltip(data)],obj) + + # Union with text targets + elif len([target for target \ + in obj_targets if target in text_targets]) > 0: + node = self.otree.add([_("Text"), + "", + obj['data'], + self.generate_text_tooltip(obj['data'])],obj) + + + + + + if self.olist: + self.otree.select_row(0) + + + def generate_addr_tooltip(self,addr): + global escape + return "%s\n"\ + "%s:\t%s\n"\ + "%s:\n"\ + "\t%s\n"\ + "\t%s\n"\ + "\t%s\n"\ + "\t%s" % (_("Address"), + _("Date"), + escape(addr.get_date()), + _("Location"), + escape(addr.get_street()), + escape(addr.get_city()), + escape(addr.get_state()), + escape(addr.get_country())) + + def generate_event_tooltip(self,event): + global escape + return "%s\n"\ + "%s:\t%s\n"\ + "%s:\t%s\n" % (_("Event"), + _("Name"), + escape(const.display_pevent(event.get_name())), + _("Description"), + escape(event.get_description())) + + def generate_url_tooltip(self,url): + global escape + return "%s\n"\ + "%s:\t%s\n"\ + "%s:\t%s" % (_("Url"), + _("Path"), + escape(url.get_path()), + _("Description"), + escape(url.get_description())) + + def generate_pattr_tooltip(self,pattr): + global escape + return "%s\n"\ + "%s:\t%s\n"\ + "%s:\t%s" % (_("Attribute"), + _("Type"), + escape(const.display_pattr(pattr.get_type())), + _("Value"), + escape(pattr.get_value())) + + def generate_srcref_tooltip(self,srcref): + global escape + base = self.db.get_source_from_handle(srcref.get_base_handle()) + return "%s\n"\ + "%s:\t%s\n"\ + "%s:\t%s" % (_("SourceRef"), + _("Title"), + escape(base.get_title()), + _("Text"), + escape(srcref.get_text())) + + def generate_text_tooltip(self,text): + global escape + return "%s\n"\ + "%s" % (_("Text"), + escape(text)) + +#------------------------------------------------------------------------- +# +# +# +#------------------------------------------------------------------------- +def ScratchPad(database,person,callback,parent=None): + ScratchPadWindow(database,parent) + +#------------------------------------------------------------------------- +# +# +# +#------------------------------------------------------------------------- +from PluginMgr import register_tool + + +register_tool( + ScratchPad, + _("Scratch Pad"), + category=_("Utilities"), + description=_("The Scratch Pad provides a tempory note pad to store " + "objects for easy reuse.") + ) + + diff --git a/gramps2/src/plugins/scratchpad.glade b/gramps2/src/plugins/scratchpad.glade new file mode 100644 index 000000000..c468caca0 --- /dev/null +++ b/gramps2/src/plugins/scratchpad.glade @@ -0,0 +1,113 @@ + + + + + + + + True + Scratch Pad + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + 400 + 200 + True + False + True + False + False + GDK_WINDOW_TYPE_HINT_NORMAL + GDK_GRAVITY_NORTH_WEST + + + + + True + False + 0 + + + + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + True + False + False + True + + + + + + 0 + True + True + + + + + + True + GTK_BUTTONBOX_DEFAULT_STYLE + 0 + + + + True + True + True + Clear_All + True + GTK_RELIEF_NORMAL + True + + + + + + + True + True + True + C_lear + True + GTK_RELIEF_NORMAL + True + + + + + + + True + True + True + gtk-close + True + GTK_RELIEF_NORMAL + True + + + + + + 3 + False + True + + + + + + +