def __init__(self, library): super(MainWindow, self).__init__('main_window') # Instance attributes for widgets. self._main_window = self._builder.get_object('main_window') self._tags_treeview = self._builder.get_object('tags_treeview') self._tags_scrolledwindow = self._builder.get_object( 'tags_scrolledwindow') self._docs_iconview = self._builder.get_object('docs_iconview') self._docs_scrolledwindow = self._builder.get_object( 'docs_scrolledwindow') self._docs_menu = self._builder.get_object('docs_menu') self._statusbar = self._builder.get_object('statusbar') self._docs_menuitem = self._builder.get_object('docs_menuitem') self._small_icons_menuitem = self._builder.get_object( 'small_icons_menuitem') self._normal_icons_menuitem = self._builder.get_object( 'normal_icons_menuitem') self._large_icons_menuitem = self._builder.get_object( 'large_icons_menuitem') self._open_docs_toolbutton = self._builder.get_object( 'open_docs_toolbutton') self._open_docs_menuitem = self._builder.get_object( 'open_docs_menuitem') self._copy_docs_toolbutton = self._builder.get_object( 'copy_docs_toolbutton') self._copy_docs_menuitem = self._builder.get_object( 'copy_docs_menuitem') self._delete_docs_toolbutton = self._builder.get_object( 'delete_docs_toolbutton') self._delete_docs_menuitem = self._builder.get_object( 'delete_docs_menuitem') self._tag_docs_toolbutton = self._builder.get_object( 'tag_docs_toolbutton') self._tag_docs_menuitem = self._builder.get_object('tag_docs_menuitem') self._search_entry = SearchEntry() # Other instance attributes. self._library = library self._search_timeout_id = 0 self._search_timeout = 1000 # milliseconds. self._update_docs_iconview_id = 0 # Initialize widgets. self._main_window.set_title(about.NAME) self._init_tags_treeview() self._init_docs_iconview() self._init_toolbar() self._init_menubar() self._query = '' self._old_query = None self._selected_tags = set() self._old_selected_tags = None self._update_tags_treeview()
def __init__(self, library): super(MainWindow, self).__init__("main_window") # Instance attributes for widgets. self._main_window = self._builder.get_object("main_window") self._tags_treeview = self._builder.get_object("tags_treeview") self._tags_scrolledwindow = self._builder.get_object("tags_scrolledwindow") self._docs_iconview = self._builder.get_object("docs_iconview") self._docs_scrolledwindow = self._builder.get_object("docs_scrolledwindow") self._docs_menu = self._builder.get_object("docs_menu") self._statusbar = self._builder.get_object("statusbar") self._docs_menuitem = self._builder.get_object("docs_menuitem") self._small_icons_menuitem = self._builder.get_object("small_icons_menuitem") self._normal_icons_menuitem = self._builder.get_object("normal_icons_menuitem") self._large_icons_menuitem = self._builder.get_object("large_icons_menuitem") self._open_docs_toolbutton = self._builder.get_object("open_docs_toolbutton") self._open_docs_menuitem = self._builder.get_object("open_docs_menuitem") self._copy_docs_toolbutton = self._builder.get_object("copy_docs_toolbutton") self._copy_docs_menuitem = self._builder.get_object("copy_docs_menuitem") self._delete_docs_toolbutton = self._builder.get_object("delete_docs_toolbutton") self._delete_docs_menuitem = self._builder.get_object("delete_docs_menuitem") self._tag_docs_toolbutton = self._builder.get_object("tag_docs_toolbutton") self._tag_docs_menuitem = self._builder.get_object("tag_docs_menuitem") self._search_entry = SearchEntry() # Other instance attributes. self._library = library self._search_timeout_id = 0 self._search_timeout = 1000 # milliseconds. self._update_docs_iconview_id = 0 # Initialize widgets. self._main_window.set_title(about.NAME) self._init_tags_treeview() self._init_docs_iconview() self._init_toolbar() self._init_menubar() self._query = "" self._old_query = None self._selected_tags = set() self._old_selected_tags = None self._update_tags_treeview()
class MainWindow(XMLWidget): TAGS_TREEVIEW_COLUMN_TYPE = 0 TAGS_TREEVIEW_COLUMN_TAG = 1 TAGS_TREEVIEW_ROW_ALL = 0 TAGS_TREEVIEW_ROW_SEPARATOR = 1 TAGS_TREEVIEW_ROW_TAG = 2 DOCS_TREEVIEW_COLUMN_ID = 0 DOCS_TREEVIEW_COLUMN_ICON_PATH = 1 DOCS_TREEVIEW_COLUMN_ICON_PIXBUF = 2 DOC_ICON_SMALL = 0 DOC_ICON_NORMAL = 1 DOC_ICON_LARGE = 2 def __init__(self, library): super(MainWindow, self).__init__('main_window') # Instance attributes for widgets. self._main_window = self._builder.get_object('main_window') self._tags_treeview = self._builder.get_object('tags_treeview') self._tags_scrolledwindow = self._builder.get_object( 'tags_scrolledwindow') self._docs_iconview = self._builder.get_object('docs_iconview') self._docs_scrolledwindow = self._builder.get_object( 'docs_scrolledwindow') self._docs_menu = self._builder.get_object('docs_menu') self._statusbar = self._builder.get_object('statusbar') self._docs_menuitem = self._builder.get_object('docs_menuitem') self._small_icons_menuitem = self._builder.get_object( 'small_icons_menuitem') self._normal_icons_menuitem = self._builder.get_object( 'normal_icons_menuitem') self._large_icons_menuitem = self._builder.get_object( 'large_icons_menuitem') self._open_docs_toolbutton = self._builder.get_object( 'open_docs_toolbutton') self._open_docs_menuitem = self._builder.get_object( 'open_docs_menuitem') self._copy_docs_toolbutton = self._builder.get_object( 'copy_docs_toolbutton') self._copy_docs_menuitem = self._builder.get_object( 'copy_docs_menuitem') self._delete_docs_toolbutton = self._builder.get_object( 'delete_docs_toolbutton') self._delete_docs_menuitem = self._builder.get_object( 'delete_docs_menuitem') self._tag_docs_toolbutton = self._builder.get_object( 'tag_docs_toolbutton') self._tag_docs_menuitem = self._builder.get_object('tag_docs_menuitem') self._search_entry = SearchEntry() # Other instance attributes. self._library = library self._search_timeout_id = 0 self._search_timeout = 1000 # milliseconds. self._update_docs_iconview_id = 0 # Initialize widgets. self._main_window.set_title(about.NAME) self._init_tags_treeview() self._init_docs_iconview() self._init_toolbar() self._init_menubar() self._query = '' self._old_query = None self._selected_tags = set() self._old_selected_tags = None self._update_tags_treeview() def _init_toolbar(self): # The combo box with the size of the icons. icon_size_toolitem = self._builder.get_object('icon_size_toolitem') self._icon_size_combobox = gtk.combo_box_new_text() self._icon_size_combobox.append_text('Small Icons') self._icon_size_combobox.append_text('Normal Icons') self._icon_size_combobox.append_text('Large Icons') self._icon_size_combobox.connect('changed', self.on_icons_size_combobox_changed) self._icon_size_combobox.set_active(self.DOC_ICON_NORMAL) icon_size_toolitem.add(self._icon_size_combobox) icon_size_toolitem.show_all() # The search text entry. search_toolitem = self._builder.get_object('search_toolitem') self._search_entry.set_width_chars(40) self._search_entry.connect('changed', self.on_search_entry_changed) search_toolitem.add(self._search_entry) search_toolitem.show_all() def _init_menubar(self): # The check menu item for the tags pane. show_tags_menuitem = self._builder.get_object('show_tags_menuitem') show_tags_menuitem.set_active(True) # The radio menu items for the size of the icons. self._normal_icons_menuitem.set_group(self._small_icons_menuitem) self._large_icons_menuitem.set_group(self._small_icons_menuitem) self._normal_icons_menuitem.set_active(True) self._small_icons_menuitem.connect('toggled', self.on_icons_size_menuitem_toggled, self.DOC_ICON_SMALL) self._normal_icons_menuitem.connect( 'toggled', self.on_icons_size_menuitem_toggled, self.DOC_ICON_NORMAL) self._large_icons_menuitem.connect('toggled', self.on_icons_size_menuitem_toggled, self.DOC_ICON_LARGE) def _init_tags_treeview(self): # Initialize the list store, the cell renderer and the column of the tree view. self._tags_liststore = gtk.ListStore(int, str) self._tags_treeview.set_model(self._tags_liststore) renderer = gtk.CellRendererText() renderer.connect('edited', self.on_tag_cellrenderer_edited) renderer.set_property('xalign', 0.5) column = gtk.TreeViewColumn(None, renderer) column.add_attribute(renderer, 'text', self.TAGS_TREEVIEW_COLUMN_TAG) # Function to disable edition of the "special" tags. f = lambda column, renderer, model, iter: renderer.set_property( 'editable', model.get_value(iter, self.TAGS_TREEVIEW_COLUMN_TYPE) == self. TAGS_TREEVIEW_ROW_TAG) column.set_cell_data_func(renderer, f) self._tags_treeview.append_column(column) # Function to draw the separator. f = lambda model, iter: model.get_value( iter, self.TAGS_TREEVIEW_COLUMN_TYPE ) == self.TAGS_TREEVIEW_ROW_SEPARATOR self._tags_treeview.set_row_separator_func(f) # Setup sorting. self._tags_liststore.set_sort_func(self.TAGS_TREEVIEW_COLUMN_TAG, self._tags_liststore_sort_func) self._tags_liststore.set_sort_column_id(self.TAGS_TREEVIEW_COLUMN_TAG, gtk.SORT_ASCENDING) # Initialize the selection. selection = self._tags_treeview.get_selection() selection.set_mode(gtk.SELECTION_MULTIPLE) selection.connect('changed', self.on_tags_treeview_selection_changed) # Other tree view settings. self._tags_treeview.set_headers_visible(False) self._tags_treeview.set_search_column(self.TAGS_TREEVIEW_COLUMN_TAG) def _init_docs_iconview(self): # Initialize the list store and the icon view. self._docs_liststore = gtk.ListStore(str, str, gtk.gdk.Pixbuf) self._docs_iconview.set_model(self._docs_liststore) self._docs_iconview.set_pixbuf_column( self.DOCS_TREEVIEW_COLUMN_ICON_PIXBUF) self._docs_iconview.set_selection_mode(gtk.SELECTION_MULTIPLE) self._docs_iconview.connect('selection-changed', self.on_docs_iconview_selection_changed) self._docs_icon_size = self.DOC_ICON_NORMAL # Configure the signals to load the thumbnails of the documents. adjustment = self._docs_scrolledwindow.get_hadjustment() adjustment.connect('value-changed', self.on_iconview_adjustment_changed) adjustment = self._docs_scrolledwindow.get_vadjustment() adjustment.connect('value-changed', self.on_iconview_adjustment_changed) # Default document icons. self._docs_icon_small = gtk.gdk.pixbuf_new_from_file_at_size( get_image('diglib-document.svg'), self._library.THUMBNAIL_SIZE_SMALL, self._library.THUMBNAIL_SIZE_SMALL) self._docs_icon_normal = gtk.gdk.pixbuf_new_from_file_at_size( get_image('diglib-document.svg'), self._library.THUMBNAIL_SIZE_NORMAL, self._library.THUMBNAIL_SIZE_NORMAL) self._docs_icon_large = gtk.gdk.pixbuf_new_from_file_at_size( get_image('diglib-document.svg'), self._library.THUMBNAIL_SIZE_LARGE, self._library.THUMBNAIL_SIZE_LARGE) def on_import_file(self, *args): dialog = ImportFileDialog() response = dialog.run() filename = dialog.get_filename() tags = dialog.get_tags() dialog.destroy() if response == gtk.RESPONSE_OK: # Stop the current query (if any). if self._update_docs_iconview_id > 0: gobject.source_remove(self._update_docs_iconview_id) try: self._library.add_doc(filename, tags) except error.DocumentDuplicatedExact: message = 'The document is already in the library.' except error.DocumentDuplicatedSimilar: message = 'A similar document is already in the library.' except error.DocumentNotRetrievable: message = 'The document is not retrievable.' except error.DocumentNotSupported: message = 'The format of the document not supported.' else: message = None if message: dialog = gtk.MessageDialog(self._main_window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, 'Could not import the document.') dialog.format_secondary_text(message) dialog.run() dialog.destroy() self._update_tags_treeview(True) def on_import_dir(self, *args): window = ImportDirectoryWindow(self._library) response = window.run() if response == gtk.RESPONSE_OK: # Stop the current query (if any). if self._update_docs_iconview_id > 0: gobject.source_remove(self._update_docs_iconview_id) self._update_tags_treeview(True) def on_open_docs(self, *args): for hash_md5 in self._iter_selected_docs(): doc = self._library.get_doc(hash_md5) open_file(doc.document_abspath) def on_copy_docs(self, *args): doc_paths = [ self._library.get_doc(hash_md5).document_abspath for hash_md5 in self._iter_selected_docs() ] if doc_paths: def get_func(clipboard, selectiondata, info, data): uris = ['file://%s' % urllib.quote(path) for path in doc_paths] text = 'copy\n%s' % "\n".join(uris) selectiondata.set(selectiondata.get_target(), 8, text) clipboard = gtk.clipboard_get() targets = [('x-special/gnome-copied-files', 0, 0)] clipboard.set_with_data(targets, get_func, lambda clipboard, data: None) def on_delete_docs(self, *args): selected_docs = list(self._iter_selected_docs()) num_docs = len(selected_docs) if num_docs > 0: message = 'Delete the %s?' % \ (('%s selected documents' % num_docs) if num_docs > 1 else 'selected document') secondary_text = 'The %s will be permanently lost.' % \ ('documents' if num_docs > 1 else 'document') dialog = gtk.MessageDialog(self._main_window, gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, message) dialog.format_secondary_text(secondary_text) response = dialog.run() dialog.destroy() if response == gtk.RESPONSE_YES: # Stop the current query (if any). if self._update_docs_iconview_id > 0: gobject.source_remove(self._update_docs_iconview_id) for hash_md5 in selected_docs: self._library.delete_doc(hash_md5) self._update_tags_treeview(True) def on_tag_docs(self, *args): common_tags = None for hash_md5 in self._iter_selected_docs(): doc_tags = self._library.get_doc(hash_md5).tags common_tags = doc_tags if common_tags is None \ else doc_tags.intersection(common_tags) dialog = EditTagsDialog(common_tags) response = dialog.run() edited_tags = dialog.get_tags() dialog.destroy() if response == gtk.RESPONSE_OK and edited_tags != common_tags: # Stop the current query (if any). if self._update_docs_iconview_id > 0: gobject.source_remove(self._update_docs_iconview_id) removed_tags = common_tags.difference(edited_tags) added_tags = edited_tags.difference(common_tags) try: for hash_md5 in self._iter_selected_docs(): doc_tags = self._library.get_doc(hash_md5).tags doc_tags.difference_update(removed_tags) doc_tags.update(added_tags) self._library.update_tags(hash_md5, doc_tags) except error.DocumentNotRetrievable: message = 'Could not remove a tag from a document.' secondary_text = 'If the tag is removed, the ' \ 'document could not be retrieved.' dialog = gtk.MessageDialog(self._main_window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message) dialog.format_secondary_text(secondary_text) dialog.run() dialog.destroy() self._update_tags_treeview(True) def on_close_menuitem_activate(self, menuitem): self._main_window.destroy() def on_about_menuitem_activate(self, menuitem): dialog = AboutDialog() dialog.run() dialog.destroy() def on_show_tags_menuitem_toggled(self, checkmenuitem): if checkmenuitem.get_active(): self._tags_scrolledwindow.show() else: self._tags_scrolledwindow.hide() def on_icons_size_menuitem_toggled(self, radiomenuitem, new_size): if radiomenuitem.get_active() and self._docs_icon_size != new_size: self._docs_icon_size = new_size self._update_icons_size_widgets() self._update_docs_iconview_wrapper(True) def on_icons_size_combobox_changed(self, combobox): new_size = combobox.get_active() if self._docs_icon_size != new_size: self._docs_icon_size = new_size self._update_icons_size_widgets() self._update_docs_iconview_wrapper(True) def on_docs_iconview_button_press_event(self, iconview, event): if event.button == 3: # Right button. path = iconview.get_path_at_pos(int(event.x), int(event.y)) if path: if not iconview.path_is_selected(path): iconview.unselect_all() iconview.select_path(path) self._docs_menu.popup(None, None, None, event.button, event.time) self._docs_menu.show() def on_iconview_adjustment_changed(self, *args): visible_range = self._docs_iconview.get_visible_range() if visible_range: self._docs_liststore.foreach(self._update_docs_iconview_icons, visible_range) def on_main_window_destroy(self, widget): gtk.main_quit() def on_tag_cellrenderer_edited(self, renderer, path, new_name): iter = self._tags_liststore.get_iter(path) new_name = new_name.strip() old_name = self._tags_liststore.get_value( iter, self.TAGS_TREEVIEW_COLUMN_TAG) if old_name != new_name: # Stop the current query (if any). if self._update_docs_iconview_id > 0: gobject.source_remove(self._update_docs_iconview_id) self._library.rename_tag(old_name, new_name) self._update_tags_treeview() def on_tags_treeview_selection_changed(self, *args): if self._search_timeout_id > 0: gobject.source_remove(self._search_timeout_id) self._search_timeout_id = gobject.timeout_add( self._search_timeout, self._search_timeout_callback) def on_search_entry_changed(self, *tags): if self._search_timeout_id > 0: gobject.source_remove(self._search_timeout_id) self._search_timeout_id = gobject.timeout_add( self._search_timeout, self._search_timeout_callback) def on_docs_iconview_selection_changed(self, iconview): selected_docs = list(self._iter_selected_docs()) sensitive = len(selected_docs) > 0 self._open_docs_toolbutton.set_sensitive(sensitive) self._open_docs_menuitem.set_sensitive(sensitive) self._copy_docs_toolbutton.set_sensitive(sensitive) self._copy_docs_menuitem.set_sensitive(sensitive) self._delete_docs_toolbutton.set_sensitive(sensitive) self._delete_docs_menuitem.set_sensitive(sensitive) self._tag_docs_toolbutton.set_sensitive(sensitive) self._tag_docs_menuitem.set_sensitive(sensitive) def _update_tags_treeview(self, force_update_docs=False): selected_tags = set( self._iter_selected_tags()) # Remember the selection. self._tags_liststore.clear() # Add the special rows. self._tags_liststore.append( [self.TAGS_TREEVIEW_ROW_ALL, 'All Documents']) self._tags_liststore.append([self.TAGS_TREEVIEW_ROW_SEPARATOR, None]) # Add one row for each tag. all_tags = self._library.get_all_tags() for tag in all_tags: self._tags_liststore.append([self.TAGS_TREEVIEW_ROW_TAG, tag]) # Restore the selection (if possible). selection = self._tags_treeview.get_selection() if selected_tags.issubset(all_tags): for row in self._tags_liststore: if (row[self.TAGS_TREEVIEW_COLUMN_TYPE] == self.TAGS_TREEVIEW_ROW_TAG and row[self.TAGS_TREEVIEW_COLUMN_TAG] in selected_tags): selection.select_path(row.path) else: self._selected_tags = set() selection.unselect_all() selection.select_path((0, )) self._update_docs_iconview_wrapper(force_update_docs) def _update_docs_iconview_wrapper(self, force=False): if (force or self._query != self._old_query or self._selected_tags != self._old_selected_tags): self._old_query = self._query self._old_selected_tags = self._selected_tags # Stop the active query (if any) and then start a new one. if self._update_docs_iconview_id > 0: gobject.source_remove(self._update_docs_iconview_id) self._docs_liststore.clear() self._update_docs_iconview_id = \ gobject.idle_add(self._update_docs_iconview, self._query, self._selected_tags) def _update_docs_iconview(self, query, selected_tags): self._statusbar.push(0, 'Loading documents...') start = len(self._docs_liststore) results = self._library.search(query, selected_tags, start, 10) if not results: num_docs = len(self._docs_liststore) text = '%s %s' % (num_docs, 'documents' if num_docs > 1 else 'document') self._statusbar.push(0, text) self._update_docs_iconview_id = 0 return False # Finished getting results. load_pixbuf = start <= 40 # Load the pixbuf of the first 50 documents. for hash_md5 in results: doc = self._library.get_doc(hash_md5) if self._docs_icon_size == self.DOC_ICON_SMALL: icon_path = doc.small_thumbnail_abspath \ if doc.small_thumbnail_abspath else '' if icon_path and load_pixbuf: icon_pixbuf = gtk.gdk.pixbuf_new_from_file(icon_path) else: icon_pixbuf = self._docs_icon_small elif self._docs_icon_size == self.DOC_ICON_NORMAL: icon_path = doc.normal_thumbnail_abspath \ if doc.normal_thumbnail_abspath else '' if icon_path and load_pixbuf: icon_pixbuf = gtk.gdk.pixbuf_new_from_file(icon_path) else: icon_pixbuf = self._docs_icon_normal elif self._docs_icon_size == self.DOC_ICON_LARGE: icon_path = doc.large_thumbnail_abspath \ if doc.large_thumbnail_abspath else '' if icon_path and load_pixbuf: icon_pixbuf = gtk.gdk.pixbuf_new_from_file(icon_path) else: icon_pixbuf = self._docs_icon_large self._docs_liststore.append([doc.hash_md5, icon_path, icon_pixbuf]) return True # Continue getting results. def _update_docs_iconview_icons(self, model, path, iter, visible_range): start_path, end_path = visible_range icon_path = model.get_value(iter, self.DOCS_TREEVIEW_COLUMN_ICON_PATH) if not icon_path or path < start_path or path > end_path: if self._docs_icon_size == self.DOC_ICON_SMALL: icon_pixbuf = self._docs_icon_small elif self._docs_icon_size == self.DOC_ICON_NORMAL: icon_pixbuf = self._docs_icon_normal elif self._docs_icon_size == self.DOC_ICON_LARGE: icon_pixbuf = self._docs_icon_large else: icon_pixbuf = gtk.gdk.pixbuf_new_from_file(icon_path) model.set_value(iter, self.DOCS_TREEVIEW_COLUMN_ICON_PIXBUF, icon_pixbuf) def _update_icons_size_widgets(self): self._icon_size_combobox.set_active(self._docs_icon_size) if self._docs_icon_size == self.DOC_ICON_SMALL: self._small_icons_menuitem.set_active(True) elif self._docs_icon_size == self.DOC_ICON_NORMAL: self._normal_icons_menuitem.set_active(True) elif self._docs_icon_size == self.DOC_ICON_LARGE: self._large_icons_menuitem.set_active(True) def _tags_liststore_sort_func(self, model, iter1, iter2): # The 'All Documents' special row goes first, then a separator, # and finally all the tags in alphanumeric order. row1_type = model.get_value(iter1, self.TAGS_TREEVIEW_COLUMN_TYPE) row2_type = model.get_value(iter2, self.TAGS_TREEVIEW_COLUMN_TYPE) if row1_type == self.TAGS_TREEVIEW_ROW_ALL: return -1 elif row2_type == self.TAGS_TREEVIEW_ROW_ALL: return 1 elif row1_type == self.TAGS_TREEVIEW_ROW_SEPARATOR: return -1 elif row2_type == self.TAGS_TREEVIEW_ROW_SEPARATOR: return 1 else: row1_tag = model.get_value(iter1, self.TAGS_TREEVIEW_COLUMN_TAG) row2_tag = model.get_value(iter2, self.TAGS_TREEVIEW_COLUMN_TAG) return cmp(row1_tag, row2_tag) def _search_timeout_callback(self): self._query = self._search_entry.get_text() self._selected_tags = set(self._iter_selected_tags()) self._search_timeout_id = 0 self._update_docs_iconview_wrapper() def _iter_selected_docs(self): paths = self._docs_iconview.get_selected_items() for path in paths: iter = self._docs_liststore.get_iter(path) hash_md5 = self._docs_liststore.get_value( iter, self.DOCS_TREEVIEW_COLUMN_ID) yield hash_md5 def _iter_selected_tags(self): selection = self._tags_treeview.get_selection() tags_liststore, paths = selection.get_selected_rows() for path in paths: iter = tags_liststore.get_iter(path) type = tags_liststore.get_value(iter, self.TAGS_TREEVIEW_COLUMN_TYPE) if type == self.TAGS_TREEVIEW_ROW_ALL: break elif type == self.TAGS_TREEVIEW_ROW_TAG: tag = tags_liststore.get_value(iter, self.TAGS_TREEVIEW_COLUMN_TAG) yield tag
class MainWindow(XMLWidget): TAGS_TREEVIEW_COLUMN_TYPE = 0 TAGS_TREEVIEW_COLUMN_TAG = 1 TAGS_TREEVIEW_ROW_ALL = 0 TAGS_TREEVIEW_ROW_SEPARATOR = 1 TAGS_TREEVIEW_ROW_TAG = 2 DOCS_TREEVIEW_COLUMN_ID = 0 DOCS_TREEVIEW_COLUMN_ICON_PATH = 1 DOCS_TREEVIEW_COLUMN_ICON_PIXBUF = 2 DOC_ICON_SMALL = 0 DOC_ICON_NORMAL = 1 DOC_ICON_LARGE = 2 def __init__(self, library): super(MainWindow, self).__init__("main_window") # Instance attributes for widgets. self._main_window = self._builder.get_object("main_window") self._tags_treeview = self._builder.get_object("tags_treeview") self._tags_scrolledwindow = self._builder.get_object("tags_scrolledwindow") self._docs_iconview = self._builder.get_object("docs_iconview") self._docs_scrolledwindow = self._builder.get_object("docs_scrolledwindow") self._docs_menu = self._builder.get_object("docs_menu") self._statusbar = self._builder.get_object("statusbar") self._docs_menuitem = self._builder.get_object("docs_menuitem") self._small_icons_menuitem = self._builder.get_object("small_icons_menuitem") self._normal_icons_menuitem = self._builder.get_object("normal_icons_menuitem") self._large_icons_menuitem = self._builder.get_object("large_icons_menuitem") self._open_docs_toolbutton = self._builder.get_object("open_docs_toolbutton") self._open_docs_menuitem = self._builder.get_object("open_docs_menuitem") self._copy_docs_toolbutton = self._builder.get_object("copy_docs_toolbutton") self._copy_docs_menuitem = self._builder.get_object("copy_docs_menuitem") self._delete_docs_toolbutton = self._builder.get_object("delete_docs_toolbutton") self._delete_docs_menuitem = self._builder.get_object("delete_docs_menuitem") self._tag_docs_toolbutton = self._builder.get_object("tag_docs_toolbutton") self._tag_docs_menuitem = self._builder.get_object("tag_docs_menuitem") self._search_entry = SearchEntry() # Other instance attributes. self._library = library self._search_timeout_id = 0 self._search_timeout = 1000 # milliseconds. self._update_docs_iconview_id = 0 # Initialize widgets. self._main_window.set_title(about.NAME) self._init_tags_treeview() self._init_docs_iconview() self._init_toolbar() self._init_menubar() self._query = "" self._old_query = None self._selected_tags = set() self._old_selected_tags = None self._update_tags_treeview() def _init_toolbar(self): # The combo box with the size of the icons. icon_size_toolitem = self._builder.get_object("icon_size_toolitem") self._icon_size_combobox = gtk.combo_box_new_text() self._icon_size_combobox.append_text("Small Icons") self._icon_size_combobox.append_text("Normal Icons") self._icon_size_combobox.append_text("Large Icons") self._icon_size_combobox.connect("changed", self.on_icons_size_combobox_changed) self._icon_size_combobox.set_active(self.DOC_ICON_NORMAL) icon_size_toolitem.add(self._icon_size_combobox) icon_size_toolitem.show_all() # The search text entry. search_toolitem = self._builder.get_object("search_toolitem") self._search_entry.set_width_chars(40) self._search_entry.connect("changed", self.on_search_entry_changed) search_toolitem.add(self._search_entry) search_toolitem.show_all() def _init_menubar(self): # The check menu item for the tags pane. show_tags_menuitem = self._builder.get_object("show_tags_menuitem") show_tags_menuitem.set_active(True) # The radio menu items for the size of the icons. self._normal_icons_menuitem.set_group(self._small_icons_menuitem) self._large_icons_menuitem.set_group(self._small_icons_menuitem) self._normal_icons_menuitem.set_active(True) self._small_icons_menuitem.connect("toggled", self.on_icons_size_menuitem_toggled, self.DOC_ICON_SMALL) self._normal_icons_menuitem.connect("toggled", self.on_icons_size_menuitem_toggled, self.DOC_ICON_NORMAL) self._large_icons_menuitem.connect("toggled", self.on_icons_size_menuitem_toggled, self.DOC_ICON_LARGE) def _init_tags_treeview(self): # Initialize the list store, the cell renderer and the column of the tree view. self._tags_liststore = gtk.ListStore(int, str) self._tags_treeview.set_model(self._tags_liststore) renderer = gtk.CellRendererText() renderer.connect("edited", self.on_tag_cellrenderer_edited) renderer.set_property("xalign", 0.5) column = gtk.TreeViewColumn(None, renderer) column.add_attribute(renderer, "text", self.TAGS_TREEVIEW_COLUMN_TAG) # Function to disable edition of the "special" tags. f = lambda column, renderer, model, iter: renderer.set_property( "editable", model.get_value(iter, self.TAGS_TREEVIEW_COLUMN_TYPE) == self.TAGS_TREEVIEW_ROW_TAG ) column.set_cell_data_func(renderer, f) self._tags_treeview.append_column(column) # Function to draw the separator. f = ( lambda model, iter: model.get_value(iter, self.TAGS_TREEVIEW_COLUMN_TYPE) == self.TAGS_TREEVIEW_ROW_SEPARATOR ) self._tags_treeview.set_row_separator_func(f) # Setup sorting. self._tags_liststore.set_sort_func(self.TAGS_TREEVIEW_COLUMN_TAG, self._tags_liststore_sort_func) self._tags_liststore.set_sort_column_id(self.TAGS_TREEVIEW_COLUMN_TAG, gtk.SORT_ASCENDING) # Initialize the selection. selection = self._tags_treeview.get_selection() selection.set_mode(gtk.SELECTION_MULTIPLE) selection.connect("changed", self.on_tags_treeview_selection_changed) # Other tree view settings. self._tags_treeview.set_headers_visible(False) self._tags_treeview.set_search_column(self.TAGS_TREEVIEW_COLUMN_TAG) def _init_docs_iconview(self): # Initialize the list store and the icon view. self._docs_liststore = gtk.ListStore(str, str, gtk.gdk.Pixbuf) self._docs_iconview.set_model(self._docs_liststore) self._docs_iconview.set_pixbuf_column(self.DOCS_TREEVIEW_COLUMN_ICON_PIXBUF) self._docs_iconview.set_selection_mode(gtk.SELECTION_MULTIPLE) self._docs_iconview.connect("selection-changed", self.on_docs_iconview_selection_changed) self._docs_icon_size = self.DOC_ICON_NORMAL # Configure the signals to load the thumbnails of the documents. adjustment = self._docs_scrolledwindow.get_hadjustment() adjustment.connect("value-changed", self.on_iconview_adjustment_changed) adjustment = self._docs_scrolledwindow.get_vadjustment() adjustment.connect("value-changed", self.on_iconview_adjustment_changed) # Default document icons. self._docs_icon_small = gtk.gdk.pixbuf_new_from_file_at_size( get_image("diglib-document.svg"), self._library.THUMBNAIL_SIZE_SMALL, self._library.THUMBNAIL_SIZE_SMALL ) self._docs_icon_normal = gtk.gdk.pixbuf_new_from_file_at_size( get_image("diglib-document.svg"), self._library.THUMBNAIL_SIZE_NORMAL, self._library.THUMBNAIL_SIZE_NORMAL ) self._docs_icon_large = gtk.gdk.pixbuf_new_from_file_at_size( get_image("diglib-document.svg"), self._library.THUMBNAIL_SIZE_LARGE, self._library.THUMBNAIL_SIZE_LARGE ) def on_import_file(self, *args): dialog = ImportFileDialog() response = dialog.run() filename = dialog.get_filename() tags = dialog.get_tags() dialog.destroy() if response == gtk.RESPONSE_OK: # Stop the current query (if any). if self._update_docs_iconview_id > 0: gobject.source_remove(self._update_docs_iconview_id) try: self._library.add_doc(filename, tags) except error.DocumentDuplicatedExact: message = "The document is already in the library." except error.DocumentDuplicatedSimilar: message = "A similar document is already in the library." except error.DocumentNotRetrievable: message = "The document is not retrievable." except error.DocumentNotSupported: message = "The format of the document not supported." else: message = None if message: dialog = gtk.MessageDialog( self._main_window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, "Could not import the document.", ) dialog.format_secondary_text(message) dialog.run() dialog.destroy() self._update_tags_treeview(True) def on_import_dir(self, *args): window = ImportDirectoryWindow(self._library) response = window.run() if response == gtk.RESPONSE_OK: # Stop the current query (if any). if self._update_docs_iconview_id > 0: gobject.source_remove(self._update_docs_iconview_id) self._update_tags_treeview(True) def on_open_docs(self, *args): for hash_md5 in self._iter_selected_docs(): doc = self._library.get_doc(hash_md5) open_file(doc.document_abspath) def on_copy_docs(self, *args): doc_paths = [self._library.get_doc(hash_md5).document_abspath for hash_md5 in self._iter_selected_docs()] if doc_paths: def get_func(clipboard, selectiondata, info, data): uris = ["file://%s" % urllib.quote(path) for path in doc_paths] text = "copy\n%s" % "\n".join(uris) selectiondata.set(selectiondata.get_target(), 8, text) clipboard = gtk.clipboard_get() targets = [("x-special/gnome-copied-files", 0, 0)] clipboard.set_with_data(targets, get_func, lambda clipboard, data: None) def on_delete_docs(self, *args): selected_docs = list(self._iter_selected_docs()) num_docs = len(selected_docs) if num_docs > 0: message = "Delete the %s?" % (("%s selected documents" % num_docs) if num_docs > 1 else "selected document") secondary_text = "The %s will be permanently lost." % ("documents" if num_docs > 1 else "document") dialog = gtk.MessageDialog( self._main_window, gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, message ) dialog.format_secondary_text(secondary_text) response = dialog.run() dialog.destroy() if response == gtk.RESPONSE_YES: # Stop the current query (if any). if self._update_docs_iconview_id > 0: gobject.source_remove(self._update_docs_iconview_id) for hash_md5 in selected_docs: self._library.delete_doc(hash_md5) self._update_tags_treeview(True) def on_tag_docs(self, *args): common_tags = None for hash_md5 in self._iter_selected_docs(): doc_tags = self._library.get_doc(hash_md5).tags common_tags = doc_tags if common_tags is None else doc_tags.intersection(common_tags) dialog = EditTagsDialog(common_tags) response = dialog.run() edited_tags = dialog.get_tags() dialog.destroy() if response == gtk.RESPONSE_OK and edited_tags != common_tags: # Stop the current query (if any). if self._update_docs_iconview_id > 0: gobject.source_remove(self._update_docs_iconview_id) removed_tags = common_tags.difference(edited_tags) added_tags = edited_tags.difference(common_tags) try: for hash_md5 in self._iter_selected_docs(): doc_tags = self._library.get_doc(hash_md5).tags doc_tags.difference_update(removed_tags) doc_tags.update(added_tags) self._library.update_tags(hash_md5, doc_tags) except error.DocumentNotRetrievable: message = "Could not remove a tag from a document." secondary_text = "If the tag is removed, the " "document could not be retrieved." dialog = gtk.MessageDialog( self._main_window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message ) dialog.format_secondary_text(secondary_text) dialog.run() dialog.destroy() self._update_tags_treeview(True) def on_close_menuitem_activate(self, menuitem): self._main_window.destroy() def on_about_menuitem_activate(self, menuitem): dialog = AboutDialog() dialog.run() dialog.destroy() def on_show_tags_menuitem_toggled(self, checkmenuitem): if checkmenuitem.get_active(): self._tags_scrolledwindow.show() else: self._tags_scrolledwindow.hide() def on_icons_size_menuitem_toggled(self, radiomenuitem, new_size): if radiomenuitem.get_active() and self._docs_icon_size != new_size: self._docs_icon_size = new_size self._update_icons_size_widgets() self._update_docs_iconview_wrapper(True) def on_icons_size_combobox_changed(self, combobox): new_size = combobox.get_active() if self._docs_icon_size != new_size: self._docs_icon_size = new_size self._update_icons_size_widgets() self._update_docs_iconview_wrapper(True) def on_docs_iconview_button_press_event(self, iconview, event): if event.button == 3: # Right button. path = iconview.get_path_at_pos(int(event.x), int(event.y)) if path: if not iconview.path_is_selected(path): iconview.unselect_all() iconview.select_path(path) self._docs_menu.popup(None, None, None, event.button, event.time) self._docs_menu.show() def on_iconview_adjustment_changed(self, *args): visible_range = self._docs_iconview.get_visible_range() if visible_range: self._docs_liststore.foreach(self._update_docs_iconview_icons, visible_range) def on_main_window_destroy(self, widget): gtk.main_quit() def on_tag_cellrenderer_edited(self, renderer, path, new_name): iter = self._tags_liststore.get_iter(path) new_name = new_name.strip() old_name = self._tags_liststore.get_value(iter, self.TAGS_TREEVIEW_COLUMN_TAG) if old_name != new_name: # Stop the current query (if any). if self._update_docs_iconview_id > 0: gobject.source_remove(self._update_docs_iconview_id) self._library.rename_tag(old_name, new_name) self._update_tags_treeview() def on_tags_treeview_selection_changed(self, *args): if self._search_timeout_id > 0: gobject.source_remove(self._search_timeout_id) self._search_timeout_id = gobject.timeout_add(self._search_timeout, self._search_timeout_callback) def on_search_entry_changed(self, *tags): if self._search_timeout_id > 0: gobject.source_remove(self._search_timeout_id) self._search_timeout_id = gobject.timeout_add(self._search_timeout, self._search_timeout_callback) def on_docs_iconview_selection_changed(self, iconview): selected_docs = list(self._iter_selected_docs()) sensitive = len(selected_docs) > 0 self._open_docs_toolbutton.set_sensitive(sensitive) self._open_docs_menuitem.set_sensitive(sensitive) self._copy_docs_toolbutton.set_sensitive(sensitive) self._copy_docs_menuitem.set_sensitive(sensitive) self._delete_docs_toolbutton.set_sensitive(sensitive) self._delete_docs_menuitem.set_sensitive(sensitive) self._tag_docs_toolbutton.set_sensitive(sensitive) self._tag_docs_menuitem.set_sensitive(sensitive) def _update_tags_treeview(self, force_update_docs=False): selected_tags = set(self._iter_selected_tags()) # Remember the selection. self._tags_liststore.clear() # Add the special rows. self._tags_liststore.append([self.TAGS_TREEVIEW_ROW_ALL, "All Documents"]) self._tags_liststore.append([self.TAGS_TREEVIEW_ROW_SEPARATOR, None]) # Add one row for each tag. all_tags = self._library.get_all_tags() for tag in all_tags: self._tags_liststore.append([self.TAGS_TREEVIEW_ROW_TAG, tag]) # Restore the selection (if possible). selection = self._tags_treeview.get_selection() if selected_tags.issubset(all_tags): for row in self._tags_liststore: if ( row[self.TAGS_TREEVIEW_COLUMN_TYPE] == self.TAGS_TREEVIEW_ROW_TAG and row[self.TAGS_TREEVIEW_COLUMN_TAG] in selected_tags ): selection.select_path(row.path) else: self._selected_tags = set() selection.unselect_all() selection.select_path((0,)) self._update_docs_iconview_wrapper(force_update_docs) def _update_docs_iconview_wrapper(self, force=False): if force or self._query != self._old_query or self._selected_tags != self._old_selected_tags: self._old_query = self._query self._old_selected_tags = self._selected_tags # Stop the active query (if any) and then start a new one. if self._update_docs_iconview_id > 0: gobject.source_remove(self._update_docs_iconview_id) self._docs_liststore.clear() self._update_docs_iconview_id = gobject.idle_add( self._update_docs_iconview, self._query, self._selected_tags ) def _update_docs_iconview(self, query, selected_tags): self._statusbar.push(0, "Loading documents...") start = len(self._docs_liststore) results = self._library.search(query, selected_tags, start, 10) if not results: num_docs = len(self._docs_liststore) text = "%s %s" % (num_docs, "documents" if num_docs > 1 else "document") self._statusbar.push(0, text) self._update_docs_iconview_id = 0 return False # Finished getting results. load_pixbuf = start <= 40 # Load the pixbuf of the first 50 documents. for hash_md5 in results: doc = self._library.get_doc(hash_md5) if self._docs_icon_size == self.DOC_ICON_SMALL: icon_path = doc.small_thumbnail_abspath if doc.small_thumbnail_abspath else "" if icon_path and load_pixbuf: icon_pixbuf = gtk.gdk.pixbuf_new_from_file(icon_path) else: icon_pixbuf = self._docs_icon_small elif self._docs_icon_size == self.DOC_ICON_NORMAL: icon_path = doc.normal_thumbnail_abspath if doc.normal_thumbnail_abspath else "" if icon_path and load_pixbuf: icon_pixbuf = gtk.gdk.pixbuf_new_from_file(icon_path) else: icon_pixbuf = self._docs_icon_normal elif self._docs_icon_size == self.DOC_ICON_LARGE: icon_path = doc.large_thumbnail_abspath if doc.large_thumbnail_abspath else "" if icon_path and load_pixbuf: icon_pixbuf = gtk.gdk.pixbuf_new_from_file(icon_path) else: icon_pixbuf = self._docs_icon_large self._docs_liststore.append([doc.hash_md5, icon_path, icon_pixbuf]) return True # Continue getting results. def _update_docs_iconview_icons(self, model, path, iter, visible_range): start_path, end_path = visible_range icon_path = model.get_value(iter, self.DOCS_TREEVIEW_COLUMN_ICON_PATH) if not icon_path or path < start_path or path > end_path: if self._docs_icon_size == self.DOC_ICON_SMALL: icon_pixbuf = self._docs_icon_small elif self._docs_icon_size == self.DOC_ICON_NORMAL: icon_pixbuf = self._docs_icon_normal elif self._docs_icon_size == self.DOC_ICON_LARGE: icon_pixbuf = self._docs_icon_large else: icon_pixbuf = gtk.gdk.pixbuf_new_from_file(icon_path) model.set_value(iter, self.DOCS_TREEVIEW_COLUMN_ICON_PIXBUF, icon_pixbuf) def _update_icons_size_widgets(self): self._icon_size_combobox.set_active(self._docs_icon_size) if self._docs_icon_size == self.DOC_ICON_SMALL: self._small_icons_menuitem.set_active(True) elif self._docs_icon_size == self.DOC_ICON_NORMAL: self._normal_icons_menuitem.set_active(True) elif self._docs_icon_size == self.DOC_ICON_LARGE: self._large_icons_menuitem.set_active(True) def _tags_liststore_sort_func(self, model, iter1, iter2): # The 'All Documents' special row goes first, then a separator, # and finally all the tags in alphanumeric order. row1_type = model.get_value(iter1, self.TAGS_TREEVIEW_COLUMN_TYPE) row2_type = model.get_value(iter2, self.TAGS_TREEVIEW_COLUMN_TYPE) if row1_type == self.TAGS_TREEVIEW_ROW_ALL: return -1 elif row2_type == self.TAGS_TREEVIEW_ROW_ALL: return 1 elif row1_type == self.TAGS_TREEVIEW_ROW_SEPARATOR: return -1 elif row2_type == self.TAGS_TREEVIEW_ROW_SEPARATOR: return 1 else: row1_tag = model.get_value(iter1, self.TAGS_TREEVIEW_COLUMN_TAG) row2_tag = model.get_value(iter2, self.TAGS_TREEVIEW_COLUMN_TAG) return cmp(row1_tag, row2_tag) def _search_timeout_callback(self): self._query = self._search_entry.get_text() self._selected_tags = set(self._iter_selected_tags()) self._search_timeout_id = 0 self._update_docs_iconview_wrapper() def _iter_selected_docs(self): paths = self._docs_iconview.get_selected_items() for path in paths: iter = self._docs_liststore.get_iter(path) hash_md5 = self._docs_liststore.get_value(iter, self.DOCS_TREEVIEW_COLUMN_ID) yield hash_md5 def _iter_selected_tags(self): selection = self._tags_treeview.get_selection() tags_liststore, paths = selection.get_selected_rows() for path in paths: iter = tags_liststore.get_iter(path) type = tags_liststore.get_value(iter, self.TAGS_TREEVIEW_COLUMN_TYPE) if type == self.TAGS_TREEVIEW_ROW_ALL: break elif type == self.TAGS_TREEVIEW_ROW_TAG: tag = tags_liststore.get_value(iter, self.TAGS_TREEVIEW_COLUMN_TAG) yield tag