コード例 #1
0
    def build_gui(self):
        """
        Build the GUI interface.
        """
        self.top = Gtk.VBox()

        hpaned = Gtk.HPaned()

        button_panel = Gtk.HBox()

        self.button_index = Gtk.ToolButton(Gtk.STOCK_INDEX)
        self.button_add = Gtk.ToolButton(Gtk.STOCK_ADD)
        self.button_del = Gtk.ToolButton(Gtk.STOCK_REMOVE)
        self.button_clear = Gtk.ToolButton(Gtk.STOCK_CLEAR)
        self.button_edit = Gtk.ToolButton(Gtk.STOCK_EDIT)
        self.button_zoom_in = Gtk.ToolButton(Gtk.STOCK_ZOOM_IN)
        self.button_zoom_out = Gtk.ToolButton(Gtk.STOCK_ZOOM_OUT)
        self.button_detect = Gtk.ToolButton(Gtk.STOCK_EXECUTE)
        self.button_settings = Gtk.ToolButton(Gtk.STOCK_PREFERENCES)

        self.button_index.connect("clicked", self.sel_person_clicked)
        self.button_add.connect("clicked", self.add_person_clicked)
        self.button_del.connect("clicked", self.clear_ref_clicked)
        self.button_clear.connect("clicked", self.del_region_clicked)
        self.button_edit.connect("clicked", self.edit_person_clicked)
        self.button_zoom_in.connect("clicked", self.zoom_in_clicked)
        self.button_zoom_out.connect("clicked", self.zoom_out_clicked)
        self.button_detect.connect("clicked", self.detect_faces_clicked)
        self.button_settings.connect("clicked", self.settings_clicked)

        button_panel.pack_start(self.button_index, expand=False, fill=False, padding=5)
        button_panel.pack_start(self.button_add, expand=False, fill=False, padding=5)
        button_panel.pack_start(self.button_del, expand=False, fill=False, padding=5)
        button_panel.pack_start(self.button_clear, expand=False, fill=False, padding=5)
        button_panel.pack_start(self.button_edit, expand=False, fill=False, padding=5)
        button_panel.pack_start(self.button_zoom_in, expand=False, fill=False, padding=5)
        button_panel.pack_start(self.button_zoom_out, expand=False, fill=False, padding=5)
        button_panel.pack_start(self.button_detect, expand=False, fill=False, padding=5)
        button_panel.pack_start(self.button_settings, expand=False, fill=False, padding=5)

        self.button_index.set_tooltip_text(_("Select Person"))
        self.button_add.set_tooltip_text(_("Add Person"))
        self.button_del.set_tooltip_text(_("Clear Reference"))
        self.button_clear.set_tooltip_text(_("Remove Selection"))
        self.button_edit.set_tooltip_text(_("Edit referenced Person"))
        self.button_zoom_in.set_tooltip_text(_("Zoom In"))
        self.button_zoom_out.set_tooltip_text(_("Zoom Out"))

        if facedetection.computer_vision_available:
            self.button_detect.set_tooltip_text(_("Detect faces"))
        else:
            self.button_detect.set_tooltip_text(_("Detect faces (cv module required)"))

        self.button_settings.set_tooltip_text(_("Settings"))

        self.top.pack_start(button_panel, expand=False, fill=True, padding=5)

        self.selection_widget = SelectionWidget()
        self.selection_widget.set_size_request(200, -1)
        self.selection_widget.connect("region-modified", self.region_modified)
        self.selection_widget.connect("region-created", self.region_created)
        self.selection_widget.connect("region-selected", self.region_selected)
        self.selection_widget.connect("selection-cleared", self.selection_cleared)
        self.selection_widget.connect("right-button-clicked", self.right_button_clicked)
        self.selection_widget.connect("zoomed-in", self.zoomed)
        self.selection_widget.connect("zoomed-out", self.zoomed)


        # Can drop a PERSON here:
        tglist = Gtk.TargetList.new([])
        tglist.add(DdTargets.PERSON_LINK.atom_drag_type,
                   DdTargets.PERSON_LINK.target_flags,
                   DdTargets.PERSON_LINK.app_id)
        # Can drop a LIST of HANDLES here:
        tglist.add(DdTargets.HANDLE_LIST.atom_drag_type, 
                   DdTargets.HANDLE_LIST.target_flags,
                   DdTargets.HANDLE_LIST.app_id)

        # Drag and Drop for selection widget:
        self.selection_widget.event_box.drag_dest_set(
            Gtk.DestDefaults.MOTION |
            Gtk.DestDefaults.DROP,
            [],
            Gdk.DragAction.COPY)
        self.selection_widget.event_box.drag_dest_set_target_list(tglist)
        self.selection_widget.event_box.connect(
            'drag_data_received',
            lambda *args: self.drag_data_received(*args, on_image=True))
        # End Drag and Drop for selection widget

        hpaned.pack1(self.selection_widget, resize=True, shrink=False)

        self.treestore = Gtk.TreeStore(int, GdkPixbuf.Pixbuf, str)

        self.treeview = Gtk.TreeView(self.treestore)
        self.treeview.set_size_request(400, -1)
        self.treeview.connect("cursor-changed", self.cursor_changed)
        self.treeview.connect("row-activated", self.row_activated)
        column1 = Gtk.TreeViewColumn(_(''))
        column2 = Gtk.TreeViewColumn(_('Preview'))
        column3 = Gtk.TreeViewColumn(_('Person'))
        self.treeview.append_column(column1)
        self.treeview.append_column(column2)
        self.treeview.append_column(column3)

        cell1 = Gtk.CellRendererText()
        cell2 = Gtk.CellRendererPixbuf()
        cell3 = Gtk.CellRendererText()
        column1.pack_start(cell1, expand=True)
        column1.add_attribute(cell1, 'text', 0)
        column2.pack_start(cell2, expand=True)
        column2.add_attribute(cell2, 'pixbuf', 1)
        column3.pack_start(cell3, expand=True)
        column3.add_attribute(cell3, 'text', 2)

        self.treeview.set_search_column(0)
        column1.set_sort_column_id(0)
        column3.set_sort_column_id(2)

        # Drag and Drop for tree view:
        self.treeview.drag_dest_set(
            Gtk.DestDefaults.MOTION |
            Gtk.DestDefaults.DROP,
            [],
            Gdk.DragAction.COPY)
        self.treeview.drag_dest_set_target_list(tglist)
        self.treeview.connect('drag_data_received', 
                              self.drag_data_received)
        # End Drag and Drop for tree_view
        
        scrolled_window2 = Gtk.ScrolledWindow()
        scrolled_window2.add(self.treeview)
        scrolled_window2.set_size_request(400, -1)
        scrolled_window2.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)

        hpaned.pack2(scrolled_window2, resize=False, shrink=False)

        self.top.pack_start(hpaned, True, True, 5)

        self.enable_buttons()

        return self.top
コード例 #2
0
    def build_gui(self):
        """
        Build the GUI interface.
        """
        self.top = Gtk.VBox()

        hpaned = Gtk.HPaned()

        button_panel = Gtk.HBox()

        self.button_index = Gtk.ToolButton(Gtk.STOCK_INDEX)
        self.button_add = Gtk.ToolButton(Gtk.STOCK_ADD)
        self.button_del = Gtk.ToolButton(Gtk.STOCK_REMOVE)
        self.button_clear = Gtk.ToolButton(Gtk.STOCK_CLEAR)
        self.button_edit = Gtk.ToolButton(Gtk.STOCK_EDIT)
        self.button_zoom_in = Gtk.ToolButton(Gtk.STOCK_ZOOM_IN)
        self.button_zoom_out = Gtk.ToolButton(Gtk.STOCK_ZOOM_OUT)
        # set custom icon for face detect button
        self.button_detect = Gtk.ToolButton()
        theme = Gtk.IconTheme.get_default()
        face_detect_icon = theme.lookup_icon('gramps-face-detection', 24,
                                             Gtk.IconLookupFlags.FORCE_SVG)
        if face_detect_icon is not None:
            self.button_detect.set_icon_name('gramps-face-detection')
        else:
            img = Gtk.Image()
            path, filename = os.path.split(__file__)
            face_detect_icon = os.path.join(path, 'gramps-face-detection.svg')
            img.set_from_file(face_detect_icon)
            self.button_detect.set_icon_widget(img)
        self.button_settings = Gtk.ToolButton(Gtk.STOCK_PREFERENCES)

        self.button_index.connect("clicked", self.sel_person_clicked)
        self.button_add.connect("clicked", self.add_person_clicked)
        self.button_del.connect("clicked", self.clear_ref_clicked)
        self.button_clear.connect("clicked", self.del_region_clicked)
        self.button_edit.connect("clicked", self.edit_person_clicked)
        self.button_zoom_in.connect("clicked", self.zoom_in_clicked)
        self.button_zoom_out.connect("clicked", self.zoom_out_clicked)
        self.button_detect.connect("clicked", self.detect_faces_clicked)
        self.button_settings.connect("clicked", self.settings_clicked)

        button_panel.pack_start(self.button_index,
                                expand=False,
                                fill=False,
                                padding=5)
        button_panel.pack_start(self.button_add,
                                expand=False,
                                fill=False,
                                padding=5)
        button_panel.pack_start(self.button_del,
                                expand=False,
                                fill=False,
                                padding=5)
        button_panel.pack_start(self.button_clear,
                                expand=False,
                                fill=False,
                                padding=5)
        button_panel.pack_start(self.button_edit,
                                expand=False,
                                fill=False,
                                padding=5)
        button_panel.pack_start(self.button_zoom_in,
                                expand=False,
                                fill=False,
                                padding=5)
        button_panel.pack_start(self.button_zoom_out,
                                expand=False,
                                fill=False,
                                padding=5)
        button_panel.pack_start(self.button_detect,
                                expand=False,
                                fill=False,
                                padding=5)
        button_panel.pack_start(self.button_settings,
                                expand=False,
                                fill=False,
                                padding=5)

        self.button_index.set_tooltip_text(_("Select Person"))
        self.button_add.set_tooltip_text(_("Add Person"))
        self.button_del.set_tooltip_text(_("Clear Reference"))
        self.button_clear.set_tooltip_text(_("Remove Selection"))
        self.button_edit.set_tooltip_text(_("Edit referenced Person"))
        self.button_zoom_in.set_tooltip_text(_("Zoom In"))
        self.button_zoom_out.set_tooltip_text(_("Zoom Out"))

        if facedetection.computer_vision_available:
            text = _("Detect faces")
        else:
            text = _("Detect faces (OpenCV module required)")
        self.button_detect.set_tooltip_text(text)

        self.button_settings.set_tooltip_text(_("Settings"))

        self.top.pack_start(button_panel, expand=False, fill=True, padding=5)

        self.selection_widget = SelectionWidget()
        self.selection_widget.set_size_request(200, -1)
        self.selection_widget.connect("region-modified", self.region_modified)
        self.selection_widget.connect("region-created", self.region_created)
        self.selection_widget.connect("region-selected", self.region_selected)
        self.selection_widget.connect("selection-cleared",
                                      self.selection_cleared)
        self.selection_widget.connect("right-button-clicked",
                                      self.right_button_clicked)
        self.selection_widget.connect("zoomed-in", self.zoomed)
        self.selection_widget.connect("zoomed-out", self.zoomed)

        # Can drop a PERSON here:
        tglist = Gtk.TargetList.new([])
        tglist.add(DdTargets.PERSON_LINK.atom_drag_type,
                   DdTargets.PERSON_LINK.target_flags,
                   DdTargets.PERSON_LINK.app_id)
        # Can drop a LIST of HANDLES here:
        tglist.add(DdTargets.HANDLE_LIST.atom_drag_type,
                   DdTargets.HANDLE_LIST.target_flags,
                   DdTargets.HANDLE_LIST.app_id)

        # Drag and Drop for selection widget:
        self.selection_widget.event_box.drag_dest_set(
            Gtk.DestDefaults.MOTION | Gtk.DestDefaults.DROP, [],
            Gdk.DragAction.COPY)
        self.selection_widget.event_box.drag_dest_set_target_list(tglist)
        self.selection_widget.event_box.connect(
            'drag_data_received',
            lambda *args: self.drag_data_received(*args, on_image=True))
        # End Drag and Drop for selection widget

        hpaned.pack1(self.selection_widget, resize=True, shrink=False)

        self.treestore = Gtk.TreeStore(int, GdkPixbuf.Pixbuf, str)

        self.treeview = Gtk.TreeView(self.treestore)
        self.treeview.set_size_request(400, -1)
        self.treeview.connect("cursor-changed", self.cursor_changed)
        self.treeview.connect("row-activated", self.row_activated)
        self.treeview.connect("button-press-event", self.row_mouse_click)
        column1 = Gtk.TreeViewColumn(_(''))
        column2 = Gtk.TreeViewColumn(_('Preview'))
        column3 = Gtk.TreeViewColumn(_('Person'))
        self.treeview.append_column(column1)
        self.treeview.append_column(column2)
        self.treeview.append_column(column3)

        cell1 = Gtk.CellRendererText()
        cell2 = Gtk.CellRendererPixbuf()
        cell3 = Gtk.CellRendererText()
        column1.pack_start(cell1, expand=True)
        column1.add_attribute(cell1, 'text', 0)
        column2.pack_start(cell2, expand=True)
        column2.add_attribute(cell2, 'pixbuf', 1)
        column3.pack_start(cell3, expand=True)
        column3.add_attribute(cell3, 'text', 2)

        self.treeview.set_search_column(0)
        column1.set_sort_column_id(0)
        column3.set_sort_column_id(2)

        # Drag and Drop for tree view:
        self.treeview.drag_dest_set(
            Gtk.DestDefaults.MOTION | Gtk.DestDefaults.DROP, [],
            Gdk.DragAction.COPY)
        self.treeview.drag_dest_set_target_list(tglist)
        self.treeview.connect('drag_data_received', self.drag_data_received)
        # End Drag and Drop for tree_view

        scrolled_window2 = Gtk.ScrolledWindow()
        scrolled_window2.add(self.treeview)
        scrolled_window2.set_size_request(400, -1)
        scrolled_window2.set_policy(Gtk.PolicyType.AUTOMATIC,
                                    Gtk.PolicyType.AUTOMATIC)

        hpaned.pack2(scrolled_window2, resize=False, shrink=False)

        self.top.pack_start(hpaned, True, True, 5)

        self.enable_buttons()

        return self.top
コード例 #3
0
class PhotoTaggingGramplet(Gramplet):
    def init(self):
        self.regions = []

        self.build_context_menu()

        self.gui.WIDGET = self.build_gui()
        self.gui.get_container_widget().remove(self.gui.textview)
        self.gui.get_container_widget().add_with_viewport(self.gui.WIDGET)
        self.top.show_all()

    def on_save(self):
        CONFIG.save()

    # ======================================================
    # building the GUI
    # ======================================================

    def build_gui(self):
        """
        Build the GUI interface.
        """
        self.top = Gtk.VBox()

        hpaned = Gtk.HPaned()

        button_panel = Gtk.HBox()

        self.button_index = Gtk.ToolButton(Gtk.STOCK_INDEX)
        self.button_add = Gtk.ToolButton(Gtk.STOCK_ADD)
        self.button_del = Gtk.ToolButton(Gtk.STOCK_REMOVE)
        self.button_clear = Gtk.ToolButton(Gtk.STOCK_CLEAR)
        self.button_edit = Gtk.ToolButton(Gtk.STOCK_EDIT)
        self.button_zoom_in = Gtk.ToolButton(Gtk.STOCK_ZOOM_IN)
        self.button_zoom_out = Gtk.ToolButton(Gtk.STOCK_ZOOM_OUT)
        self.button_detect = Gtk.ToolButton(Gtk.STOCK_EXECUTE)
        self.button_settings = Gtk.ToolButton(Gtk.STOCK_PREFERENCES)

        self.button_index.connect("clicked", self.sel_person_clicked)
        self.button_add.connect("clicked", self.add_person_clicked)
        self.button_del.connect("clicked", self.clear_ref_clicked)
        self.button_clear.connect("clicked", self.del_region_clicked)
        self.button_edit.connect("clicked", self.edit_person_clicked)
        self.button_zoom_in.connect("clicked", self.zoom_in_clicked)
        self.button_zoom_out.connect("clicked", self.zoom_out_clicked)
        self.button_detect.connect("clicked", self.detect_faces_clicked)
        self.button_settings.connect("clicked", self.settings_clicked)

        button_panel.pack_start(self.button_index, expand=False, fill=False, padding=5)
        button_panel.pack_start(self.button_add, expand=False, fill=False, padding=5)
        button_panel.pack_start(self.button_del, expand=False, fill=False, padding=5)
        button_panel.pack_start(self.button_clear, expand=False, fill=False, padding=5)
        button_panel.pack_start(self.button_edit, expand=False, fill=False, padding=5)
        button_panel.pack_start(self.button_zoom_in, expand=False, fill=False, padding=5)
        button_panel.pack_start(self.button_zoom_out, expand=False, fill=False, padding=5)
        button_panel.pack_start(self.button_detect, expand=False, fill=False, padding=5)
        button_panel.pack_start(self.button_settings, expand=False, fill=False, padding=5)

        self.button_index.set_tooltip_text(_("Select Person"))
        self.button_add.set_tooltip_text(_("Add Person"))
        self.button_del.set_tooltip_text(_("Clear Reference"))
        self.button_clear.set_tooltip_text(_("Remove Selection"))
        self.button_edit.set_tooltip_text(_("Edit referenced Person"))
        self.button_zoom_in.set_tooltip_text(_("Zoom In"))
        self.button_zoom_out.set_tooltip_text(_("Zoom Out"))

        if facedetection.computer_vision_available:
            self.button_detect.set_tooltip_text(_("Detect faces"))
        else:
            self.button_detect.set_tooltip_text(_("Detect faces (cv module required)"))

        self.button_settings.set_tooltip_text(_("Settings"))

        self.top.pack_start(button_panel, expand=False, fill=True, padding=5)

        self.selection_widget = SelectionWidget()
        self.selection_widget.set_size_request(200, -1)
        self.selection_widget.connect("region-modified", self.region_modified)
        self.selection_widget.connect("region-created", self.region_created)
        self.selection_widget.connect("region-selected", self.region_selected)
        self.selection_widget.connect("selection-cleared", self.selection_cleared)
        self.selection_widget.connect("right-button-clicked", self.right_button_clicked)
        self.selection_widget.connect("zoomed-in", self.zoomed)
        self.selection_widget.connect("zoomed-out", self.zoomed)


        # Can drop a PERSON here:
        tglist = Gtk.TargetList.new([])
        tglist.add(DdTargets.PERSON_LINK.atom_drag_type,
                   DdTargets.PERSON_LINK.target_flags,
                   DdTargets.PERSON_LINK.app_id)
        # Can drop a LIST of HANDLES here:
        tglist.add(DdTargets.HANDLE_LIST.atom_drag_type, 
                   DdTargets.HANDLE_LIST.target_flags,
                   DdTargets.HANDLE_LIST.app_id)

        # Drag and Drop for selection widget:
        self.selection_widget.event_box.drag_dest_set(
            Gtk.DestDefaults.MOTION |
            Gtk.DestDefaults.DROP,
            [],
            Gdk.DragAction.COPY)
        self.selection_widget.event_box.drag_dest_set_target_list(tglist)
        self.selection_widget.event_box.connect(
            'drag_data_received',
            lambda *args: self.drag_data_received(*args, on_image=True))
        # End Drag and Drop for selection widget

        hpaned.pack1(self.selection_widget, resize=True, shrink=False)

        self.treestore = Gtk.TreeStore(int, GdkPixbuf.Pixbuf, str)

        self.treeview = Gtk.TreeView(self.treestore)
        self.treeview.set_size_request(400, -1)
        self.treeview.connect("cursor-changed", self.cursor_changed)
        self.treeview.connect("row-activated", self.row_activated)
        column1 = Gtk.TreeViewColumn(_(''))
        column2 = Gtk.TreeViewColumn(_('Preview'))
        column3 = Gtk.TreeViewColumn(_('Person'))
        self.treeview.append_column(column1)
        self.treeview.append_column(column2)
        self.treeview.append_column(column3)

        cell1 = Gtk.CellRendererText()
        cell2 = Gtk.CellRendererPixbuf()
        cell3 = Gtk.CellRendererText()
        column1.pack_start(cell1, expand=True)
        column1.add_attribute(cell1, 'text', 0)
        column2.pack_start(cell2, expand=True)
        column2.add_attribute(cell2, 'pixbuf', 1)
        column3.pack_start(cell3, expand=True)
        column3.add_attribute(cell3, 'text', 2)

        self.treeview.set_search_column(0)
        column1.set_sort_column_id(0)
        column3.set_sort_column_id(2)

        # Drag and Drop for tree view:
        self.treeview.drag_dest_set(
            Gtk.DestDefaults.MOTION |
            Gtk.DestDefaults.DROP,
            [],
            Gdk.DragAction.COPY)
        self.treeview.drag_dest_set_target_list(tglist)
        self.treeview.connect('drag_data_received', 
                              self.drag_data_received)
        # End Drag and Drop for tree_view
        
        scrolled_window2 = Gtk.ScrolledWindow()
        scrolled_window2.add(self.treeview)
        scrolled_window2.set_size_request(400, -1)
        scrolled_window2.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)

        hpaned.pack2(scrolled_window2, resize=False, shrink=False)

        self.top.pack_start(hpaned, True, True, 5)

        self.enable_buttons()

        return self.top

    def drag_data_received(self, widget, context, x, y, 
                           sel_data, info, time, on_image=None):
        """
        Receive a dropped person onto the treeview.
        """
        if sel_data:
            pickled_data =  sel_data.get_data()
            if not pickled_data:
                return
            data = pickle.loads(pickled_data)
            # Perhaps allow multiple person drops
            # Sometimes, more than one person could be in a selected area
            people = []
            # Just get the first one for now:, if a list:
            if sel_data.get_data_type() == DdTargets.HANDLE_LIST.atom_drag_type:
                if data[0][0] == "Person":
                    handle = data[0][1]
                    person = self.dbstate.db.get_person_from_handle(handle)
                    if person:
                        people.append(person)
                elif data[0][0] == "Event":
                    # get first, primary person of event:
                    event_handle = data[0][1]
                    event = self.dbstate.db.get_event_from_handle(event_handle)
                    for obj_class, handle in event.get_referenced_handles():
                        if obj_class == "Person":
                            person = self.dbstate.db.get_person_from_handle(handle)
                            if person:
                                people.append(person)
                                break
            elif sel_data.get_data_type() == DdTargets.PERSON_LINK.atom_drag_type:
                (drag_type, idval, handle, val) = data
                person = self.dbstate.db.get_person_from_handle(handle)
                if person:
                    people.append(person)
            else: # other formats work like this:
                handle = None
                try:
                    (drag_type, idval, handle, val) = data
                except:
                    pass
                if handle:
                    person = self.dbstate.db.get_person_from_handle(handle)
                    if person:
                        people.append(person)
                else:
                    LOG.warn("Can't handle this type of of drop: '%s'" % sel_data.get_data_type())
                    return
            for person in people:
                if on_image: # drop on image
                    region = self.selection_widget.find_region(x, y)
                    current = self.selection_widget.get_current()
                    if region and (current is None or current == region):
                        self.ask_and_set_person(region, person)
                        self.selection_widget.clear_selection()
                        self.refresh()
                        self.enable_buttons()
                else: # drop on list
                    drop_info = self.treeview.get_dest_row_at_pos(x, y)
                    if drop_info:
                        #path, position = drop_info
                        #self.treeview.set_cursor(path)
                        self.set_current_person(person)
                        self.selection_widget.clear_selection()
                        self.refresh()
                        self.enable_buttons()

    def build_context_menu(self):
        self.context_menu = Gtk.Menu()

        self.context_button_select = Gtk.ImageMenuItem(Gtk.STOCK_INDEX)
        self.context_button_select.set_label(_("Select"))
        self.context_button_select.set_always_show_image(True)
        self.context_button_select.connect("activate", self.sel_person_clicked)

        self.context_button_add = Gtk.ImageMenuItem(Gtk.STOCK_ADD)
        self.context_button_add.set_label(_("Add"))
        self.context_button_add.set_always_show_image(True)
        self.context_button_add.connect("activate", self.add_person_clicked)

        self.context_button_clear = Gtk.ImageMenuItem(Gtk.STOCK_REMOVE)
        self.context_button_clear.set_label(_("Clear"))
        self.context_button_clear.set_always_show_image(True)
        self.context_button_clear.connect("activate", self.clear_ref_clicked)
       
        self.context_button_remove = Gtk.ImageMenuItem(Gtk.STOCK_CLEAR)
        self.context_button_remove.set_label(_("Remove"))
        self.context_button_remove.set_always_show_image(True)
        self.context_button_remove.connect("activate", self.del_region_clicked)

        self.context_menu.append(self.context_button_select)
        self.context_menu.append(self.context_button_add)
        self.context_menu.append(self.context_button_clear)
        self.context_menu.append(self.context_button_remove)

        self.additional_items = []

    def refresh(self):
        self.selection_widget.refresh()
        self.refresh_list()
        self.refresh_selection()

    # ======================================================
    # gramplet event handlers
    # ======================================================

    def db_changed(self):
        self.dbstate.db.connect('media-update', self.update)
        self.connect_signal('Media', self.update)

    def main(self):
        media = self.get_current_object()
        self.top.hide()
        if media and media.mime.startswith("image"):
            self.load_image(media)
        else:
            self.selection_widget.show_missing()
        self.refresh_list()
        self.enable_buttons()
        self.top.show()

    # ======================================================
    # loading the image
    # ======================================================

    def load_image(self, media):
        self.regions = []
        image_path = media_path_full(self.dbstate.db, media.get_path())
        self.selection_widget.load_image(image_path)
        self.retrieve_backrefs()
        self.selection_widget.set_regions(self.regions)

    def retrieve_backrefs(self):
        """
        Finds the media references pointing to the current image
        """
        backrefs = self.dbstate.db.find_backlink_handles(self.get_current_handle())
        for (reftype, ref) in backrefs:
            if reftype == "Person":
                person = self.dbstate.db.get_person_from_handle(ref)
                name = person.get_primary_name()
                gallery = person.get_media_list()
                for mediaref in gallery:
                    referenced_handles = mediaref.get_referenced_handles()
                    if len(referenced_handles) == 1:
                        handle_type, handle = referenced_handles[0]
                        if handle_type == "MediaObject" and handle == self.get_current_handle():
                            rect = mediaref.get_rectangle()
                            if rect is None:
                                rect = (0, 0, 100, 100)
                            coords = self.selection_widget.proportional_to_real_rect(rect)
                            region = Region(*coords)
                            region.person = person
                            region.mediaref = mediaref
                            self.regions.append(region)

    # ======================================================
    # managing regions
    # ======================================================

    def check_and_translate_to_proportional(self, mediaref, rect):
        if mediaref:
            return mediaref.get_rectangle()
        else:
            return self.selection_widget.real_to_proportional_rect(rect)

    def intersects_any(self, region):
        for r in self.regions:
            if r.intersects(region):
                return True
        return False

    def enclosing_region(self, region):
        for r in self.regions:
            if r.contains_rect(region):
                return r
        return None

    def regions_referencing_person(self, person):
        result = []
        for r in self.regions:
            if r.person == person:
                result.append(r)
        return result

    def all_referenced_persons(self):
        result = []
        for r in self.regions:
            if r.person is not None:
                result.append(r.person)
        return result

    # ======================================================
    # utility functions for retrieving properties
    # ======================================================

    def get_current_handle(self):
        return self.get_active('Media')

    def get_current_object(self):
        return self.dbstate.db.get_object_from_handle(self.get_current_handle())

    # ======================================================
    # helpers for updating database objects
    # ======================================================

    def add_reference(self, person, rect):
        """
        Add a reference to the media object to the specified person.
        """
        mediaref = MediaRef()
        mediaref.ref = self.get_current_handle()
        mediaref.set_rectangle(rect)
        person.add_media_reference(mediaref)
        self.commit_person(person)
        return mediaref

    def remove_reference(self, person, mediaref):
        """
        Removes the reference to the media object from the person.
        """
        if mediaref in person.get_media_list():
            person.get_media_list().remove(mediaref)
            self.commit_person(person)

    def commit_person(self, person):
        """
        Save the modifications made to a Person object to the database.
        """
        with DbTxn('', self.dbstate.db) as trans:
            self.dbstate.db.commit_person(person, trans)
            msg = _("Edit Person (%s)") % \
                  name_displayer.display(person)
            trans.set_description(msg)

    # ======================================================
    # managing toolbar buttons
    # ======================================================
    def enable_buttons(self):
        selected = self.selection_widget.get_current()
        self.button_index.set_sensitive(selected is not None)
        self.button_add.set_sensitive(selected is not None)
        self.button_del.set_sensitive(
          selected is not None and
          selected.person is not None)
        self.button_clear.set_sensitive(selected is not None)
        self.button_edit.set_sensitive(
          selected is not None and
          selected.person is not None)
        self.button_zoom_in.set_sensitive(
          self.selection_widget.is_image_loaded() and
          self.selection_widget.can_zoom_in())
        self.button_zoom_out.set_sensitive(
          self.selection_widget.is_image_loaded() and
          self.selection_widget.can_zoom_out())
        self.button_detect.set_sensitive(
          self.selection_widget.is_image_loaded() and
          facedetection.computer_vision_available)

    # ======================================================
    # managing context menu buttons
    # ======================================================

    def prepare_context_menu(self):
        selected = self.selection_widget.get_current()
        self.context_button_add.set_sensitive(selected is not None)
        self.context_button_select.set_sensitive(selected is not None)
        self.context_button_clear.set_sensitive(
          selected is not None and selected.person is not None)
        self.context_button_remove.set_sensitive(selected is not None)

        # clear temporary items
        for item in self.additional_items:
            self.context_menu.remove(item)

        self.additional_items = []

        # populate the context menu
        persons = self.all_referenced_persons()
        if selected.person is not None:
            persons.remove(selected.person)
        if persons:
            self.additional_items.append(Gtk.SeparatorMenuItem())
            sorted_persons = sorted(list(persons), key=name_displayer.display)
            for person in sorted_persons:
                item = Gtk.MenuItem(
                  _("Replace {0}").format(name_displayer.display(person)))
                item.connect("activate", self.replace_reference, person)
                self.additional_items.append(item)
            for item in self.additional_items:
                self.context_menu.append(item)

    # ======================================================
    # selection event handlers
    # ======================================================

    def region_modified(self, sender):
        region = self.selection_widget.get_current()
        person = region.person
        mediaref = region.mediaref
        if person and mediaref:
            selection = self.selection_widget.get_selection()
            rect = self.selection_widget.real_to_proportional_rect(selection)
            mediaref.set_rectangle(rect)
            self.commit_person(person)
        self.enable_buttons()
        self.refresh_list()
        self.refresh_selection()

    def region_created(self, sender):
        self.enable_buttons()
        self.refresh_list()
        self.refresh_selection()
        self.context_menu.popup(None, None, None, None, 0, 0)
        self.prepare_context_menu()
        self.context_menu.show_all()

    def right_button_clicked(self, sender):
        self.context_menu.popup(None, None, None, None, 0, 0)
        self.prepare_context_menu()
        self.context_menu.show_all()

    def region_selected(self, sender):
        self.enable_buttons()
        self.refresh_selection()

    def selection_cleared(self, sender):
        self.enable_buttons()
        self.refresh_selection()

    def zoomed(self, sender):
        self.enable_buttons()

    # ======================================================
    # toolbar button event handles
    # ======================================================
    def add_person_clicked(self, event):
        if self.selection_widget.get_current():
            person = Person()
            EditPerson(self.dbstate, self.uistate, self.track, person, 
                       self.new_person_added)

    def sel_person_clicked(self, event):
        if self.selection_widget.get_current():
            SelectPerson = SelectorFactory('Person')
            sel = SelectPerson(self.dbstate, self.uistate, self.track, 
                               _("Select Person"))
            person = sel.run()
            if person:
                self.set_current_person(person)
                self.selection_widget.clear_selection()
                self.refresh()
                self.enable_buttons()

    def del_region_clicked(self, event):
        if self.selection_widget.get_current():
            self.delete_region(self.selection_widget.get_current())
            self.selection_widget.clear_selection()
            self.refresh()
            self.enable_buttons()

    def clear_ref_clicked(self, event):
        if self.clear_ref(self.selection_widget.get_current()):
            self.refresh()

    def edit_person_clicked(self, event):
        person = self.selection_widget.get_current().person
        if person:
            EditPerson(self.dbstate, self.uistate, self.track, person)
            self.refresh()

    def zoom_in_clicked(self, event):
        self.selection_widget.zoom_in()

    def zoom_out_clicked(self, event):
        self.selection_widget.zoom_out()

    def detect_faces_clicked(self, event):
        self.uistate.push_message(self.dbstate, _("Detecting faces..."))
        media = self.get_current_object()
        image_path = media_path_full(self.dbstate.db, media.get_path())
        faces = facedetection.detect_faces(image_path, MIN_FACE_SIZE)
        for ((x, y, width, height), neighbors) in faces:
            region = Region(x - DETECTED_REGION_PADDING,
                            y - DETECTED_REGION_PADDING,
                            x + width + DETECTED_REGION_PADDING,
                            y + height + DETECTED_REGION_PADDING)
            if DETECT_INSIDE_EXISTING_BOXES or self.enclosing_region(region) is None:
                self.regions.append(region)
        self.refresh()
        self.uistate.push_message(self.dbstate, _("Detection finished"))

    def settings_clicked(self, event):
        try:
            SettingsDialog(self.gui.dbstate, self.gui.uistate, 
                           _("Settings"), PhotoTaggingOptions())
        except Errors.WindowActiveError:
            pass

    # ======================================================
    # helpers for toolbar event handlers
    # ======================================================

    def delete_region(self, region):
        self.regions.remove(region)
        if region.person is not None:
            self.remove_reference(region.person, region.mediaref)

    def delete_regions(self, regions):
        for r in regions:
            self.delete_region(r)

    def new_person_added(self, person):
        self.set_current_person(person)
        self.selection_widget.clear_selection()
        self.refresh()
        self.enable_buttons()

    def set_current_person(self, person):
        self.ask_and_set_person(self.selection_widget.get_current(), person)

    def ask_and_set_person(self, region, person):
        if region and person:
            other_references = self.regions_referencing_person(person)
            ref_count = len(other_references)
            if ref_count > 0:
                person = other_references[0].person
                if REPLACE_WITHOUT_ASKING:
                    self.delete_regions(other_references)
                else:
                    if ref_count == 1:
                        message_text = _("Another region of this image is associated with {name}. Remove it?")
                    else:
                        message_text = _("{count} other regions of this image are associated with {name}. Remove them?")
                    message = message_text.format(
                                name=name_displayer.display(person),
                                count=ref_count)
                    dialog = Gtk.MessageDialog(
                                parent=None,
                                type=Gtk.MessageType.QUESTION, 
                                buttons=Gtk.ButtonsType.YES_NO, 
                                message_format=message)
                    response = dialog.run()
                    dialog.destroy()
                    if response == Gtk.ResponseType.YES:
                        self.delete_regions(other_references)
            self.set_person(region, person)

    def set_person(self, region, person):
        rect = self.check_and_translate_to_proportional(
          region.mediaref, region.coords())
        self.clear_ref(region)
        mediaref = self.add_reference(person, rect)
        region.person = person
        region.mediaref = mediaref

    def clear_ref(self, region):
        if region:
            if region.person:
                self.remove_reference(region.person, region.mediaref)
                region.person = None
                region.mediaref = None
                return True
        return False

    # ======================================================
    # context menu event handles
    # ======================================================

    def replace_reference(self, event, person):
        other_references = self.regions_referencing_person(person)
        self.delete_regions(other_references)
        self.set_person(self.selection_widget.get_current(), person)
        self.selection_widget.clear_selection()
        self.refresh()
        self.enable_buttons()

    # ======================================================
    # list event handles
    # ======================================================

    def cursor_changed(self, treeview):
        selected = self.get_selected_region()
        self.selection_widget.select(selected)
        self.enable_buttons()

    def row_activated(self, treeview, path, view_column):
        self.edit_person_clicked(None)

    # ======================================================
    # helpers for list event handlers
    # ======================================================

    def get_selected_region(self):
        selection = self.treeview.get_selection()
        if selection:
            (model, pathlist) = selection.get_selected_rows()
            for path in pathlist:
                tree_iter = model.get_iter(path)
                i = model.get_value(tree_iter, 0)
                try:
                    return self.regions[i - 1]   
                except:
                    return None
        return None

    # ======================================================
    # refreshing the list
    # ======================================================

    def refresh_list(self):
        self.treestore.clear()
        for (i, region) in enumerate(self.regions, start=1):
            name = name_displayer.display(region.person) if region.person else ""
            thumbnail = self.selection_widget.get_thumbnail(region, THUMBNAIL_IMAGE_SIZE)
            self.treestore.append(None, (i, thumbnail, name))

    def refresh_selection(self):
        current = self.selection_widget.get_current()
        if current and current in self.regions:
            self.treeview.get_selection().select_path(
                (self.regions.index(current),))
        else:
            self.treeview.get_selection().unselect_all()
コード例 #4
0
class PhotoTaggingGramplet(Gramplet):
    def init(self):
        self.regions = []

        self.build_context_menu()

        self.gui.WIDGET = self.build_gui()
        self.gui.get_container_widget().remove(self.gui.textview)
        self.gui.get_container_widget().add_with_viewport(self.gui.WIDGET)
        self.top.show_all()

    def on_save(self):
        CONFIG.save()

    # ======================================================
    # building the GUI
    # ======================================================

    def build_gui(self):
        """
        Build the GUI interface.
        """
        self.top = Gtk.VBox()

        hpaned = Gtk.HPaned()

        button_panel = Gtk.HBox()

        self.button_index = Gtk.ToolButton(Gtk.STOCK_INDEX)
        self.button_add = Gtk.ToolButton(Gtk.STOCK_ADD)
        self.button_del = Gtk.ToolButton(Gtk.STOCK_REMOVE)
        self.button_clear = Gtk.ToolButton(Gtk.STOCK_CLEAR)
        self.button_edit = Gtk.ToolButton(Gtk.STOCK_EDIT)
        self.button_zoom_in = Gtk.ToolButton(Gtk.STOCK_ZOOM_IN)
        self.button_zoom_out = Gtk.ToolButton(Gtk.STOCK_ZOOM_OUT)
        # set custom icon for face detect button
        self.button_detect = Gtk.ToolButton()
        theme = Gtk.IconTheme.get_default()
        face_detect_icon = theme.lookup_icon('gramps-face-detection', 24,
                                             Gtk.IconLookupFlags.FORCE_SVG)
        if face_detect_icon is not None:
            self.button_detect.set_icon_name('gramps-face-detection')
        else:
            img = Gtk.Image()
            path, filename = os.path.split(__file__)
            face_detect_icon = os.path.join(path, 'gramps-face-detection.svg')
            img.set_from_file(face_detect_icon)
            self.button_detect.set_icon_widget(img)
        self.button_settings = Gtk.ToolButton(Gtk.STOCK_PREFERENCES)

        self.button_index.connect("clicked", self.sel_person_clicked)
        self.button_add.connect("clicked", self.add_person_clicked)
        self.button_del.connect("clicked", self.clear_ref_clicked)
        self.button_clear.connect("clicked", self.del_region_clicked)
        self.button_edit.connect("clicked", self.edit_person_clicked)
        self.button_zoom_in.connect("clicked", self.zoom_in_clicked)
        self.button_zoom_out.connect("clicked", self.zoom_out_clicked)
        self.button_detect.connect("clicked", self.detect_faces_clicked)
        self.button_settings.connect("clicked", self.settings_clicked)

        button_panel.pack_start(self.button_index,
                                expand=False,
                                fill=False,
                                padding=5)
        button_panel.pack_start(self.button_add,
                                expand=False,
                                fill=False,
                                padding=5)
        button_panel.pack_start(self.button_del,
                                expand=False,
                                fill=False,
                                padding=5)
        button_panel.pack_start(self.button_clear,
                                expand=False,
                                fill=False,
                                padding=5)
        button_panel.pack_start(self.button_edit,
                                expand=False,
                                fill=False,
                                padding=5)
        button_panel.pack_start(self.button_zoom_in,
                                expand=False,
                                fill=False,
                                padding=5)
        button_panel.pack_start(self.button_zoom_out,
                                expand=False,
                                fill=False,
                                padding=5)
        button_panel.pack_start(self.button_detect,
                                expand=False,
                                fill=False,
                                padding=5)
        button_panel.pack_start(self.button_settings,
                                expand=False,
                                fill=False,
                                padding=5)

        self.button_index.set_tooltip_text(_("Select Person"))
        self.button_add.set_tooltip_text(_("Add Person"))
        self.button_del.set_tooltip_text(_("Clear Reference"))
        self.button_clear.set_tooltip_text(_("Remove Selection"))
        self.button_edit.set_tooltip_text(_("Edit referenced Person"))
        self.button_zoom_in.set_tooltip_text(_("Zoom In"))
        self.button_zoom_out.set_tooltip_text(_("Zoom Out"))

        if facedetection.computer_vision_available:
            text = _("Detect faces")
        else:
            text = _("Detect faces (OpenCV module required)")
        self.button_detect.set_tooltip_text(text)

        self.button_settings.set_tooltip_text(_("Settings"))

        self.top.pack_start(button_panel, expand=False, fill=True, padding=5)

        self.selection_widget = SelectionWidget()
        self.selection_widget.set_size_request(200, -1)
        self.selection_widget.connect("region-modified", self.region_modified)
        self.selection_widget.connect("region-created", self.region_created)
        self.selection_widget.connect("region-selected", self.region_selected)
        self.selection_widget.connect("selection-cleared",
                                      self.selection_cleared)
        self.selection_widget.connect("right-button-clicked",
                                      self.right_button_clicked)
        self.selection_widget.connect("zoomed-in", self.zoomed)
        self.selection_widget.connect("zoomed-out", self.zoomed)

        # Can drop a PERSON here:
        tglist = Gtk.TargetList.new([])
        tglist.add(DdTargets.PERSON_LINK.atom_drag_type,
                   DdTargets.PERSON_LINK.target_flags,
                   DdTargets.PERSON_LINK.app_id)
        # Can drop a LIST of HANDLES here:
        tglist.add(DdTargets.HANDLE_LIST.atom_drag_type,
                   DdTargets.HANDLE_LIST.target_flags,
                   DdTargets.HANDLE_LIST.app_id)

        # Drag and Drop for selection widget:
        self.selection_widget.event_box.drag_dest_set(
            Gtk.DestDefaults.MOTION | Gtk.DestDefaults.DROP, [],
            Gdk.DragAction.COPY)
        self.selection_widget.event_box.drag_dest_set_target_list(tglist)
        self.selection_widget.event_box.connect(
            'drag_data_received',
            lambda *args: self.drag_data_received(*args, on_image=True))
        # End Drag and Drop for selection widget

        hpaned.pack1(self.selection_widget, resize=True, shrink=False)

        self.treestore = Gtk.TreeStore(int, GdkPixbuf.Pixbuf, str)

        self.treeview = Gtk.TreeView(self.treestore)
        self.treeview.set_size_request(400, -1)
        self.treeview.connect("cursor-changed", self.cursor_changed)
        self.treeview.connect("row-activated", self.row_activated)
        self.treeview.connect("button-press-event", self.row_mouse_click)
        column1 = Gtk.TreeViewColumn(_(''))
        column2 = Gtk.TreeViewColumn(_('Preview'))
        column3 = Gtk.TreeViewColumn(_('Person'))
        self.treeview.append_column(column1)
        self.treeview.append_column(column2)
        self.treeview.append_column(column3)

        cell1 = Gtk.CellRendererText()
        cell2 = Gtk.CellRendererPixbuf()
        cell3 = Gtk.CellRendererText()
        column1.pack_start(cell1, expand=True)
        column1.add_attribute(cell1, 'text', 0)
        column2.pack_start(cell2, expand=True)
        column2.add_attribute(cell2, 'pixbuf', 1)
        column3.pack_start(cell3, expand=True)
        column3.add_attribute(cell3, 'text', 2)

        self.treeview.set_search_column(0)
        column1.set_sort_column_id(0)
        column3.set_sort_column_id(2)

        # Drag and Drop for tree view:
        self.treeview.drag_dest_set(
            Gtk.DestDefaults.MOTION | Gtk.DestDefaults.DROP, [],
            Gdk.DragAction.COPY)
        self.treeview.drag_dest_set_target_list(tglist)
        self.treeview.connect('drag_data_received', self.drag_data_received)
        # End Drag and Drop for tree_view

        scrolled_window2 = Gtk.ScrolledWindow()
        scrolled_window2.add(self.treeview)
        scrolled_window2.set_size_request(400, -1)
        scrolled_window2.set_policy(Gtk.PolicyType.AUTOMATIC,
                                    Gtk.PolicyType.AUTOMATIC)

        hpaned.pack2(scrolled_window2, resize=False, shrink=False)

        self.top.pack_start(hpaned, True, True, 5)

        self.enable_buttons()

        return self.top

    def drag_data_received(self,
                           widget,
                           context,
                           x,
                           y,
                           sel_data,
                           info,
                           time,
                           on_image=None):
        """
        Receive a dropped person onto the treeview.
        """
        if sel_data:
            pickled_data = sel_data.get_data()
            if not pickled_data:
                return
            data = pickle.loads(pickled_data)
            # Perhaps allow multiple person drops
            # Sometimes, more than one person could be in a selected area
            people = []
            # Just get the first one for now:, if a list:
            if sel_data.get_data_type(
            ) == DdTargets.HANDLE_LIST.atom_drag_type:
                if data[0][0] == "Person":
                    handle = data[0][1]
                    person = self.dbstate.db.get_person_from_handle(handle)
                    if person:
                        people.append(person)
                elif data[0][0] == "Event":
                    # get first, primary person of event:
                    event_handle = data[0][1]
                    event = self.dbstate.db.get_event_from_handle(event_handle)
                    for obj_class, handle in event.get_referenced_handles():
                        if obj_class == "Person":
                            person = self.dbstate.db.get_person_from_handle(
                                handle)
                            if person:
                                people.append(person)
                                break
            elif sel_data.get_data_type(
            ) == DdTargets.PERSON_LINK.atom_drag_type:
                (drag_type, idval, handle, val) = data
                person = self.dbstate.db.get_person_from_handle(handle)
                if person:
                    people.append(person)
            else:  # other formats work like this:
                handle = None
                try:
                    (drag_type, idval, handle, val) = data
                except:
                    pass
                if handle:
                    person = self.dbstate.db.get_person_from_handle(handle)
                    if person:
                        people.append(person)
                else:
                    LOG.warn("Can't handle this type of drop: '%s'" %
                             sel_data.get_data_type())
                    return
            for person in people:
                if on_image:  # drop on image
                    region = self.selection_widget.find_region(x, y)
                    current = self.selection_widget.get_current()
                    if region and (current is None or current == region):
                        self.ask_and_set_person(region, person)
                        self.selection_widget.clear_selection()
                        self.refresh()
                        self.enable_buttons()
                else:  # drop on list
                    drop_info = self.treeview.get_dest_row_at_pos(x, y)
                    if drop_info:
                        #path, position = drop_info
                        #self.treeview.set_cursor(path)
                        self.set_current_person(person)
                        self.selection_widget.clear_selection()
                        self.refresh()
                        self.enable_buttons()

    def build_context_menu(self):
        self.context_menu = Gtk.Menu()
        self.context_menu.set_reserve_toggle_size(False)

        self.context_button_active = Gtk.MenuItem.new_with_mnemonic(
            _("Set as active person"))
        self.context_button_active.connect("activate", self.set_active_person)

        self.context_button_select = Gtk.MenuItem.new_with_mnemonic(
            _("_Select"))
        self.context_button_select.connect("activate", self.sel_person_clicked)

        self.context_button_add = Gtk.MenuItem.new_with_mnemonic(_("_Add"))
        self.context_button_add.connect("activate", self.add_person_clicked)

        self.context_button_clear = Gtk.MenuItem.new_with_mnemonic(_("_Clear"))
        self.context_button_clear.connect("activate", self.clear_ref_clicked)

        self.context_button_remove = Gtk.MenuItem.new_with_mnemonic(
            _("_Remove"))
        self.context_button_remove.connect("activate", self.del_region_clicked)

        self.context_menu.append(self.context_button_active)
        self.context_menu.append(self.context_button_select)
        self.context_menu.append(self.context_button_add)
        self.context_menu.append(self.context_button_clear)
        self.context_menu.append(self.context_button_remove)

        self.additional_items = []

    def refresh(self):
        self.selection_widget.refresh()
        self.refresh_list()
        self.refresh_selection()

    # ======================================================
    # gramplet event handlers
    # ======================================================

    def db_changed(self):
        self.connect(self.dbstate.db, 'media-update', self.update)
        self.connect_signal('Media', self.update)

    def main(self):
        media = self.get_current_object()
        self.top.hide()
        if media and media.mime.startswith("image"):
            self.load_image(media)
        else:
            self.selection_widget.show_missing()
        self.refresh_list()
        self.enable_buttons()
        self.top.show()

    # ======================================================
    # loading the image
    # ======================================================

    def load_image(self, media):
        self.regions = []
        image_path = media_path_full(self.dbstate.db, media.get_path())
        self.selection_widget.load_image(image_path)
        self.retrieve_backrefs()
        self.selection_widget.set_regions(self.regions)

    def retrieve_backrefs(self):
        """
        Finds the media references pointing to the current image
        """
        backrefs = self.dbstate.db.find_backlink_handles(
            self.get_current_handle())
        for (reftype, ref) in backrefs:
            if reftype == "Person":
                person = self.dbstate.db.get_person_from_handle(ref)
                gallery = person.get_media_list()
                for mediaref in gallery:
                    referenced_handles = mediaref.get_referenced_handles()
                    if len(referenced_handles) == 1:
                        handle_type, handle = referenced_handles[0]
                        if handle_type == "Media" and handle == self.get_current_handle(
                        ):
                            rect = mediaref.get_rectangle()
                            if rect is None:
                                rect = (0, 0, 100, 100)
                            coords = self.selection_widget.proportional_to_real_rect(
                                rect)
                            region = Region(*coords)
                            region.person = person
                            region.mediaref = mediaref
                            self.regions.append(region)

    # ======================================================
    # managing regions
    # ======================================================

    def check_and_translate_to_proportional(self, mediaref, rect):
        if mediaref:
            return mediaref.get_rectangle()
        else:
            return self.selection_widget.real_to_proportional_rect(rect)

    def intersects_any(self, region):
        for r in self.regions:
            if r.intersects(region):
                return True
        return False

    def enclosing_region(self, region):
        for r in self.regions:
            if r.contains_rect(region):
                return r
        return None

    def regions_referencing_person(self, person):
        result = []
        for r in self.regions:
            if r.person == person:
                result.append(r)
        return result

    def all_referenced_persons(self):
        result = []
        for r in self.regions:
            if r.person is not None:
                result.append(r.person)
        return result

    # ======================================================
    # utility functions for retrieving properties
    # ======================================================

    def get_current_handle(self):
        return self.get_active('Media')

    def get_current_object(self):
        try:
            return self.dbstate.db.get_media_from_handle(
                self.get_current_handle())
        except:
            return None

    # ======================================================
    # helpers for updating database objects
    # ======================================================

    def add_reference(self, person, rect):
        """
        Add a reference to the media object to the specified person.
        """
        mediaref = MediaRef()
        mediaref.ref = self.get_current_handle()
        mediaref.set_rectangle(rect)
        person.add_media_reference(mediaref)
        self.commit_person(person)
        return mediaref

    def remove_reference(self, person, mediaref):
        """
        Removes the reference to the media object from the person.
        """
        if mediaref in person.get_media_list():
            person.get_media_list().remove(mediaref)
            self.commit_person(person)

    def commit_person(self, person):
        """
        Save the modifications made to a Person object to the database.
        """
        with DbTxn('', self.dbstate.db) as trans:
            self.dbstate.db.commit_person(person, trans)
            msg = _("Edit Person (%s)") % name_displayer.display(person)
            trans.set_description(msg)

    # ======================================================
    # managing toolbar buttons
    # ======================================================
    def enable_buttons(self):
        selected = self.selection_widget.get_current()
        self.button_index.set_sensitive(selected is not None)
        self.button_add.set_sensitive(selected is not None)
        self.button_del.set_sensitive(selected is not None
                                      and selected.person is not None)
        self.button_clear.set_sensitive(selected is not None)
        self.button_edit.set_sensitive(selected is not None
                                       and selected.person is not None)
        self.button_zoom_in.set_sensitive(
            self.selection_widget.is_image_loaded()
            and self.selection_widget.can_zoom_in())
        self.button_zoom_out.set_sensitive(
            self.selection_widget.is_image_loaded()
            and self.selection_widget.can_zoom_out())
        self.button_detect.set_sensitive(
            self.selection_widget.is_image_loaded()
            and facedetection.computer_vision_available)

    # ======================================================
    # managing context menu buttons
    # ======================================================

    def prepare_context_menu(self):
        selected = self.selection_widget.get_current()
        has_person = selected is not None and selected.person is not None

        self.context_button_active.set_sensitive(has_person)
        self.context_button_add.set_sensitive(selected is not None)
        self.context_button_select.set_sensitive(selected is not None)
        self.context_button_clear.set_sensitive(has_person)
        self.context_button_remove.set_sensitive(selected is not None)

        # clear temporary items
        for item in self.additional_items:
            self.context_menu.remove(item)

        self.additional_items = []

        # populate the context menu
        persons = self.all_referenced_persons()
        if selected is not None and selected.person is not None:
            persons.remove(selected.person)
        if persons:
            self.additional_items.append(Gtk.SeparatorMenuItem())
            sorted_persons = sorted(list(persons), key=name_displayer.display)
            for person in sorted_persons:
                item = Gtk.MenuItem(
                    _("Replace to {0}").format(name_displayer.display(person)))
                item.connect("activate", self.replace_reference, person)
                self.additional_items.append(item)
            for item in self.additional_items:
                self.context_menu.append(item)

    def show_context_menu(self):
        """
        Show popup menu using different functions according to Gtk version.
        'Gtk.Menu.popup' is deprecated since version 3.22, see:
        https://lazka.github.io/pgi-docs/index.html#Gtk-3.0/classes/Menu.html#Gtk.Menu.popup
        """
        self.prepare_context_menu()
        self.context_menu.show_all()
        if (Gtk.MAJOR_VERSION >= 3) and (Gtk.MINOR_VERSION >= 22):
            self.context_menu.popup_at_pointer(None)
        else:
            self.context_menu.popup(None, None, None, None, 0, 0)

    # ======================================================
    # selection event handlers
    # ======================================================

    def region_modified(self, sender):
        region = self.selection_widget.get_current()
        person = region.person
        mediaref = region.mediaref
        if person and mediaref:
            selection = self.selection_widget.get_selection()
            rect = self.selection_widget.real_to_proportional_rect(selection)
            mediaref.set_rectangle(rect)
            self.commit_person(person)
        self.enable_buttons()
        self.refresh_list()
        self.refresh_selection()

    def region_created(self, sender):
        self.enable_buttons()
        self.refresh_list()
        self.refresh_selection()
        self.show_context_menu()

    def right_button_clicked(self, sender):
        self.show_context_menu()

    def region_selected(self, sender):
        self.enable_buttons()
        self.refresh_selection()

    def selection_cleared(self, sender):
        self.enable_buttons()
        self.refresh_selection()

    def zoomed(self, sender):
        self.enable_buttons()

    # ======================================================
    # toolbar button event handles
    # ======================================================
    def add_person_clicked(self, event):
        if self.selection_widget.get_current():
            person = Person()
            EditPerson(self.dbstate, self.uistate, self.track, person,
                       self.new_person_added)

    def sel_person_clicked(self, event):
        if self.selection_widget.get_current():
            SelectPerson = SelectorFactory('Person')
            sel = SelectPerson(self.dbstate, self.uistate, self.track,
                               _("Select Person"))
            person = sel.run()
            if person:
                self.set_current_person(person)
                self.selection_widget.clear_selection()
                self.refresh()
                self.enable_buttons()

    def del_region_clicked(self, event):
        if self.selection_widget.get_current():
            self.delete_region(self.selection_widget.get_current())
            self.selection_widget.clear_selection()
            self.refresh()
            self.enable_buttons()

    def clear_ref_clicked(self, event):
        if self.clear_ref(self.selection_widget.get_current()):
            self.refresh()

    def edit_person_clicked(self, event):
        person = self.selection_widget.get_current().person
        if person:
            EditPerson(self.dbstate, self.uistate, self.track, person)
            self.refresh()

    def zoom_in_clicked(self, event):
        self.selection_widget.zoom_in()

    def zoom_out_clicked(self, event):
        self.selection_widget.zoom_out()

    def detect_faces_clicked(self, event):
        self.uistate.push_message(self.dbstate, _("Detecting faces..."))
        media = self.get_current_object()
        image_path = media_path_full(self.dbstate.db, media.get_path())
        faces, img_size = facedetection.detect_faces(image_path, MIN_FACE_SIZE,
                                                     SENSITIVITY)
        # verify and enlarge found faces regions
        for (x, y, width, height) in faces:
            # calculate enlarged region
            new_x1 = x - width / 5
            new_y1 = y - height / 3
            new_x2 = x + width * 6 / 5
            new_y2 = y + height * 7 / 5
            # prevent overflow image size
            new_y1 = 0 if new_y1 < 0 else new_y1
            new_y2 = img_size[0] if img_size[0] < new_y2 else new_y2
            new_x1 = 0 if new_x1 < 0 else new_x1
            new_x2 = img_size[1] if img_size[1] < new_x2 else new_x2

            region = Region(new_x1, new_y1, new_x2, new_y2)

            if (DETECT_INSIDE_EXISTING_BOXES
                    or self.enclosing_region(region) is None):
                self.regions.append(region)

        self.refresh()
        self.uistate.push_message(self.dbstate, _("Detection finished"))

    def settings_clicked(self, event):
        try:
            SettingsDialog(self.gui.dbstate, self.gui.uistate, _("Settings"),
                           PhotoTaggingOptions())
        except WindowActiveError:
            pass

    def set_active_person(self, event):
        """
        Set selected person as active.
        """
        person = self.selection_widget.get_current().person
        if person:
            person_handle = person.get_handle()
            self.set_active('Person', person_handle)

    # ======================================================
    # helpers for toolbar event handlers
    # ======================================================

    def delete_region(self, region):
        self.regions.remove(region)
        if region.person is not None:
            self.remove_reference(region.person, region.mediaref)

    def delete_regions(self, regions):
        for r in regions:
            self.delete_region(r)

    def new_person_added(self, person):
        self.set_current_person(person)
        self.selection_widget.clear_selection()
        self.refresh()
        self.enable_buttons()

    def set_current_person(self, person):
        self.ask_and_set_person(self.selection_widget.get_current(), person)

    def ask_and_set_person(self, region, person):
        if region and person:
            other_references = self.regions_referencing_person(person)
            ref_count = len(other_references)
            if ref_count > 0:
                person = other_references[0].person
                if REPLACE_WITHOUT_ASKING:
                    self.delete_regions(other_references)
                else:
                    if ref_count == 1:
                        text = _("Another region of this image "
                                 "is associated with {name}. Remove it?")
                    else:
                        text = _("{count} other regions of this image "
                                 "are associated with {name}. Remove them?")
                    message = text.format(name=name_displayer.display(person),
                                          count=ref_count)
                    dialog = Gtk.MessageDialog(parent=None,
                                               type=Gtk.MessageType.QUESTION,
                                               buttons=Gtk.ButtonsType.YES_NO,
                                               message_format=message)
                    response = dialog.run()
                    dialog.destroy()
                    if response == Gtk.ResponseType.YES:
                        self.delete_regions(other_references)
            self.set_person(region, person)

    def set_person(self, region, person):
        rect = self.check_and_translate_to_proportional(
            region.mediaref, region.coords())
        self.clear_ref(region)
        mediaref = self.add_reference(person, rect)
        region.person = person
        region.mediaref = mediaref

    def clear_ref(self, region):
        if region:
            if region.person:
                self.remove_reference(region.person, region.mediaref)
                region.person = None
                region.mediaref = None
                return True
        return False

    # ======================================================
    # context menu event handles
    # ======================================================

    def replace_reference(self, event, person):
        other_references = self.regions_referencing_person(person)
        self.delete_regions(other_references)
        self.set_person(self.selection_widget.get_current(), person)
        self.selection_widget.clear_selection()
        self.refresh()
        self.enable_buttons()

    # ======================================================
    # list event handles
    # ======================================================

    def cursor_changed(self, treeview):
        selected = self.get_selected_region()
        self.selection_widget.select(selected)
        self.enable_buttons()

    def row_activated(self, treeview, path, view_column):
        self.edit_person_clicked(None)

    def row_mouse_click(self, treeview, event):
        """
        Handle right mouse click on treeview.
        Show popup menu for row the same as for region.
        """
        button = event.get_button()[1]
        # right mouse button
        if button == 3:
            # change cursor position to apply row selection
            pthinfo = self.treeview.get_path_at_pos(event.x, event.y)
            if pthinfo is not None:
                path, col, cellx, celly = pthinfo
                self.treeview.grab_focus()
                self.treeview.set_cursor(path, col, 0)
                self.show_context_menu()
                # stop signal emission
                return True

    # ======================================================
    # helpers for list event handlers
    # ======================================================

    def get_selected_region(self):
        selection = self.treeview.get_selection()
        if selection:
            (model, pathlist) = selection.get_selected_rows()
            for path in pathlist:
                tree_iter = model.get_iter(path)
                i = model.get_value(tree_iter, 0)
                try:
                    return self.regions[i - 1]
                except:
                    return None
        return None

    # ======================================================
    # refreshing the list
    # ======================================================

    def refresh_list(self):
        self.treestore.clear()
        for (i, region) in enumerate(self.regions, start=1):
            name = name_displayer.display(
                region.person) if region.person else ""
            thumbnail = self.selection_widget.get_thumbnail(
                region, THUMBNAIL_IMAGE_SIZE)
            self.treestore.append(None, (i, thumbnail, name))

    def refresh_selection(self):
        current = self.selection_widget.get_current()
        selection = self.treeview.get_selection()
        if current and current in self.regions:
            selection.select_path(self.regions.index(current), )
        else:
            selection.unselect_all()