def details_context_menu_event(view, ev, book_info, add_popup_action=False, edit_metadata=None): url = view.anchorAt(ev.pos()) menu = QMenu(view) copy_menu = menu.addMenu(QIcon(I('edit-copy.png')), _('Copy')) copy_menu.addAction(QIcon(I('edit-copy.png')), _('All book details'), partial(copy_all, view)) if view.textCursor().hasSelection(): copy_menu.addAction(QIcon(I('edit-copy.png')), _('Selected text'), view.copy) copy_menu.addSeparator() copy_links_added = False search_internet_added = False search_menu = QMenu(_('Search'), menu) search_menu.setIcon(QIcon(I('search.png'))) if url and url.startswith('action:'): data = json_loads(from_hex_bytes(url.split(':', 1)[1])) search_internet_added = add_item_specific_entries( menu, data, book_info, copy_menu, search_menu) create_copy_links(copy_menu, data) copy_links_added = True elif url and not url.startswith('#'): ac = book_info.copy_link_action ac.current_url = url ac.setText(_('Copy link location')) menu.addAction(ac) if not copy_links_added: create_copy_links(copy_menu) if not search_internet_added and hasattr(book_info, 'search_internet'): sim = create_search_internet_menu(book_info.search_internet) if search_menu.isEmpty(): search_menu = sim else: search_menu.addSeparator() for ac in sim.actions(): search_menu.addAction(ac) ac.setText(_('Search {0} for this book').format(ac.text())) if not search_menu.isEmpty(): menu.addMenu(search_menu) for ac in tuple(menu.actions()): if not ac.isEnabled(): menu.removeAction(ac) menu.addSeparator() if add_popup_action: ac = menu.addAction(_('Open the Book details window')) ac.triggered.connect(book_info.show_book_info) else: from calibre.gui2.ui import get_gui ema = get_gui().iactions['Edit Metadata'].menuless_qaction menu.addAction( _('Open the Edit metadata window') + '\t' + ema.shortcut().toString(QKeySequence.SequenceFormat.NativeText), edit_metadata) if len(menu.actions()) > 0: menu.exec_(ev.globalPos())
def details_context_menu_event(view, ev, book_info, add_popup_action=False): url = view.anchorAt(ev.pos()) menu = QMenu(view) menu.addAction(QIcon(I('edit-copy.png')), _('Copy all book details'), partial(copy_all, view)) cm = QMenu(_('Copy link to book'), menu) cm.setIcon(QIcon(I('edit-copy.png'))) copy_links_added = False search_internet_added = False if url and url.startswith('action:'): data = json_loads(from_hex_bytes(url.split(':', 1)[1])) create_copy_links(cm, data) copy_links_added = True search_internet_added = add_item_specific_entries( menu, data, book_info) elif url and not url.startswith('#'): ac = book_info.copy_link_action ac.current_url = url ac.setText(_('Copy link location')) menu.addAction(ac) if not copy_links_added: create_copy_links(cm) if list(cm.actions()): menu.addMenu(cm) if not search_internet_added and hasattr(book_info, 'search_internet'): menu.addSeparator() menu.si = create_search_internet_menu(book_info.search_internet) menu.addMenu(menu.si) for ac in tuple(menu.actions()): if not ac.isEnabled(): menu.removeAction(ac) if add_popup_action: menu.addSeparator() ac = menu.addAction(_('Open the Book details window')) ac.triggered.connect(book_info.show_book_info) if len(menu.actions()) > 0: menu.exec_(ev.globalPos())
def init_find_in_grouped_search(menu, field, value, book_info): from calibre.gui2.ui import get_gui db = get_gui().current_db fm = db.field_metadata field_name = fm.get(field, {}).get('name', None) if field_name is None: # I don't think this can ever happen, but ... return gsts = db.prefs.get('grouped_search_terms', {}) gsts_to_show = [] for v in gsts: fk = fm.search_term_to_field_key(v) if field in fk: gsts_to_show.append(v) if gsts_to_show: m = QMenu( (_('Search calibre for %s') + '...') % escape_for_menu(value), menu) m.setIcon(QIcon(I('search.png'))) menu.addMenu(m) m.addAction(QIcon(get_icon_path(field, '')), _('in category %s') % escape_for_menu(field_name), lambda g=field: book_info.search_requested( '{}:"={}"'.format(g, value.replace('"', r'\"')))) for gst in gsts_to_show: icon_path = get_icon_path(gst, '@') m.addAction(QIcon(icon_path), _('in grouped search %s') % gst, lambda g=gst: book_info.search_requested( '{}:"={}"'.format(g, value.replace('"', r'\"')))) else: menu.addAction(QIcon(I('search.png')), _('Search calibre for {val} in category {name}').format( val=escape_for_menu(value), name=escape_for_menu(field_name)), lambda g=field: book_info.search_requested( '{}:"={}"'.format(g, value.replace('"', r'\"'))))
def show_context_menu(self, point): def display_name(tag): ans = tag.name if tag.category == 'search': n = tag.name if len(n) > 45: n = n[:45] + '...' ans = "'" + n + "'" elif tag.is_hierarchical and not tag.is_editable: ans = tag.original_name if ans: ans = ans.replace('&', '&&') return ans index = self.indexAt(point) self.context_menu = QMenu(self) if index.isValid(): item = index.data(Qt.UserRole) tag = None tag_item = item if item.type == TagTreeItem.TAG: tag = item.tag while item.type != TagTreeItem.CATEGORY: item = item.parent if item.type == TagTreeItem.CATEGORY: if not item.category_key.startswith('@'): while item.parent != self._model.root_item: item = item.parent category = unicode_type(item.name or '') key = item.category_key # Verify that we are working with a field that we know something about if key not in self.db.field_metadata: return True fm = self.db.field_metadata[key] # Did the user click on a leaf node? if tag: # If the user right-clicked on an editable item, then offer # the possibility of renaming that item. if tag.is_editable or tag.is_hierarchical: # Add the 'rename' items to both interior and leaf nodes if self.model().get_in_vl(): self.context_menu.addAction(self.rename_icon, _('Rename %s in Virtual library')%display_name(tag), partial(self.context_menu_handler, action='edit_item_in_vl', index=index, category=key)) self.context_menu.addAction(self.rename_icon, _('Rename %s')%display_name(tag), partial(self.context_menu_handler, action='edit_item_no_vl', index=index, category=key)) if tag.is_editable: if key in ('tags', 'series', 'publisher') or \ self._model.db.field_metadata.is_custom_field(key): if self.model().get_in_vl(): self.context_menu.addAction(self.delete_icon, _('Delete %s in Virtual library')%display_name(tag), partial(self.context_menu_handler, action='delete_item_in_vl', key=key, index=tag_item)) self.context_menu.addAction(self.delete_icon, _('Delete %s')%display_name(tag), partial(self.context_menu_handler, action='delete_item_no_vl', key=key, index=tag_item)) if key == 'authors': self.context_menu.addAction(_('Edit sort for %s')%display_name(tag), partial(self.context_menu_handler, action='edit_author_sort', index=tag.id)) self.context_menu.addAction(_('Edit link for %s')%display_name(tag), partial(self.context_menu_handler, action='edit_author_link', index=tag.id)) # is_editable is also overloaded to mean 'can be added # to a User category' m = QMenu(_('Add %s to User category')%display_name(tag), self.context_menu) m.setIcon(self.user_category_icon) added = [False] def add_node_tree(tree_dict, m, path): p = path[:] for k in sorted(tree_dict.keys(), key=sort_key): p.append(k) n = k[1:] if k.startswith('@') else k m.addAction(self.user_category_icon, n, partial(self.context_menu_handler, 'add_to_category', category='.'.join(p), index=tag_item)) added[0] = True if len(tree_dict[k]): tm = m.addMenu(self.user_category_icon, _('Children of %s')%n) add_node_tree(tree_dict[k], tm, p) p.pop() add_node_tree(self.model().user_category_node_tree, m, []) if added[0]: self.context_menu.addMenu(m) # is_editable also means the tag can be applied/removed # from selected books if fm['datatype'] != 'rating': m = self.context_menu.addMenu(self.edit_metadata_icon, _('Apply %s to selected books')%display_name(tag)) m.addAction(QIcon(I('plus.png')), _('Add %s to selected books') % display_name(tag), partial(self.context_menu_handler, action='add_tag', index=index)) m.addAction(QIcon(I('minus.png')), _('Remove %s from selected books') % display_name(tag), partial(self.context_menu_handler, action='remove_tag', index=index)) elif key == 'search' and tag.is_searchable: self.context_menu.addAction(self.rename_icon, _('Rename %s')%display_name(tag), partial(self.context_menu_handler, action='edit_item_no_vl', index=index)) self.context_menu.addAction(self.delete_icon, _('Delete search %s')%display_name(tag), partial(self.context_menu_handler, action='delete_search', key=tag.original_name)) if key.startswith('@') and not item.is_gst: self.context_menu.addAction(self.user_category_icon, _('Remove %(item)s from category %(cat)s')% dict(item=display_name(tag), cat=item.py_name), partial(self.context_menu_handler, action='delete_item_from_user_category', key=key, index=tag_item)) if tag.is_searchable: # Add the search for value items. All leaf nodes are searchable self.context_menu.addAction(self.search_icon, _('Search for %s')%display_name(tag), partial(self.context_menu_handler, action='search', search_state=TAG_SEARCH_STATES['mark_plus'], index=index)) self.context_menu.addAction(self.search_icon, _('Search for everything but %s')%display_name(tag), partial(self.context_menu_handler, action='search', search_state=TAG_SEARCH_STATES['mark_minus'], index=index)) self.context_menu.addAction(self.search_copy_icon, _('Search using saved search expression'), partial(self.context_menu_handler, action='raw_search', key=tag.name)) self.context_menu.addSeparator() elif key.startswith('@') and not item.is_gst: if item.can_be_edited: self.context_menu.addAction(self.rename_icon, _('Rename %s')%item.py_name, partial(self.context_menu_handler, action='edit_item_no_vl', index=index)) self.context_menu.addAction(self.user_category_icon, _('Add sub-category to %s')%item.py_name, partial(self.context_menu_handler, action='add_subcategory', key=key)) self.context_menu.addAction(self.delete_icon, _('Delete User category %s')%item.py_name, partial(self.context_menu_handler, action='delete_user_category', key=key)) self.context_menu.addSeparator() # Hide/Show/Restore categories self.context_menu.addAction(_('Hide category %s') % category, partial(self.context_menu_handler, action='hide', category=key)) if self.hidden_categories: m = self.context_menu.addMenu(_('Show category')) for col in sorted(self.hidden_categories, key=lambda x: sort_key(self.db.field_metadata[x]['name'])): m.addAction(self.db.field_metadata[col]['name'], partial(self.context_menu_handler, action='show', category=col)) # search by category. Some categories are not searchable, such # as search and news if item.tag.is_searchable: self.context_menu.addAction(self.search_icon, _('Search for books in category %s')%category, partial(self.context_menu_handler, action='search_category', index=self._model.createIndex(item.row(), 0, item), search_state=TAG_SEARCH_STATES['mark_plus'])) self.context_menu.addAction(self.search_icon, _('Search for books not in category %s')%category, partial(self.context_menu_handler, action='search_category', index=self._model.createIndex(item.row(), 0, item), search_state=TAG_SEARCH_STATES['mark_minus'])) # Offer specific editors for tags/series/publishers/saved searches self.context_menu.addSeparator() if key in ['tags', 'publisher', 'series'] or ( self.db.field_metadata[key]['is_custom'] and self.db.field_metadata[key]['datatype'] != 'composite'): if tag_item.type == TagTreeItem.CATEGORY and tag_item.temporary: self.context_menu.addAction(_('Manage %s')%category, partial(self.context_menu_handler, action='open_editor', category=tag_item.name, key=key, is_first_letter=True)) else: self.context_menu.addAction(_('Manage %s')%category, partial(self.context_menu_handler, action='open_editor', category=tag.original_name if tag else None, key=key)) elif key == 'authors': if tag_item.type == TagTreeItem.CATEGORY: if tag_item.temporary: self.context_menu.addAction(_('Manage %s')%category, partial(self.context_menu_handler, action='edit_authors', index=tag_item.name, is_first_letter=True)) else: self.context_menu.addAction(_('Manage %s')%category, partial(self.context_menu_handler, action='edit_authors')) else: self.context_menu.addAction(_('Manage %s')%category, partial(self.context_menu_handler, action='edit_authors', index=tag.id)) elif key == 'search': self.context_menu.addAction(_('Manage Saved searches'), partial(self.context_menu_handler, action='manage_searches', category=tag.name if tag else None)) if tag is None: self.context_menu.addSeparator() self.context_menu.addAction(_('Change category icon'), partial(self.context_menu_handler, action='set_icon', key=key)) self.context_menu.addAction(_('Restore default icon'), partial(self.context_menu_handler, action='clear_icon', key=key)) # Always show the User categories editor self.context_menu.addSeparator() if key.startswith('@') and \ key[1:] in self.db.prefs.get('user_categories', {}).keys(): self.context_menu.addAction(_('Manage User categories'), partial(self.context_menu_handler, action='manage_categories', category=key[1:])) else: self.context_menu.addAction(_('Manage User categories'), partial(self.context_menu_handler, action='manage_categories', category=None)) if self.hidden_categories: if not self.context_menu.isEmpty(): self.context_menu.addSeparator() self.context_menu.addAction(_('Show all categories'), partial(self.context_menu_handler, action='defaults')) m = self.context_menu.addMenu(_('Change sub-categorization scheme')) da = m.addAction(_('Disable'), partial(self.context_menu_handler, action='categorization', category='disable')) fla = m.addAction(_('By first letter'), partial(self.context_menu_handler, action='categorization', category='first letter')) pa = m.addAction(_('Partition'), partial(self.context_menu_handler, action='categorization', category='partition')) if self.collapse_model == 'disable': da.setCheckable(True) da.setChecked(True) elif self.collapse_model == 'first letter': fla.setCheckable(True) fla.setChecked(True) else: pa.setCheckable(True) pa.setChecked(True) if config['sort_tags_by'] != "name": fla.setEnabled(False) m.hovered.connect(self.collapse_menu_hovered) fla.setToolTip(_('First letter is usable only when sorting by name')) # Apparently one cannot set a tooltip to empty, so use a star and # deal with it in the hover method da.setToolTip('*') pa.setToolTip('*') if index.isValid() and self.model().rowCount(index) > 0: self.context_menu.addSeparator() self.context_menu.addAction(_('E&xpand all children'), partial(self.expand_node_and_descendants, index)) self.context_menu.addAction(_('Collapse all levels'), self.collapseAll) if not self.context_menu.isEmpty(): self.context_menu.popup(self.mapToGlobal(point)) return True