Example #1
0
class ExtractCity(tool.BatchTool, ManagedWindow.ManagedWindow):
    """
    Extracts city, state, and zip code information from an place description
    if the title is empty and the description falls into the category of:

       New York, NY 10000

    Sorry for those not in the US or Canada. I doubt this will work for any
    other locales.
    Works for Sweden if the decriptions is like
        Stockholm (A)
    where the letter A is the abbreviation letter for laen.
    Works for France if the description is like
        Paris, IDF 75000, FRA
    or  Paris, ILE DE FRANCE 75000, FRA
    """

    def __init__(self, dbstate, uistate, options_class, name, callback=None):
        self.label = _('Extract Place data')
        
        ManagedWindow.ManagedWindow.__init__(self, uistate, [], self.__class__)
        self.set_window(gtk.Window(), gtk.Label(), '')

        tool.BatchTool.__init__(self, dbstate, uistate, options_class, name)

        if not self.fail:
            uistate.set_busy_cursor(True)
            self.run(dbstate.db)
            uistate.set_busy_cursor(False)

    def run(self, db):
        """
        Performs the actual extraction of information
        """

        self.progress = ProgressMeter(_('Checking Place Titles'), '')
        self.progress.set_pass(_('Looking for place fields'), 
                               self.db.get_number_of_places())

        self.name_list = []

        for place in db.iter_places():
            descr = place.get_title()
            loc = place.get_main_location()
            self.progress.step()

            if loc.get_street() == loc.get_city() == \
               loc.get_state() == loc.get_postal_code() == "":

                match = CITY_STATE_ZIP.match(descr.strip())
                if match:
                    data = match.groups()
                    city = data[0] 
                    state = data[2]
                    postal = data[5]
                    
                    val = " ".join(state.strip().split()).upper()
                    if state:
                        new_state = STATE_MAP.get(val.upper())
                        if new_state:
                            self.name_list.append(
                                (place.handle, (city, new_state[0], postal, 
                                          COUNTRY[new_state[1]])))
                    continue

                # Check if there is a left parant. in the string, might be Swedish laen.
                match = CITY_LAEN.match(descr.strip().replace(","," "))
                if match:
                    data = match.groups()
                    city = data[0] 
                    state = '(' + data[1] + ')'
                    postal = None
                    val = " ".join(state.strip().split()).upper()
                    if state:
                        new_state = STATE_MAP.get(val.upper())
                        if new_state:
                            self.name_list.append(
                                (place.handle, (city, new_state[0], postal, 
                                          COUNTRY[new_state[1]])))
                    continue
                match = CITY_STATE.match(descr.strip())
                if match:
                    data = match.groups()
                    city = data[0] 
                    state = data[1]
                    postal = None
                    if state:
                        m0 = STATE_ZIP.match(state)
                        if m0:
                            (state, postal) = m0.groups() 

                    val = " ".join(state.strip().split()).upper()
                    if state:
                        new_state = STATE_MAP.get(val.upper())
                        if new_state:
                            self.name_list.append(
                                (place.handle, (city, new_state[0], postal, 
                                          COUNTRY[new_state[1]])))
                    continue

                val = " ".join(descr.strip().split()).upper()
                new_state = STATE_MAP.get(val)
                if new_state:
                    self.name_list.append(
                        (place.handle, (None, new_state[0], None, 
                                  COUNTRY[new_state[1]])))
        self.progress.close()

        if self.name_list:
            self.display()
        else:
            self.close()
            from QuestionDialog import OkDialog
            OkDialog(_('No modifications made'), 
                     _("No place information could be extracted."))

    def display(self):

        self.top = Glade("changenames.glade")
        window = self.top.toplevel
        self.top.connect_signals({
            "destroy_passed_object" : self.close, 
            "on_ok_clicked" : self.on_ok_clicked, 
            "on_help_clicked" : self.on_help_clicked, 
            "on_delete_event"   : self.close,
            })
        
        self.list = self.top.get_object("list")
        self.set_window(window, self.top.get_object('title'), self.label)
        lbl = self.top.get_object('info')
        lbl.set_line_wrap(True)
        lbl.set_text(
            _('Below is a list of Places with the possible data that can '
              'be extracted from the place title. Select the places you '
              'wish Gramps to convert.'))

        self.model = gtk.ListStore(gobject.TYPE_BOOLEAN, gobject.TYPE_STRING, 
                                   gobject.TYPE_STRING, gobject.TYPE_STRING, 
                                   gobject.TYPE_STRING, gobject.TYPE_STRING, 
                                   gobject.TYPE_STRING)

        r = gtk.CellRendererToggle()
        r.connect('toggled', self.toggled)
        c = gtk.TreeViewColumn(_('Select'), r, active=0)
        self.list.append_column(c)

        for (title, col) in COLS:
            render = gtk.CellRendererText()
            if col > 1:
                render.set_property('editable', True)
                render.connect('edited', self.__change_name, col)
            
            self.list.append_column(
                gtk.TreeViewColumn(title, render, text=col))
        self.list.set_model(self.model)

        self.iter_list = []
        self.progress.set_pass(_('Building display'), len(self.name_list))
        for (id, data) in self.name_list:

            place = self.db.get_place_from_handle(id)
            descr = place.get_title()

            handle = self.model.append()
            self.model.set_value(handle, 0, True)
            self.model.set_value(handle, 1, descr)
            if data[0]:
                self.model.set_value(handle, 2, data[0])
            if data[1]:
                self.model.set_value(handle, 3, data[1])
            if data[2]:
                self.model.set_value(handle, 4, data[2])
            if data[3]:
                self.model.set_value(handle, 5, data[3])
            self.model.set_value(handle, 6, id)
            self.iter_list.append(handle)
            self.progress.step()
        self.progress.close()
            
        self.show()

    def __change_name(self, text, path, new_text, col):
        self.model[path][col] = new_text
        return

    def toggled(self, cell, path_string):
        path = tuple(map(int, path_string.split(':')))
        row = self.model[path]
        row[0] = not row[0]

    def build_menu_names(self, obj):
        return (self.label, None)

    def on_help_clicked(self, obj):
        """Display the relevant portion of GRAMPS manual"""
        GrampsDisplay.help()

    def on_ok_clicked(self, obj):
        with DbTxn(_("Extract Place data"), self.db, batch=True) as self.trans:
            self.db.disable_signals()
            changelist = [node for node in self.iter_list
                          if self.model.get_value(node, 0)]

            for change in changelist:
                row = self.model[change]
                place = self.db.get_place_from_handle(row[6])
                (city, state, postal, country) = (row[2], row[3], row[4], row[5])

                if city:
                    place.get_main_location().set_city(city)
                if state:
                    place.get_main_location().set_state(state)
                if postal:
                    place.get_main_location().set_postal_code(postal)
                if country:
                    place.get_main_location().set_country(country)
                self.db.commit_place(place, self.trans)

        self.db.enable_signals()
        self.db.request_rebuild()
        self.close()
Example #2
0
    def display(self):

        self.top = Glade("changenames.glade")
        window = self.top.toplevel
        self.top.connect_signals({
            "destroy_passed_object" : self.close, 
            "on_ok_clicked" : self.on_ok_clicked, 
            "on_help_clicked" : self.on_help_clicked, 
            "on_delete_event"   : self.close,
            })
        
        self.list = self.top.get_object("list")
        self.set_window(window, self.top.get_object('title'), self.label)
        lbl = self.top.get_object('info')
        lbl.set_line_wrap(True)
        lbl.set_text(
            _('Below is a list of Places with the possible data that can '
              'be extracted from the place title. Select the places you '
              'wish Gramps to convert.'))

        self.model = gtk.ListStore(gobject.TYPE_BOOLEAN, gobject.TYPE_STRING, 
                                   gobject.TYPE_STRING, gobject.TYPE_STRING, 
                                   gobject.TYPE_STRING, gobject.TYPE_STRING, 
                                   gobject.TYPE_STRING)

        r = gtk.CellRendererToggle()
        r.connect('toggled', self.toggled)
        c = gtk.TreeViewColumn(_('Select'), r, active=0)
        self.list.append_column(c)

        for (title, col) in COLS:
            render = gtk.CellRendererText()
            if col > 1:
                render.set_property('editable', True)
                render.connect('edited', self.__change_name, col)
            
            self.list.append_column(
                gtk.TreeViewColumn(title, render, text=col))
        self.list.set_model(self.model)

        self.iter_list = []
        self.progress.set_pass(_('Building display'), len(self.name_list))
        for (id, data) in self.name_list:

            place = self.db.get_place_from_handle(id)
            descr = place.get_title()

            handle = self.model.append()
            self.model.set_value(handle, 0, True)
            self.model.set_value(handle, 1, descr)
            if data[0]:
                self.model.set_value(handle, 2, data[0])
            if data[1]:
                self.model.set_value(handle, 3, data[1])
            if data[2]:
                self.model.set_value(handle, 4, data[2])
            if data[3]:
                self.model.set_value(handle, 5, data[3])
            self.model.set_value(handle, 6, id)
            self.iter_list.append(handle)
            self.progress.step()
        self.progress.close()
            
        self.show()
Example #3
0
    def __init__(self, dbstate, uistate, options_class, name, callback=None):
        """
        Relationship calculator class.
        """

        tool.Tool.__init__(self, dbstate, options_class, name)
        ManagedWindow.ManagedWindow.__init__(self, uistate, [], self.__class__)

        #set the columns to see
        for data in BasePersonView.CONFIGSETTINGS:
            if data[0] == 'columns.rank':
                colord = data[1]
            elif data[0] == 'columns.visible':
                colvis = data[1]
            elif data[0] == 'columns.size':
                colsize = data[1]
        self.colord = []
        for col, size in zip(colord, colsize):
            if col in colvis:
                self.colord.append((1, col, size))
            else:
                self.colord.append((0, col, size))

        self.dbstate = dbstate
        self.relationship = Relationship.get_relationship_calculator()
        self.relationship.connect_db_signals(dbstate)

        self.glade = Glade()
        self.person = self.db.get_person_from_handle(
            uistate.get_active('Person'))
        name = ''
        if self.person:
            name = name_displayer.display(self.person)
        self.title = _('Relationship calculator: %(person_name)s') % {
            'person_name': name
        }
        window = self.glade.toplevel
        self.titlelabel = self.glade.get_object('title')
        self.set_window(
            window, self.titlelabel,
            _('Relationship to %(person_name)s') % {'person_name': name},
            self.title)

        self.tree = self.glade.get_object("peopleList")
        self.text = self.glade.get_object("text1")
        self.textbuffer = gtk.TextBuffer()
        self.text.set_buffer(self.textbuffer)

        self.model = PersonTreeModel(self.db)
        self.tree.set_model(self.model)

        self.tree.connect('key-press-event', self._key_press)
        self.selection = self.tree.get_selection()
        self.selection.set_mode(gtk.SELECTION_SINGLE)

        #keep reference of column so garbage collection works
        self.columns = []
        for pair in self.colord:
            if not pair[0]:
                continue
            name = column_names[pair[1]]
            column = gtk.TreeViewColumn(name,
                                        gtk.CellRendererText(),
                                        markup=pair[1])
            column.set_resizable(True)
            column.set_min_width(60)
            column.set_sizing(gtk.TREE_VIEW_COLUMN_GROW_ONLY)
            self.tree.append_column(column)
            #keep reference of column so garbage collection works
            self.columns.append(column)

        self.sel = self.tree.get_selection()
        self.changedkey = self.sel.connect('changed', self.on_apply_clicked)
        self.closebtn = self.glade.get_object("button5")
        self.closebtn.connect('clicked', self.close)

        if not self.person:
            self.window.hide()
            ErrorDialog(
                _('Active person has not been set'),
                _('You must select an active person for this '
                  'tool to work properly.'))
            self.close()
            return

        self.show()
Example #4
0
class ChangeTypes(tool.BatchTool, ManagedWindow.ManagedWindow):
    def __init__(self, dbstate, uistate, options_class, name, callback=None):

        tool.BatchTool.__init__(self, dbstate, uistate, options_class, name)
        if self.fail:
            return

        if uistate:
            self.title = _('Change Event Types')
            ManagedWindow.ManagedWindow.__init__(self, uistate, [],
                                                 self.__class__)
            self.init_gui()
        else:
            self.run_tool(cli=True)

    def init_gui(self):
        # Draw dialog and make it handle everything

        self.glade = Glade()

        self.auto1 = self.glade.get_object("original")
        self.auto2 = self.glade.get_object("new")

        # Need to display localized event names
        etype = EventType()
        event_names = sorted(etype.get_standard_names(), key=locale.strxfrm)

        AutoComp.fill_combo(self.auto1, event_names)
        AutoComp.fill_combo(self.auto2, event_names)

        etype.set_from_xml_str(self.options.handler.options_dict['fromtype'])
        self.auto1.child.set_text(str(etype))

        etype.set_from_xml_str(self.options.handler.options_dict['totype'])
        self.auto2.child.set_text(str(etype))

        window = self.glade.toplevel
        self.set_window(window, self.glade.get_object('title'), self.title)

        self.glade.connect_signals({
            "on_close_clicked": self.close,
            "on_apply_clicked": self.on_apply_clicked,
            "on_delete_event": self.close,
        })

        self.show()

    def build_menu_names(self, obj):
        return (self.title, None)

    def run_tool(self, cli=False):
        # Run tool and return results
        # These are English names, no conversion needed
        fromtype = self.options.handler.options_dict['fromtype']
        totype = self.options.handler.options_dict['totype']

        modified = 0

        with DbTxn(_('Change types'), self.db, batch=True) as self.trans:
            self.db.disable_signals()
            if not cli:
                progress = ProgressMeter(_('Analyzing Events'), '')
                progress.set_pass('', self.db.get_number_of_events())

            for event_handle in self.db.get_event_handles():
                event = self.db.get_event_from_handle(event_handle)
                if event.get_type().xml_str() == fromtype:
                    event.type.set_from_xml_str(totype)
                    modified += 1
                    self.db.commit_event(event, self.trans)
                if not cli:
                    progress.step()
            if not cli:
                progress.close()
        self.db.enable_signals()
        self.db.request_rebuild()

        if modified == 0:
            msg = _("No event record was modified.")
        else:
            msg = ngettext("%d event record was modified.",
                           "%d event records were modified.",
                           modified) % modified

        if cli:
            print "Done: ", msg
        return (bool(modified), msg)

    def on_apply_clicked(self, obj):
        # Need to store English names for later comparison
        the_type = EventType()

        the_type.set(self.auto1.child.get_text())
        self.options.handler.options_dict['fromtype'] = the_type.xml_str()

        the_type.set(self.auto2.child.get_text())
        self.options.handler.options_dict['totype'] = the_type.xml_str()

        modified, msg = self.run_tool(cli=False)
        OkDialog(_('Change types'), msg, self.window)

        # Save options
        self.options.handler.save_options()

        self.close()
Example #5
0
class EditSource(EditPrimary):
    def __init__(self, dbstate, uistate, track, source, callback=None):

        EditPrimary.__init__(self, dbstate, uistate, track, source,
                             dbstate.db.get_source_from_handle,
                             dbstate.db.get_source_from_gramps_id, callback)

    def empty_object(self):
        return gen.lib.Source()

    def get_menu_title(self):
        title = self.obj.get_title()
        if title:
            title = _('Source') + ": " + title
        else:
            title = _('New Source')
        return title

    def _local_init(self):
        self.width_key = 'interface.source-width'
        self.height_key = 'interface.source-height'
        assert (self.obj)

        self.glade = Glade()
        self.set_window(self.glade.toplevel, None, self.get_menu_title())

    def _connect_signals(self):
        self.define_ok_button(self.glade.get_object('ok'), self.save)
        self.define_cancel_button(self.glade.get_object('cancel'))
        self.define_help_button(self.glade.get_object('help'))

    def _connect_db_signals(self):
        """
        Connect any signals that need to be connected. 
        Called by the init routine of the base class (_EditPrimary).
        """
        self._add_db_signal('source-rebuild', self._do_close)
        self._add_db_signal('source-delete', self.check_for_close)

    def _setup_fields(self):
        self.author = MonitoredEntry(self.glade.get_object("author"),
                                     self.obj.set_author, self.obj.get_author,
                                     self.db.readonly)

        self.pubinfo = MonitoredEntry(self.glade.get_object("pubinfo"),
                                      self.obj.set_publication_info,
                                      self.obj.get_publication_info,
                                      self.db.readonly)

        self.gid = MonitoredEntry(self.glade.get_object("gid"),
                                  self.obj.set_gramps_id,
                                  self.obj.get_gramps_id, self.db.readonly)

        self.priv = PrivacyButton(self.glade.get_object("private"), self.obj,
                                  self.db.readonly)

        self.abbrev = MonitoredEntry(self.glade.get_object("abbrev"),
                                     self.obj.set_abbreviation,
                                     self.obj.get_abbreviation,
                                     self.db.readonly)

        self.title = MonitoredEntry(self.glade.get_object("source_title"),
                                    self.obj.set_title, self.obj.get_title,
                                    self.db.readonly)

    def _create_tabbed_pages(self):
        notebook = gtk.Notebook()

        self.note_tab = NoteTab(self.dbstate, self.uistate, self.track,
                                self.obj.get_note_list(),
                                self.get_menu_title(), gen.lib.NoteType.SOURCE)
        self._add_tab(notebook, self.note_tab)
        self.track_ref_for_deletion("note_tab")

        self.gallery_tab = GalleryTab(self.dbstate, self.uistate, self.track,
                                      self.obj.get_media_list())
        self._add_tab(notebook, self.gallery_tab)
        self.track_ref_for_deletion("gallery_tab")

        self.data_tab = DataEmbedList(self.dbstate, self.uistate, self.track,
                                      self.obj)
        self._add_tab(notebook, self.data_tab)
        self.track_ref_for_deletion("data_tab")

        self.repo_tab = RepoEmbedList(self.dbstate, self.uistate, self.track,
                                      self.obj.get_reporef_list())
        self._add_tab(notebook, self.repo_tab)
        self.track_ref_for_deletion("repo_tab")

        self.backref_list = CitationBackRefList(
            self.dbstate, self.uistate, self.track,
            self.db.find_backlink_handles(self.obj.handle))
        self.backref_tab = self._add_tab(notebook, self.backref_list)
        self.track_ref_for_deletion("backref_tab")
        self.track_ref_for_deletion("backref_list")

        self._setup_notebook_tabs(notebook)
        notebook.show_all()
        self.glade.get_object('vbox').pack_start(notebook, True)

    def build_menu_names(self, source):
        return (_('Edit Source'), self.get_menu_title())

    def save(self, *obj):
        self.ok_button.set_sensitive(False)
        if self.object_is_empty():
            ErrorDialog(
                _("Cannot save source"),
                _("No data exists for this source. Please "
                  "enter data or cancel the edit."))
            self.ok_button.set_sensitive(True)
            return

        (uses_dupe_id, id) = self._uses_duplicate_id()
        if uses_dupe_id:
            prim_object = self.get_from_gramps_id(id)
            name = prim_object.get_title()
            msg1 = _("Cannot save source. ID already exists.")
            msg2 = _("You have attempted to use the existing Gramps ID with "
                     "value %(id)s. This value is already used by '"
                     "%(prim_object)s'. Please enter a different ID or leave "
                     "blank to get the next available ID value.") % {
                         'id': id,
                         'prim_object': name
                     }
            ErrorDialog(msg1, msg2)
            self.ok_button.set_sensitive(True)
            return

        with DbTxn('', self.db) as trans:
            if not self.obj.get_handle():
                self.db.add_source(self.obj, trans)
                msg = _("Add Source (%s)") % self.obj.get_title()
            else:
                if not self.obj.get_gramps_id():
                    self.obj.set_gramps_id(
                        self.db.find_next_source_gramps_id())
                self.db.commit_source(self.obj, trans)
                msg = _("Edit Source (%s)") % self.obj.get_title()
            trans.set_description(msg)

        self.close()
        if self.callback:
            self.callback(self.obj)
Example #6
0
class RelCalc(tool.Tool, ManagedWindow.ManagedWindow):
    def __init__(self, dbstate, uistate, options_class, name, callback=None):
        """
        Relationship calculator class.
        """

        tool.Tool.__init__(self, dbstate, options_class, name)
        ManagedWindow.ManagedWindow.__init__(self, uistate, [], self.__class__)

        #set the columns to see
        for data in BasePersonView.CONFIGSETTINGS:
            if data[0] == 'columns.rank':
                colord = data[1]
            elif data[0] == 'columns.visible':
                colvis = data[1]
            elif data[0] == 'columns.size':
                colsize = data[1]
        self.colord = []
        for col, size in zip(colord, colsize):
            if col in colvis:
                self.colord.append((1, col, size))
            else:
                self.colord.append((0, col, size))

        self.dbstate = dbstate
        self.relationship = Relationship.get_relationship_calculator()
        self.relationship.connect_db_signals(dbstate)

        self.glade = Glade()
        self.person = self.db.get_person_from_handle(
            uistate.get_active('Person'))
        name = ''
        if self.person:
            name = name_displayer.display(self.person)
        self.title = _('Relationship calculator: %(person_name)s') % {
            'person_name': name
        }
        window = self.glade.toplevel
        self.titlelabel = self.glade.get_object('title')
        self.set_window(
            window, self.titlelabel,
            _('Relationship to %(person_name)s') % {'person_name': name},
            self.title)

        self.tree = self.glade.get_object("peopleList")
        self.text = self.glade.get_object("text1")
        self.textbuffer = gtk.TextBuffer()
        self.text.set_buffer(self.textbuffer)

        self.model = PersonTreeModel(self.db)
        self.tree.set_model(self.model)

        self.tree.connect('key-press-event', self._key_press)
        self.selection = self.tree.get_selection()
        self.selection.set_mode(gtk.SELECTION_SINGLE)

        #keep reference of column so garbage collection works
        self.columns = []
        for pair in self.colord:
            if not pair[0]:
                continue
            name = column_names[pair[1]]
            column = gtk.TreeViewColumn(name,
                                        gtk.CellRendererText(),
                                        markup=pair[1])
            column.set_resizable(True)
            column.set_min_width(60)
            column.set_sizing(gtk.TREE_VIEW_COLUMN_GROW_ONLY)
            self.tree.append_column(column)
            #keep reference of column so garbage collection works
            self.columns.append(column)

        self.sel = self.tree.get_selection()
        self.changedkey = self.sel.connect('changed', self.on_apply_clicked)
        self.closebtn = self.glade.get_object("button5")
        self.closebtn.connect('clicked', self.close)

        if not self.person:
            self.window.hide()
            ErrorDialog(
                _('Active person has not been set'),
                _('You must select an active person for this '
                  'tool to work properly.'))
            self.close()
            return

        self.show()

    def close(self, *obj):
        """ Close relcalc tool. Remove non-gtk connections so garbage
            collection can do its magic.
        """
        self.relationship.disconnect_db_signals(self.dbstate)
        self.sel.disconnect(self.changedkey)
        ManagedWindow.ManagedWindow.close(self, *obj)

    def build_menu_names(self, obj):
        return (_("Relationship Calculator tool"), None)

    def on_apply_clicked(self, obj):
        model, node = self.tree.get_selection().get_selected()
        if not node:
            return

        handle = model.get_value(node, PeopleBaseModel.COLUMN_INT_ID)
        other_person = self.db.get_person_from_handle(handle)
        if other_person is None:
            self.textbuffer.set_text("")
            return

        #now determine the relation, and print it out
        rel_strings, common_an = self.relationship.get_all_relationships(
            self.db, self.person, other_person)

        p1 = name_displayer.display(self.person)
        p2 = name_displayer.display(other_person)

        text = []
        if other_person is None:
            pass
        elif self.person.handle == other_person.handle:
            rstr = _(
                "%(person)s and %(active_person)s are the same person.") % {
                    'person': p1,
                    'active_person': p2
                }
            text.append((rstr, ""))
        elif len(rel_strings) == 0:
            rstr = _("%(person)s and %(active_person)s are not related.") % {
                'person': p2,
                'active_person': p1
            }
            text.append((rstr, ""))

        for rel_string, common in zip(rel_strings, common_an):
            rstr = _(
                "%(person)s is the %(relationship)s of %(active_person)s.") % {
                    'person': p2,
                    'relationship': rel_string,
                    'active_person': p1
                }
            length = len(common)
            if length == 1:
                person = self.db.get_person_from_handle(common[0])
                if common[0] in [other_person.handle, self.person.handle]:
                    commontext = ''
                else:
                    name = name_displayer.display(person)
                    commontext = " " + _("Their common ancestor is %s.") % name
            elif length == 2:
                p1c = self.db.get_person_from_handle(common[0])
                p2c = self.db.get_person_from_handle(common[1])
                p1str = name_displayer.display(p1c)
                p2str = name_displayer.display(p2c)
                commontext = " " + _(
                    "Their common ancestors are %(ancestor1)s and %(ancestor2)s."
                ) % {
                    'ancestor1': p1str,
                    'ancestor2': p2str
                }
            elif length > 2:
                index = 0
                commontext = " " + _("Their common ancestors are: ")
                for person_handle in common:
                    person = self.db.get_person_from_handle(person_handle)
                    if index:
                        commontext += ", "
                    commontext += name_displayer.display(person)
                    index += 1
                commontext += "."
            else:
                commontext = ""
            text.append((rstr, commontext))

        textval = ""
        for val in text:
            textval += "%s %s\n" % (val[0], val[1])
        self.textbuffer.set_text(textval)

    def _key_press(self, obj, event):
        if event.keyval in (gtk.keysyms.Return, gtk.keysyms.KP_Enter):
            store, paths = self.selection.get_selected_rows()
            if paths and len(paths[0]) == 1:
                if self.tree.row_expanded(paths[0]):
                    self.tree.collapse_row(paths[0])
                else:
                    self.tree.expand_row(paths[0], 0)
                return True
        return False
Example #7
0
 def define_glade(self, top_module, glade_file=None):
     if glade_file is None:
         raise TypeError, "ManagedWindow.define_glade: no glade file"
         glade_file = const.GLADE_FILE
     self._gladeobj = Glade(glade_file, None, top_module)
     return self._gladeobj
Example #8
0
class EditPerson(EditPrimary):
    """
    The EditPerson dialog is derived from the EditPrimary class.

    It allows for the editing of the primary object type of Person.

    """

    QR_CATEGORY = CATEGORY_QR_PERSON

    def __init__(self, dbstate, uistate, track, person, callback=None):
        """
        Create an EditPerson window.

        Associate a person with the window.

        """
        EditPrimary.__init__(self, dbstate, uistate, track, person,
                             dbstate.db.get_person_from_handle,
                             dbstate.db.get_person_from_gramps_id, callback)

    def empty_object(self):
        """
        Return an empty Person object for comparison for changes.

        This is used by the base class (EditPrimary).

        """
        person = gen.lib.Person()
        #the editor requires a surname
        person.primary_name.add_surname(gen.lib.Surname())
        person.primary_name.set_primary_surname(0)
        return person

    def get_menu_title(self):
        if self.obj and self.obj.get_handle():
            name = name_displayer.display(self.obj)
            title = _('Person: %(name)s') % {'name': name}
        else:
            name = name_displayer.display(self.obj)
            if name:
                title = _('New Person: %(name)s') % {'name': name}
            else:
                title = _('New Person')
        return title

    def get_preview_name(self):
        prevname = name_displayer.display(self.obj)
        return prevname

    def _local_init(self):
        """
        Performs basic initialization, including setting up widgets and the
        glade interface.

        Local initialization function.
        This is called by the base class of EditPrimary, and overridden here.

        """
        self.width_key = 'interface.person-width'
        self.height_key = 'interface.person-height'
        self.pname = self.obj.get_primary_name()
        self.should_guess_gender = (not self.obj.get_gramps_id()
                                    and self.obj.get_gender()
                                    == gen.lib.Person.UNKNOWN)

        self.top = Glade()

        self.set_window(self.top.toplevel, None, self.get_menu_title())

        self.obj_photo = self.top.get_object("personPix")
        self.frame_photo = self.top.get_object("frame5")
        self.eventbox = self.top.get_object("eventbox1")
        self.singsurnfr = SingSurn(self.top)
        self.multsurnfr = self.top.get_object("hboxmultsurnames")
        self.singlesurn_active = True
        self.surntab = SurnameTab(self.dbstate,
                                  self.uistate,
                                  self.track,
                                  self.obj.get_primary_name(),
                                  on_change=self._changed_name)

        self.set_contexteventbox(self.top.get_object("eventboxtop"))

    def _post_init(self):
        """
        Handle any initialization that needs to be done after the interface is
        brought up.

        Post initalization function.
        This is called by _EditPrimary's init routine, and overridden in the
        derived class (this class).

        """
        self.load_person_image()
        self.given.grab_focus()
        self._changed_name(None)
        self.top.get_object("hboxmultsurnames").pack_start(self.surntab)

        if len(self.obj.get_primary_name().get_surname_list()) > 1:
            self.multsurnfr.set_size_request(
                -1, int(config.get('interface.surname-box-height')))
            self.singsurnfr.hide_all()
            self.multsurnfr.show_all()
            self.singlesurn_active = False
        else:
            self.multsurnfr.hide_all()
            self.singsurnfr.show_all()
            self.singlesurn_active = True
        #if self.pname.get_surname() and not self.pname.get_first_name():
        #    self.given.grab_focus()
        #else:
        #    self.surname_field.grab_focus()

    def _connect_signals(self):
        """
        Connect any signals that need to be connected.
        Called by the init routine of the base class (_EditPrimary).
        """
        self.define_cancel_button(self.top.get_object("button15"))
        self.define_ok_button(self.top.get_object("ok"), self.save)
        self.define_help_button(
            self.top.get_object("button134"),
            _('Gramps_3.4_Wiki_Manual_-_Entering_and_editing_data:_detailed_-_part_1'
              ), _('manpage section id|Editing_information_about_people'))

        self.given.connect("focus_out_event", self._given_focus_out_event)
        self.top.get_object("editnamebtn").connect("clicked",
                                                   self._edit_name_clicked)
        self.top.get_object("multsurnamebtn").connect("clicked",
                                                      self._mult_surn_clicked)

        self.eventbox.connect('button-press-event', self._image_button_press)

    def _connect_db_signals(self):
        """
        Connect any signals that need to be connected.
        Called by the init routine of the base class (_EditPrimary).
        """
        self._add_db_signal('person-rebuild', self._do_close)
        self._add_db_signal('person-delete', self.check_for_close)
        self._add_db_signal('family-rebuild', self.family_change)
        self._add_db_signal('family-delete', self.family_change)
        self._add_db_signal('family-update', self.family_change)
        self._add_db_signal('family-add', self.family_change)
        self._add_db_signal('event-update', self.event_updated)
        self._add_db_signal('event-rebuild', self.event_updated)
        self._add_db_signal('event-delete', self.event_updated)

    def family_change(self, handle_list=[]):
        """
        Callback for family change signals.

        This should rebuild the
           backreferences to family in person when:
            1)a family the person is parent of changes. Person could have
              been removed
            2)a family the person is child in changes. Child could have been
              removed
            3)a family is changed. The person could be added as child or
              parent

        """
        #As this would be an extensive check, we choose the easy path and
        #   rebuild family backreferences on all family changes
        self._update_families()

    def _update_families(self):
        phandle = self.obj.get_handle()
        if phandle:
            #new person has no handle yet and cannot be in a family.
            person = self.dbstate.db.get_person_from_handle(phandle)
            self.obj.set_family_handle_list(person.get_family_handle_list())
            self.obj.set_parent_family_handle_list(
                person.get_parent_family_handle_list())
            #a family groupname in event_list might need to be changed
            # we just rebuild the view always
            self.event_list.rebuild_callback()

    def event_updated(self, obj):
        #place in event might have changed, or person event shown in the list
        # we just rebuild the view always
        self.event_list.rebuild_callback()

    def _validate_call(self, widget, text):
        """ a callname must be a part of the given name, see if this is the
            case """
        validcall = self.given.obj.get_text().split()
        dummy = copy(validcall)
        for item in dummy:
            validcall += item.split('-')
        if text in validcall:
            return
        return ValidationError(
            _("Call name must be the given name that "
              "is normally used."))

    def _setup_fields(self):
        """
        Connect the GrampsWidget objects to field in the interface.

        This allows the widgets to keep the data in the attached Person object
        up to date at all times, eliminating a lot of need in 'save' routine.

        """

        self.private = widgets.PrivacyButton(self.top.get_object('private'),
                                             self.obj, self.db.readonly)

        self.gender = widgets.MonitoredMenu(
            self.top.get_object('gender'), self.obj.set_gender,
            self.obj.get_gender, ((_('female'), gen.lib.Person.FEMALE),
                                  (_('male'), gen.lib.Person.MALE),
                                  (_('unknown'), gen.lib.Person.UNKNOWN)),
            self.db.readonly)

        self.ntype_field = widgets.MonitoredDataType(
            self.top.get_object("ntype"), self.pname.set_type,
            self.pname.get_type, self.db.readonly, self.db.get_name_types())

        #part of Given Name section
        self.given = widgets.MonitoredEntry(self.top.get_object("given_name"),
                                            self.pname.set_first_name,
                                            self.pname.get_first_name,
                                            self.db.readonly)

        self.call = widgets.MonitoredEntry(self.top.get_object("call"),
                                           self.pname.set_call_name,
                                           self.pname.get_call_name,
                                           self.db.readonly)
        self.call.connect("validate", self._validate_call)
        #force validation now with initial entry
        self.call.obj.validate(force=True)

        self.title = widgets.MonitoredEntry(self.top.get_object("title"),
                                            self.pname.set_title,
                                            self.pname.get_title,
                                            self.db.readonly)

        self.suffix = widgets.MonitoredEntryIndicator(
            self.top.get_object("suffix"), self.pname.set_suffix,
            self.pname.get_suffix, _('suffix'), self.db.readonly)

        self.nick = widgets.MonitoredEntry(self.top.get_object("nickname"),
                                           self.pname.set_nick_name,
                                           self.pname.get_nick_name,
                                           self.db.readonly)

        #part of Single Surname section
        self.surname_field = widgets.MonitoredEntry(
            self.top.get_object("surname"),
            self.pname.get_primary_surname().set_surname,
            self.pname.get_primary_surname().get_surname,
            self.db.readonly,
            autolist=self.db.get_surname_list()
            if not self.db.readonly else [])

        self.prefix = widgets.MonitoredEntryIndicator(
            self.top.get_object("prefix"),
            self.pname.get_primary_surname().set_prefix,
            self.pname.get_primary_surname().get_prefix, _('prefix'),
            self.db.readonly)

        self.ortype_field = widgets.MonitoredDataType(
            self.top.get_object("cmborigin"),
            self.pname.get_primary_surname().set_origintype,
            self.pname.get_primary_surname().get_origintype, self.db.readonly,
            self.db.get_origin_types())

        #other fields

        self.tags = widgets.MonitoredTagList(self.top.get_object("tag_label"),
                                             self.top.get_object("tag_button"),
                                             self.obj.set_tag_list,
                                             self.obj.get_tag_list, self.db,
                                             self.uistate, self.track,
                                             self.db.readonly)

        self.gid = widgets.MonitoredEntry(self.top.get_object("gid"),
                                          self.obj.set_gramps_id,
                                          self.obj.get_gramps_id,
                                          self.db.readonly)

        #make sure title updates automatically
        for obj in [
                self.top.get_object("given_name"),
                self.top.get_object("call"),
                self.top.get_object("suffix"),
                self.top.get_object("prefix"),
                self.top.get_object("surname"),
        ]:
            obj.connect('changed', self._changed_name)

        self.preview_name = self.top.get_object("full_name")
        self.preview_name.modify_font(pango.FontDescription('sans bold 12'))

    def _create_tabbed_pages(self):
        """
        Create the notebook tabs and insert them into the main window.
        """
        notebook = gtk.Notebook()
        notebook.set_scrollable(True)

        self.event_list = PersonEventEmbedList(self.dbstate, self.uistate,
                                               self.track, self.obj)
        self._add_tab(notebook, self.event_list)
        self.track_ref_for_deletion("event_list")

        self.name_list = NameEmbedList(self.dbstate, self.uistate, self.track,
                                       self.obj.get_alternate_names(),
                                       self.obj, self.name_callback)
        self._add_tab(notebook, self.name_list)
        self.track_ref_for_deletion("name_list")

        self.srcref_list = CitationEmbedList(self.dbstate, self.uistate,
                                             self.track,
                                             self.obj.get_citation_list(),
                                             self.get_menu_title())
        self._add_tab(notebook, self.srcref_list)
        self.track_ref_for_deletion("srcref_list")

        self.attr_list = AttrEmbedList(self.dbstate, self.uistate, self.track,
                                       self.obj.get_attribute_list())
        self._add_tab(notebook, self.attr_list)
        self.track_ref_for_deletion("attr_list")

        self.addr_list = AddrEmbedList(self.dbstate, self.uistate, self.track,
                                       self.obj.get_address_list())
        self._add_tab(notebook, self.addr_list)
        self.track_ref_for_deletion("addr_list")

        self.note_tab = NoteTab(self.dbstate,
                                self.uistate,
                                self.track,
                                self.obj.get_note_list(),
                                self.get_menu_title(),
                                notetype=gen.lib.NoteType.PERSON)
        self._add_tab(notebook, self.note_tab)
        self.track_ref_for_deletion("note_tab")

        self.gallery_tab = GalleryTab(self.dbstate, self.uistate, self.track,
                                      self.obj.get_media_list(),
                                      self.load_person_image)
        self._add_tab(notebook, self.gallery_tab)
        self.track_ref_for_deletion("gallery_tab")

        self.web_list = WebEmbedList(self.dbstate, self.uistate, self.track,
                                     self.obj.get_url_list())
        self._add_tab(notebook, self.web_list)
        self.track_ref_for_deletion("web_list")

        self.person_ref_list = PersonRefEmbedList(
            self.dbstate, self.uistate, self.track,
            self.obj.get_person_ref_list())
        self._add_tab(notebook, self.person_ref_list)
        self.track_ref_for_deletion("person_ref_list")

        self.lds_list = LdsEmbedList(self.dbstate, self.uistate, self.track,
                                     self.obj.get_lds_ord_list())
        self._add_tab(notebook, self.lds_list)
        self.track_ref_for_deletion("lds_list")

        self.backref_tab = PersonBackRefList(
            self.dbstate, self.uistate, self.track,
            self.db.find_backlink_handles(self.obj.handle))
        self._add_tab(notebook, self.backref_tab)
        self.track_ref_for_deletion("backref_tab")

        self._setup_notebook_tabs(notebook)
        notebook.show_all()
        self.top.get_object('vbox').pack_start(notebook, True)

    def _changed_name(self, *obj):
        """
        callback to changes typed by user to the person name.
        Update the window title, and default name in name tab
        """
        self.update_title(self.get_menu_title())
        self.preview_name.set_text(self.get_preview_name())
        self.name_list.update_defname()

    def name_callback(self):
        """
        Callback if changes happen in the name tab that impact the preferred
        name.
        """
        self.pname = self.obj.get_primary_name()

        self.ntype_field.reinit(self.pname.set_type, self.pname.get_type)
        self.given.reinit(self.pname.set_first_name, self.pname.get_first_name)
        self.call.reinit(self.pname.set_call_name, self.pname.get_call_name)
        self.title.reinit(self.pname.set_title, self.pname.get_title)
        self.suffix.reinit(self.pname.set_suffix, self.pname.get_suffix)
        self.nick.reinit(self.pname.set_nick_name, self.pname.get_nick_name)
        #part of Single Surname section
        self.surname_field.reinit(self.pname.get_primary_surname().set_surname,
                                  self.pname.get_primary_surname().get_surname)

        self.prefix.reinit(self.pname.get_primary_surname().set_prefix,
                           self.pname.get_primary_surname().get_prefix)

        self.ortype_field.reinit(
            self.pname.get_primary_surname().set_origintype,
            self.pname.get_primary_surname().get_origintype)

        self.__renewmultsurnames()

        if len(self.pname.get_surname_list()) == 1:
            self.singlesurn_active = True
        else:
            self.singlesurn_active = False
        if self.singlesurn_active:
            self.multsurnfr.hide_all()
            self.singsurnfr.show_all()
        else:
            self.singsurnfr.hide_all()
            self.multsurnfr.show_all()

    def build_menu_names(self, person):
        """
        Provide the information needed by the base class to define the
        window management menu entries.
        """
        return (_('Edit Person'), self.get_menu_title())

    def _image_button_press(self, obj, event):
        """
        Button press event that is caught when a button has been pressed while
        on the image on the main form.

        This does not apply to the images in galleries, just the image on the
        main form.

        """
        if event.type == gtk.gdk._2BUTTON_PRESS and event.button == 1:

            media_list = self.obj.get_media_list()
            if media_list:
                media_ref = media_list[0]
                object_handle = media_ref.get_reference_handle()
                media_obj = self.db.get_object_from_handle(object_handle)

                try:
                    EditMediaRef(self.dbstate, self.uistate, self.track,
                                 media_obj, media_ref, self.load_photo)
                except Errors.WindowActiveError:
                    pass

        elif gui.utils.is_right_click(event):
            media_list = self.obj.get_media_list()
            if media_list:
                photo = media_list[0]
                self._show_popup(photo, event)
        #do not propagate further:
        return True

    def _show_popup(self, photo, event):
        """
        Look for right-clicks on a picture and create a popup menu of the
        available actions.
        """

        menu = gtk.Menu()
        menu.set_title(_("Media Object"))
        obj = self.db.get_object_from_handle(photo.get_reference_handle())
        if obj:
            gui.utils.add_menuitem(menu, _("View"), photo,
                                   self._popup_view_photo)
        gui.utils.add_menuitem(menu, _("Edit Object Properties"), photo,
                               self._popup_change_description)
        menu.popup(None, None, None, event.button, event.time)

    def _popup_view_photo(self, obj):
        """
        Open this picture in the default picture viewer.
        """
        media_list = self.obj.get_media_list()
        if media_list:
            photo = media_list[0]
            object_handle = photo.get_reference_handle()
            ref_obj = self.db.get_object_from_handle(object_handle)
            photo_path = Utils.media_path_full(self.db, ref_obj.get_path())
            gui.utils.open_file_with_default_application(photo_path)

    def _popup_change_description(self, obj):
        """
        Bring up the EditMediaRef dialog for the image on the main form.
        """
        media_list = self.obj.get_media_list()
        if media_list:
            media_ref = media_list[0]
            object_handle = media_ref.get_reference_handle()
            media_obj = self.db.get_object_from_handle(object_handle)
            EditMediaRef(self.dbstate, self.uistate, self.track, media_obj,
                         media_ref, self.load_photo)

    def _top_contextmenu(self):
        """
        Override from base class, the menuitems and actiongroups for the top
        of context menu.
        """
        self.all_action = gtk.ActionGroup("/PersonAll")
        self.home_action = gtk.ActionGroup("/PersonHome")
        self.track_ref_for_deletion("all_action")
        self.track_ref_for_deletion("home_action")

        self.all_action.add_actions([
            ('ActivePerson', gtk.STOCK_APPLY, _("Make Active Person"), None,
             None, self._make_active),
        ])
        self.home_action.add_actions([
            ('HomePerson', gtk.STOCK_HOME, _("Make Home Person"), None, None,
             self._make_home_person),
        ])

        self.all_action.set_visible(True)
        self.home_action.set_visible(True)

        ui_top_cm = '''
            <menuitem action="ActivePerson"/>
            <menuitem action="HomePerson"/>'''

        return ui_top_cm, [self.all_action, self.home_action]

    def _post_build_popup_ui(self):
        """
        Override base class, make inactive home action if not needed.
        """
        if (self.dbstate.db.get_default_person() and self.obj.get_handle()
                == self.dbstate.db.get_default_person().get_handle()):
            self.home_action.set_sensitive(False)
        else:
            self.home_action.set_sensitive(True)

    def _make_active(self, obj):
        self.uistate.set_active(self.obj.get_handle(), 'Person')

    def _make_home_person(self, obj):
        handle = self.obj.get_handle()
        if handle:
            self.dbstate.db.set_default_person_handle(handle)

    def _given_focus_out_event(self, entry, event):
        """
        Callback that occurs when the user leaves the given name field,
        allowing us to attempt to guess the gender of the person if
        so requested.
        """
        if not self.should_guess_gender:
            return False
        try:
            gender_type = self.db.genderStats.guess_gender(
                unicode(entry.get_text()))
            self.gender.force(gender_type)
        except:
            return False
        return False

    def _check_for_unknown_gender(self):
        if self.obj.get_gender() == gen.lib.Person.UNKNOWN:
            d = GenderDialog(self.window)
            gender = d.run()
            d.destroy()
            if gender >= 0:
                self.obj.set_gender(gender)

    def _update_family_ids(self):
        # Update each of the families child lists to reflect any
        # change in ordering due to the new birth date
        family = self.obj.get_main_parents_family_handle()
        if (family):
            f = self.db.get_family_from_handle(family)
            new_order = self.reorder_child_ref_list(self.obj,
                                                    f.get_child_ref_list())
            f.set_child_ref_list(new_order)
        for family in self.obj.get_parent_family_handle_list():
            f = self.db.get_family_from_handle(family)
            new_order = self.reorder_child_ref_list(self.obj,
                                                    f.get_child_ref_list())
            f.set_child_ref_list(new_order)

        error = False
        original = self.db.get_person_from_handle(self.obj.handle)

        if original:
            (female, male, unknown) = _select_gender[self.obj.get_gender()]
            if male and original.get_gender() != gen.lib.Person.MALE:
                for tmp_handle in self.obj.get_family_handle_list():
                    temp_family = self.db.get_family_from_handle(tmp_handle)
                    if self.obj == temp_family.get_mother_handle():
                        if temp_family.get_father_handle() is not None:
                            error = True
                        else:
                            temp_family.set_mother_handle(None)
                            temp_family.set_father_handle(self.obj)
            elif female and original != gen.lib.Person.FEMALE:
                for tmp_handle in self.obj.get_family_handle_list():
                    temp_family = self.db.get_family_from_handle(tmp_handle)
                    if self.obj == temp_family.get_father_handle():
                        if temp_family.get_mother_handle() is not None:
                            error = True
                        else:
                            temp_family.set_father_handle(None)
                            temp_family.set_mother_handle(self.obj)
            elif unknown and original.get_gender() != gen.lib.Person.UNKNOWN:
                for tmp_handle in self.obj.get_family_handle_list():
                    temp_family = self.db.get_family_from_handle(tmp_handle)
                    if self.obj == temp_family.get_father_handle():
                        if temp_family.get_mother_handle() is not None:
                            error = True
                        else:
                            temp_family.set_father_handle(None)
                            temp_family.set_mother_handle(self.obj)
                    if self.obj == temp_family.get_mother_handle():
                        if temp_family.get_father_handle() is not None:
                            error = True
                        else:
                            temp_family.set_mother_handle(None)
                            temp_family.set_father_handle(self.obj)

            if error:
                msg2 = _("Problem changing the gender")
                msg = _("Changing the gender caused problems "
                        "with marriage information.\nPlease check "
                        "the person's marriages.")
                ErrorDialog(msg2, msg)

    def save(self, *obj):
        """
        Save the data.
        """
        self.ok_button.set_sensitive(False)
        if self.object_is_empty():
            ErrorDialog(
                _("Cannot save person"),
                _("No data exists for this person. Please "
                  "enter data or cancel the edit."))
            self.ok_button.set_sensitive(True)
            return
        # fix surname problems
        for name in [self.obj.get_primary_name()
                     ] + self.obj.get_alternate_names():
            if len(name.get_surname_list()) > 1:
                newlist = [
                    surn for surn in name.get_surname_list()
                    if not surn.is_empty()
                ]
                if len(newlist) != len(name.get_surname_list()):
                    name.set_surname_list(newlist)
            if len(name.get_surname_list()) == 0:
                name.set_surname_list([gen.lib.Surname()])
            try:
                primind = [
                    surn.get_primary() for surn in name.get_surname_list()
                ].index(True)
            except ValueError:
                primind = 0  # no match
            name.set_primary_surname(primind)

        # fix ide problems
        (uses_dupe_id, id) = self._uses_duplicate_id()
        if uses_dupe_id:
            prim_object = self.get_from_gramps_id(id)
            name = self.name_displayer.display(prim_object)
            msg1 = _("Cannot save person. ID already exists.")
            msg2 = _("You have attempted to use the existing Gramps ID with "
                     "value %(id)s. This value is already used by '"
                     "%(prim_object)s'. Please enter a different ID or leave "
                     "blank to get the next available ID value.") % {
                         'id': id,
                         'prim_object': name
                     }
            ErrorDialog(msg1, msg2)
            self.ok_button.set_sensitive(True)
            return

        self._check_for_unknown_gender()

        self.db.set_birth_death_index(self.obj)

        with DbTxn('', self.db) as trans:
            self._update_family_ids()
            if not self.obj.get_handle():
                self.db.add_person(self.obj, trans)
                msg = _("Add Person (%s)") % \
                        self.name_displayer.display(self.obj)
            else:
                if not self.obj.get_gramps_id():
                    self.obj.set_gramps_id(
                        self.db.find_next_person_gramps_id())
                self.db.commit_person(self.obj, trans)
                msg = _("Edit Person (%s)") % \
                        self.name_displayer.display(self.obj)
            trans.set_description(msg)

        self.close()
        if self.callback:
            self.callback(self.obj)
        self.callback = None

    def _edit_name_clicked(self, obj):
        """
        Bring up the EditName dialog for this name.

        Called when the edit name button is clicked for the primary name
        on the main form (not in the names tab).

        """
        try:
            EditName(self.dbstate, self.uistate, self.track, self.pname,
                     self._update_name)
        except Errors.WindowActiveError:
            pass

    def _mult_surn_clicked(self, obj):
        """
        Show the list entry of multiple surnames
        """
        self.singsurnfr.hide_all()
        self.singlesurn_active = False
        #update multsurnfr for possible changes
        self.__renewmultsurnames()
        self.multsurnfr.show_all()

    def _update_name(self, name):
        """
        Called when the primary name has been changed by the EditName
        dialog.

        This allows us to update the main form in response to any changes.

        """
        for obj in (self.ntype_field, self.given, self.call, self.title,
                    self.suffix, self.nick, self.surname_field, self.prefix,
                    self.ortype_field):
            obj.update()

        self.__renewmultsurnames()

        if len(self.obj.get_primary_name().get_surname_list()) == 1:
            self.singlesurn_active = True
        else:
            self.singlesurn_active = False
        if self.singlesurn_active:
            self.multsurnfr.hide_all()
            self.singsurnfr.show_all()

        else:
            self.singsurnfr.hide_all()
            self.multsurnfr.show_all()

    def __renewmultsurnames(self):
        """Update mult surnames section with what is presently the
           correct surname.
           It is easier to recreate the entire mult surnames GUI than
           changing what has changed visually.
        """
        #remove present surname tab, and put new one
        msurhbox = self.top.get_object("hboxmultsurnames")
        msurhbox.remove(self.surntab)
        self.surntab = SurnameTab(self.dbstate, self.uistate, self.track,
                                  self.obj.get_primary_name())
        self.multsurnfr.set_size_request(
            -1, int(config.get('interface.surname-box-height')))
        msurhbox.pack_start(self.surntab)

    def load_person_image(self):
        """
        Load the primary image into the main form if it exists.

        Used as callback on Gallery Tab too.

        """
        media_list = self.obj.get_media_list()
        if media_list:
            ref = media_list[0]
            handle = ref.get_reference_handle()
            obj = self.dbstate.db.get_object_from_handle(handle)
            if obj is None:
                #notify user of error
                from QuestionDialog import RunDatabaseRepair
                RunDatabaseRepair(_('Non existing media found in the Gallery'))
            else:
                self.load_photo(ref, obj)
        else:
            self.obj_photo.hide()
            self.frame_photo.hide_all()

    def load_photo(self, ref, obj):
        """
        Load the person's main photo using the Thumbnailer.
        """
        pixbuf = ThumbNails.get_thumbnail_image(
            Utils.media_path_full(self.dbstate.db, obj.get_path()),
            obj.get_mime_type(), ref.get_rectangle())

        self.obj_photo.set_from_pixbuf(pixbuf)
        self.obj_photo.show()
        self.frame_photo.show_all()

    def birth_dates_in_order(self, child_ref_list):
        """
        Check any *valid* birthdates in the list to insure that they are in
        numerically increasing order.
        """
        inorder = True
        prev_date = 0
        handle_list = [ref.ref for ref in child_ref_list]
        for i in range(len(handle_list)):
            child_handle = handle_list[i]
            child = self.db.get_person_from_handle(child_handle)
            if child.get_birth_ref():
                event_handle = child.get_birth_ref().ref
                event = self.db.get_event_from_handle(event_handle)
                child_date = event.get_date_object().get_sort_value()
            else:
                continue
            if (prev_date <= child_date):  # <= allows for twins
                prev_date = child_date
            else:
                inorder = False
        return inorder

    def reorder_child_ref_list(self, person, child_ref_list):
        """
        Reorder the child list to put the specified person in his/her
        correct birth order.

        Only check *valid* birthdates.  Move the person as short a distance as
        possible.

        """

        if self.birth_dates_in_order(child_ref_list):
            return (child_ref_list)

        # Build the person's date string once
        event_ref = person.get_birth_ref()
        if event_ref:
            event = self.db.get_event_from_handle(event_ref.ref)
            person_bday = event.get_date_object().get_sort_value()
        else:
            person_bday = 0

        # First, see if the person needs to be moved forward in the list
        handle_list = [ref.ref for ref in child_ref_list]

        index = handle_list.index(person.get_handle())
        target = index
        for i in range(index - 1, -1, -1):
            other = self.db.get_person_from_handle(handle_list[i])
            event_ref = other.get_birth_ref()
            if event_ref:
                event = self.db.get_event_from_handle(event_ref.ref)
                other_bday = event.get_date_object().get_sort_value()
                if other_bday == 0:
                    continue
                if person_bday < other_bday:
                    target = i
            else:
                continue

        # Now try moving to a later position in the list
        if (target == index):
            for i in range(index, len(handle_list)):
                other = self.db.get_person_from_handle(handle_list[i])
                event_ref = other.get_birth_ref()
                if event_ref:
                    event = self.db.get_event_from_handle(event_ref.ref)
                    other_bday = event.get_date_object().get_sort_value()
                    if other_bday == "99999999":
                        continue
                    if person_bday > other_bday:
                        target = i
                else:
                    continue

        # Actually need to move?  Do it now.
        if (target != index):
            ref = child_ref_list.pop(index)
            child_ref_list.insert(target, ref)
        return child_ref_list

    def _cleanup_on_exit(self):
        """Unset all things that can block garbage collection.
        Finalize rest
        """
        ##        self.private.destroy()
        ##        self.gender.destroy()
        ##        self.ntype_field.destroy()
        ##        self.given.destroy()
        ##        self.call.destroy()
        ##        self.title.destroy()
        ##        self.suffix.destroy()
        ##        self.nick.destroy()
        ##        self.surname_field.destroy()
        ##        self.prefix.destroy()
        ##        self.ortype_field.destroy()
        ##        self.tags.destroy()
        ##        self.gid.destroy()
        EditPrimary._cleanup_on_exit(self)
Example #9
0
class EditPersonRef(EditSecondary):
    """
    Displays a dialog that allows the user to edit a person reference.
    """

    def __init__(self, dbstate, uistate, track, addr, callback):
        """
        Displays the dialog box.

        parent - The class that called the PersonRef editor.
        addr - The address that is to be edited
        """
        EditSecondary.__init__(self, dbstate, uistate, track, addr, callback)

    def _local_init(self):
        self.width_key = 'interface.person-ref-width'
        self.height_key = 'interface.person-ref-height'
        
        self.top = Glade()
                
        self.set_window(self.top.toplevel,
                        self.top.get_object("title"),
                        _('Person Reference Editor'))
        self.person_label = self.top.get_object('person')

    def _setup_fields(self):

        if self.obj.ref:
            p = self.dbstate.db.get_person_from_handle(self.obj.ref)
            self.person_label.set_text(name_displayer.display(p))
        
        self.street = MonitoredEntry(
            self.top.get_object("relationship"),
            self.obj.set_relation,
            self.obj.get_relation,
            self.db.readonly)

        self.priv = PrivacyButton(
            self.top.get_object("private"),
            self.obj,
            self.db.readonly)

    def _connect_signals(self):
        #self.define_help_button(self.top.get_object('help'))
        self.define_cancel_button(self.top.get_object('cancel'))
        self.define_ok_button(self.top.get_object('ok'),self.save)
        self.top.get_object('select').connect('clicked',self._select_person)

    def _connect_db_signals(self):
        """
        Connect any signals that need to be connected. 
        Called by the init routine of the base class (_EditPrimary).
        """
        self._add_db_signal('person-rebuild', self.close)
        self._add_db_signal('person-delete', self.check_for_close)

    def check_for_close(self, handles):
        """
        Callback method for delete signals. 
        If there is a delete signal of the primary object we are editing, the
        editor (and all child windows spawned) should be closed
        """
        if self.obj.ref in handles:
            self.close()

    def _select_person(self, obj):
        SelectPerson = SelectorFactory('Person')

        sel = SelectPerson(self.dbstate, self.uistate, self.track)
        person = sel.run()

        if person:
            self.obj.ref = person.get_handle()
            self.person_label.set_text(name_displayer.display(person))

    def _create_tabbed_pages(self):
        """
        Create the notebook tabs and inserts them into the main
        window.
        """
        
        notebook = gtk.Notebook()
        
        self.srcref_list = CitationEmbedList(self.dbstate, self.uistate,
                                             self.track, 
                                             self.obj.get_citation_list())
        self._add_tab(notebook, self.srcref_list)
        self.track_ref_for_deletion("srcref_list")

        self.note_tab = NoteTab(self.dbstate, self.uistate, self.track,
                                self.obj.get_note_list(),
                                notetype=NoteType.ASSOCIATION)
        self._add_tab(notebook, self.note_tab)
        self.track_ref_for_deletion("note_tab")

        self._setup_notebook_tabs(notebook)
        notebook.show_all()
        self.top.get_object('vbox').pack_start(notebook, True)

    def build_menu_names(self, obj):
        return (_('Person Reference'),_('Person Reference Editor'))

    def save(self,*obj):
        """
        Called when the OK button is pressed. Gets data from the
        form and updates the Address data structure.
        """

        if self.obj.ref:
            if self.callback:
                self.callback(self.obj)
            self.callback = None
            self.close()
        else:
            from QuestionDialog import ErrorDialog

            ErrorDialog(
                _('No person selected'),
                _('You must either select a person or Cancel '
                  'the edit'))
Example #10
0
class ManagedWindow(object):
    """
    Managed window base class.
    
    This class provides all the goodies necessary for user-friendly window
    management in GRAMPS: registering the menu item under the Windows
    menu, keeping track of child windows, closing them on close/delete
    event, and presenting itself when selected or attempted to create again.
    """
    def __init__(self, uistate, track, obj):
        """
        Create child windows and add itself to menu, if not there already.
        
        
        The usage from derived classes is envisioned as follows:
        
        
        import ManagedWindow
        class SomeWindowClass(ManagedWindow.ManagedWindow):
            def __init__(self,uistate,dbstate,track):
                window_id = self        # Or e.g. window_id = person.handle
                submenu_label = None    # This window cannot have children
                menu_label = 'Menu label for this window'
                ManagedWindow.ManagedWindow.__init__(self,
                                                    uistate,
                                                    track,
                                                    window_id,
                                                    submenu_label,
                                                    menu_label)
                # Proceed with the class.
                ...
                
        :param uistate:  gramps uistate
        :param track:    {list of parent windows, [] if the main GRAMPS window 
                            is the parent}
        :param obj:      The object that is used to id the managed window, 
                            The inheriting object needs a method build_menu_names(self, obj)
                            which works on this obj and creates menu labels
                            for use in the Gramps Window Menu.
                            If self.submenu_label ='' then leaf, otherwise branch
         
                
        """
        window_key = self.build_window_key(obj)
        menu_label, submenu_label = self.build_menu_names(obj)
        self._gladeobj = None
        self.isWindow = None
        self.width_key = None
        self.height_key = None
        self.__refs_for_deletion = []

        if uistate and uistate.gwm.get_item_from_id(window_key):
            uistate.gwm.get_item_from_id(window_key).present()
            raise Errors.WindowActiveError('This window is already active')
        else:
            self.window_id = window_key
            self.submenu_label = submenu_label
            self.menu_label = menu_label
            self.uistate = uistate
            if uistate:
                self.track = self.uistate.gwm.add_item(track, self)
            else:
                self.track = []
            # Work out parent_window
            if len(self.track) > 1:
                # We don't belong to the lop level
                if self.track[-1] > 0:
                    # If we are not the first in the group,
                    # then first in that same group is our parent
                    parent_item_track = self.track[:-1]
                    parent_item_track.append(0)
                else:
                    # If we're first in the group, then our parent
                    # is the first in the group one level up
                    parent_item_track = self.track[:-2]
                    parent_item_track.append(0)

                # Based on the track, get item and then window object
                self.parent_window = self.uistate.gwm.get_item_from_track(
                    parent_item_track).window
            else:
                # On the top level: we use gramps top window
                if self.uistate:
                    self.parent_window = self.uistate.window
                else:
                    self.parent_window = None

    def set_window(self, window, title, text, msg=None, isWindow=False):
        """
        Set the window that is managed.
        
        :param window:   if isWindow=False window must be a gtk.Window() object, otherwise None
        :param title:    a label widget in which to write the title, None if not needed
        :param text:     text to use as title of window and in title param
        :param msg:      if not None, use msg as title of window instead of text
        :param isWindow: {if isWindow than self is the window 
                            (so self inherits from gtk.Window and 
                            from ManagedWindow.ManagedWindow)
                         if not isWindow, than window is the Window to manage, 
                            and after this method self.window stores it.
                        }
        
        """
        self.isWindow = isWindow
        self.msg = msg
        self.titlelabel = title
        if self.isWindow:
            set_titles(self, title, text, msg)
        else:
            set_titles(window, title, text, msg)
            #closing the gtk.Window must also close ManagedWindow
            self.window = window
            self.window.connect('delete-event', self.close)

    def update_title(self, text):
        if self.isWindow:
            set_titles(self, self.titlelabel, text, self.msg)
        else:
            set_titles(self.window, self.titlelabel, text, self.msg)

    def build_menu_names(self, obj):
        return ('Undefined Menu', 'Undefined Submenu')

    def build_window_key(self, obj):
        return id(obj)

    def define_glade(self, top_module, glade_file=None):
        if glade_file is None:
            raise TypeError, "ManagedWindow.define_glade: no glade file"
            glade_file = const.GLADE_FILE
        self._gladeobj = Glade(glade_file, None, top_module)
        return self._gladeobj

    def get_widget(self, name):
        assert (self._gladeobj)
        object = self._gladeobj.get_child_object(name)
        if object is not None:
            return object
        raise ValueError, (
            'ManagedWindow.get_widget: "%s" widget not found in "%s/%s"' %
            (name, self._gladeobj.dirname, self._gladeobj.filename))
        return object

    def connect_button(self, button_name, function):
        assert (self._gladeobj)
        self.get_widget(button_name).connect('clicked', function)

    def show(self):
        if self.isWindow:
            self.set_transient_for(self.parent_window)
            self.opened = True
            self.show_all()

        else:
            assert self.window, "ManagedWindow: self.window does not exist!"
            self.window.set_transient_for(self.parent_window)
            self.opened = True
            self.window.show_all()

    def modal_call(self, after_ok_func=None):
        """ 
            Method to do modal run of the ManagedWindow.
            Connect the OK button to a method that checks if all is ok, 
                Do not call close, close is called here.
                (if not ok, do self.window.run() to obtain new response )
                TODO: remove close here and do close in ReportDialog,
                      this can only be done, once all methods use modal_call()
                      instead of their own implementation
            Connect Cancel to do close, delete event is connected to close 
                here in ManagedWindow.
            Do not generete RESPONSE_OK/CANCEL/DELETE_EVENT on button clicks 
            of other buttons
            after_ok_func is called on ok click in this method
        """
        #self.show()
        while True:
            response = self.window.run()
            if response == gtk.RESPONSE_OK:
                # dialog will be closed by connect, now continue work while
                # rest of dialog is unresponsive, release when finished
                self.close()
                if after_ok_func is not None:
                    after_ok_func()
                break
            elif (response == gtk.RESPONSE_DELETE_EVENT
                  or response == gtk.RESPONSE_CANCEL):
                # connect buttons generating this to a close call
                break

    def close(self, *obj):
        """
        Close itself.

        Takes care of closing children and removing itself from menu.
        """
        self._save_size()
        self.clean_up()
        self.uistate.gwm.close_track(self.track)
        self.opened = False
        self.parent_window.present()

    def present(self):
        """
        Present window (unroll/unminimize/bring to top).
        """
        if self.isWindow:
            self.present(self)
        else:
            assert hasattr(self, 'window'), \
                   "ManagedWindow: self.window does not exist!"
            self.window.present()

    def _set_size(self):
        """
        Set the dimensions of the window
        """
        if self.width_key is not None:
            width = config.get(self.width_key)
            height = config.get(self.height_key)
            self.window.resize(width, height)

    def _save_size(self):
        """
        Save the dimensions of the window to the config file
        """
        if self.width_key is not None:
            (width, height) = self.window.get_size()
            config.set(self.width_key, width)
            config.set(self.height_key, height)
            config.save()

    def track_ref_for_deletion(self, ref):
        """
        Record references of instance variables that need to be removed
        from scope so that the class can be garbage collected
        """
        if ref not in self.__refs_for_deletion:
            self.__refs_for_deletion.append(ref)

    def clean_up(self):
        """
        Remove any instance variables from scope which point to non-glade
        GTK objects so that the class can be garbage collected.
        If the object is a Gramps widget then it should have a clean_up method
        which can be called that removes any other GTK object it contains.
        """
        while len(self.__refs_for_deletion):
            attr = self.__refs_for_deletion.pop()
            obj = getattr(self, attr)
            if hasattr(obj, 'clean_up'):
                obj.clean_up()
            delattr(self, attr)
Example #11
0
class EditMediaRef(EditReference):
    def __init__(self, state, uistate, track, media, media_ref, update):
        EditReference.__init__(self, state, uistate, track, media, media_ref,
                               update)
        if not self.source.get_handle():
            #show the addmedia dialog immediately, with track of parent.
            AddMediaObject(state, self.uistate, self.track, self.source,
                           self._update_addmedia)

    def _local_init(self):
        self.width_key = 'interface.media-ref-width'
        self.height_key = 'interface.media-ref-height'
        self.top = Glade()

        self.set_window(self.top.toplevel, self.top.get_object('title'),
                        _('Media Reference Editor'))
        self.define_warn_box(self.top.get_object("warn_box"))
        self.top.get_object("label427").set_text(_("Y coordinate|Y"))
        self.top.get_object("label428").set_text(_("Y coordinate|Y"))

        tblref = self.top.get_object('table50')
        self.notebook_ref = self.top.get_object('notebook_ref')
        self.track_ref_for_deletion("notebook_ref")
        #recreate start page as GrampsTab
        self.notebook_ref.remove_page(0)
        self.reftab = RefTab(self.dbstate, self.uistate, self.track,
                             _('General'), tblref)
        self.track_ref_for_deletion("reftab")
        tblref = self.top.get_object('table2')
        self.notebook_shared = self.top.get_object('notebook_shared')
        #recreate start page as GrampsTab
        self.notebook_shared.remove_page(0)
        self.track_ref_for_deletion("notebook_shared")
        self.primtab = RefTab(self.dbstate, self.uistate, self.track,
                              _('_General'), tblref)
        self.track_ref_for_deletion("primtab")
        self.rect_pixbuf = None

    def setup_filepath(self):
        self.select = self.top.get_object('file_select')
        self.track_ref_for_deletion("select")
        self.file_path = self.top.get_object("path")
        self.track_ref_for_deletion("file_path")

        self.file_path.set_text(self.source.get_path())
        self.select.connect('clicked', self.select_file)

    def determine_mime(self):
        descr = gen.mime.get_description(self.source.get_mime_type())
        if descr:
            self.mimetext.set_text(descr)

        path = self.file_path.get_text()
        path_full = Utils.media_path_full(self.db, path)
        if path != self.source.get_path(
        ) and path_full != self.source.get_path():
            #redetermine mime
            mime = gen.mime.get_type(Utils.find_file(path_full))
            self.source.set_mime_type(mime)
            descr = gen.mime.get_description(mime)
            if descr:
                self.mimetext.set_text(descr)
            else:
                self.mimetext.set_text(_('Unknown'))
        #if mime type not set, is note
        if not self.source.get_mime_type():
            self.mimetext.set_text(_('Note'))

    def draw_preview(self):
        """
        Draw the two preview images. This method can be called on eg change of
        the path.
        """
        mtype = self.source.get_mime_type()
        if mtype:
            fullpath = Utils.media_path_full(self.db, self.source.get_path())
            pb = ThumbNails.get_thumbnail_image(fullpath, mtype)
            self.pixmap.set_from_pixbuf(pb)
            subpix = ThumbNails.get_thumbnail_image(fullpath, mtype,
                                                    self.rectangle)
            self.subpixmap.set_from_pixbuf(subpix)
        else:
            pb = ThumbNails.find_mime_type_pixbuf('text/plain')
            self.pixmap.set_from_pixbuf(pb)
            self.subpixmap.set_from_pixbuf(pb)

    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(gtk.gdk.BUTTON_PRESS_MASK)
            ebox_ref.add_events(gtk.gdk.BUTTON_RELEASE_MASK)

        self.pixmap = self.top.get_object("pixmap")
        self.mimetext = self.top.get_object("type")
        self.track_ref_for_deletion("mimetext")

        coord = self.source_ref.get_rectangle()
        #upgrade path: set invalid (from eg old db) to none

        if coord is not None and coord in ((None, ) * 4, (0, 0, 100, 100),
                                           (coord[0], coord[1]) * 2):
            coord = None

        self.rectangle = coord
        self.subpixmap = self.top.get_object("subpixmap")
        self.track_ref_for_deletion("subpixmap")

        self.setup_filepath()
        self.determine_mime()
        self.draw_preview()

        corners = ["corner1_x", "corner1_y", "corner2_x", "corner2_y"]

        if coord and isinstance(coord, tuple):
            for index, corner in enumerate(corners):
                self.top.get_object(corner).set_value(coord[index])
        else:
            for corner, value in zip(corners, [0, 0, 100, 100]):
                self.top.get_object(corner).set_value(value)

        if self.dbstate.db.readonly:
            for corner in corners:
                self.top.get_object(corner).set_sensitive(False)

        self.corner1_x_spinbutton = MonitoredSpinButton(
            self.top.get_object("corner1_x"), self.set_corner1_x,
            self.get_corner1_x, self.db.readonly)
        self.track_ref_for_deletion("corner1_x_spinbutton")

        self.corner1_y_spinbutton = MonitoredSpinButton(
            self.top.get_object("corner1_y"), self.set_corner1_y,
            self.get_corner1_y, self.db.readonly)
        self.track_ref_for_deletion("corner1_y_spinbutton")

        self.corner2_x_spinbutton = MonitoredSpinButton(
            self.top.get_object("corner2_x"), self.set_corner2_x,
            self.get_corner2_x, self.db.readonly)
        self.track_ref_for_deletion("corner2_x_spinbutton")

        self.corner2_y_spinbutton = MonitoredSpinButton(
            self.top.get_object("corner2_y"), self.set_corner2_y,
            self.get_corner2_y, self.db.readonly)
        self.track_ref_for_deletion("corner2_y_spinbutton")

        self.descr_window = MonitoredEntry(self.top.get_object("description"),
                                           self.source.set_description,
                                           self.source.get_description,
                                           self.db.readonly)

        self.ref_privacy = PrivacyButton(self.top.get_object("private"),
                                         self.source_ref, self.db.readonly)

        self.gid = MonitoredEntry(self.top.get_object("gid"),
                                  self.source.set_gramps_id,
                                  self.source.get_gramps_id, self.db.readonly)

        self.privacy = PrivacyButton(self.top.get_object("privacy"),
                                     self.source, self.db.readonly)

        self.path_obj = MonitoredEntry(self.top.get_object("path"),
                                       self.source.set_path,
                                       self.source.get_path, self.db.readonly)

        self.date_field = MonitoredDate(self.top.get_object("date_entry"),
                                        self.top.get_object("date_edit"),
                                        self.source.get_date_object(),
                                        self.uistate, self.track,
                                        self.db.readonly)

        self.tags = MonitoredTagList(self.top.get_object("tag_label"),
                                     self.top.get_object("tag_button"),
                                     self.source.set_tag_list,
                                     self.source.get_tag_list, self.db,
                                     self.uistate, self.track,
                                     self.db.readonly)

    def set_corner1_x(self, value):
        """
        Callback for the signal handling of the spinbutton for the first
        corner x coordinate of the subsection.
        Updates the subsection thumbnail using the given value
        
        @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()

    def set_corner1_y(self, value):
        """
        Callback for the signal handling of the spinbutton for the first
        corner y coordinate of the subsection.
        Updates the subsection thumbnail using the given value
        
        @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()

    def set_corner2_x(self, value):
        """
        Callback for the signal handling of the spinbutton for the second
        corner x coordinate of the subsection.
        Updates the subsection thumbnail using the given value
        
        @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()

    def set_corner2_y(self, value):
        """
        Callback for the signal handling of the spinbutton for the second
        corner y coordinate of the subsection.
        Updates the subsection thumbnail using the given value
        
        @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()

    def get_corner1_x(self):
        """
        Callback for the signal handling of the spinbutton for the first corner 
        x coordinate of the subsection.
        
        @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

    def get_corner1_y(self):
        """
        Callback for the signal handling of the spinbutton for the first corner 
        y coordinate of the subsection.
        
        @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

    def get_corner2_x(self):
        """
        Callback for the signal handling of the spinbutton for the second
        corner x coordinate of the subsection.
        
        @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

    def get_corner2_y(self):
        """
        Callback for the signal handling of the spinbutton for the second
        corner x coordinate of the subsection.
        
        @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

    def update_subpixmap(self):
        """
        Updates the thumbnail of the specified subsection
        """

        path = self.source.get_path()
        if path is None:
            self.subpixmap.hide()
        else:
            try:
                fullpath = Utils.media_path_full(self.db, path)
                pixbuf = gtk.gdk.pixbuf_new_from_file(fullpath)
                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):
        """
        Provide the information needed by the base class to define the
        window management menu entries.
        """
        if self.source:
            submenu_label = _('Media: %s') % self.source.get_gramps_id()
        else:
            submenu_label = _('New Media')
        return (_('Media Reference Editor'), submenu_label)

    def button_press_event(self, obj, event):
        if event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS:
            photo_path = Utils.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.rect_pixbuf = self.subpixmap.get_pixbuf()
        w, h = self.rect_pixbuf.get_width(), self.rect_pixbuf.get_height()
        self.rect_pixbuf_render = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False,
                                                 8, w, h)
        cm = gtk.gdk.colormap_get_system()
        color = cm.alloc_color(gtk.gdk.Color("blue"))
        self.rect_pixmap = gtk.gdk.Pixmap(None, w, h, cm.get_visual().depth)
        self.rect_pixmap.set_colormap(cm)
        self.rect_gc = self.rect_pixmap.new_gc()
        self.rect_gc.set_foreground(color)

    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 const.THUMBSCALE)
        w, h = self.rect_pixbuf.get_width(), self.rect_pixbuf.get_height()
        offset_x = (const.THUMBSCALE - w) / 2
        offset_y = 0

        self.rect_pixmap.draw_pixbuf(self.rect_gc, self.rect_pixbuf, 0, 0, 0,
                                     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)

        self.rect_pixmap.draw_rectangle(self.rect_gc, False, x1, y1, width,
                                        height)

        self.rect_pixbuf_render.get_from_drawable(
            self.rect_pixmap, gtk.gdk.colormap_get_system(), 0, 0, 0, 0, w, h)
        self.subpixmap.set_from_pixbuf(self.rect_pixbuf_render)

    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 == gtk.gdk._2BUTTON_PRESS) or \
            (event.button==1 and (event.state & gtk.gdk.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.rect_pixbuf == None):
                return
            self.subpixmap.set_from_pixbuf(self.rect_pixbuf)

            # 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 const.THUMBSCALE)
                w = self.rect_pixbuf.get_width()
                h = self.rect_pixbuf.get_height()
                x = (const.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.rect_pixbuf = None

    def _update_addmedia(self, obj):
        """
        Called when the add media dialog has been called.
        This allows us to update the main form in response to
        any changes: Redraw relevant fields: description, mimetype and path
        """
        for obj in (self.descr_window, self.path_obj):
            obj.update()
        self.determine_mime()
        self.draw_preview()

    def select_file(self, val):
        self.determine_mime()
        path = self.file_path.get_text()
        self.source.set_path(Utils.get_unicode_path_from_file_chooser(path))
        AddMediaObject(self.dbstate, self.uistate, self.track, self.source,
                       self._update_addmedia)

    def _connect_signals(self):
        self.define_cancel_button(self.top.get_object('button84'))
        self.define_ok_button(self.top.get_object('button82'), self.save)

    def _connect_db_signals(self):
        """
        Connect any signals that need to be connected. 
        Called by the init routine of the base class (_EditPrimary).
        """
        self._add_db_signal('media-rebuild', self.close)
        self._add_db_signal('media-delete', self.check_for_close)

    def _create_tabbed_pages(self):
        """
        Create the notebook tabs and inserts them into the main
        window.
        """
        notebook_ref = self.top.get_object('notebook_ref')
        notebook_src = self.top.get_object('notebook_shared')

        self._add_tab(notebook_src, self.primtab)
        self._add_tab(notebook_ref, self.reftab)

        self.srcref_list = CitationEmbedList(
            self.dbstate, self.uistate, self.track,
            self.source_ref.get_citation_list())
        self._add_tab(notebook_ref, self.srcref_list)
        self.track_ref_for_deletion("srcref_list")

        self.attr_list = MediaAttrEmbedList(
            self.dbstate, self.uistate, self.track,
            self.source_ref.get_attribute_list())
        self._add_tab(notebook_ref, self.attr_list)
        self.track_ref_for_deletion("attr_list")

        self.backref_list = MediaBackRefList(
            self.dbstate, self.uistate, self.track,
            self.db.find_backlink_handles(self.source.handle),
            self.enable_warnbox)
        self._add_tab(notebook_src, self.backref_list)
        self.track_ref_for_deletion("backref_list")

        self.note_ref_tab = NoteTab(self.dbstate,
                                    self.uistate,
                                    self.track,
                                    self.source_ref.get_note_list(),
                                    notetype=NoteType.MEDIAREF)
        self._add_tab(notebook_ref, self.note_ref_tab)
        self.track_ref_for_deletion("note_ref_tab")

        self.src_srcref_list = CitationEmbedList(
            self.dbstate, self.uistate, self.track,
            self.source.get_citation_list())
        self._add_tab(notebook_src, self.src_srcref_list)
        self.track_ref_for_deletion("src_srcref_list")

        self.src_attr_list = MediaAttrEmbedList(
            self.dbstate, self.uistate, self.track,
            self.source.get_attribute_list())
        self._add_tab(notebook_src, self.src_attr_list)
        self.track_ref_for_deletion("src_attr_list")

        self.src_note_ref_tab = NoteTab(self.dbstate,
                                        self.uistate,
                                        self.track,
                                        self.source.get_note_list(),
                                        notetype=NoteType.MEDIA)
        self._add_tab(notebook_src, self.src_note_ref_tab)
        self.track_ref_for_deletion("src_note_ref_tab")

        self._setup_notebook_tabs(notebook_src)
        self._setup_notebook_tabs(notebook_ref)

    def save(self, *obj):

        #first save primary object
        if self.source.handle:
            with DbTxn(
                    _("Edit Media Object (%s)") %
                    self.source.get_description(), self.db) as trans:
                self.db.commit_media_object(self.source, trans)
        else:
            if self.check_for_duplicate_id('Media'):
                return
            with DbTxn(
                    _("Add Media Object (%s)") % self.source.get_description(),
                    self.db) as trans:
                self.db.add_object(self.source, trans)

        #save reference object in memory
        coord = (
            self.top.get_object("corner1_x").get_value_as_int(),
            self.top.get_object("corner1_y").get_value_as_int(),
            self.top.get_object("corner2_x").get_value_as_int(),
            self.top.get_object("corner2_y").get_value_as_int(),
        )

        #do not set unset or invalid coord

        if coord is not None and coord in ((None, ) * 4, (0, 0, 100, 100),
                                           (coord[0], coord[1]) * 2):
            coord = None

        self.source_ref.set_rectangle(coord)

        #call callback if given
        if self.update:
            self.update(self.source_ref, self.source)
            self.update = None
        self.close()
Example #12
0
    def __init__(self,
                 dbstate,
                 uistate,
                 track=[],
                 filter=None,
                 skip=set(),
                 show_search_bar=True,
                 default=None):
        """Set up the dialog with the dbstate and uistate, track of parent
            windows for ManagedWindow, initial filter for the model, skip with
            set of handles to skip in the view, and search_bar to show the 
            SearchBar at the top or not. 
        """
        self.filter = (2, filter, False)

        # Set window title, some selectors may set self.title in their __init__
        if not hasattr(self, 'title'):
            self.title = self.get_window_title()

        ManagedWindow.ManagedWindow.__init__(self, uistate, track, self)

        self.renderer = gtk.CellRendererText()
        self.track_ref_for_deletion("renderer")
        self.renderer.set_property('ellipsize', pango.ELLIPSIZE_END)

        self.db = dbstate.db
        self.tree = None
        self.model = None

        self.glade = Glade()

        window = self.glade.toplevel
        self.showall = self.glade.get_object('showall')
        title_label = self.glade.get_object('title')
        vbox = self.glade.get_object('select_person_vbox')
        self.tree = self.glade.get_object('plist')
        self.tree.set_headers_visible(True)
        self.tree.set_headers_clickable(True)
        self.tree.connect('row-activated', self._on_row_activated)
        self.tree.grab_focus()

        #add the search bar
        self.search_bar = SearchBar(dbstate, uistate, self.build_tree)
        filter_box = self.search_bar.build()
        self.setup_filter()
        vbox.pack_start(filter_box, False, False)
        vbox.reorder_child(filter_box, 1)

        self.set_window(window, title_label, self.title)

        #set up sorting
        self.sort_col = 0
        self.setupcols = True
        self.columns = []
        self.sortorder = gtk.SORT_ASCENDING

        self.skip_list = skip
        self.build_tree()
        self.selection = self.tree.get_selection()
        self.track_ref_for_deletion("selection")

        self._local_init()
        self._set_size()

        self.show()
        #show or hide search bar?
        self.set_show_search_bar(show_search_bar)
        #Hide showall if no filter is specified
        if self.filter[1] is not None:
            self.showall.connect('toggled', self.show_toggle)
            self.showall.show()
        else:
            self.showall.hide()
        if default:
            self.goto_handle(default)
Example #13
0
class BaseSelector(ManagedWindow.ManagedWindow):
    """Base class for the selectors, showing a dialog from which to select
        one of the primary objects
    """

    NONE = -1
    TEXT = 0
    MARKUP = 1
    IMAGE = 2

    def __init__(self,
                 dbstate,
                 uistate,
                 track=[],
                 filter=None,
                 skip=set(),
                 show_search_bar=True,
                 default=None):
        """Set up the dialog with the dbstate and uistate, track of parent
            windows for ManagedWindow, initial filter for the model, skip with
            set of handles to skip in the view, and search_bar to show the 
            SearchBar at the top or not. 
        """
        self.filter = (2, filter, False)

        # Set window title, some selectors may set self.title in their __init__
        if not hasattr(self, 'title'):
            self.title = self.get_window_title()

        ManagedWindow.ManagedWindow.__init__(self, uistate, track, self)

        self.renderer = gtk.CellRendererText()
        self.track_ref_for_deletion("renderer")
        self.renderer.set_property('ellipsize', pango.ELLIPSIZE_END)

        self.db = dbstate.db
        self.tree = None
        self.model = None

        self.glade = Glade()

        window = self.glade.toplevel
        self.showall = self.glade.get_object('showall')
        title_label = self.glade.get_object('title')
        vbox = self.glade.get_object('select_person_vbox')
        self.tree = self.glade.get_object('plist')
        self.tree.set_headers_visible(True)
        self.tree.set_headers_clickable(True)
        self.tree.connect('row-activated', self._on_row_activated)
        self.tree.grab_focus()

        #add the search bar
        self.search_bar = SearchBar(dbstate, uistate, self.build_tree)
        filter_box = self.search_bar.build()
        self.setup_filter()
        vbox.pack_start(filter_box, False, False)
        vbox.reorder_child(filter_box, 1)

        self.set_window(window, title_label, self.title)

        #set up sorting
        self.sort_col = 0
        self.setupcols = True
        self.columns = []
        self.sortorder = gtk.SORT_ASCENDING

        self.skip_list = skip
        self.build_tree()
        self.selection = self.tree.get_selection()
        self.track_ref_for_deletion("selection")

        self._local_init()
        self._set_size()

        self.show()
        #show or hide search bar?
        self.set_show_search_bar(show_search_bar)
        #Hide showall if no filter is specified
        if self.filter[1] is not None:
            self.showall.connect('toggled', self.show_toggle)
            self.showall.show()
        else:
            self.showall.hide()
        if default:
            self.goto_handle(default)

    def goto_handle(self, handle):
        """
        Goto the correct row.
        """
        try:  # tree:
            path = None
            node = self.model.get_node(handle)
            if node:
                parent_node = self.model.on_iter_parent(node)
                if parent_node:
                    parent_path = self.model.on_get_path(parent_node)
                    if parent_path:
                        for i in range(len(parent_path)):
                            expand_path = tuple(
                                [x for x in parent_path[:i + 1]])
                            self.tree.expand_row(expand_path, False)
                path = self.model.on_get_path(node)
        except:  # flat:
            try:
                path = self.model.on_get_path(handle)
            except:
                path = None

        if path is not None:
            self.selection.unselect_all()
            self.selection.select_path(path)
            self.tree.scroll_to_cell(path, None, 1, 0.5, 0)
        else:
            # not in list
            self.selection.unselect_all()

    def add_columns(self, tree):
        tree.set_fixed_height_mode(True)
        titles = self.get_column_titles()
        for ix in range(len(titles)):
            item = titles[ix]
            if item[2] == BaseSelector.NONE:
                continue
            elif item[2] == BaseSelector.TEXT:
                column = gtk.TreeViewColumn(item[0],
                                            self.renderer,
                                            text=item[3])
            elif item[2] == BaseSelector.MARKUP:
                column = gtk.TreeViewColumn(item[0],
                                            self.renderer,
                                            markup=item[3])
            column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
            column.set_fixed_width(item[1])
            column.set_resizable(True)
            #connect click
            column.connect('clicked', self.column_clicked, ix)
            column.set_clickable(True)
            ##column.set_sort_column_id(ix) # model has its own sort implemented
            self.columns.append(column)
            tree.append_column(column)

    def build_menu_names(self, obj):
        return (self.title, None)

    def get_selected_ids(self):
        mlist = []
        self.selection.selected_foreach(self.select_function, mlist)
        return mlist

    def select_function(self, store, path, iter, id_list):
        handle_column = self.get_handle_column()
        id_list.append(self.model.get_value(iter, handle_column))

    def run(self):
        val = self.window.run()
        result = None
        if val == gtk.RESPONSE_OK:
            id_list = self.get_selected_ids()
            if id_list and id_list[0]:
                result = self.get_from_handle_func()(id_list[0])
                if result is None and self.get_from_handle_func2:
                    result = self.get_from_handle_func2()(id_list[0])
            self.close()
        elif val != gtk.RESPONSE_DELETE_EVENT:
            self.close()
        return result

    def _on_row_activated(self, treeview, path, view_col):
        self.window.response(gtk.RESPONSE_OK)

    def _local_init(self):
        # define selector-specific init routine
        pass

    def get_window_title(self):
        assert False, "Must be defined in the subclass"

    def get_model_class(self):
        assert False, "Must be defined in the subclass"

    def get_column_titles(self):
        """
        Defines the columns to show in the selector. Must be defined in the
        subclasses.
        :returns: a list of tuples with four entries. The four entries should
                be 0: column header string, 1: column width, 
                2: TEXT, MARKUP or IMAGE, 3: column in the model that must be 
                used.
        """
        raise NotImplementedError

    def get_from_handle_func(self):
        assert False, "Must be defined in the subclass"

    def get_from_handle_func2(self):
        return None

    def get_handle_column(self):
        # return 3
        assert False, "Must be defined in the subclass"

    def set_show_search_bar(self, value):
        """make the search bar at the top shown
        """
        self.show_search_bar = value
        if not self.search_bar:
            return
        if self.show_search_bar:
            self.search_bar.show()
        else:
            self.search_bar.hide()

    def begintree(self, store, path, node, sel_list):
        handle_column = self.get_handle_column()
        handle = store.get_value(node, handle_column)
        sel_list.append(handle)

    def first_selected(self):
        """ first selected entry in the Selector tree
        """
        mlist = []
        self.selection.selected_foreach(self.begintree, mlist)
        return mlist[0] if mlist else None

    def column_order(self):
        """
        returns a tuple indicating the column order of the model
        """
        return [(1, row[3], row[1], row[0])
                for row in self.get_column_titles()]

    def exact_search(self):
        """
        Returns a tuple indicating columns requiring an exact search
        """
        return ()

    def setup_filter(self):
        """
        Builds the default filters and add them to the filter bar.
        """
        cols = [(pair[3], pair[1], pair[0] in self.exact_search())
                for pair in self.column_order() if pair[0]]
        self.search_bar.setup_filter(cols)

    def build_tree(self):
        """
        Builds the selection people see in the Selector
        """
        if self.filter[1]:
            filter_info = self.filter
        else:
            #search info for the
            if self.search_bar.get_value()[0] in self.exact_search():
                filter_info = (0, self.search_bar.get_value(), True)
            else:
                filter_info = (0, self.search_bar.get_value(), False)

        #set up cols the first time
        if self.setupcols:
            self.add_columns(self.tree)

        #reset the model with correct sorting
        self.clear_model()
        self.model = self.get_model_class()(self.db,
                                            self.sort_col,
                                            self.sortorder,
                                            sort_map=self.column_order(),
                                            skip=self.skip_list,
                                            search=filter_info)

        self.tree.set_model(self.model)

        #sorting arrow in column header (not on start, only on click)
        if not self.setupcols:
            for i in xrange(len(self.columns)):
                enable_sort_flag = (i == self.sort_col)
                self.columns[i].set_sort_indicator(enable_sort_flag)
            self.columns[self.sort_col].set_sort_order(self.sortorder)

        # set the search column to be the sorted column
        search_col = self.column_order()[self.sort_col][1]
        self.tree.set_search_column(search_col)

        self.setupcols = False

    def column_clicked(self, obj, data):
        if self.sort_col != data:
            self.sortorder = gtk.SORT_ASCENDING
            self.sort_col = data
        else:
            if (self.columns[data].get_sort_order() == gtk.SORT_DESCENDING
                    or not self.columns[data].get_sort_indicator()):
                self.sortorder = gtk.SORT_ASCENDING
            else:
                self.sortorder = gtk.SORT_DESCENDING
            self.model.reverse_order()
        self.build_tree()

        handle = self.first_selected()
        if handle:
            path = self.model.on_get_path(handle)
            self.selection.select_path(path)
            self.tree.scroll_to_cell(path, None, 1, 0.5, 0)

        return True

    def show_toggle(self, obj):
        filter_info = None if obj.get_active() else self.filter
        self.clear_model()
        self.model = self.get_model_class()(self.db,
                                            self.sort_col,
                                            self.sortorder,
                                            sort_map=self.column_order(),
                                            skip=self.skip_list,
                                            search=filter_info)
        self.tree.set_model(self.model)
        self.tree.grab_focus()

    def clear_model(self):
        if self.model:
            self.tree.set_model(None)
            if hasattr(self.model, 'destroy'):
                self.model.destroy()
            self.model = None

    def _cleanup_on_exit(self):
        """Unset all things that can block garbage collection.
        Finalize rest
        """
        self.clear_model()
        self.db = None
        self.tree = None
        self.columns = None
        self.search_bar.destroy()

    def close(self, *obj):
        ManagedWindow.ManagedWindow.close(self)
        self._cleanup_on_exit()
Example #14
0
    def __init__(self, date, uistate, track):
        """
        Initiate and display the dialog.
        """
        ManagedWindow.ManagedWindow.__init__(self, uistate, track, self)

        # Create self.date as a copy of the given Date object.
        self.date = Date(date)

        self.top = Glade()

        self.set_window(self.top.toplevel, self.top.get_object('title'),
                        _('Date selection'))

        self.statusbar = self.top.get_object('statusbar')

        self.ok_button = self.top.get_object('ok_button')
        self.calendar_box = self.top.get_object('calendar_box')
        for name in Date.ui_calendar_names:
            self.calendar_box.get_model().append([name])

        self.calendar_box.set_active(self.date.get_calendar())
        self.calendar_box.connect('changed', self.switch_calendar)

        self.quality_box = self.top.get_object('quality_box')
        for item_number in range(len(QUAL_TEXT)):
            self.quality_box.append_text(QUAL_TEXT[item_number][1])
            if self.date.get_quality() == QUAL_TEXT[item_number][0]:
                self.quality_box.set_active(item_number)

        self.type_box = self.top.get_object('type_box')
        for item_number in range(len(MOD_TEXT)):
            self.type_box.append_text(MOD_TEXT[item_number][1])
            if self.date.get_modifier() == MOD_TEXT[item_number][0]:
                self.type_box.set_active(item_number)
        self.type_box.connect('changed', self.switch_type)

        self.start_month_box = self.top.get_object('start_month_box')
        self.stop_month_box = self.top.get_object('stop_month_box')
        month_names = CAL_TO_MONTHS_NAMES[self.date.get_calendar()]
        for name in month_names:
            self.start_month_box.append_text(name)
            self.stop_month_box.append_text(name)
        self.start_month_box.set_active(self.date.get_month())
        self.stop_month_box.set_active(self.date.get_stop_month())

        self.start_day = self.top.get_object('start_day')
        self.start_day.set_value(self.date.get_day())
        self.start_year = self.top.get_object('start_year')
        self.start_year.set_value(self.date.get_year())

        self.stop_day = self.top.get_object('stop_day')
        self.stop_day.set_value(self.date.get_stop_day())
        self.stop_year = self.top.get_object('stop_year')
        self.stop_year.set_value(self.date.get_stop_year())

        self.dual_dated = self.top.get_object('dualdated')
        self.new_year = self.top.get_object('newyear')
        self.new_year.set_text(self.date.newyear_to_str())

        # Disable second date controls if not compound date
        if not self.date.is_compound():
            self.stop_day.set_sensitive(0)
            self.stop_month_box.set_sensitive(0)
            self.stop_year.set_sensitive(0)

        # Disable the rest of controls if a text-only date
        if self.date.get_modifier() == Date.MOD_TEXTONLY:
            self.start_day.set_sensitive(0)
            self.start_month_box.set_sensitive(0)
            self.start_year.set_sensitive(0)
            self.calendar_box.set_sensitive(0)
            self.quality_box.set_sensitive(0)
            self.dual_dated.set_sensitive(0)
            self.new_year.set_sensitive(0)

        self.text_entry = self.top.get_object('date_text_entry')
        self.text_entry.set_text(self.date.get_text())

        if self.date.get_slash():
            self.dual_dated.set_active(1)
            self.calendar_box.set_sensitive(0)
            self.calendar_box.set_active(Date.CAL_JULIAN)
        self.dual_dated.connect('toggled', self.switch_dual_dated)

        # The dialog is modal -- since dates don't have names, we don't
        # want to have several open dialogs, since then the user will
        # loose track of which is which. Much like opening files.

        self.validated_date = self.return_date = None

        for o in self.top.get_objects():
            try:
                if o != self.ok_button:
                    o.connect_after('changed', self.revalidate)
            except TypeError:
                pass  # some of them don't support the signal, ignore them...
        self.revalidate()
        self.show()

        while True:
            response = self.window.run()
            if response == gtk.RESPONSE_HELP:
                GrampsDisplay.help(webpage=WIKI_HELP_PAGE,
                                   section=WIKI_HELP_SEC)
            elif response == gtk.RESPONSE_DELETE_EVENT:
                break
            else:
                if response == gtk.RESPONSE_OK:
                    # if the user pressed OK/enter while inside an edit field,
                    # e.g., the year,
                    # build_date_from_ui won't pick up the new text in the
                    # run of revalidate that allowed the OK!
                    if not self.revalidate():
                        continue
                    self.return_date = Date()
                    self.return_date.copy(self.validated_date)
                self.close()
                break
Example #15
0
class DateEditorDialog(ManagedWindow.ManagedWindow):
    """
    Dialog allowing to build the date precisely, to correct possible 
    limitations of parsing and/or underlying structure of Date.
    """
    def __init__(self, date, uistate, track):
        """
        Initiate and display the dialog.
        """
        ManagedWindow.ManagedWindow.__init__(self, uistate, track, self)

        # Create self.date as a copy of the given Date object.
        self.date = Date(date)

        self.top = Glade()

        self.set_window(self.top.toplevel, self.top.get_object('title'),
                        _('Date selection'))

        self.statusbar = self.top.get_object('statusbar')

        self.ok_button = self.top.get_object('ok_button')
        self.calendar_box = self.top.get_object('calendar_box')
        for name in Date.ui_calendar_names:
            self.calendar_box.get_model().append([name])

        self.calendar_box.set_active(self.date.get_calendar())
        self.calendar_box.connect('changed', self.switch_calendar)

        self.quality_box = self.top.get_object('quality_box')
        for item_number in range(len(QUAL_TEXT)):
            self.quality_box.append_text(QUAL_TEXT[item_number][1])
            if self.date.get_quality() == QUAL_TEXT[item_number][0]:
                self.quality_box.set_active(item_number)

        self.type_box = self.top.get_object('type_box')
        for item_number in range(len(MOD_TEXT)):
            self.type_box.append_text(MOD_TEXT[item_number][1])
            if self.date.get_modifier() == MOD_TEXT[item_number][0]:
                self.type_box.set_active(item_number)
        self.type_box.connect('changed', self.switch_type)

        self.start_month_box = self.top.get_object('start_month_box')
        self.stop_month_box = self.top.get_object('stop_month_box')
        month_names = CAL_TO_MONTHS_NAMES[self.date.get_calendar()]
        for name in month_names:
            self.start_month_box.append_text(name)
            self.stop_month_box.append_text(name)
        self.start_month_box.set_active(self.date.get_month())
        self.stop_month_box.set_active(self.date.get_stop_month())

        self.start_day = self.top.get_object('start_day')
        self.start_day.set_value(self.date.get_day())
        self.start_year = self.top.get_object('start_year')
        self.start_year.set_value(self.date.get_year())

        self.stop_day = self.top.get_object('stop_day')
        self.stop_day.set_value(self.date.get_stop_day())
        self.stop_year = self.top.get_object('stop_year')
        self.stop_year.set_value(self.date.get_stop_year())

        self.dual_dated = self.top.get_object('dualdated')
        self.new_year = self.top.get_object('newyear')
        self.new_year.set_text(self.date.newyear_to_str())

        # Disable second date controls if not compound date
        if not self.date.is_compound():
            self.stop_day.set_sensitive(0)
            self.stop_month_box.set_sensitive(0)
            self.stop_year.set_sensitive(0)

        # Disable the rest of controls if a text-only date
        if self.date.get_modifier() == Date.MOD_TEXTONLY:
            self.start_day.set_sensitive(0)
            self.start_month_box.set_sensitive(0)
            self.start_year.set_sensitive(0)
            self.calendar_box.set_sensitive(0)
            self.quality_box.set_sensitive(0)
            self.dual_dated.set_sensitive(0)
            self.new_year.set_sensitive(0)

        self.text_entry = self.top.get_object('date_text_entry')
        self.text_entry.set_text(self.date.get_text())

        if self.date.get_slash():
            self.dual_dated.set_active(1)
            self.calendar_box.set_sensitive(0)
            self.calendar_box.set_active(Date.CAL_JULIAN)
        self.dual_dated.connect('toggled', self.switch_dual_dated)

        # The dialog is modal -- since dates don't have names, we don't
        # want to have several open dialogs, since then the user will
        # loose track of which is which. Much like opening files.

        self.validated_date = self.return_date = None

        for o in self.top.get_objects():
            try:
                if o != self.ok_button:
                    o.connect_after('changed', self.revalidate)
            except TypeError:
                pass  # some of them don't support the signal, ignore them...
        self.revalidate()
        self.show()

        while True:
            response = self.window.run()
            if response == gtk.RESPONSE_HELP:
                GrampsDisplay.help(webpage=WIKI_HELP_PAGE,
                                   section=WIKI_HELP_SEC)
            elif response == gtk.RESPONSE_DELETE_EVENT:
                break
            else:
                if response == gtk.RESPONSE_OK:
                    # if the user pressed OK/enter while inside an edit field,
                    # e.g., the year,
                    # build_date_from_ui won't pick up the new text in the
                    # run of revalidate that allowed the OK!
                    if not self.revalidate():
                        continue
                    self.return_date = Date()
                    self.return_date.copy(self.validated_date)
                self.close()
                break

    def revalidate(self, obj=None):
        """
        If anything changed, revalidate the date and
        enable/disable the "OK" button based on the result.
        """
        (the_quality, the_modifier, the_calendar, the_value, the_text,
         the_newyear) = self.build_date_from_ui()
        LOG.debug("revalidate: {0} changed, value: {1}".format(obj, the_value))
        d = Date(self.date)
        if not self.ok_button.get_sensitive():
            self.statusbar.pop(1)
        try:
            d.set(quality=the_quality,
                  modifier=the_modifier,
                  calendar=the_calendar,
                  value=the_value,
                  text=the_text,
                  newyear=the_newyear)
            # didn't throw yet?
            self.validated_date = d
            LOG.debug("validated_date set to: {0}".format(d.dateval))
            self.ok_button.set_sensitive(1)
            self.calendar_box.set_sensitive(1)
            return True
        except DateError as e:
            self.ok_button.set_sensitive(0)
            self.calendar_box.set_sensitive(0)
            self.statusbar.push(
                1,
                _("Correct the date or switch from `{cur_mode}' to `{text_mode}'"
                  ).format(cur_mode=MOD_TEXT[self.type_box.get_active()][1],
                           text_mode=MOD_TEXT[-1][1]))
            return False

    def build_menu_names(self, obj):
        """
        Define the menu entry for the ManagedWindows
        """
        return (_("Date selection"), None)

    def build_date_from_ui(self):
        """
        Collect information from the UI controls and return 
        5-tuple of (quality,modifier,calendar,value,text) 
        """
        # It is important to not set date based on these controls.
        # For example, changing the caledar makes the date inconsistent
        # until the callback of the calendar menu is finished.
        # We need to be able to use this function from that callback,
        # so here we just report on the state of all widgets, without
        # actually modifying the date yet.

        modifier = MOD_TEXT[self.type_box.get_active()][0]
        text = self.text_entry.get_text()

        if modifier == Date.MOD_TEXTONLY:
            return (Date.QUAL_NONE, Date.MOD_TEXTONLY, Date.CAL_GREGORIAN,
                    Date.EMPTY, text, Date.NEWYEAR_JAN1)

        quality = QUAL_TEXT[self.quality_box.get_active()][0]

        if modifier in (Date.MOD_RANGE, Date.MOD_SPAN):
            value = (self.start_day.get_value_as_int(),
                     self.start_month_box.get_active(),
                     self.start_year.get_value_as_int(),
                     self.dual_dated.get_active(),
                     self.stop_day.get_value_as_int(),
                     self.stop_month_box.get_active(),
                     self.stop_year.get_value_as_int(),
                     self.dual_dated.get_active())
        else:
            value = (self.start_day.get_value_as_int(),
                     self.start_month_box.get_active(),
                     self.start_year.get_value_as_int(),
                     self.dual_dated.get_active())
        calendar = self.calendar_box.get_active()
        newyear = Date.newyear_to_code(self.new_year.get_text())
        return (quality, modifier, calendar, value, text, newyear)

    def switch_type(self, obj):
        """
        Disable/enable various date controls depending on the date 
        type selected via the menu.
        """

        the_modifier = MOD_TEXT[self.type_box.get_active()][0]

        # Disable/enable second date controls based on whether
        # the type allows compound dates
        if the_modifier in (Date.MOD_RANGE, Date.MOD_SPAN):
            stop_date_sensitivity = 1
        else:
            stop_date_sensitivity = 0
        self.stop_day.set_sensitive(stop_date_sensitivity)
        self.stop_month_box.set_sensitive(stop_date_sensitivity)
        self.stop_year.set_sensitive(stop_date_sensitivity)

        # Disable/enable the rest of the controls if the type is text-only.
        date_sensitivity = not the_modifier == Date.MOD_TEXTONLY
        self.start_day.set_sensitive(date_sensitivity)
        self.start_month_box.set_sensitive(date_sensitivity)
        self.start_year.set_sensitive(date_sensitivity)
        self.calendar_box.set_sensitive(date_sensitivity)
        self.quality_box.set_sensitive(date_sensitivity)
        self.dual_dated.set_sensitive(date_sensitivity)
        self.new_year.set_sensitive(date_sensitivity)

    def switch_dual_dated(self, obj):
        """
        Changed whether this is a dual dated year, or not.
        Dual dated years are represented in the Julian calendar
        so that the day/months don't changed in the Text representation.
        """
        if self.dual_dated.get_active():
            self.calendar_box.set_active(Date.CAL_JULIAN)
            self.calendar_box.set_sensitive(0)
        else:
            self.calendar_box.set_sensitive(1)

    def switch_calendar(self, obj):
        """
        Change month names and convert the date based on the calendar 
        selected via the menu.
        """

        old_cal = self.date.get_calendar()
        new_cal = self.calendar_box.get_active()
        LOG.debug(">>>switch_calendar: {0} changed, {1} -> {2}".format(
            obj, old_cal, new_cal))

        (the_quality, the_modifier, the_calendar, the_value, the_text,
         the_newyear) = self.build_date_from_ui()
        try:
            self.date.set(quality=the_quality,
                          modifier=the_modifier,
                          calendar=old_cal,
                          value=the_value,
                          text=the_text,
                          newyear=the_newyear)
        except DateError:
            pass
        else:
            if not self.date.is_empty():
                self.date.convert_calendar(new_cal)

        self.start_month_box.get_model().clear()
        self.stop_month_box.get_model().clear()
        month_names = CAL_TO_MONTHS_NAMES[new_cal]
        for name in month_names:
            self.start_month_box.append_text(name)
            self.stop_month_box.append_text(name)

        self.start_day.set_value(self.date.get_day())
        self.start_month_box.set_active(self.date.get_month())
        self.start_year.set_value(self.date.get_year())
        self.stop_day.set_value(self.date.get_stop_day())
        self.stop_month_box.set_active(self.date.get_stop_month())
        self.stop_year.set_value(self.date.get_stop_year())
        LOG.debug("<<<switch_calendar: {0} changed, {1} -> {2}".format(
            obj, old_cal, new_cal))