def show_context_menu(self, point): item = self.currentItem() def key(k): sc = str(QKeySequence(k | Qt.KeyboardModifier.ControlModifier).toString(QKeySequence.SequenceFormat.NativeText)) return ' [%s]'%sc if item is not None: m = QMenu(self) m.addAction(QIcon(I('edit_input.png')), _('Change the location this entry points to'), self.edit_item) m.addAction(QIcon(I('modified.png')), _('Bulk rename all selected items'), self.bulk_rename) m.addAction(QIcon(I('trash.png')), _('Remove all selected items'), self.del_items) m.addSeparator() ci = str(item.data(0, Qt.ItemDataRole.DisplayRole) or '') p = item.parent() or self.invisibleRootItem() idx = p.indexOfChild(item) if idx > 0: m.addAction(QIcon(I('arrow-up.png')), (_('Move "%s" up')%ci)+key(Qt.Key.Key_Up), self.move_up) if idx + 1 < p.childCount(): m.addAction(QIcon(I('arrow-down.png')), (_('Move "%s" down')%ci)+key(Qt.Key.Key_Down), self.move_down) if item.parent() is not None: m.addAction(QIcon(I('back.png')), (_('Unindent "%s"')%ci)+key(Qt.Key.Key_Left), self.move_left) if idx > 0: m.addAction(QIcon(I('forward.png')), (_('Indent "%s"')%ci)+key(Qt.Key.Key_Right), self.move_right) m.addSeparator() case_menu = QMenu(_('Change case'), m) case_menu.addAction(_('Upper case'), self.upper_case) case_menu.addAction(_('Lower case'), self.lower_case) case_menu.addAction(_('Swap case'), self.swap_case) case_menu.addAction(_('Title case'), self.title_case) case_menu.addAction(_('Capitalize'), self.capitalize) m.addMenu(case_menu) m.exec(QCursor.pos())
def contextMenuEvent(self, ev): cm = QMenu(self) paste = cm.addAction(QIcon.ic('edit-paste.png'), _('Paste cover')) copy = cm.addAction(QIcon.ic('edit-copy.png'), _('Copy cover')) save = cm.addAction(QIcon.ic('save.png'), _('Save cover to disk')) remove = cm.addAction(QIcon.ic('trash.png'), _('Remove cover')) gc = cm.addAction(QIcon.ic('default_cover.png'), _('Generate cover from metadata')) cm.addSeparator() if self.pixmap is not self.default_pixmap and self.data.get('id'): book_id = self.data['id'] cm.tc = QMenu(_('Trim cover')) cm.tc.addAction(QIcon.ic('trim.png'), _('Automatically trim borders'), self.trim_cover) cm.tc.addAction(_('Trim borders manually'), self.manual_trim_cover) cm.tc.addSeparator() cm.tc.addAction(QIcon.ic('edit-undo.png'), _('Undo last trim'), self.undo_last_trim).setEnabled(self.last_trim_id == book_id) cm.addMenu(cm.tc) cm.addSeparator() if not QApplication.instance().clipboard().mimeData().hasImage(): paste.setEnabled(False) copy.triggered.connect(self.copy_to_clipboard) paste.triggered.connect(self.paste_from_clipboard) remove.triggered.connect(self.remove_cover) gc.triggered.connect(self.generate_cover) save.triggered.connect(self.save_cover) create_open_cover_with_menu(self, cm) cm.si = m = create_search_internet_menu(self.search_internet.emit) cm.addMenu(m) cm.exec(ev.globalPos())
def show_context_menu(self, pos): item = self.itemAt(pos) if item is not None: result = item.data(0, Qt.ItemDataRole.UserRole) else: result = None items = self.selectedItems() m = QMenu(self) if isinstance(result, dict): m.addAction(_('Open in viewer'), partial(self.item_activated, item)) m.addAction(_('Show in calibre'), partial(self.show_in_calibre, item)) if result.get('annotation', {}).get('type') == 'highlight': m.addAction(_('Edit notes'), partial(self.edit_notes, item)) if items: m.addSeparator() m.addAction( ngettext('Export selected item', 'Export {} selected items', len(items)).format(len(items)), self.export_requested.emit) m.addAction( ngettext('Delete selected item', 'Delete {} selected items', len(items)).format(len(items)), self.delete_requested.emit) m.addSeparator() m.addAction(_('Expand all'), self.expandAll) m.addAction(_('Collapse all'), self.collapseAll) m.exec(self.mapToGlobal(pos))
def contextMenuEvent(self, ev): m = QMenu(self) m.addAction(_('Set to undefined') + '\t' + QKeySequence(Qt.Key.Key_Space).toString(QKeySequence.SequenceFormat.NativeText), self.clear_to_undefined) m.addSeparator() populate_standard_spinbox_context_menu(self, m) m.popup(ev.globalPos())
def contextMenuEvent(self, ev): m = QMenu(self) m.addAction(QIcon.ic('sort.png'), _('Sort tabs alphabetically'), self.sort_alphabetically) hidden = self.current_db.new_api.pref('virt_libs_hidden') if hidden: s = m._s = m.addMenu(_('Restore hidden tabs')) for x in hidden: s.addAction(x, partial(self.restore, x)) m.addAction(_('Hide Virtual library tabs'), self.disable_bar) if gprefs['vl_tabs_closable']: m.addAction(QIcon.ic('drm-locked.png'), _('Lock Virtual library tabs'), self.lock_tab) else: m.addAction(QIcon.ic('drm-unlocked.png'), _('Unlock Virtual library tabs'), self.unlock_tab) i = self.tabAt(ev.pos()) if i > -1: vl = str(self.tabData(i) or '') if vl: vln = vl.replace('&', '&&') m.addSeparator() m.addAction(QIcon.ic('edit_input.png'), _('Edit "%s"') % vln, partial(self.gui.do_create_edit, name=vl)) m.addAction(QIcon.ic('trash.png'), _('Delete "%s"') % vln, partial(self.gui.remove_vl_triggered, name=vl)) m.exec(ev.globalPos())
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 add_format_entries(menu, data, book_info, copy_menu, search_menu): from calibre.ebooks.oeb.polish.main import SUPPORTED from calibre.gui2.ui import get_gui book_id = int(data['book_id']) fmt = data['fmt'] init_find_in_tag_browser(search_menu, book_info.find_in_tag_browser_action, 'formats', fmt) init_find_in_grouped_search(search_menu, 'formats', fmt, book_info) db = get_gui().current_db.new_api ofmt = fmt.upper() if fmt.startswith('ORIGINAL_') else 'ORIGINAL_' + fmt nfmt = ofmt[len('ORIGINAL_'):] fmts = {x.upper() for x in db.formats(book_id)} for a, t in [ ('remove', _('Delete the %s format')), ('save', _('Save the %s format to disk')), ('restore', _('Restore the %s format')), ('compare', ''), ('set_cover', _('Set the book cover from the %s file')), ]: if a == 'restore' and not fmt.startswith('ORIGINAL_'): continue if a == 'compare': if ofmt not in fmts or nfmt not in SUPPORTED: continue t = _('Compare to the %s format') % (fmt[9:] if fmt.startswith('ORIGINAL_') else ofmt) else: t = t % fmt ac = getattr(book_info, '%s_format_action'%a) ac.current_fmt = (book_id, fmt) ac.setText(t) menu.addAction(ac) if not fmt.upper().startswith('ORIGINAL_'): from calibre.gui2.open_with import edit_programs, populate_menu m = QMenu(_('Open %s with...') % fmt.upper()) def connect_action(ac, entry): connect_lambda(ac.triggered, book_info, lambda book_info: book_info.open_with(book_id, fmt, entry)) populate_menu(m, connect_action, fmt) if len(m.actions()) == 0: menu.addAction(_('Open %s with...') % fmt.upper(), partial(book_info.choose_open_with, book_id, fmt)) else: m.addSeparator() m.addAction(_('Add other application for %s files...') % fmt.upper(), partial(book_info.choose_open_with, book_id, fmt)) m.addAction(_('Edit Open with applications...'), partial(edit_programs, fmt, book_info)) menu.addMenu(m) menu.ow = m if fmt.upper() in SUPPORTED: menu.addSeparator() menu.addAction(_('Edit %s format') % fmt.upper(), partial(book_info.edit_fmt, book_id, fmt)) path = data['path'] if path: if data.get('fname'): path = os.path.join(path, data['fname'] + '.' + data['fmt'].lower()) ac = book_info.copy_link_action ac.current_url = path ac.setText(_('Path to file')) copy_menu.addAction(ac)
def create_context_menu(self): m = QMenu(self) m.addAction(_('Set date to undefined') + '\t' + QKeySequence(Qt.Key.Key_Minus).toString(QKeySequence.SequenceFormat.NativeText), self.clear_date) m.addAction(_('Set date to today') + '\t' + QKeySequence(Qt.Key.Key_Equal).toString(QKeySequence.SequenceFormat.NativeText), self.today_date) m.addSeparator() populate_standard_spinbox_context_menu(self, m, use_self_for_copy_actions=True) return m
def scrollbar_context_menu(self, x, y, frac): m = QMenu(self) amap = {} def a(text, name): m.addAction(text) amap[text] = name a(_('Scroll here'), 'here') m.addSeparator() a(_('Start of book'), 'start_of_book') a(_('End of book'), 'end_of_book') m.addSeparator() a(_('Previous section'), 'previous_section') a(_('Next section'), 'next_section') m.addSeparator() a(_('Start of current file'), 'start_of_file') a(_('End of current file'), 'end_of_file') m.addSeparator() a(_('Hide this scrollbar'), 'toggle_scrollbar') q = m.exec_(QCursor.pos()) if not q: return q = amap[q.text()] if q == 'here': self.web_view.goto_frac(frac) else: self.web_view.trigger_shortcut(q)
def context_menu(self, pos): index = self.indexAt(pos) m = QMenu(self) if index.isValid(): m.addAction( _('Expand all items under %s') % index.data(), partial(self.expand_tree, index)) m.addSeparator() m.addAction(_('Expand all items'), self.expandAll) m.addAction(_('Collapse all items'), self.collapseAll) m.addSeparator() m.addAction(_('Copy table of contents to clipboard'), self.copy_to_clipboard) m.exec_(self.mapToGlobal(pos))
def contextMenuEvent(self, event): index = self.indexAt(event.pos()) if not index.isValid(): return result = self.model().get_result(index) menu = QMenu(self) da = menu.addAction(_('Download...'), partial(self.download_requested.emit, result)) if not result.downloads: da.setEnabled(False) menu.addSeparator() menu.addAction(_('Show in store'), partial(self.open_requested.emit, result)) menu.exec(event.globalPos())
def create_open_cover_with_menu(self, parent_menu): from calibre.gui2.open_with import edit_programs, populate_menu m = QMenu(_('Open cover with...')) def connect_action(ac, entry): connect_lambda(ac.triggered, self, lambda self: self.open_with(entry)) populate_menu(m, connect_action, 'cover_image') if len(m.actions()) == 0: parent_menu.addAction(_('Open cover with...'), self.choose_open_with) else: m.addSeparator() m.addAction(_('Add another application to open cover with...'), self.choose_open_with) m.addAction(_('Edit Open with applications...'), partial(edit_programs, 'cover_image', self)) parent_menu.ocw = m parent_menu.addMenu(m) return m
def add_open_with_actions(self, menu, file_name): from calibre.gui2.open_with import edit_programs, populate_menu fmt = file_name.rpartition('.')[-1].lower() if not fmt: return m = QMenu(_('Open %s with...') % file_name) def connect_action(ac, entry): connect_lambda(ac.triggered, self, lambda self: self.open_with(file_name, fmt, entry)) populate_menu(m, connect_action, fmt) if len(m.actions()) == 0: menu.addAction(_('Open %s with...') % file_name, partial(self.choose_open_with, file_name, fmt)) else: m.addSeparator() m.addAction(_('Add other application for %s files...') % fmt.upper(), partial(self.choose_open_with, file_name, fmt)) m.addAction(_('Edit Open with applications...'), partial(edit_programs, fmt, self)) menu.addMenu(m) menu.ow = m
def contextMenuEvent(self, ev): cm = QMenu(self) paste = cm.addAction(_('Paste cover')) copy = cm.addAction(_('Copy cover')) save = cm.addAction(_('Save cover to disk')) remove = cm.addAction(_('Remove cover')) gc = cm.addAction(_('Generate cover from metadata')) cm.addSeparator() if not QApplication.instance().clipboard().mimeData().hasImage(): paste.setEnabled(False) copy.triggered.connect(self.copy_to_clipboard) paste.triggered.connect(self.paste_from_clipboard) remove.triggered.connect(self.remove_cover) gc.triggered.connect(self.generate_cover) save.triggered.connect(self.save_cover) create_open_cover_with_menu(self, cm) cm.si = m = create_search_internet_menu(self.search_internet.emit) cm.addMenu(m) cm.exec_(ev.globalPos())
def eventFilter(self, obj, event): base = super(Central, self) if obj is not self.editor_tabs.tabBar() or event.type() != QEvent.Type.MouseButtonPress or event.button() not in ( Qt.MouseButton.RightButton, Qt.MouseButton.MidButton): return base.eventFilter(obj, event) index = self.editor_tabs.tabBar().tabAt(event.pos()) if index < 0: return base.eventFilter(obj, event) if event.button() == Qt.MouseButton.MidButton: self._close_requested(index) ed = self.editor_tabs.widget(index) if ed is not None: menu = QMenu(self) menu.addAction(actions['close-current-tab'].icon(), _('Close tab'), partial(self.close_requested.emit, ed)) menu.addSeparator() menu.addAction(actions['close-all-but-current-tab'].icon(), _('Close other tabs'), partial(self.close_all_but, ed)) menu.addAction(actions['close-tabs-to-right-of'].icon(), _('Close tabs to the right of this tab'), partial(self.close_to_right, ed)) menu.exec_(self.editor_tabs.tabBar().mapToGlobal(event.pos())) return True
def show_context_menu(self, pos): index = self.indexAt(pos) m = QMenu(self) if index.isValid(): m.addAction(QIcon.ic('plus.png'), _('Expand all items under %s') % index.data(), partial(self.expand_tree, index)) m.addSeparator() m.addAction(QIcon.ic('plus.png'), _('Expand all items'), self.expandAll) m.addAction(QIcon.ic('minus.png'), _('Collapse all items'), self.collapseAll) m.addSeparator() if index.isValid(): m.addAction( QIcon.ic('plus.png'), _('Expand all items at the level of {}').format(index.data()), partial(self.expand_at_level, index)) m.addAction( QIcon.ic('minus.png'), _('Collapse all items at the level of {}').format( index.data()), partial(self.collapse_at_level, index)) m.addSeparator() m.addAction(QIcon.ic('edit-copy.png'), _('Copy Table of Contents to clipboard'), self.copy_to_clipboard) self.context_menu = m m.exec(self.mapToGlobal(pos))
def show_context_menu(self, pos): m = QMenu(self) a = m.addAction c = self.editor.cursorForPosition(pos) origc = QTextCursor(c) current_cursor = self.editor.textCursor() r = origr = self.editor.syntax_range_for_cursor(c) if ( r is None or not r.format.property(SPELL_PROPERTY) ) and c.positionInBlock() > 0 and not current_cursor.hasSelection(): c.setPosition(c.position() - 1) r = self.editor.syntax_range_for_cursor(c) if r is not None and r.format.property(SPELL_PROPERTY): word = self.editor.text_for_range(c.block(), r) locale = self.editor.spellcheck_locale_for_cursor(c) orig_pos = c.position() c.setPosition(orig_pos - utf16_length(word)) found = False self.editor.setTextCursor(c) if self.editor.find_spell_word([word], locale.langcode, center_on_cursor=False): found = True fc = self.editor.textCursor() if fc.position() < c.position(): self.editor.find_spell_word([word], locale.langcode, center_on_cursor=False) spell_cursor = self.editor.textCursor() if current_cursor.hasSelection(): # Restore the current cursor so that any selection is preserved # for the change case actions self.editor.setTextCursor(current_cursor) if found: suggestions = dictionaries.suggestions(word, locale)[:7] if suggestions: for suggestion in suggestions: ac = m.addAction( suggestion, partial(self.editor.simple_replace, suggestion, cursor=spell_cursor)) f = ac.font() f.setBold(True), ac.setFont(f) m.addSeparator() m.addAction(actions['spell-next']) m.addAction(_('Ignore this word'), partial(self._nuke_word, None, word, locale)) dics = dictionaries.active_user_dictionaries if len(dics) > 0: if len(dics) == 1: m.addAction( _('Add this word to the dictionary: {0}').format( dics[0].name), partial(self._nuke_word, dics[0].name, word, locale)) else: ac = m.addAction(_('Add this word to the dictionary')) dmenu = QMenu(m) ac.setMenu(dmenu) for dic in dics: dmenu.addAction( dic.name, partial(self._nuke_word, dic.name, word, locale)) m.addSeparator() if origr is not None and origr.format.property(LINK_PROPERTY): href = self.editor.text_for_range(origc.block(), origr) m.addAction( _('Open %s') % href, partial(self.link_clicked.emit, href)) if origr is not None and (origr.format.property(TAG_NAME_PROPERTY) or origr.format.property(CSS_PROPERTY)): word = self.editor.text_for_range(origc.block(), origr) item_type = 'tag_name' if origr.format.property( TAG_NAME_PROPERTY) else 'css_property' url = help_url(word, item_type, self.editor.highlighter.doc_name, extra_data=current_container().opf_version) if url is not None: m.addAction( _('Show help for: %s') % word, partial(open_url, url)) for x in ('undo', 'redo'): ac = actions['editor-%s' % x] if ac.isEnabled(): a(ac) m.addSeparator() for x in ('cut', 'copy', 'paste'): ac = actions['editor-' + x] if ac.isEnabled(): a(ac) m.addSeparator() m.addAction(_('&Select all'), self.editor.select_all) if self.selected_text or self.has_marked_text: update_mark_text_action(self) m.addAction(actions['mark-selected-text']) if self.syntax != 'css' and actions['editor-cut'].isEnabled(): cm = QMenu(_('Change &case'), m) for ac in 'upper lower swap title capitalize'.split(): cm.addAction(actions['transform-case-' + ac]) m.addMenu(cm) if self.syntax == 'html': m.addAction(actions['multisplit']) m.exec_(self.editor.viewport().mapToGlobal(pos))
class LocationManager(QObject): # {{{ locations_changed = pyqtSignal() unmount_device = pyqtSignal() location_selected = pyqtSignal(object) configure_device = pyqtSignal() update_device_metadata = pyqtSignal() def __init__(self, parent=None): QObject.__init__(self, parent) self.free = [-1, -1, -1] self.count = 0 self.location_actions = QActionGroup(self) self.location_actions.setExclusive(True) self.current_location = 'library' self._mem = [] self.tooltips = {} self.all_actions = [] def ac(name, text, icon, tooltip): icon = QIcon(I(icon)) ac = self.location_actions.addAction(icon, text) setattr(self, 'location_' + name, ac) ac.setAutoRepeat(False) ac.setCheckable(True) receiver = partial(self._location_selected, name) ac.triggered.connect(receiver) self.tooltips[name] = tooltip m = QMenu(parent) self._mem.append(m) a = m.addAction(icon, tooltip) a.triggered.connect(receiver) if name != 'library': self._mem.append(a) a = m.addAction(QIcon(I('eject.png')), _('Eject this device')) a.triggered.connect(self._eject_requested) self._mem.append(a) a = m.addAction(QIcon(I('config.png')), _('Configure this device')) a.triggered.connect(self._configure_requested) self._mem.append(a) a = m.addAction(QIcon(I('sync.png')), _('Update cached metadata on device')) a.triggered.connect( lambda x: self.update_device_metadata.emit()) self._mem.append(a) else: ac.setToolTip(tooltip) ac.setMenu(m) ac.calibre_name = name self.all_actions.append(ac) return ac self.library_action = ac('library', _('Library'), 'lt.png', _('Show books in calibre library')) ac('main', _('Device'), 'reader.png', _('Show books in the main memory of the device')) ac('carda', _('Card A'), 'sd.png', _('Show books in storage card A')) ac('cardb', _('Card B'), 'sd.png', _('Show books in storage card B')) def set_switch_actions(self, quick_actions, rename_actions, delete_actions, switch_actions, choose_action): self.switch_menu = self.library_action.menu() if self.switch_menu: self.switch_menu.addSeparator() else: self.switch_menu = QMenu() self.switch_menu.addAction(choose_action) self.cs_menus = [] for t, acs in [(_('Quick switch'), quick_actions), (_('Rename library'), rename_actions), (_('Delete library'), delete_actions)]: if acs: self.cs_menus.append(QMenu(t)) for ac in acs: self.cs_menus[-1].addAction(ac) self.switch_menu.addMenu(self.cs_menus[-1]) self.switch_menu.addSeparator() for ac in switch_actions: self.switch_menu.addAction(ac) if self.switch_menu != self.library_action.menu(): self.library_action.setMenu(self.switch_menu) def _location_selected(self, location, *args): if location != self.current_location and hasattr( self, 'location_' + location): self.current_location = location self.location_selected.emit(location) getattr(self, 'location_' + location).setChecked(True) def _eject_requested(self, *args): self.unmount_device.emit() def _configure_requested(self): self.configure_device.emit() def update_devices(self, cp=(None, None), fs=[-1, -1, -1], icon=None): if icon is None: icon = I('reader.png') self.location_main.setIcon(QIcon(icon)) had_device = self.has_device if cp is None: cp = (None, None) if isinstance(cp, (bytes, unicode_type)): cp = (cp, None) if len(fs) < 3: fs = list(fs) + [0] self.free[0] = fs[0] self.free[1] = fs[1] self.free[2] = fs[2] cpa, cpb = cp self.free[1] = fs[1] if fs[1] is not None and cpa is not None else -1 self.free[2] = fs[2] if fs[2] is not None and cpb is not None else -1 self.update_tooltips() if self.has_device != had_device: self.location_library.setChecked(True) self.locations_changed.emit() if not self.has_device: self.location_library.trigger() def update_tooltips(self): for i, loc in enumerate(('main', 'carda', 'cardb')): t = self.tooltips[loc] if self.free[i] > -1: t += '\n\n%s ' % human_readable(self.free[i]) + _('available') ac = getattr(self, 'location_' + loc) ac.setToolTip(t) ac.setWhatsThis(t) ac.setStatusTip(t) @property def has_device(self): return max(self.free) > -1 @property def available_actions(self): ans = [self.location_library] for i, loc in enumerate(('main', 'carda', 'cardb')): if self.free[i] > -1: ans.append(getattr(self, 'location_' + loc)) return ans
class Scheduler(QObject): INTERVAL = 1 # minutes delete_old_news = pyqtSignal(object) start_recipe_fetch = pyqtSignal(object) def __init__(self, parent, db): QObject.__init__(self, parent) self.internet_connection_failed = False self._parent = parent self.no_internet_msg = _( 'Cannot download news as no internet connection ' 'is active') self.no_internet_dialog = d = error_dialog(self._parent, self.no_internet_msg, _('No internet connection'), show_copy_button=False) d.setModal(False) self.recipe_model = RecipeModel() self.db = db self.lock = QMutex(QMutex.RecursionMode.Recursive) self.download_queue = set() self.news_menu = QMenu() self.news_icon = QIcon(I('news.png')) self.scheduler_action = QAction(QIcon(I('scheduler.png')), _('Schedule news download'), self) self.news_menu.addAction(self.scheduler_action) self.scheduler_action.triggered[bool].connect(self.show_dialog) self.cac = QAction(QIcon(I('user_profile.png')), _('Add or edit a custom news source'), self) self.cac.triggered[bool].connect(self.customize_feeds) self.news_menu.addAction(self.cac) self.news_menu.addSeparator() self.all_action = self.news_menu.addAction( _('Download all scheduled news sources'), self.download_all_scheduled) self.timer = QTimer(self) self.timer.start(int(self.INTERVAL * 60 * 1000)) self.timer.timeout.connect(self.check) self.oldest = gconf['oldest_news'] QTimer.singleShot(5 * 1000, self.oldest_check) def database_changed(self, db): self.db = db def oldest_check(self): if self.oldest > 0: delta = timedelta(days=self.oldest) try: ids = list( self.db.tags_older_than(_('News'), delta, must_have_authors=['calibre'])) except: # Happens if library is being switched ids = [] if ids: if ids: self.delete_old_news.emit(ids) QTimer.singleShot(60 * 60 * 1000, self.oldest_check) def show_dialog(self, *args): self.lock.lock() try: d = SchedulerDialog(self.recipe_model) d.download.connect(self.download_clicked) d.exec_() gconf['oldest_news'] = self.oldest = d.old_news.value() d.break_cycles() finally: self.lock.unlock() def customize_feeds(self, *args): from calibre.gui2.dialogs.custom_recipes import CustomRecipes d = CustomRecipes(self.recipe_model, self._parent) try: d.exec_() finally: d.deleteLater() def do_download(self, urn): self.lock.lock() try: account_info = self.recipe_model.get_account_info(urn) customize_info = self.recipe_model.get_customize_info(urn) recipe = self.recipe_model.recipe_from_urn(urn) un = pw = None if account_info is not None: un, pw = account_info add_title_tag, custom_tags, keep_issues = customize_info arg = { 'username': un, 'password': pw, 'add_title_tag': add_title_tag, 'custom_tags': custom_tags, 'title': recipe.get('title', ''), 'urn': urn, 'keep_issues': keep_issues } self.download_queue.add(urn) self.start_recipe_fetch.emit(arg) finally: self.lock.unlock() def recipe_downloaded(self, arg): self.lock.lock() try: self.recipe_model.update_last_downloaded(arg['urn']) self.download_queue.remove(arg['urn']) finally: self.lock.unlock() def recipe_download_failed(self, arg): self.lock.lock() try: self.recipe_model.update_last_downloaded(arg['urn']) self.download_queue.remove(arg['urn']) finally: self.lock.unlock() def download_clicked(self, urn): if urn is not None: return self.download(urn) for urn in self.recipe_model.scheduled_urns(): if not self.download(urn): break def download_all_scheduled(self): self.download_clicked(None) def has_internet_connection(self): if not internet_connected(): if not self.internet_connection_failed: self.internet_connection_failed = True if self._parent.is_minimized_to_tray: self._parent.status_bar.show_message( self.no_internet_msg, 5000) elif not self.no_internet_dialog.isVisible(): self.no_internet_dialog.show() return False self.internet_connection_failed = False if self.no_internet_dialog.isVisible(): self.no_internet_dialog.hide() return True def download(self, urn): self.lock.lock() if not self.has_internet_connection(): return False doit = urn not in self.download_queue self.lock.unlock() if doit: self.do_download(urn) return True def check(self): recipes = self.recipe_model.get_to_be_downloaded_recipes() for urn in recipes: if not self.download(urn): # No internet connection, we will try again in a minute break
def build_item_context_menu(self, item): m = QMenu(self) sel = self.selectedItems() num = len(sel) container = current_container() ci = self.currentItem() if ci is not None: cn = str(ci.data(0, NAME_ROLE) or '') mt = str(ci.data(0, MIME_ROLE) or '') cat = str(ci.data(0, CATEGORY_ROLE) or '') n = elided_text(cn.rpartition('/')[-1]) m.addAction(QIcon(I('save.png')), _('Export %s') % n, partial(self.export, cn)) if cn not in container.names_that_must_not_be_changed and cn not in container.names_that_must_not_be_removed and mt not in OEB_FONTS: m.addAction(_('Replace %s with file...') % n, partial(self.replace, cn)) if num > 1: m.addAction(QIcon(I('save.png')), _('Export all %d selected files') % num, self.export_selected) if cn not in container.names_that_must_not_be_changed: self.add_open_with_actions(m, cn) m.addSeparator() m.addAction(QIcon(I('modified.png')), _('&Rename %s') % n, self.edit_current_item) if is_raster_image(mt): m.addAction(QIcon(I('default_cover.png')), _('Mark %s as cover image') % n, partial(self.mark_as_cover, cn)) elif current_container().SUPPORTS_TITLEPAGES and mt in OEB_DOCS and cat == 'text': m.addAction(QIcon(I('default_cover.png')), _('Mark %s as cover page') % n, partial(self.mark_as_titlepage, cn)) m.addSeparator() if num > 0: m.addSeparator() if num > 1: m.addAction(QIcon(I('modified.png')), _('&Bulk rename the selected files'), self.request_bulk_rename) m.addAction(QIcon(I('modified.png')), _('Change the file extension for the selected files'), self.request_change_ext) m.addAction(QIcon(I('trash.png')), ngettext( '&Delete the selected file', '&Delete the {} selected files', num).format(num), self.request_delete) m.addAction(QIcon(I('edit-copy.png')), ngettext( '&Copy the selected file to another editor instance', '&Copy the {} selected files to another editor instance', num).format(num), self.copy_selected_files) m.addSeparator() md = QApplication.instance().clipboard().mimeData() if md.hasUrls() and md.hasFormat(FILE_COPY_MIME): import json name_map = json.loads(bytes(md.data(FILE_COPY_MIME))) m.addAction(ngettext( _('Paste file from other editor instance'), _('Paste {} files from other editor instance'), len(name_map)).format(len(name_map)), self.paste_from_other_instance) selected_map = defaultdict(list) for item in sel: selected_map[str(item.data(0, CATEGORY_ROLE) or '')].append(str(item.data(0, NAME_ROLE) or '')) for items in selected_map.values(): items.sort(key=self.index_of_name) if selected_map['text']: m.addAction(QIcon(I('format-text-color.png')), _('Link &stylesheets...'), partial(self.link_stylesheets, selected_map['text'])) if len(selected_map['text']) > 1: m.addAction(QIcon(I('merge.png')), _('&Merge selected text files'), partial(self.start_merge, 'text', selected_map['text'])) if len(selected_map['styles']) > 1: m.addAction(QIcon(I('merge.png')), _('&Merge selected style files'), partial(self.start_merge, 'styles', selected_map['styles'])) return m