def __init__(self, vertical, parent=None): QWebView.__init__(self, parent) s = self.settings() s.setAttribute(s.JavascriptEnabled, False) self.vertical = vertical self.page().setLinkDelegationPolicy(self.page().DelegateAllLinks) self.linkClicked.connect(self.link_activated) self._link_clicked = False self.setAttribute(Qt.WA_OpaquePaintEvent, False) palette = self.palette() self.setAcceptDrops(False) palette.setBrush(QPalette.Base, Qt.transparent) self.page().setPalette(palette) self.css = P("templates/book_details.css", data=True).decode("utf-8") for x, icon in [ ("remove_format", "trash.png"), ("save_format", "save.png"), ("restore_format", "edit-undo.png"), ("copy_link", "edit-copy.png"), ("manage_author", "user_profile.png"), ("compare_format", "diff.png"), ]: ac = QAction(QIcon(I(icon)), "", self) ac.current_fmt = None ac.current_url = None ac.triggered.connect(getattr(self, "%s_triggerred" % x)) setattr(self, "%s_action" % x, ac)
def __init__(self, vertical, parent=None): QWebView.__init__(self, parent) s = self.settings() s.setAttribute(s.JavascriptEnabled, False) self.vertical = vertical self.page().setLinkDelegationPolicy(self.page().DelegateAllLinks) self.linkClicked.connect(self.link_activated) self._link_clicked = False self.setAttribute(Qt.WA_OpaquePaintEvent, False) palette = self.palette() self.setAcceptDrops(False) palette.setBrush(QPalette.Base, Qt.transparent) self.page().setPalette(palette) for x, icon in [ ('remove_format', 'trash.png'), ('save_format', 'save.png'), ('restore_format', 'edit-undo.png'), ('copy_link','edit-copy.png'), ('compare_format', 'diff.png'), ('set_cover_format', 'default_cover.png'), ]: ac = QAction(QIcon(I(icon)), '', self) ac.current_fmt = None ac.current_url = None ac.triggered.connect(getattr(self, '%s_triggerred'%x)) setattr(self, '%s_action'%x, ac) self.manage_action = QAction(self) self.manage_action.current_fmt = self.manage_action.current_url = None self.manage_action.triggered.connect(self.manage_action_triggered) self.remove_item_action = ac = QAction(QIcon(I('minus.png')), '...', self) ac.data = (None, None, None) ac.triggered.connect(self.remove_item_triggered) self.setFocusPolicy(Qt.NoFocus)
def init_menu(window): exitAction = QAction('&Exit', window) exitAction.setShortcut('Ctrl+Q') exitAction.setStatusTip('Exit application') exitAction.triggered.connect(window.close) menu = window.menuBar() menuitem = menu.addMenu('&File') menuitem.addAction(exitAction)
def __init__(self, clone, parent, is_top_level=False, clone_shortcuts=True): QAction.__init__(self, clone.text().replace('&&', '&'), parent) self.setMenuRole(QAction.NoRole) # ensure this action is not moved around by Qt self.is_top_level = is_top_level self.clone_shortcuts = clone_shortcuts self.clone = clone clone.changed.connect(self.clone_changed) self.clone_changed() self.triggered.connect(self.do_trigger)
class MainWindow(QDialog): def __init__(self, default_status_msg=_('Welcome to') + ' ' + __appname__+' console', parent=None): QDialog.__init__(self, parent) self.restart_requested = False self.l = QVBoxLayout() self.setLayout(self.l) self.resize(800, 600) geom = dynamic.get('console_window_geometry', None) if geom is not None: self.restoreGeometry(geom) # Setup tool bar {{{ self.tool_bar = QToolBar(self) self.tool_bar.setToolButtonStyle(Qt.ToolButtonTextOnly) self.l.addWidget(self.tool_bar) # }}} # Setup status bar {{{ self.status_bar = QStatusBar(self) self.status_bar.defmsg = QLabel(__appname__ + _(' console ') + __version__) self.status_bar._font = QFont() self.status_bar._font.setBold(True) self.status_bar.defmsg.setFont(self.status_bar._font) self.status_bar.addWidget(self.status_bar.defmsg) # }}} self.console = Console(parent=self) self.console.running.connect(partial(self.status_bar.showMessage, _('Code is running'))) self.console.running_done.connect(self.status_bar.clearMessage) self.l.addWidget(self.console) self.l.addWidget(self.status_bar) self.setWindowTitle(__appname__ + ' console') self.setWindowIcon(QIcon(I('console.png'))) self.restart_action = QAction(_('Restart console'), self) self.restart_action.setShortcut(_('Ctrl+R')) self.addAction(self.restart_action) self.restart_action.triggered.connect(self.restart) self.console.context_menu.addAction(self.restart_action) def restart(self): self.restart_requested = True self.reject() def closeEvent(self, *args): dynamic.set('console_window_geometry', bytearray(self.saveGeometry())) self.console.shutdown() return QDialog.closeEvent(self, *args)
def __init__(self, wac, icon, text, checkable, view): QAction.__init__(self, QIcon(I(icon+'.png')), text, view) self._page_action = getattr(QWebPage, wac) self.setCheckable(checkable) self.triggered.connect(self.trigger_page_action) view.selectionChanged.connect(self.update_state, type=Qt.QueuedConnection) self.page_action.changed.connect(self.update_state, type=Qt.QueuedConnection) self.update_state()
class MenuBar(QMenuBar): def __init__(self, location_manager, parent): QMenuBar.__init__(self, parent) parent.setMenuBar(self) self.gui = parent self.location_manager = location_manager self.added_actions = [] self.donate_action = QAction(_("Donate"), self) self.donate_menu = QMenu() self.donate_menu.addAction(self.gui.donate_action) self.donate_action.setMenu(self.donate_menu) def init_bar(self, actions): for ac in self.added_actions: m = ac.menu() if m is not None: m.setVisible(False) self.clear() self.added_actions = [] for what in actions: if what is None: continue elif what == "Location Manager": for ac in self.location_manager.all_actions: ac = self.build_menu(ac) self.addAction(ac) self.added_actions.append(ac) ac.setVisible(False) elif what == "Donate": self.addAction(self.donate_action) elif what in self.gui.iactions: action = self.gui.iactions[what] ac = self.build_menu(action.qaction) self.addAction(ac) self.added_actions.append(ac) def build_menu(self, action): m = action.menu() ac = MenuAction(action, self) if m is None: m = QMenu() m.addAction(action) ac.setMenu(m) return ac def update_lm_actions(self): for ac in self.added_actions: clone = getattr(ac, "clone", None) if clone is not None and clone in self.location_manager.all_actions: ac.setVisible(clone in self.location_manager.available_actions)
def clone_action(ac, parent): if ac.isSeparator(): ans = QAction(parent) ans.setSeparator(True) return ans sc = ac.shortcut() sc = '' if sc.isEmpty() else sc.toString(sc.NativeText) ans = QAction(ac.icon(), ac.text() + '\t' + sc, parent) ans.triggered.connect(ac.trigger) ans.setEnabled(ac.isEnabled()) ans.setStatusTip(ac.statusTip()) ans.setVisible(ac.isVisible()) return ans
def __init__(self, name, label, icon, initial_show=True, initial_side_size=120, connect_button=True, orientation=Qt.Horizontal, side_index=0, parent=None, shortcut=None): QSplitter.__init__(self, parent) self.resize_timer = QTimer(self) self.resize_timer.setSingleShot(True) self.desired_side_size = initial_side_size self.desired_show = initial_show self.resize_timer.setInterval(5) self.resize_timer.timeout.connect(self.do_resize) self.setOrientation(orientation) self.side_index = side_index self._name = name self.label = label self.initial_side_size = initial_side_size self.initial_show = initial_show self.splitterMoved.connect(self.splitter_moved, type=Qt.QueuedConnection) self.button = LayoutButton(icon, label, self, shortcut=shortcut) if connect_button: self.button.clicked.connect(self.double_clicked) if shortcut is not None: self.action_toggle = QAction(QIcon(icon), _('Toggle') + ' ' + label, self) self.action_toggle.triggered.connect(self.toggle_triggered) if parent is not None: parent.addAction(self.action_toggle) if hasattr(parent, 'keyboard'): parent.keyboard.register_shortcut('splitter %s %s'%(name, label), unicode(self.action_toggle.text()), default_keys=(shortcut,), action=self.action_toggle) else: self.action_toggle.setShortcut(shortcut) else: self.action_toggle.setShortcut(shortcut)
def genesis(self): self.gui.keyboard.register_shortcut('Toggle Quickview', _('Toggle Quickview'), description=_('Open/close the Quickview panel/window'), default_keys=('Q',), action=self.qaction, group=self.action_spec[0]) self.focus_action = QAction(self.gui) self.gui.addAction(self.focus_action) self.gui.keyboard.register_shortcut('Focus To Quickview', _('Focus to Quickview'), description=_('Move the focus to the Quickview panel/window'), default_keys=('Shift+Q',), action=self.focus_action, group=self.action_spec[0]) self.focus_action.triggered.connect(self.focus_quickview) self.focus_bl_action = QAction(self.gui) self.gui.addAction(self.focus_bl_action) self.gui.keyboard.register_shortcut('Focus from Quickview', _('Focus from Quickview to the book list'), description=_('Move the focus from Quickview to the book list'), default_keys=('Shift+Alt+Q',), action=self.focus_bl_action, group=self.action_spec[0]) self.focus_bl_action.triggered.connect(self.focus_booklist) self.search_action = QAction(self.gui) self.gui.addAction(self.search_action) self.gui.keyboard.register_shortcut('Search from Quickview', _('Search from Quickview'), description=_('Search for the currently selected Quickview item'), default_keys=('Shift+S',), action=self.search_action, group=self.action_spec[0]) self.search_action.triggered.connect(self.search_quickview) self.search_action.changed.connect(self.set_search_shortcut) self.menuless_qaction.changed.connect(self.set_search_shortcut) self.qv_button = QuickviewButton(self.gui, self)
def __init__(self, horizontal=False, size=48, parent=None): QFrame.__init__(self, parent) if horizontal: size = 24 self.pi = ProgressIndicator(self, size) self._jobs = QLabel('<b>'+_('Jobs:')+' 0') self._jobs.mouseReleaseEvent = self.mouseReleaseEvent self.shortcut = 'Shift+Alt+J' if horizontal: self.setLayout(QHBoxLayout()) self.layout().setDirection(self.layout().RightToLeft) else: self.setLayout(QVBoxLayout()) self._jobs.setAlignment(Qt.AlignHCenter|Qt.AlignBottom) self.layout().addWidget(self.pi) self.layout().addWidget(self._jobs) if not horizontal: self.layout().setAlignment(self._jobs, Qt.AlignHCenter) self._jobs.setMargin(0) self.layout().setContentsMargins(0, 0, 0, 0) self._jobs.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.setCursor(Qt.PointingHandCursor) b = _('Click to see list of jobs') self.setToolTip(b + u' (%s)'%self.shortcut) self.action_toggle = QAction(b, parent) parent.addAction(self.action_toggle) self.action_toggle.setShortcut(self.shortcut) self.action_toggle.triggered.connect(self.toggle)
def setupEditActions(self): tb = QToolBar(self) tb.setWindowTitle("Edit Actions") self.addToolBar(tb) menu = QMenu("&Edit", self) self.menuBar().addMenu(menu) self.actionUndo = QAction("&Undo", self, shortcut=QKeySequence.Undo) tb.addAction(self.actionUndo) menu.addAction(self.actionUndo) self.actionRedo = QAction("&Redo", self, priority=QAction.LowPriority, shortcut=QKeySequence.Redo) tb.addAction(self.actionRedo) menu.addAction(self.actionRedo) menu.addSeparator() self.actionCut = QAction("Cu&t", self, priority=QAction.LowPriority, shortcut=QKeySequence.Cut) tb.addAction(self.actionCut) menu.addAction(self.actionCut) self.actionCopy = QAction("&Copy", self, priority=QAction.LowPriority, shortcut=QKeySequence.Copy) tb.addAction(self.actionCopy) menu.addAction(self.actionCopy) self.actionPaste = QAction("&Paste", self, priority=QAction.LowPriority, shortcut=QKeySequence.Paste, enabled=(len(QApplication.clipboard().text()) != 0)) tb.addAction(self.actionPaste) menu.addAction(self.actionPaste)
class SearchBarButton(LayoutButton): # {{{ def __init__(self, gui): sc = 'Alt+Shift+F' LayoutButton.__init__(self, I('search.png'), _('Search bar'), parent=gui, shortcut=sc) self.set_state_to_show() self.action_toggle = QAction(self.icon(), _('Toggle') + ' ' + self.label, self) gui.addAction(self.action_toggle) gui.keyboard.register_shortcut('search bar toggle' + self.label, unicode(self.action_toggle.text()), default_keys=(sc,), action=self.action_toggle) self.action_toggle.triggered.connect(self.toggle) self.action_toggle.changed.connect(self.update_shortcut) self.toggled.connect(self.update_state) def update_state(self, checked): if checked: self.set_state_to_hide() else: self.set_state_to_show() def save_state(self): gprefs['search bar visible'] = bool(self.isChecked()) def restore_state(self): self.setChecked(bool(gprefs.get('search bar visible', True)))
class GridViewButton(LayoutButton): # {{{ def __init__(self, gui): sc = 'Shift+Alt+G' LayoutButton.__init__(self, I('grid.png'), _('Cover Grid'), parent=gui, shortcut=sc) self.set_state_to_show() self.action_toggle = QAction(self.icon(), _('Toggle') + ' ' + self.label, self) gui.addAction(self.action_toggle) gui.keyboard.register_shortcut('grid view toggle' + self.label, unicode(self.action_toggle.text()), default_keys=(sc,), action=self.action_toggle) self.action_toggle.triggered.connect(self.toggle) self.toggled.connect(self.update_state) def update_state(self, checked): if checked: self.set_state_to_hide() else: self.set_state_to_show() def save_state(self): gprefs['grid view visible'] = bool(self.isChecked()) def restore_state(self): if gprefs.get('grid view visible', False): self.toggle()
def __init__(self, gui): sc = 'Shift+Alt+G' LayoutButton.__init__(self, I('grid.png'), _('Cover Grid'), parent=gui, shortcut=sc) self.set_state_to_show() self.action_toggle = QAction(self.icon(), _('Toggle') + ' ' + self.label, self) gui.addAction(self.action_toggle) gui.keyboard.register_shortcut('grid view toggle' + self.label, unicode(self.action_toggle.text()), default_keys=(sc,), action=self.action_toggle) self.action_toggle.triggered.connect(self.toggle) self.toggled.connect(self.update_state)
def __init__(self, gui): sc = 'Alt+Shift+F' LayoutButton.__init__(self, I('search.png'), _('Search bar'), parent=gui, shortcut=sc) self.set_state_to_show() self.action_toggle = QAction(self.icon(), _('Toggle') + ' ' + self.label, self) gui.addAction(self.action_toggle) gui.keyboard.register_shortcut('search bar toggle' + self.label, unicode(self.action_toggle.text()), default_keys=(sc,), action=self.action_toggle) self.action_toggle.triggered.connect(self.toggle) self.action_toggle.changed.connect(self.update_shortcut) self.toggled.connect(self.update_state)
def __init__(self, location_manager, parent): QMenuBar.__init__(self, parent) parent.setMenuBar(self) self.gui = parent self.location_manager = location_manager self.added_actions = [] self.donate_action = QAction(_("Donate"), self) self.donate_menu = QMenu() self.donate_menu.addAction(self.gui.donate_action) self.donate_action.setMenu(self.donate_menu)
def __init__(self, location_manager, parent): QObject.__init__(self, parent) self.gui = parent self.location_manager = location_manager self.added_actions = [] self.last_actions = [] self.donate_action = QAction(_('Donate'), self) self.donate_menu = QMenu() self.donate_menu.addAction(self.gui.donate_action) self.donate_action.setMenu(self.donate_menu) self.refresh_timer = t = QTimer(self) t.setInterval(200), t.setSingleShot(True), t.timeout.connect(self.refresh_bar)
def __init__(self, location_manager, parent): QObject.__init__(self, parent) f = factory(app_id='com.calibre-ebook.gui') self.menu_bar = f.create_window_menubar(parent) self.is_native_menubar = self.menu_bar.is_native_menubar self.gui = parent self.location_manager = location_manager self.added_actions = [] self.donate_action = QAction(_('Donate'), self) self.donate_menu = QMenu() self.donate_menu.addAction(self.gui.donate_action) self.donate_action.setMenu(self.donate_menu)
def create_action(self, spec=None, attr='qaction', shortcut_name=None): if spec is None: spec = self.action_spec text, icon, tooltip, shortcut = spec if icon is not None: action = QAction(QIcon(I(icon)), text, self.gui) else: action = QAction(text, self.gui) if attr == 'qaction': mt = (action.text() if self.action_menu_clone_qaction is True else unicode(self.action_menu_clone_qaction)) self.menuless_qaction = ma = QAction(action.icon(), mt, self.gui) ma.triggered.connect(action.trigger) for a in ((action, ma) if attr == 'qaction' else (action,)): a.setAutoRepeat(self.auto_repeat) text = tooltip if tooltip else text a.setToolTip(text) a.setStatusTip(text) a.setWhatsThis(text) shortcut_action = action desc = tooltip if tooltip else None if attr == 'qaction': shortcut_action = ma if shortcut is not None: keys = ((shortcut,) if isinstance(shortcut, basestring) else tuple(shortcut)) if shortcut_name is None and spec[0]: shortcut_name = unicode(spec[0]) if shortcut_name and self.action_spec[0] and not ( attr == 'qaction' and self.popup_type == QToolButton.InstantPopup): try: self.gui.keyboard.register_shortcut(self.unique_name + ' - ' + attr, shortcut_name, default_keys=keys, action=shortcut_action, description=desc, group=self.action_spec[0]) except NameConflict as e: try: prints(unicode(e)) except: pass shortcut_action.setShortcuts([QKeySequence(key, QKeySequence.PortableText) for key in keys]) else: if isosx: # In Qt 5 keyboard shortcuts dont work unless the # action is explicitly added to the main window self.gui.addAction(shortcut_action) if attr is not None: setattr(self, attr, action) if attr == 'qaction' and self.action_add_menu: menu = QMenu() action.setMenu(menu) if self.action_menu_clone_qaction: menu.addAction(self.menuless_qaction) return action
def __init__(self, vertical, parent=None): QWebView.__init__(self, parent) s = self.settings() s.setAttribute(s.JavascriptEnabled, False) self.vertical = vertical self.page().setLinkDelegationPolicy(self.page().DelegateAllLinks) self.linkClicked.connect(self.link_activated) self._link_clicked = False self.setAttribute(Qt.WA_OpaquePaintEvent, False) palette = self.palette() self.setAcceptDrops(False) palette.setBrush(QPalette.Base, Qt.transparent) self.page().setPalette(palette) self.css = P('templates/book_details.css', data=True).decode('utf-8') for x, icon in [ ('remove_format', 'trash.png'), ('save_format', 'save.png'), ('restore_format', 'edit-undo.png'), ('copy_link','edit-copy.png'), ('manage_author', 'user_profile.png'), ('compare_format', 'diff.png')]: ac = QAction(QIcon(I(icon)), '', self) ac.current_fmt = None ac.current_url = None ac.triggered.connect(getattr(self, '%s_triggerred'%x)) setattr(self, '%s_action'%x, ac) self.setFocusPolicy(Qt.NoFocus)
def a(name, text, icon, tb=None, sc_name=None, menu_name=None, popup_mode=QToolButton.MenuButtonPopup): name = 'action_' + name if isinstance(text, QDockWidget): ac = text.toggleViewAction() ac.setIcon(QIcon(I(icon))) else: ac = QAction(QIcon(I(icon)), text, self) setattr(self, name, ac) ac.setObjectName(name) (tb or self.tool_bar).addAction(ac) if sc_name: ac.setToolTip(unicode(ac.text()) + (' [%s]' % _(' or ').join(self.view.shortcuts.get_shortcuts(sc_name)))) if menu_name is not None: menu_name += '_menu' m = QMenu() setattr(self, menu_name, m) ac.setMenu(m) w = (tb or self.tool_bar).widgetForAction(ac) w.setPopupMode(popup_mode) return ac
def setupRunActions(self): tb = QToolBar(self) tb.setWindowTitle("Run Actions") self.addToolBar(tb) menu = QMenu("Run", self) self.menuBar().addMenu(menu) self.actionRun = QAction( "&Run", self, shortcut=Qt.CTRL + Qt.Key_R) tb.addAction(self.actionRun) menu.addAction(self.actionRun) self.comboScannoFile = QComboBox(tb) self.comboScannoFile.setObjectName("comboScannoFile") tb.addWidget(self.comboScannoFile) self.comboScannoFile.setEditable(True) self.comboLogFile= QComboBox(tb) self.comboLogFile.setObjectName("comboLogFile") self.comboLogFile.setEditable(True) tb.addWidget(self.comboLogFile)
def init_file_toolbar(window): # QIcon.fromTheme('exit'), exitAction = QAction('Exit', window) exitAction.setShortcut('Ctrl+Q') exitAction.triggered.connect(window.controller.handle_close) printAction = QAction('Print', window) printAction.setShortcut('Ctrl+P') printAction.triggered.connect(window.sig_print) exportAction = QAction('Export', window) exportAction.triggered.connect(window.sig_export) importAction = QAction('Import', window) importAction.triggered.connect(window.sig_import) undoAction = QAction('Undo', window) window.toolbar = window.addToolBar('Exit') window.toolbar.addAction(exitAction) window.toolbar.addAction(printAction) window.toolbar.addAction(exportAction) window.toolbar.addAction(importAction) window.toolbar.addAction(undoAction)
def __init__(self, path, parent): self.path = path QAction.__init__(self, os.path.basename(path), parent)
def _create_context_menu(self): self.plugin_view.setContextMenuPolicy( Qt.ContextMenuPolicy.ActionsContextMenu) self.install_action = QAction( QIcon(I('plugins/plugin_upgrade_ok.png')), _('&Install'), self) self.install_action.setToolTip(_('Install the selected plugin')) self.install_action.triggered.connect(self._install_clicked) self.install_action.setEnabled(False) self.plugin_view.addAction(self.install_action) self.forum_action = QAction(QIcon(I('plugins/mobileread.png')), _('Plugin &forum thread'), self) self.forum_action.triggered.connect(self._forum_label_activated) self.forum_action.setEnabled(False) self.plugin_view.addAction(self.forum_action) sep1 = QAction(self) sep1.setSeparator(True) self.plugin_view.addAction(sep1) self.toggle_enabled_action = QAction(_('Enable/&disable plugin'), self) self.toggle_enabled_action.setToolTip( _('Enable or disable this plugin')) self.toggle_enabled_action.triggered.connect( self._toggle_enabled_clicked) self.toggle_enabled_action.setEnabled(False) self.plugin_view.addAction(self.toggle_enabled_action) self.uninstall_action = QAction(_('&Remove plugin'), self) self.uninstall_action.setToolTip(_('Uninstall the selected plugin')) self.uninstall_action.triggered.connect(self._uninstall_clicked) self.uninstall_action.setEnabled(False) self.plugin_view.addAction(self.uninstall_action) sep2 = QAction(self) sep2.setSeparator(True) self.plugin_view.addAction(sep2) self.donate_enabled_action = QAction(QIcon(I('donate.png')), _('Donate to developer'), self) self.donate_enabled_action.setToolTip( _('Donate to the developer of this plugin')) self.donate_enabled_action.triggered.connect(self._donate_clicked) self.donate_enabled_action.setEnabled(False) self.plugin_view.addAction(self.donate_enabled_action) sep3 = QAction(self) sep3.setSeparator(True) self.plugin_view.addAction(sep3) self.configure_action = QAction(QIcon(I('config.png')), _('&Customize plugin'), self) self.configure_action.setToolTip( _('Customize the options for this plugin')) self.configure_action.triggered.connect(self._configure_clicked) self.configure_action.setEnabled(False) self.plugin_view.addAction(self.configure_action)
def __init__(self, type_, title, msg, det_msg='', q_icon=None, show_copy_button=True, parent=None, default_yes=True, yes_text=None, no_text=None, yes_icon=None, no_icon=None): QDialog.__init__(self, parent) if q_icon is None: icon = { self.ERROR: 'error', self.WARNING: 'warning', self.INFO: 'information', self.QUESTION: 'question', }[type_] icon = 'dialog_%s.png' % icon self.icon = QIcon(I(icon)) else: self.icon = q_icon if isinstance(q_icon, QIcon) else QIcon( I(q_icon)) self.setup_ui() self.setWindowTitle(title) self.setWindowIcon(self.icon) self.icon_widget.set_icon(self.icon) self.msg.setText(msg) self.det_msg.setPlainText(det_msg) self.det_msg.setVisible(False) self.toggle_checkbox.setVisible(False) if show_copy_button: self.ctc_button = self.bb.addButton( _('&Copy to clipboard'), QDialogButtonBox.ButtonRole.ActionRole) self.ctc_button.clicked.connect(self.copy_to_clipboard) self.show_det_msg = _('Show &details') self.hide_det_msg = _('Hide &details') self.det_msg_toggle = self.bb.addButton( self.show_det_msg, QDialogButtonBox.ButtonRole.ActionRole) self.det_msg_toggle.clicked.connect(self.toggle_det_msg) self.det_msg_toggle.setToolTip( _('Show detailed information about this error')) self.copy_action = QAction(self) self.addAction(self.copy_action) self.copy_action.setShortcuts(QKeySequence.StandardKey.Copy) self.copy_action.triggered.connect(self.copy_to_clipboard) self.is_question = type_ == self.QUESTION if self.is_question: self.bb.setStandardButtons(QDialogButtonBox.StandardButton.Yes | QDialogButtonBox.StandardButton.No) self.bb.button(QDialogButtonBox.StandardButton.Yes if default_yes else QDialogButtonBox.StandardButton.No).setDefault( True) self.default_yes = default_yes if yes_text is not None: self.bb.button( QDialogButtonBox.StandardButton.Yes).setText(yes_text) if no_text is not None: self.bb.button( QDialogButtonBox.StandardButton.No).setText(no_text) if yes_icon is not None: self.bb.button(QDialogButtonBox.StandardButton.Yes).setIcon( yes_icon if isinstance(yes_icon, QIcon ) else QIcon(I(yes_icon))) if no_icon is not None: self.bb.button(QDialogButtonBox.StandardButton.No).setIcon( no_icon if isinstance(no_icon, QIcon) else QIcon(I(no_icon) )) else: self.bb.button(QDialogButtonBox.StandardButton.Ok).setDefault(True) if not det_msg: self.det_msg_toggle.setVisible(False) self.resize_needed.connect(self.do_resize, type=Qt.ConnectionType.QueuedConnection) self.do_resize()
def __init__(self, clone, parent): QAction.__init__(self, clone.text(), parent) self.clone = clone clone.changed.connect(self.clone_changed)
class MenuBar(QObject): is_native_menubar = False def __init__(self, location_manager, parent): QObject.__init__(self, parent) f = factory(app_id='com.calibre-ebook.gui') self.menu_bar = f.create_window_menubar(parent) self.is_native_menubar = self.menu_bar.is_native_menubar self.gui = parent self.location_manager = location_manager self.added_actions = [] self.donate_action = QAction(_('Donate'), self) self.donate_menu = QMenu() self.donate_menu.addAction(self.gui.donate_action) self.donate_action.setMenu(self.donate_menu) def addAction(self, *args): self.menu_bar.addAction(*args) def setVisible(self, visible): self.menu_bar.setVisible(visible) def clear(self): self.menu_bar.clear() def init_bar(self, actions): for ac in self.added_actions: m = ac.menu() if m is not None: m.setVisible(False) self.clear() self.added_actions = [] for what in actions: if what is None: continue elif what == 'Location Manager': for ac in self.location_manager.all_actions: ac = self.build_menu(ac) self.addAction(ac) self.added_actions.append(ac) ac.setVisible(False) elif what == 'Donate': self.addAction(self.donate_action) elif what in self.gui.iactions: action = self.gui.iactions[what] ac = self.build_menu(action.qaction) self.addAction(ac) self.added_actions.append(ac) def build_menu(self, action): m = action.menu() ac = MenuAction(action, self) if m is None: m = QMenu() m.addAction(action) ac.setMenu(m) return ac def update_lm_actions(self): for ac in self.added_actions: clone = getattr(ac, 'clone', None) if clone is not None and clone in self.location_manager.all_actions: ac.setVisible(clone in self.location_manager.available_actions)
def __init__(self, parent, file_dialog_service, h_margin=(0.8, 0.1), v_margin=(0.5, 0.15), h_axes=[Size.Scaled(1.0)], v_axes=[Size.Scaled(1.0)], nx_default=1, ny_default=1): QWidget.__init__(self, parent) self._file_dialog_service = file_dialog_service self._figure = Figure() self._canvas = FigureCanvas(self._figure) h = [Size.Fixed(h_margin[0]), *h_axes, Size.Fixed(h_margin[1])] v = [Size.Fixed(v_margin[0]), *v_axes, Size.Fixed(v_margin[1])] self._divider = Divider(self._figure, (0.0, 0.0, 1.0, 1.0), h, v, aspect=False) self._axes = LocatableAxes(self._figure, self._divider.get_position()) self._axes.set_axes_locator( self._divider.new_locator(nx=nx_default, ny=ny_default)) self._axes.set_zorder(2) self._axes.patch.set_visible(False) for spine in ['top', 'right']: self._axes.spines[spine].set_visible(False) self._figure.add_axes(self._axes) self._canvas.setParent(self) self._layout = QVBoxLayout(self) self._layout.setContentsMargins(0, 0, 0, 0) self._layout.addWidget(self._canvas) self.setLayout(self._layout) self._figure.canvas.mpl_connect('scroll_event', self._on_scroll) self._xy_extents = None self._background_cache = None self._decoration_artists = [] self._is_panning = False self._zoom_selector = _RectangleSelector(self._axes, self._zoom_selected) self._zoom_selector.set_active(False) self._x_extent_padding = 0.01 self._y_extent_padding = 0.01 self._axes.ticklabel_format(style='sci', axis='x', scilimits=(-4, 4)) self._axes.ticklabel_format(style='sci', axis='y', scilimits=(-4, 4)) self._active_tools = {} self._span = _SpanSeletor(self._axes, self._handle_span_select, 'horizontal', rectprops=dict(alpha=0.2, facecolor='red', edgecolor='k'), span_stays=True) self._span.set_on_select_none(self._handle_span_select_none) self.span = self._previous_span = None self._span_center_mouse_event = None self._span_left_mouse_event = None self._span_right_mouse_event = None self._figure.canvas.mpl_connect('button_press_event', self._handle_press) self._figure.canvas.mpl_connect('motion_notify_event', self._handle_move) self._figure.canvas.mpl_connect('button_release_event', self._handle_release) self._figure.canvas.mpl_connect('resize_event', self._handle_resize) self.activateTool(ToolType.span, self.isActiveDefault(ToolType.span)) self._pan_event = None self._pending_draw = None self._pending_artists_draw = None self._other_draw_events = [] self._draw_timer = QTimer(self) self._draw_timer.timeout.connect(self._do_draw_events) self._draw_timer.start(20) self._zoom_skew = None self._menu = QMenu(self) self._copy_image_action = QAction(self.tr('Copy To Clipboard'), self) self._copy_image_action.triggered.connect(self.copyToClipboard) self._copy_image_action.setShortcuts(QKeySequence.Copy) self._save_image_action = QAction(self.tr('Save As Image'), self) self._save_image_action.triggered.connect(self.saveAsImage) self._show_table_action = QAction(self.tr('Show Table'), self) self._show_table_action.triggered.connect(self.showTable) self._menu.addAction(self._copy_image_action) self._menu.addAction(self._save_image_action) self._menu.addAction(self._show_table_action) self.addAction(self._copy_image_action) self._table_view = None self._single_axis_zoom_enabled = True self._cached_label_width_height = None if hasattr(type(self), 'dataChanged'): self.dataChanged.connect(self._on_data_changed) self._options_view = None self._secondary_axes = self._secondary_y_extent = self._secondary_x_extent = None self._legend = None self._draggable_legend = None self._setting_axis_limits = False self.hasHiddenSeries = False
def __init__(self, parent): QWidget.__init__(self, parent) self._layout = l = QHBoxLayout() self.setLayout(self._layout) self._layout.setContentsMargins(0, 5, 0, 0) x = QToolButton(self) x.setText(_('Vi&rtual Library')) x.setIcon(QIcon(I('lt.png'))) x.setObjectName("virtual_library") x.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) l.addWidget(x) parent.virtual_library = x x = QToolButton(self) x.setIcon(QIcon(I('minus.png'))) x.setObjectName('clear_vl') l.addWidget(x) x.setVisible(False) x.setToolTip(_('Close the Virtual Library')) parent.clear_vl = x x = QLabel(self) x.setObjectName("search_count") l.addWidget(x) parent.search_count = x x.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) parent.advanced_search_button = x = QToolButton(self) parent.advanced_search_toggle_action = ac = QAction(parent) parent.addAction(ac) parent.keyboard.register_shortcut('advanced search toggle', _('Advanced search'), default_keys=("Shift+Ctrl+F", ), action=ac) ac.triggered.connect(x.click) x.setIcon(QIcon(I('search.png'))) l.addWidget(x) x.setToolTip(_("Advanced search")) x = parent.search = SearchBox2(self) x.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) x.setObjectName("search") x.setToolTip( _("<p>Search the list of books by title, author, publisher, " "tags, comments, etc.<br><br>Words separated by spaces are ANDed" )) x.setMinimumContentsLength(10) l.addWidget(x) self.search_button = QToolButton() self.search_button.setToolButtonStyle(Qt.ToolButtonTextOnly) self.search_button.setText(_('&Go!')) l.addWidget(self.search_button) self.search_button.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.search_button.clicked.connect(parent.do_search_button) self.search_button.setToolTip( _('Do Quick Search (you can also press the Enter key)')) x = parent.clear_button = QToolButton(self) x.setIcon(QIcon(I('clear_left.png'))) x.setObjectName("clear_button") l.addWidget(x) x.setToolTip(_("Reset Quick Search")) x = parent.highlight_only_button = QToolButton(self) x.setIcon(QIcon(I('arrow-down.png'))) l.addWidget(x) x = parent.saved_search = SavedSearchBox(self) x.setMaximumSize(QSize(150, 16777215)) x.setMinimumContentsLength(10) x.setObjectName("saved_search") l.addWidget(x) x = parent.copy_search_button = QToolButton(self) x.setIcon(QIcon(I("search_copy_saved.png"))) x.setObjectName("copy_search_button") l.addWidget(x) x.setToolTip(_("Copy current search text (instead of search name)")) x = parent.save_search_button = RightClickButton(self) x.setIcon(QIcon(I("search_add_saved.png"))) x.setObjectName("save_search_button") l.addWidget(x)
class PluginUpdaterDialog(SizePersistedDialog): initial_extra_size = QSize(350, 100) forum_label_text = _('Plugin homepage') def __init__(self, gui, initial_filter=FILTER_UPDATE_AVAILABLE): SizePersistedDialog.__init__(self, gui, 'Plugin Updater plugin:plugin updater dialog') self.gui = gui self.forum_link = None self.zip_url = None self.model = None self.do_restart = False self._initialize_controls() self._create_context_menu() try: display_plugins = read_available_plugins(raise_error=True) except Exception: display_plugins = [] import traceback error_dialog(self.gui, _('Update Check Failed'), _('Unable to reach the plugin index page.'), det_msg=traceback.format_exc(), show=True) if display_plugins: self.model = DisplayPluginModel(display_plugins) self.proxy_model = DisplayPluginSortFilterModel(self) self.proxy_model.setSourceModel(self.model) self.plugin_view.setModel(self.proxy_model) self.plugin_view.resizeColumnsToContents() self.plugin_view.selectionModel().currentRowChanged.connect(self._plugin_current_changed) self.plugin_view.doubleClicked.connect(self.install_button.click) self.filter_combo.setCurrentIndex(initial_filter) self._select_and_focus_view() else: self.filter_combo.setEnabled(False) # Cause our dialog size to be restored from prefs or created on first usage self.resize_dialog() def _initialize_controls(self): self.setWindowTitle(_('User plugins')) self.setWindowIcon(QIcon(I('plugins/plugin_updater.png'))) layout = QVBoxLayout(self) self.setLayout(layout) title_layout = ImageTitleLayout(self, 'plugins/plugin_updater.png', _('User plugins')) layout.addLayout(title_layout) header_layout = QHBoxLayout() layout.addLayout(header_layout) self.filter_combo = PluginFilterComboBox(self) self.filter_combo.setMinimumContentsLength(20) self.filter_combo.currentIndexChanged[int].connect(self._filter_combo_changed) la = QLabel(_('Filter list of &plugins')+':', self) la.setBuddy(self.filter_combo) header_layout.addWidget(la) header_layout.addWidget(self.filter_combo) header_layout.addStretch(10) # filter plugins by name la = QLabel(_('Filter by &name')+':', self) header_layout.addWidget(la) self.filter_by_name_lineedit = QLineEdit(self) la.setBuddy(self.filter_by_name_lineedit) self.filter_by_name_lineedit.setText("") self.filter_by_name_lineedit.textChanged.connect(self._filter_name_lineedit_changed) header_layout.addWidget(self.filter_by_name_lineedit) self.plugin_view = QTableView(self) self.plugin_view.horizontalHeader().setStretchLastSection(True) self.plugin_view.setSelectionBehavior(QAbstractItemView.SelectRows) self.plugin_view.setSelectionMode(QAbstractItemView.SingleSelection) self.plugin_view.setAlternatingRowColors(True) self.plugin_view.setSortingEnabled(True) self.plugin_view.setIconSize(QSize(28, 28)) layout.addWidget(self.plugin_view) details_layout = QHBoxLayout() layout.addLayout(details_layout) forum_label = self.forum_label = QLabel('') forum_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard) forum_label.linkActivated.connect(self._forum_label_activated) details_layout.addWidget(QLabel(_('Description')+':', self), 0, Qt.AlignLeft) details_layout.addWidget(forum_label, 1, Qt.AlignRight) self.description = QLabel(self) self.description.setFrameStyle(QFrame.Panel | QFrame.Sunken) self.description.setAlignment(Qt.AlignTop | Qt.AlignLeft) self.description.setMinimumHeight(40) self.description.setWordWrap(True) layout.addWidget(self.description) self.button_box = QDialogButtonBox(QDialogButtonBox.Close) self.button_box.rejected.connect(self.reject) self.finished.connect(self._finished) self.install_button = self.button_box.addButton(_('&Install'), QDialogButtonBox.AcceptRole) self.install_button.setToolTip(_('Install the selected plugin')) self.install_button.clicked.connect(self._install_clicked) self.install_button.setEnabled(False) self.configure_button = self.button_box.addButton(' '+_('&Customize plugin ')+' ', QDialogButtonBox.ResetRole) self.configure_button.setToolTip(_('Customize the options for this plugin')) self.configure_button.clicked.connect(self._configure_clicked) self.configure_button.setEnabled(False) layout.addWidget(self.button_box) def update_forum_label(self): txt = '' if self.forum_link: txt = '<a href="%s">%s</a>' % (self.forum_link, self.forum_label_text) self.forum_label.setText(txt) def _create_context_menu(self): self.plugin_view.setContextMenuPolicy(Qt.ActionsContextMenu) self.install_action = QAction(QIcon(I('plugins/plugin_upgrade_ok.png')), _('&Install'), self) self.install_action.setToolTip(_('Install the selected plugin')) self.install_action.triggered.connect(self._install_clicked) self.install_action.setEnabled(False) self.plugin_view.addAction(self.install_action) self.history_action = QAction(QIcon(I('chapters.png')), _('Version &history'), self) self.history_action.setToolTip(_('Show history of changes to this plugin')) self.history_action.triggered.connect(self._history_clicked) self.history_action.setEnabled(False) self.plugin_view.addAction(self.history_action) self.forum_action = QAction(QIcon(I('plugins/mobileread.png')), _('Plugin &forum thread'), self) self.forum_action.triggered.connect(self._forum_label_activated) self.forum_action.setEnabled(False) self.plugin_view.addAction(self.forum_action) sep1 = QAction(self) sep1.setSeparator(True) self.plugin_view.addAction(sep1) self.toggle_enabled_action = QAction(_('Enable/&disable plugin'), self) self.toggle_enabled_action.setToolTip(_('Enable or disable this plugin')) self.toggle_enabled_action.triggered.connect(self._toggle_enabled_clicked) self.toggle_enabled_action.setEnabled(False) self.plugin_view.addAction(self.toggle_enabled_action) self.uninstall_action = QAction(_('&Remove plugin'), self) self.uninstall_action.setToolTip(_('Uninstall the selected plugin')) self.uninstall_action.triggered.connect(self._uninstall_clicked) self.uninstall_action.setEnabled(False) self.plugin_view.addAction(self.uninstall_action) sep2 = QAction(self) sep2.setSeparator(True) self.plugin_view.addAction(sep2) self.donate_enabled_action = QAction(QIcon(I('donate.png')), _('Donate to developer'), self) self.donate_enabled_action.setToolTip(_('Donate to the developer of this plugin')) self.donate_enabled_action.triggered.connect(self._donate_clicked) self.donate_enabled_action.setEnabled(False) self.plugin_view.addAction(self.donate_enabled_action) sep3 = QAction(self) sep3.setSeparator(True) self.plugin_view.addAction(sep3) self.configure_action = QAction(QIcon(I('config.png')), _('&Customize plugin'), self) self.configure_action.setToolTip(_('Customize the options for this plugin')) self.configure_action.triggered.connect(self._configure_clicked) self.configure_action.setEnabled(False) self.plugin_view.addAction(self.configure_action) def _finished(self, *args): if self.model: update_plugins = list(filter(filter_upgradeable_plugins, self.model.display_plugins)) self.gui.recalc_update_label(len(update_plugins)) def _plugin_current_changed(self, current, previous): if current.isValid(): actual_idx = self.proxy_model.mapToSource(current) display_plugin = self.model.display_plugins[actual_idx.row()] self.description.setText(display_plugin.description) self.forum_link = display_plugin.forum_link self.zip_url = display_plugin.zip_url self.forum_action.setEnabled(bool(self.forum_link)) self.install_button.setEnabled(display_plugin.is_valid_to_install()) self.install_action.setEnabled(self.install_button.isEnabled()) self.uninstall_action.setEnabled(display_plugin.is_installed()) self.history_action.setEnabled(display_plugin.has_changelog) self.configure_button.setEnabled(display_plugin.is_installed()) self.configure_action.setEnabled(self.configure_button.isEnabled()) self.toggle_enabled_action.setEnabled(display_plugin.is_installed()) self.donate_enabled_action.setEnabled(bool(display_plugin.donation_link)) else: self.description.setText('') self.forum_link = None self.zip_url = None self.forum_action.setEnabled(False) self.install_button.setEnabled(False) self.install_action.setEnabled(False) self.uninstall_action.setEnabled(False) self.history_action.setEnabled(False) self.configure_button.setEnabled(False) self.configure_action.setEnabled(False) self.toggle_enabled_action.setEnabled(False) self.donate_enabled_action.setEnabled(False) self.update_forum_label() def _donate_clicked(self): plugin = self._selected_display_plugin() if plugin and plugin.donation_link: open_url(QUrl(plugin.donation_link)) def _select_and_focus_view(self, change_selection=True): if change_selection and self.plugin_view.model().rowCount() > 0: self.plugin_view.selectRow(0) else: idx = self.plugin_view.selectionModel().currentIndex() self._plugin_current_changed(idx, 0) self.plugin_view.setFocus() def _filter_combo_changed(self, idx): self.filter_by_name_lineedit.setText("") # clear the name filter text when a different group was selected self.proxy_model.set_filter_criteria(idx) if idx == FILTER_NOT_INSTALLED: self.plugin_view.sortByColumn(5, Qt.DescendingOrder) else: self.plugin_view.sortByColumn(0, Qt.AscendingOrder) self._select_and_focus_view() def _filter_name_lineedit_changed(self, text): self.proxy_model.set_filter_text(text) # set the filter text for filterAcceptsRow def _forum_label_activated(self): if self.forum_link: open_url(QUrl(self.forum_link)) def _selected_display_plugin(self): idx = self.plugin_view.selectionModel().currentIndex() actual_idx = self.proxy_model.mapToSource(idx) return self.model.display_plugins[actual_idx.row()] def _uninstall_plugin(self, name_to_remove): if DEBUG: prints('Removing plugin: ', name_to_remove) remove_plugin(name_to_remove) # Make sure that any other plugins that required this plugin # to be uninstalled first have the requirement removed for display_plugin in self.model.display_plugins: # Make sure we update the status and display of the # plugin we just uninstalled if name_to_remove in display_plugin.uninstall_plugins: if DEBUG: prints('Removing uninstall dependency for: ', display_plugin.name) display_plugin.uninstall_plugins.remove(name_to_remove) if display_plugin.qname == name_to_remove: if DEBUG: prints('Resetting plugin to uninstalled status: ', display_plugin.name) display_plugin.installed_version = None display_plugin.plugin = None display_plugin.uninstall_plugins = [] if self.proxy_model.filter_criteria not in [FILTER_INSTALLED, FILTER_UPDATE_AVAILABLE]: self.model.refresh_plugin(display_plugin) def _uninstall_clicked(self): display_plugin = self._selected_display_plugin() if not question_dialog(self, _('Are you sure?'), '<p>'+ _('Are you sure you want to uninstall the <b>%s</b> plugin?')%display_plugin.name, show_copy_button=False): return self._uninstall_plugin(display_plugin.qname) if self.proxy_model.filter_criteria in [FILTER_INSTALLED, FILTER_UPDATE_AVAILABLE]: self.model.beginResetModel(), self.model.endResetModel() self._select_and_focus_view() else: self._select_and_focus_view(change_selection=False) def _install_clicked(self): display_plugin = self._selected_display_plugin() if not question_dialog(self, _('Install %s')%display_plugin.name, '<p>' + _('Installing plugins is a <b>security risk</b>. ' 'Plugins can contain a virus/malware. ' 'Only install it if you got it from a trusted source.' ' Are you sure you want to proceed?'), show_copy_button=False): return if display_plugin.uninstall_plugins: uninstall_names = list(display_plugin.uninstall_plugins) if DEBUG: prints('Uninstalling plugin: ', ', '.join(uninstall_names)) for name_to_remove in uninstall_names: self._uninstall_plugin(name_to_remove) plugin_zip_url = display_plugin.zip_url if DEBUG: prints('Downloading plugin ZIP attachment: ', plugin_zip_url) self.gui.status_bar.showMessage(_('Downloading plugin ZIP attachment: %s') % plugin_zip_url) zip_path = self._download_zip(plugin_zip_url) if DEBUG: prints('Installing plugin: ', zip_path) self.gui.status_bar.showMessage(_('Installing plugin: %s') % zip_path) do_restart = False try: from calibre.customize.ui import config installed_plugins = frozenset(config['plugins']) try: plugin = add_plugin(zip_path) except NameConflict as e: return error_dialog(self.gui, _('Already exists'), unicode_type(e), show=True) # Check for any toolbars to add to. widget = ConfigWidget(self.gui) widget.gui = self.gui widget.check_for_add_to_toolbars(plugin, previously_installed=plugin.name in installed_plugins) self.gui.status_bar.showMessage(_('Plugin installed: %s') % display_plugin.name) d = info_dialog(self.gui, _('Success'), _('Plugin <b>{0}</b> successfully installed under <b>' ' {1} plugins</b>. You may have to restart calibre ' 'for the plugin to take effect.').format(plugin.name, plugin.type), show_copy_button=False) b = d.bb.addButton(_('&Restart calibre now'), d.bb.AcceptRole) b.setIcon(QIcon(I('lt.png'))) d.do_restart = False def rf(): d.do_restart = True b.clicked.connect(rf) d.set_details('') d.exec_() b.clicked.disconnect() do_restart = d.do_restart display_plugin.plugin = plugin # We cannot read the 'actual' version information as the plugin will not be loaded yet display_plugin.installed_version = display_plugin.available_version except: if DEBUG: prints('ERROR occurred while installing plugin: %s'%display_plugin.name) traceback.print_exc() error_dialog(self.gui, _('Install plugin failed'), _('A problem occurred while installing this plugin.' ' This plugin will now be uninstalled.' ' Please post the error message in details below into' ' the forum thread for this plugin and restart calibre.'), det_msg=traceback.format_exc(), show=True) if DEBUG: prints('Due to error now uninstalling plugin: %s'%display_plugin.name) remove_plugin(display_plugin.name) display_plugin.plugin = None display_plugin.uninstall_plugins = [] if self.proxy_model.filter_criteria in [FILTER_NOT_INSTALLED, FILTER_UPDATE_AVAILABLE]: self.model.beginResetModel(), self.model.endResetModel() self._select_and_focus_view() else: self.model.refresh_plugin(display_plugin) self._select_and_focus_view(change_selection=False) if do_restart: self.do_restart = True self.accept() def _history_clicked(self): display_plugin = self._selected_display_plugin() text = self._read_version_history_html(display_plugin.forum_link) if text: dlg = VersionHistoryDialog(self, display_plugin.name, text) dlg.exec_() else: return error_dialog(self, _('Version history missing'), _('Unable to find the version history for %s')%display_plugin.name, show=True) def _configure_clicked(self): display_plugin = self._selected_display_plugin() plugin = display_plugin.plugin if not plugin.is_customizable(): return info_dialog(self, _('Plugin not customizable'), _('Plugin: %s does not need customization')%plugin.name, show=True) from calibre.customize import InterfaceActionBase if isinstance(plugin, InterfaceActionBase) and not getattr(plugin, 'actual_iaction_plugin_loaded', False): return error_dialog(self, _('Must restart'), _('You must restart calibre before you can' ' configure the <b>%s</b> plugin')%plugin.name, show=True) plugin.do_user_config(self.parent()) def _toggle_enabled_clicked(self): display_plugin = self._selected_display_plugin() plugin = display_plugin.plugin if not plugin.can_be_disabled: return error_dialog(self,_('Plugin cannot be disabled'), _('The plugin: %s cannot be disabled')%plugin.name, show=True) if is_disabled(plugin): enable_plugin(plugin) else: disable_plugin(plugin) self.model.refresh_plugin(display_plugin) def _read_version_history_html(self, forum_link): br = browser() br.set_handle_gzip(True) try: raw = br.open_novisit(forum_link).read() if not raw: return None except: traceback.print_exc() return None raw = raw.decode('utf-8', errors='replace') root = html.fromstring(raw) spoiler_nodes = root.xpath('//div[@class="smallfont" and strong="Spoiler"]') for spoiler_node in spoiler_nodes: try: if spoiler_node.getprevious() is None: # This is a spoiler node that has been indented using [INDENT] # Need to go up to parent div, then previous node to get header heading_node = spoiler_node.getparent().getprevious() else: # This is a spoiler node after a BR tag from the heading heading_node = spoiler_node.getprevious().getprevious() if heading_node is None: continue if heading_node.text_content().lower().find('version history') != -1: div_node = spoiler_node.xpath('div')[0] text = html.tostring(div_node, method='html', encoding='unicode') return re.sub(r'<div\s.*?>', '<div>', text) except: if DEBUG: prints('======= MobileRead Parse Error =======') traceback.print_exc() prints(html.tostring(spoiler_node)) return None def _download_zip(self, plugin_zip_url): from calibre.ptempfile import PersistentTemporaryFile raw = get_https_resource_securely(plugin_zip_url, headers={'User-Agent':'%s %s' % (__appname__, __version__)}) with PersistentTemporaryFile('.zip') as pt: pt.write(raw) return pt.name
def __init__(self, parent): QWidget.__init__(self, parent) self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Preferred) parent = parent.parent() self.l = l = QHBoxLayout(self) l.setContentsMargins(0, 0, 0, 0) self.alter_tb = parent.alter_tb = b = QToolButton(self) b.setAutoRaise(True) b.setText(_('Configure')), b.setToolButtonStyle( Qt.ToolButtonTextBesideIcon) b.setCursor(Qt.PointingHandCursor) b.setPopupMode(b.InstantPopup) b.setToolTip( textwrap.fill( _('Change how the Tag browser works, such as,' ' how it is sorted, what happens when you click' ' items, etc.'))) b.setIcon(QIcon(I('config.png'))) b.m = QMenu() b.setMenu(b.m) self.item_search = FindBox(parent) self.item_search.setMinimumContentsLength(5) self.item_search.setSizeAdjustPolicy( self.item_search.AdjustToMinimumContentsLengthWithIcon) self.item_search.initialize('tag_browser_search') self.item_search.completer().setCaseSensitivity(Qt.CaseSensitive) self.item_search.setToolTip( _('Search for items. This is a "contains" search; items containing the\n' 'text anywhere in the name will be found. You can limit the search\n' 'to particular categories using syntax similar to search. For example,\n' 'tags:foo will find foo in any tag, but not in authors etc. Entering\n' '*foo will filter all categories at once, showing only those items\n' 'containing the text "foo"')) ac = QAction(parent) parent.addAction(ac) parent.keyboard.register_shortcut('tag browser find box', _('Find next match'), default_keys=(), action=ac, group=_('Tag browser')) ac.triggered.connect(self.set_focus_to_find_box) self.search_button = QToolButton() self.search_button.setAutoRaise(True) self.search_button.setCursor(Qt.PointingHandCursor) self.search_button.setIcon(QIcon(I('search.png'))) self.search_button.setToolTip(_('Find the first/next matching item')) ac = QAction(parent) parent.addAction(ac) parent.keyboard.register_shortcut('tag browser find button', _('Find in Tag browser'), default_keys=(), action=ac, group=_('Tag browser')) ac.triggered.connect(self.search_button.click) self.toggle_search_button = b = QToolButton(self) le = self.item_search.lineEdit() le.addAction(QIcon(I('window-close.png')), le.LeadingPosition).triggered.connect(self.close_find_box) b.setText(_('Find')), b.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) b.setCursor(Qt.PointingHandCursor) b.setIcon(QIcon(I('search.png'))) b.setCheckable(True) b.setChecked(gprefs.get('tag browser search box visible', False)) b.setToolTip(_('Search for items in the Tag browser')) b.setAutoRaise(True) b.toggled.connect(self.update_searchbar_state) self.update_searchbar_state()
def __init__(self, parent=None): QTextEdit.__init__(self, parent) self.setTabChangesFocus(True) self.document().setDefaultStyleSheet( css() + '\n\nli { margin-top: 0.5ex; margin-bottom: 0.5ex; }') font = self.font() f = QFontInfo(font) delta = tweaks['change_book_details_font_size_by'] + 1 if delta: font.setPixelSize(f.pixelSize() + delta) self.setFont(font) f = QFontMetrics(self.font()) self.em_size = f.horizontalAdvance('m') self.base_url = None self._parent = weakref.ref(parent) self.comments_pat = re.compile(r'<!--.*?-->', re.DOTALL) extra_shortcuts = { 'bold': 'Bold', 'italic': 'Italic', 'underline': 'Underline', } for rec in ( ('bold', 'format-text-bold', _('Bold'), True), ('italic', 'format-text-italic', _('Italic'), True), ('underline', 'format-text-underline', _('Underline'), True), ('strikethrough', 'format-text-strikethrough', _('Strikethrough'), True), ('superscript', 'format-text-superscript', _('Superscript'), True), ('subscript', 'format-text-subscript', _('Subscript'), True), ('ordered_list', 'format-list-ordered', _('Ordered list'), True), ('unordered_list', 'format-list-unordered', _('Unordered list'), True), ('align_left', 'format-justify-left', _('Align left'), True), ('align_center', 'format-justify-center', _('Align center'), True), ('align_right', 'format-justify-right', _('Align right'), True), ('align_justified', 'format-justify-fill', _('Align justified'), True), ( 'undo', 'edit-undo', _('Undo'), ), ( 'redo', 'edit-redo', _('Redo'), ), ( 'remove_format', 'edit-clear', _('Remove formatting'), ), ( 'copy', 'edit-copy', _('Copy'), ), ( 'paste', 'edit-paste', _('Paste'), ), ( 'paste_and_match_style', 'edit-paste', _('Paste and match style'), ), ( 'cut', 'edit-cut', _('Cut'), ), ( 'indent', 'format-indent-more', _('Increase indentation'), ), ( 'outdent', 'format-indent-less', _('Decrease indentation'), ), ( 'select_all', 'edit-select-all', _('Select all'), ), ('color', 'format-text-color', _('Foreground color')), ('background', 'format-fill-color', _('Background color')), ( 'insert_link', 'insert-link', _('Insert link or image'), ), ( 'insert_hr', 'format-text-hr', _('Insert separator'), ), ('clear', 'trash', _('Clear')), ): name, icon, text = rec[:3] checkable = len(rec) == 4 ac = QAction(QIcon(I(icon + '.png')), text, self) if checkable: ac.setCheckable(checkable) setattr(self, 'action_' + name, ac) ss = extra_shortcuts.get(name) if ss is not None: ac.setShortcut(QKeySequence(getattr(QKeySequence, ss))) ac.triggered.connect(getattr(self, 'do_' + name)) self.action_block_style = QAction(QIcon(I('format-text-heading.png')), _('Style text block'), self) self.action_block_style.setToolTip(_('Style the selected text block')) self.block_style_menu = QMenu(self) self.action_block_style.setMenu(self.block_style_menu) self.block_style_actions = [] h = _('Heading {0}') for text, name in ( (_('Normal'), 'p'), (h.format(1), 'h1'), (h.format(2), 'h2'), (h.format(3), 'h3'), (h.format(4), 'h4'), (h.format(5), 'h5'), (h.format(6), 'h6'), (_('Blockquote'), 'blockquote'), ): ac = QAction(text, self) self.block_style_menu.addAction(ac) ac.block_name = name ac.setCheckable(True) self.block_style_actions.append(ac) ac.triggered.connect(self.do_format_block) self.setHtml('') self.copyAvailable.connect(self.update_clipboard_actions) self.update_clipboard_actions(False) self.selectionChanged.connect(self.update_selection_based_actions) self.update_selection_based_actions() connect_lambda(self.undoAvailable, self, lambda self, yes: self.action_undo.setEnabled(yes)) connect_lambda(self.redoAvailable, self, lambda self, yes: self.action_redo.setEnabled(yes)) self.action_undo.setEnabled(False), self.action_redo.setEnabled(False) self.textChanged.connect(self.update_cursor_position_actions) self.cursorPositionChanged.connect(self.update_cursor_position_actions) self.textChanged.connect(self.data_changed) self.update_cursor_position_actions()
class EditorWidget(QTextEdit, LineEditECM): # {{{ data_changed = pyqtSignal() @property def readonly(self): return self.isReadOnly() @readonly.setter def readonly(self, val): self.setReadOnly(bool(val)) @contextmanager def editing_cursor(self, set_cursor=True): c = self.textCursor() c.beginEditBlock() yield c c.endEditBlock() if set_cursor: self.setTextCursor(c) self.focus_self() def __init__(self, parent=None): QTextEdit.__init__(self, parent) self.setTabChangesFocus(True) self.document().setDefaultStyleSheet( css() + '\n\nli { margin-top: 0.5ex; margin-bottom: 0.5ex; }') font = self.font() f = QFontInfo(font) delta = tweaks['change_book_details_font_size_by'] + 1 if delta: font.setPixelSize(f.pixelSize() + delta) self.setFont(font) f = QFontMetrics(self.font()) self.em_size = f.horizontalAdvance('m') self.base_url = None self._parent = weakref.ref(parent) self.comments_pat = re.compile(r'<!--.*?-->', re.DOTALL) extra_shortcuts = { 'bold': 'Bold', 'italic': 'Italic', 'underline': 'Underline', } for rec in ( ('bold', 'format-text-bold', _('Bold'), True), ('italic', 'format-text-italic', _('Italic'), True), ('underline', 'format-text-underline', _('Underline'), True), ('strikethrough', 'format-text-strikethrough', _('Strikethrough'), True), ('superscript', 'format-text-superscript', _('Superscript'), True), ('subscript', 'format-text-subscript', _('Subscript'), True), ('ordered_list', 'format-list-ordered', _('Ordered list'), True), ('unordered_list', 'format-list-unordered', _('Unordered list'), True), ('align_left', 'format-justify-left', _('Align left'), True), ('align_center', 'format-justify-center', _('Align center'), True), ('align_right', 'format-justify-right', _('Align right'), True), ('align_justified', 'format-justify-fill', _('Align justified'), True), ( 'undo', 'edit-undo', _('Undo'), ), ( 'redo', 'edit-redo', _('Redo'), ), ( 'remove_format', 'edit-clear', _('Remove formatting'), ), ( 'copy', 'edit-copy', _('Copy'), ), ( 'paste', 'edit-paste', _('Paste'), ), ( 'paste_and_match_style', 'edit-paste', _('Paste and match style'), ), ( 'cut', 'edit-cut', _('Cut'), ), ( 'indent', 'format-indent-more', _('Increase indentation'), ), ( 'outdent', 'format-indent-less', _('Decrease indentation'), ), ( 'select_all', 'edit-select-all', _('Select all'), ), ('color', 'format-text-color', _('Foreground color')), ('background', 'format-fill-color', _('Background color')), ( 'insert_link', 'insert-link', _('Insert link or image'), ), ( 'insert_hr', 'format-text-hr', _('Insert separator'), ), ('clear', 'trash', _('Clear')), ): name, icon, text = rec[:3] checkable = len(rec) == 4 ac = QAction(QIcon(I(icon + '.png')), text, self) if checkable: ac.setCheckable(checkable) setattr(self, 'action_' + name, ac) ss = extra_shortcuts.get(name) if ss is not None: ac.setShortcut(QKeySequence(getattr(QKeySequence, ss))) ac.triggered.connect(getattr(self, 'do_' + name)) self.action_block_style = QAction(QIcon(I('format-text-heading.png')), _('Style text block'), self) self.action_block_style.setToolTip(_('Style the selected text block')) self.block_style_menu = QMenu(self) self.action_block_style.setMenu(self.block_style_menu) self.block_style_actions = [] h = _('Heading {0}') for text, name in ( (_('Normal'), 'p'), (h.format(1), 'h1'), (h.format(2), 'h2'), (h.format(3), 'h3'), (h.format(4), 'h4'), (h.format(5), 'h5'), (h.format(6), 'h6'), (_('Blockquote'), 'blockquote'), ): ac = QAction(text, self) self.block_style_menu.addAction(ac) ac.block_name = name ac.setCheckable(True) self.block_style_actions.append(ac) ac.triggered.connect(self.do_format_block) self.setHtml('') self.copyAvailable.connect(self.update_clipboard_actions) self.update_clipboard_actions(False) self.selectionChanged.connect(self.update_selection_based_actions) self.update_selection_based_actions() connect_lambda(self.undoAvailable, self, lambda self, yes: self.action_undo.setEnabled(yes)) connect_lambda(self.redoAvailable, self, lambda self, yes: self.action_redo.setEnabled(yes)) self.action_undo.setEnabled(False), self.action_redo.setEnabled(False) self.textChanged.connect(self.update_cursor_position_actions) self.cursorPositionChanged.connect(self.update_cursor_position_actions) self.textChanged.connect(self.data_changed) self.update_cursor_position_actions() def update_clipboard_actions(self, copy_available): self.action_copy.setEnabled(copy_available) self.action_cut.setEnabled(copy_available) def update_selection_based_actions(self): pass def update_cursor_position_actions(self): c = self.textCursor() ls = c.currentList() self.action_ordered_list.setChecked( ls is not None and ls.format().style() == QTextListFormat.ListDecimal) self.action_unordered_list.setChecked( ls is not None and ls.format().style() == QTextListFormat.ListDisc) tcf = c.charFormat() vert = tcf.verticalAlignment() self.action_superscript.setChecked( vert == QTextCharFormat.AlignSuperScript) self.action_subscript.setChecked( vert == QTextCharFormat.AlignSubScript) self.action_bold.setChecked(tcf.fontWeight() == QFont.Bold) self.action_italic.setChecked(tcf.fontItalic()) self.action_underline.setChecked(tcf.fontUnderline()) self.action_strikethrough.setChecked(tcf.fontStrikeOut()) bf = c.blockFormat() a = bf.alignment() self.action_align_left.setChecked(a == Qt.AlignLeft) self.action_align_right.setChecked(a == Qt.AlignRight) self.action_align_center.setChecked(a == Qt.AlignHCenter) self.action_align_justified.setChecked(a == Qt.AlignJustify) lvl = bf.headingLevel() name = 'p' if lvl == 0: if bf.leftMargin() == bf.rightMargin() and bf.leftMargin() > 0: name = 'blockquote' else: name = 'h{}'.format(lvl) for ac in self.block_style_actions: ac.setChecked(ac.block_name == name) def set_readonly(self, what): self.readonly = what def focus_self(self): self.setFocus(Qt.TabFocusReason) def do_clear(self, *args): c = self.textCursor() c.beginEditBlock() c.movePosition(QTextCursor.Start, QTextCursor.MoveAnchor) c.movePosition(QTextCursor.End, QTextCursor.KeepAnchor) c.removeSelectedText() c.endEditBlock() self.focus_self() clear_text = do_clear def do_bold(self): with self.editing_cursor() as c: fmt = QTextCharFormat() fmt.setFontWeight(QFont.Bold if c.charFormat().fontWeight( ) != QFont.Bold else QFont.Normal) c.mergeCharFormat(fmt) def do_italic(self): with self.editing_cursor() as c: fmt = QTextCharFormat() fmt.setFontItalic(not c.charFormat().fontItalic()) c.mergeCharFormat(fmt) def do_underline(self): with self.editing_cursor() as c: fmt = QTextCharFormat() fmt.setFontUnderline(not c.charFormat().fontUnderline()) c.mergeCharFormat(fmt) def do_strikethrough(self): with self.editing_cursor() as c: fmt = QTextCharFormat() fmt.setFontStrikeOut(not c.charFormat().fontStrikeOut()) c.mergeCharFormat(fmt) def do_vertical_align(self, which): with self.editing_cursor() as c: fmt = QTextCharFormat() fmt.setVerticalAlignment(which) c.mergeCharFormat(fmt) def do_superscript(self): self.do_vertical_align(QTextCharFormat.AlignSuperScript) def do_subscript(self): self.do_vertical_align(QTextCharFormat.AlignSubScript) def do_list(self, fmt): with self.editing_cursor() as c: ls = c.currentList() if ls is not None: lf = ls.format() if lf.style() == fmt: c.setBlockFormat(QTextBlockFormat()) else: lf.setStyle(fmt) ls.setFormat(lf) else: ls = c.createList(fmt) def do_ordered_list(self): self.do_list(QTextListFormat.ListDecimal) def do_unordered_list(self): self.do_list(QTextListFormat.ListDisc) def do_alignment(self, which): with self.editing_cursor() as c: fmt = QTextBlockFormat() fmt.setAlignment(which) c.setBlockFormat(fmt) def do_align_left(self): self.do_alignment(Qt.AlignLeft) def do_align_center(self): self.do_alignment(Qt.AlignHCenter) def do_align_right(self): self.do_alignment(Qt.AlignRight) def do_align_justified(self): self.do_alignment(Qt.AlignJustify) def do_undo(self): self.undo() self.focus_self() def do_redo(self): self.redo() self.focus_self() def do_remove_format(self): with self.editing_cursor() as c: c.setBlockFormat(QTextBlockFormat()) c.setCharFormat(QTextCharFormat()) def do_copy(self): self.copy() self.focus_self() def do_paste(self): self.paste() self.focus_self() def do_paste_and_match_style(self): text = QApplication.instance().clipboard().text() if text: self.setText(text) def do_cut(self): self.cut() self.focus_self() def indent_block(self, mult=1): with self.editing_cursor() as c: bf = c.blockFormat() bf.setTextIndent(bf.textIndent() + 2 * self.em_size * mult) c.setBlockFormat(bf) def do_indent(self): self.indent_block() def do_outdent(self): self.indent_block(-1) def do_select_all(self): with self.editing_cursor() as c: c.movePosition(QTextCursor.Start, QTextCursor.MoveAnchor) c.movePosition(QTextCursor.End, QTextCursor.KeepAnchor) def level_for_block_type(self, name): if name == 'blockquote': return 0 return {q: i for i, q in enumerate('p h1 h2 h3 h4 h5 h6'.split())}[name] def do_format_block(self): name = self.sender().block_name with self.editing_cursor() as c: bf = QTextBlockFormat() cf = QTextCharFormat() bcf = c.blockCharFormat() lvl = self.level_for_block_type(name) wt = QFont.Bold if lvl else None adjust = (0, 3, 2, 1, 0, -1, -1)[lvl] pos = None if not c.hasSelection(): pos = c.position() c.movePosition(QTextCursor.StartOfBlock, QTextCursor.MoveAnchor) c.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) # margin values are taken from qtexthtmlparser.cpp hmargin = 0 if name == 'blockquote': hmargin = 40 tmargin = bmargin = 12 if name == 'h1': tmargin, bmargin = 18, 12 elif name == 'h2': tmargin, bmargin = 16, 12 elif name == 'h3': tmargin, bmargin = 14, 12 elif name == 'h4': tmargin, bmargin = 12, 12 elif name == 'h5': tmargin, bmargin = 12, 4 bf.setLeftMargin(hmargin), bf.setRightMargin(hmargin) bf.setTopMargin(tmargin), bf.setBottomMargin(bmargin) bf.setHeadingLevel(lvl) if adjust: bcf.setProperty(QTextCharFormat.FontSizeAdjustment, adjust) cf.setProperty(QTextCharFormat.FontSizeAdjustment, adjust) if wt: bcf.setProperty(QTextCharFormat.FontWeight, wt) cf.setProperty(QTextCharFormat.FontWeight, wt) c.setBlockCharFormat(bcf) c.mergeCharFormat(cf) c.mergeBlockFormat(bf) if pos is not None: c.setPosition(pos) def do_color(self): col = QColorDialog.getColor(Qt.black, self, _('Choose foreground color'), QColorDialog.ShowAlphaChannel) if col.isValid(): fmt = QTextCharFormat() fmt.setForeground(QBrush(col)) with self.editing_cursor() as c: c.mergeCharFormat(fmt) def do_background(self): col = QColorDialog.getColor(Qt.white, self, _('Choose background color'), QColorDialog.ShowAlphaChannel) if col.isValid(): fmt = QTextCharFormat() fmt.setBackground(QBrush(col)) with self.editing_cursor() as c: c.mergeCharFormat(fmt) def do_insert_hr(self, *args): with self.editing_cursor() as c: c.movePosition(c.EndOfBlock, c.MoveAnchor) c.insertHtml('<hr>') def do_insert_link(self, *args): link, name, is_image = self.ask_link() if not link: return url = self.parse_link(link) if url.isValid(): url = unicode_type(url.toString(NO_URL_FORMATTING)) self.focus_self() with self.editing_cursor() as c: if is_image: c.insertImage(url) else: oldfmt = QTextCharFormat(c.charFormat()) fmt = QTextCharFormat() fmt.setAnchor(True) fmt.setAnchorHref(url) fmt.setForeground( QBrush(self.palette().color(QPalette.Link))) if name or not c.hasSelection(): c.mergeCharFormat(fmt) c.insertText(name or url) else: pos, anchor = c.position(), c.anchor() start, end = min(pos, anchor), max(pos, anchor) for i in range(start, end): cur = self.textCursor() cur.setPosition(i), cur.setPosition( i + 1, c.KeepAnchor) cur.mergeCharFormat(fmt) c.setPosition(c.position()) c.setCharFormat(oldfmt) else: error_dialog(self, _('Invalid URL'), _('The url %r is invalid') % link, show=True) def ask_link(self): class Ask(QDialog): def accept(self): if self.treat_as_image.isChecked(): url = self.url.text() if url.lower().split(':', 1)[0] in ('http', 'https'): error_dialog( self, _('Remote images not supported'), _('You must download the image to your computer, URLs pointing' ' to remote images are not supported.'), show=True) return QDialog.accept(self) d = Ask(self) d.setWindowTitle(_('Create link')) l = QFormLayout() l.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow) d.setLayout(l) d.url = QLineEdit(d) d.name = QLineEdit(d) d.treat_as_image = QCheckBox(d) d.setMinimumWidth(600) d.bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) d.br = b = QPushButton(_('&Browse')) b.setIcon(QIcon(I('document_open.png'))) def cf(): filetypes = [] if d.treat_as_image.isChecked(): filetypes = [(_('Images'), 'png jpeg jpg gif'.split())] files = choose_files(d, 'select link file', _('Choose file'), filetypes, select_only_single_file=True) if files: path = files[0] d.url.setText(path) if path and os.path.exists(path): with lopen(path, 'rb') as f: q = what(f) is_image = q in {'jpeg', 'png', 'gif'} d.treat_as_image.setChecked(is_image) b.clicked.connect(cf) d.la = la = QLabel( _('Enter a URL. If you check the "Treat the URL as an image" box ' 'then the URL will be added as an image reference instead of as ' 'a link. You can also choose to create a link to a file on ' 'your computer. ' 'Note that if you create a link to a file on your computer, it ' 'will stop working if the file is moved.')) la.setWordWrap(True) la.setStyleSheet('QLabel { margin-bottom: 1.5ex }') l.setWidget(0, l.SpanningRole, la) l.addRow(_('Enter &URL:'), d.url) l.addRow(_('Treat the URL as an &image'), d.treat_as_image) l.addRow(_('Enter &name (optional):'), d.name) l.addRow(_('Choose a file on your computer:'), d.br) l.addRow(d.bb) d.bb.accepted.connect(d.accept) d.bb.rejected.connect(d.reject) d.resize(d.sizeHint()) link, name, is_image = None, None, False if d.exec_() == d.Accepted: link, name = unicode_type(d.url.text()).strip(), unicode_type( d.name.text()).strip() is_image = d.treat_as_image.isChecked() return link, name, is_image def parse_link(self, link): link = link.strip() if link and os.path.exists(link): return QUrl.fromLocalFile(link) has_schema = re.match(r'^[a-zA-Z]+:', link) if has_schema is not None: url = QUrl(link, QUrl.TolerantMode) if url.isValid(): return url if os.path.exists(link): return QUrl.fromLocalFile(link) if has_schema is None: first, _, rest = link.partition('.') prefix = 'http' if first == 'ftp': prefix = 'ftp' url = QUrl(prefix + '://' + link, QUrl.TolerantMode) if url.isValid(): return url return QUrl(link, QUrl.TolerantMode) def sizeHint(self): return QSize(150, 150) @property def html(self): raw = original_html = self.toHtml() check = self.toPlainText().strip() raw = xml_to_unicode(raw, strip_encoding_pats=True, resolve_entities=True)[0] raw = self.comments_pat.sub('', raw) if not check and '<img' not in raw.lower(): return '' root = parse(raw, maybe_xhtml=False, sanitize_names=True) if root.xpath('//meta[@name="calibre-dont-sanitize"]'): # Bypass cleanup if special meta tag exists return original_html try: cleanup_qt_markup(root) except Exception: import traceback traceback.print_exc() elems = [] for body in root.xpath('//body'): if body.text: elems.append(body.text) elems += [ html.tostring(x, encoding='unicode') for x in body if x.tag not in ('script', 'style') ] if len(elems) > 1: ans = '<div>%s</div>' % (u''.join(elems)) else: ans = ''.join(elems) if not ans.startswith('<'): ans = '<p>%s</p>' % ans return xml_replace_entities(ans) @html.setter def html(self, val): self.setHtml(val) def set_base_url(self, qurl): self.base_url = qurl @pyqtSlot(int, 'QUrl', result='QVariant') def loadResource(self, rtype, qurl): if self.base_url: if qurl.isRelative(): qurl = self.base_url.resolved(qurl) if qurl.isLocalFile(): path = qurl.toLocalFile() try: with lopen(path, 'rb') as f: data = f.read() except EnvironmentError: if path.rpartition('.')[-1].lower() in { 'jpg', 'jpeg', 'gif', 'png', 'bmp', 'webp' }: return QByteArray( bytearray.fromhex( '89504e470d0a1a0a0000000d49484452' '000000010000000108060000001f15c4' '890000000a49444154789c6300010000' '0500010d0a2db40000000049454e44ae' '426082')) else: return QByteArray(data) def set_html(self, val, allow_undo=True): if not allow_undo or self.readonly: self.html = val return with self.editing_cursor() as c: c.movePosition(QTextCursor.Start, QTextCursor.MoveAnchor) c.movePosition(QTextCursor.End, QTextCursor.KeepAnchor) c.removeSelectedText() c.insertHtml(val) def text(self): return self.textCursor().selectedText() def setText(self, text): with self.editing_cursor() as c: c.insertText(text) def contextMenuEvent(self, ev): menu = self.createStandardContextMenu() for action in menu.actions(): parts = action.text().split('\t') if len(parts) == 2 and QKeySequence(QKeySequence.Paste).toString( QKeySequence.NativeText) in parts[-1]: menu.insertAction(action, self.action_paste_and_match_style) break else: menu.addAction(self.action_paste_and_match_style) st = self.text() m = QMenu(_('Fonts')) m.addAction(self.action_bold), m.addAction( self.action_italic), m.addAction(self.action_underline) menu.addMenu(m) if st and st.strip(): self.create_change_case_menu(menu) parent = self._parent() if hasattr(parent, 'toolbars_visible'): vis = parent.toolbars_visible menu.addAction( _('%s toolbars') % (_('Hide') if vis else _('Show')), parent.toggle_toolbars) menu.addSeparator() menu.addAction(_('Smarten punctuation'), parent.smarten_punctuation) menu.exec_(ev.globalPos())
def register_text_editor_actions(_reg, palette): def reg(*args, **kw): ac = _reg(*args) for s in kw.get('syntaxes', ('format', )): editor_toolbar_actions[s][args[3]] = ac return ac ac = reg('format-text-bold.png', _('&Bold'), ('format_text', 'bold'), 'format-text-bold', 'Ctrl+B', _('Make the selected text bold')) ac.setToolTip(_('<h3>Bold</h3>Make the selected text bold')) ac = reg('format-text-italic.png', _('&Italic'), ('format_text', 'italic'), 'format-text-italic', 'Ctrl+I', _('Make the selected text italic')) ac.setToolTip(_('<h3>Italic</h3>Make the selected text italic')) ac = reg('format-text-underline.png', _('&Underline'), ('format_text', 'underline'), 'format-text-underline', (), _('Underline the selected text')) ac.setToolTip(_('<h3>Underline</h3>Underline the selected text')) ac = reg('format-text-strikethrough.png', _('&Strikethrough'), ('format_text', 'strikethrough'), 'format-text-strikethrough', (), _('Draw a line through the selected text')) ac.setToolTip( _('<h3>Strikethrough</h3>Draw a line through the selected text')) ac = reg('format-text-superscript.png', _('&Superscript'), ('format_text', 'superscript'), 'format-text-superscript', (), _('Make the selected text a superscript')) ac.setToolTip( _('<h3>Superscript</h3>Set the selected text slightly smaller and above the normal line' )) ac = reg('format-text-subscript.png', _('&Subscript'), ('format_text', 'subscript'), 'format-text-subscript', (), _('Make the selected text a subscript')) ac.setToolTip( _('<h3>Subscript</h3>Set the selected text slightly smaller and below the normal line' )) ac = reg('format-text-color.png', _('&Color'), ('format_text', 'color'), 'format-text-color', (), _('Change text color')) ac.setToolTip(_('<h3>Color</h3>Change the color of the selected text')) ac = reg('format-fill-color.png', _('&Background color'), ('format_text', 'background-color'), 'format-text-background-color', (), _('Change background color of text')) ac.setToolTip( _('<h3>Background color</h3>Change the background color of the selected text' )) ac = reg('format-justify-left.png', _('Align &left'), ('format_text', 'justify_left'), 'format-text-justify-left', (), _('Align left')) ac.setToolTip(_('<h3>Align left</h3>Align the paragraph to the left')) ac = reg('format-justify-center.png', _('&Center'), ('format_text', 'justify_center'), 'format-text-justify-center', (), _('Center')) ac.setToolTip(_('<h3>Center</h3>Center the paragraph')) ac = reg('format-justify-right.png', _('Align &right'), ('format_text', 'justify_right'), 'format-text-justify-right', (), _('Align right')) ac.setToolTip(_('<h3>Align right</h3>Align the paragraph to the right')) ac = reg('format-justify-fill.png', _('&Justify'), ('format_text', 'justify_justify'), 'format-text-justify-fill', (), _('Justify')) ac.setToolTip( _('<h3>Justify</h3>Align the paragraph to both the left and right margins' )) ac = reg('sort.png', _('&Sort style rules'), ('sort_css', ), 'editor-sort-css', (), _('Sort the style rules'), syntaxes=('css', )) ac = reg('view-image.png', _('&Insert image'), ('insert_resource', 'image'), 'insert-image', (), _('Insert an image into the text'), syntaxes=('html', 'css')) ac.setToolTip(_('<h3>Insert image</h3>Insert an image into the text')) ac = reg('insert-link.png', _('Insert &hyperlink'), ('insert_hyperlink', ), 'insert-hyperlink', (), _('Insert hyperlink'), syntaxes=('html', )) ac.setToolTip( _('<h3>Insert hyperlink</h3>Insert a hyperlink into the text')) ac = reg(create_icon('/*', divider=1, fill=None), _('Smart &comment'), ('smart_comment', ), 'editor-smart-comment', ('Ctrl+`', ), _('Smart comment (toggle block comments)'), syntaxes=()) ac.setToolTip( _('<h3>Smart comment</h3>Comment or uncomment text<br><br>' 'If the cursor is inside an existing block comment, uncomment it, otherwise comment out the selected text.' )) for i, name in enumerate(('h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p')): text = ('&' + name) if name == 'p' else (name[0] + '&' + name[1]) desc = _('Convert the paragraph to <%s>') % name ac = reg(create_icon(name), text, ('rename_block_tag', name), 'rename-block-tag-' + name, 'Ctrl+%d' % (i + 1), desc, syntaxes=()) ac.setToolTip(desc) for transform, text in [('upper', _('&Upper case')), ('lower', _('&Lower case')), ('swap', _('&Swap case')), ('title', _('&Title case')), ('capitalize', _('&Capitalize'))]: desc = _('Change the case of the selected text: %s') % text ac = reg(None, text, ('change_case', transform), 'transform-case-' + transform, (), desc, syntaxes=()) ac.setToolTip(desc) ac = reg('code.png', _('Insert &tag'), ('insert_tag', ), 'insert-tag', ('Ctrl+<'), _('Insert tag'), syntaxes=('html', 'xml')) ac.setToolTip( _('<h3>Insert tag</h3>Insert a tag, if some text is selected the tag will be inserted around the selected text' )) ac = reg('trash.png', _('Remove &tag'), ('remove_tag', ), 'remove-tag', ('Ctrl+>'), _('Remove tag'), syntaxes=('html', 'xml')) ac.setToolTip(_('<h3>Remove tag</h3>Remove the currently highlighted tag')) editor_toolbar_actions['html']['fix-html-current'] = actions[ 'fix-html-current'] for s in ('xml', 'html', 'css'): editor_toolbar_actions[s]['pretty-current'] = actions['pretty-current'] editor_toolbar_actions['html']['change-paragraph'] = actions[ 'change-paragraph'] = QAction(QIcon(I('format-text-heading.png')), _('Change paragraph to heading'), ac.parent())
def setupUi(self, StudentsManagement): StudentsManagement.setObjectName("StudentsManagement") StudentsManagement.resize(1010, 767) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( StudentsManagement.sizePolicy().hasHeightForWidth()) StudentsManagement.setSizePolicy(sizePolicy) StudentsManagement.setMinimumSize(QtCore.QSize(0, 604)) StudentsManagement.setMaximumSize(QtCore.QSize(16777215, 860)) StudentsManagement.setStyleSheet( "background-color: rgb(255, 248, 238);") self.centralwidget = QtWidgets.QWidget(StudentsManagement) self.centralwidget.setObjectName("centralwidget") self.gridLayout_2 = QtWidgets.QGridLayout(self.centralwidget) self.gridLayout_2.setObjectName("gridLayout_2") self.gridLayout = QtWidgets.QGridLayout() self.gridLayout.setObjectName("gridLayout") spacerItem = QtWidgets.QSpacerItem(20, 18, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) self.gridLayout.addItem(spacerItem, 6, 0, 1, 2) self.verticalLayout = QtWidgets.QVBoxLayout() self.verticalLayout.setObjectName("verticalLayout") self.horizontalLayout_2 = QtWidgets.QHBoxLayout() self.horizontalLayout_2.setSizeConstraint( QtWidgets.QLayout.SetFixedSize) self.horizontalLayout_2.setContentsMargins(30, -1, 30, -1) self.horizontalLayout_2.setSpacing(0) self.horizontalLayout_2.setObjectName("horizontalLayout_2") spacerItem1 = QtWidgets.QSpacerItem(80, 20, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem1) self.label_st_list = QtWidgets.QLabel(self.centralwidget) font = QtGui.QFont() font.setFamily("Monotype Corsiva") font.setPointSize(22) font.setBold(False) font.setItalic(True) font.setUnderline(False) font.setWeight(50) self.label_st_list.setFont(font) self.label_st_list.setObjectName("label_st_list") self.horizontalLayout_2.addWidget(self.label_st_list) spacerItem2 = QtWidgets.QSpacerItem(100, 20, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem2) self.label_as_list = QtWidgets.QLabel(self.centralwidget) font = QtGui.QFont() font.setFamily("Monotype Corsiva") font.setPointSize(22) font.setBold(False) font.setItalic(True) font.setUnderline(False) font.setWeight(50) self.label_as_list.setFont(font) self.label_as_list.setObjectName("label_as_list") self.horizontalLayout_2.addWidget(self.label_as_list) spacerItem3 = QtWidgets.QSpacerItem(150, 20, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem3) self.label_gr_list = QtWidgets.QLabel(self.centralwidget) font = QtGui.QFont() font.setFamily("Monotype Corsiva") font.setPointSize(22) font.setBold(False) font.setItalic(True) font.setUnderline(False) font.setWeight(50) self.label_gr_list.setFont(font) self.label_gr_list.setObjectName("label_gr_list") self.horizontalLayout_2.addWidget(self.label_gr_list) self.verticalLayout.addLayout(self.horizontalLayout_2) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) self.horizontalLayout.setContentsMargins(30, -1, 30, -1) self.horizontalLayout.setSpacing(10) self.horizontalLayout.setObjectName("horizontalLayout") self.listWidget_Students = QtWidgets.QListWidget(self.centralwidget) self.listWidget_Students.setStyleSheet( "background-color: rgb(255, 255, 255);\n" "border: 2px solid black;\n" "font-family: Arial;\n" "font-size: 12px;") self.listWidget_Students.setObjectName("listWidget_Students") self.horizontalLayout.addWidget(self.listWidget_Students) self.listWidget_Assignments = QtWidgets.QListWidget(self.centralwidget) self.listWidget_Assignments.setStyleSheet( "background-color: rgb(255, 255, 255);\n" "border: 2px solid black;") self.listWidget_Assignments.setObjectName("listWidget_Assignments") self.horizontalLayout.addWidget(self.listWidget_Assignments) self.listWidget_Grade = QtWidgets.QListWidget(self.centralwidget) self.listWidget_Grade.setStyleSheet( "background-color: rgb(255, 255, 255);\n" "border: 2px solid black;") self.listWidget_Grade.setObjectName("listWidget_Grade") self.horizontalLayout.addWidget(self.listWidget_Grade) self.verticalLayout.addLayout(self.horizontalLayout) self.gridLayout.addLayout(self.verticalLayout, 0, 0, 1, 2) spacerItem4 = QtWidgets.QSpacerItem(20, 18, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) self.gridLayout.addItem(spacerItem4, 4, 0, 1, 2) self.horizontalLayout_3 = QtWidgets.QHBoxLayout() self.horizontalLayout_3.setContentsMargins(50, -1, 50, -1) self.horizontalLayout_3.setSpacing(50) self.horizontalLayout_3.setObjectName("horizontalLayout_3") self.splitter_3 = QtWidgets.QSplitter(self.centralwidget) self.splitter_3.setOrientation(QtCore.Qt.Horizontal) self.splitter_3.setObjectName("splitter_3") self.splitter = QtWidgets.QSplitter(self.splitter_3) self.splitter.setOrientation(QtCore.Qt.Vertical) self.splitter.setObjectName("splitter") self.pushButton_addStud = QtWidgets.QPushButton(self.splitter) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.pushButton_addStud.sizePolicy().hasHeightForWidth()) self.pushButton_addStud.setSizePolicy(sizePolicy) font = QtGui.QFont() font.setPointSize(-1) font.setUnderline(False) font.setStrikeOut(False) self.pushButton_addStud.setFont(font) self.pushButton_addStud.setStyleSheet("background-color: #ffb162;\n" "border: 1px solid #684a25;\n" "color: black;\n" "padding: 5px;\n" "text-align: center;\n" "text-decoration: none;\n" "display: inline-block;\n" "font-size: 16px;\n" "cursor: pointer;\n" "border-radius: 8px;") self.pushButton_addStud.setObjectName("pushButton_addStud") self.pushButton_UpdSt = QtWidgets.QPushButton(self.splitter) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.pushButton_UpdSt.sizePolicy().hasHeightForWidth()) self.pushButton_UpdSt.setSizePolicy(sizePolicy) font = QtGui.QFont() font.setPointSize(-1) font.setUnderline(False) font.setStrikeOut(False) self.pushButton_UpdSt.setFont(font) self.pushButton_UpdSt.setStyleSheet("background-color: #ffb162;\n" "border: 1px solid #684a25;\n" "color: black;\n" "padding: 5px;\n" "text-align: center;\n" "text-decoration: none;\n" "display: inline-block;\n" "font-size: 16px;\n" "cursor: pointer;\n" "border-radius: 8px;") self.pushButton_UpdSt.setObjectName("pushButton_UpdSt") self.splitter_2 = QtWidgets.QSplitter(self.splitter_3) self.splitter_2.setOrientation(QtCore.Qt.Vertical) self.splitter_2.setObjectName("splitter_2") self.pushButton_RmvSt = QtWidgets.QPushButton(self.splitter_2) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.pushButton_RmvSt.sizePolicy().hasHeightForWidth()) self.pushButton_RmvSt.setSizePolicy(sizePolicy) font = QtGui.QFont() font.setPointSize(-1) font.setUnderline(False) font.setStrikeOut(False) self.pushButton_RmvSt.setFont(font) self.pushButton_RmvSt.setStyleSheet("background-color: #ffb162;\n" "border: 1px solid #684a25;\n" "color: black;\n" "padding: 5px;\n" "text-align: center;\n" "text-decoration: none;\n" "display: inline-block;\n" "font-size: 16px;\n" "cursor: pointer;\n" "border-radius: 8px;") self.pushButton_RmvSt.setObjectName("pushButton_RmvSt") self.pushButton_listSt = QtWidgets.QPushButton(self.splitter_2) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.pushButton_listSt.sizePolicy().hasHeightForWidth()) self.pushButton_listSt.setSizePolicy(sizePolicy) font = QtGui.QFont() font.setPointSize(-1) font.setUnderline(False) font.setStrikeOut(False) self.pushButton_listSt.setFont(font) self.pushButton_listSt.setStyleSheet("background-color: #ffb162;\n" "border: 1px solid #684a25;\n" "color: black;\n" "padding: 5px;\n" "text-align: center;\n" "text-decoration: none;\n" "display: inline-block;\n" "font-size: 16px;\n" "cursor: pointer;\n" "border-radius: 8px;") self.pushButton_listSt.setObjectName("pushButton_listSt") self.horizontalLayout_3.addWidget(self.splitter_3) self.splitter_6 = QtWidgets.QSplitter(self.centralwidget) self.splitter_6.setOrientation(QtCore.Qt.Horizontal) self.splitter_6.setObjectName("splitter_6") self.splitter_4 = QtWidgets.QSplitter(self.splitter_6) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.splitter_4.sizePolicy().hasHeightForWidth()) self.splitter_4.setSizePolicy(sizePolicy) self.splitter_4.setOrientation(QtCore.Qt.Vertical) self.splitter_4.setObjectName("splitter_4") self.pushButton_addAsig = QtWidgets.QPushButton(self.splitter_4) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.pushButton_addAsig.sizePolicy().hasHeightForWidth()) self.pushButton_addAsig.setSizePolicy(sizePolicy) font = QtGui.QFont() font.setPointSize(-1) font.setUnderline(False) font.setStrikeOut(False) self.pushButton_addAsig.setFont(font) self.pushButton_addAsig.setStyleSheet("background-color: #ffb162;\n" "border: 1px solid #684a25;\n" "color: black;\n" "padding: 5px;\n" "text-align: center;\n" "text-decoration: none;\n" "display: inline-block;\n" "font-size: 16px;\n" "cursor: pointer;\n" "border-radius: 8px;") self.pushButton_addAsig.setObjectName("pushButton_addAsig") self.pushButton_UpdAs = QtWidgets.QPushButton(self.splitter_4) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.pushButton_UpdAs.sizePolicy().hasHeightForWidth()) self.pushButton_UpdAs.setSizePolicy(sizePolicy) font = QtGui.QFont() font.setPointSize(-1) font.setUnderline(False) font.setStrikeOut(False) self.pushButton_UpdAs.setFont(font) self.pushButton_UpdAs.setStyleSheet("background-color: #ffb162;\n" "border: 1px solid #684a25;\n" "color: black;\n" "padding: 5px;\n" "text-align: center;\n" "text-decoration: none;\n" "display: inline-block;\n" "font-size: 16px;\n" "cursor: pointer;\n" "border-radius: 8px;") self.pushButton_UpdAs.setObjectName("pushButton_UpdAs") self.splitter_5 = QtWidgets.QSplitter(self.splitter_6) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.splitter_5.sizePolicy().hasHeightForWidth()) self.splitter_5.setSizePolicy(sizePolicy) self.splitter_5.setOrientation(QtCore.Qt.Vertical) self.splitter_5.setObjectName("splitter_5") self.pushButton_RmvAs = QtWidgets.QPushButton(self.splitter_5) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.pushButton_RmvAs.sizePolicy().hasHeightForWidth()) self.pushButton_RmvAs.setSizePolicy(sizePolicy) font = QtGui.QFont() font.setPointSize(-1) font.setUnderline(False) font.setStrikeOut(False) self.pushButton_RmvAs.setFont(font) self.pushButton_RmvAs.setStyleSheet("background-color: #ffb162;\n" "border: 1px solid #684a25;\n" "color: black;\n" "padding: 5px;\n" "text-align: center;\n" "text-decoration: none;\n" "display: inline-block;\n" "font-size: 16px;\n" "cursor: pointer;\n" "border-radius: 8px;") self.pushButton_RmvAs.setObjectName("pushButton_RmvAs") self.pushButton_listAs = QtWidgets.QPushButton(self.splitter_5) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.pushButton_listAs.sizePolicy().hasHeightForWidth()) self.pushButton_listAs.setSizePolicy(sizePolicy) font = QtGui.QFont() font.setPointSize(-1) font.setUnderline(False) font.setStrikeOut(False) self.pushButton_listAs.setFont(font) self.pushButton_listAs.setStyleSheet("background-color: #ffb162;\n" "border: 1px solid #684a25;\n" "color: black;\n" "padding: 5px;\n" "text-align: center;\n" "text-decoration: none;\n" "display: inline-block;\n" "font-size: 16px;\n" "cursor: pointer;\n" "border-radius: 8px;") self.pushButton_listAs.setObjectName("pushButton_listAs") self.horizontalLayout_3.addWidget(self.splitter_6) self.splitter_9 = QtWidgets.QSplitter(self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.splitter_9.sizePolicy().hasHeightForWidth()) self.splitter_9.setSizePolicy(sizePolicy) self.splitter_9.setOrientation(QtCore.Qt.Horizontal) self.splitter_9.setObjectName("splitter_9") self.splitter_7 = QtWidgets.QSplitter(self.splitter_9) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.splitter_7.sizePolicy().hasHeightForWidth()) self.splitter_7.setSizePolicy(sizePolicy) self.splitter_7.setOrientation(QtCore.Qt.Vertical) self.splitter_7.setObjectName("splitter_7") self.pushButton_addAsigToSt = QtWidgets.QPushButton(self.splitter_7) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.pushButton_addAsigToSt.sizePolicy().hasHeightForWidth()) self.pushButton_addAsigToSt.setSizePolicy(sizePolicy) font = QtGui.QFont() font.setPointSize(-1) font.setUnderline(False) font.setStrikeOut(False) self.pushButton_addAsigToSt.setFont(font) self.pushButton_addAsigToSt.setContextMenuPolicy( QtCore.Qt.CustomContextMenu) self.pushButton_addAsigToSt.setLayoutDirection(QtCore.Qt.LeftToRight) self.pushButton_addAsigToSt.setAutoFillBackground(False) self.pushButton_addAsigToSt.setStyleSheet( "background-color: #ffb162;\n" "border: 1px solid #684a25;\n" "color: black;\n" "padding: 5px;\n" "text-align: center;\n" "text-decoration: none;\n" "display: inline-block;\n" "font-size: 16px;\n" "cursor: pointer;\n" "border-radius: 8px;") self.pushButton_addAsigToSt.setIconSize(QtCore.QSize(40, 40)) self.pushButton_addAsigToSt.setAutoDefault(False) self.pushButton_addAsigToSt.setObjectName("pushButton_addAsigToSt") self.pushButton_addAsigToGr = QtWidgets.QPushButton(self.splitter_7) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.pushButton_addAsigToGr.sizePolicy().hasHeightForWidth()) self.pushButton_addAsigToGr.setSizePolicy(sizePolicy) font = QtGui.QFont() font.setPointSize(-1) font.setUnderline(False) font.setStrikeOut(False) self.pushButton_addAsigToGr.setFont(font) self.pushButton_addAsigToGr.setStyleSheet( "background-color: #ffb162;\n" "border: 1px solid #684a25;\n" "color: black;\n" "padding: 5px;\n" "text-align: center;\n" "text-decoration: none;\n" "display: inline-block;\n" "font-size: 16px;\n" "cursor: pointer;\n" "border-radius: 8px;") self.pushButton_addAsigToGr.setObjectName("pushButton_addAsigToGr") self.splitter_8 = QtWidgets.QSplitter(self.splitter_9) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.splitter_8.sizePolicy().hasHeightForWidth()) self.splitter_8.setSizePolicy(sizePolicy) self.splitter_8.setOrientation(QtCore.Qt.Vertical) self.splitter_8.setObjectName("splitter_8") self.pushButton_SetGr = QtWidgets.QPushButton(self.splitter_8) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.pushButton_SetGr.sizePolicy().hasHeightForWidth()) self.pushButton_SetGr.setSizePolicy(sizePolicy) font = QtGui.QFont() font.setPointSize(-1) font.setUnderline(False) font.setStrikeOut(False) self.pushButton_SetGr.setFont(font) self.pushButton_SetGr.setStyleSheet("background-color: #ffb162;\n" "border: 1px solid #684a25;\n" "color: black;\n" "padding: 5px;\n" "text-align: center;\n" "text-decoration: none;\n" "display: inline-block;\n" "font-size: 16px;\n" "cursor: pointer;\n" "border-radius: 8px;") self.pushButton_SetGr.setObjectName("pushButton_SetGr") self.pushButton_listGr = QtWidgets.QPushButton(self.splitter_8) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.pushButton_listGr.sizePolicy().hasHeightForWidth()) self.pushButton_listGr.setSizePolicy(sizePolicy) font = QtGui.QFont() font.setPointSize(-1) font.setUnderline(False) font.setStrikeOut(False) self.pushButton_listGr.setFont(font) self.pushButton_listGr.setStyleSheet("background-color: #ffb162;\n" "border: 1px solid #684a25;\n" "color: black;\n" "padding: 5px;\n" "text-align: center;\n" "text-decoration: none;\n" "display: inline-block;\n" "font-size: 16px;\n" "cursor: pointer;\n" "border-radius: 8px;") self.pushButton_listGr.setObjectName("pushButton_listGr") self.horizontalLayout_3.addWidget(self.splitter_9) self.gridLayout.addLayout(self.horizontalLayout_3, 2, 0, 2, 2) self.horizontalLayout_4 = QtWidgets.QHBoxLayout() self.horizontalLayout_4.setContentsMargins(100, -1, 100, -1) self.horizontalLayout_4.setSpacing(250) self.horizontalLayout_4.setObjectName("horizontalLayout_4") self.splitter_11 = QtWidgets.QSplitter(self.centralwidget) self.splitter_11.setOrientation(QtCore.Qt.Horizontal) self.splitter_11.setObjectName("splitter_11") self.pushButton_statStGivAs = QtWidgets.QPushButton(self.splitter_11) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.pushButton_statStGivAs.sizePolicy().hasHeightForWidth()) self.pushButton_statStGivAs.setSizePolicy(sizePolicy) self.pushButton_statStGivAs.setStyleSheet( "background-color: #ffb162;\n" "border: 1px solid #684a25;\n" "color: black;\n" "padding: 5px;\n" "text-align: center;\n" "text-decoration: none;\n" "display: inline-block;\n" "font-size: 16px;\n" "cursor: pointer;\n" "border-radius: 8px;") self.pushButton_statStGivAs.setObjectName("pushButton_statStGivAs") self.pushButton_statLateSt = QtWidgets.QPushButton(self.splitter_11) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.pushButton_statLateSt.sizePolicy().hasHeightForWidth()) self.pushButton_statLateSt.setSizePolicy(sizePolicy) self.pushButton_statLateSt.setStyleSheet("background-color: #ffb162;\n" "border: 1px solid #684a25;\n" "color: black;\n" "padding: 5px;\n" "text-align: center;\n" "text-decoration: none;\n" "display: inline-block;\n" "font-size: 16px;\n" "cursor: pointer;\n" "border-radius: 8px;") self.pushButton_statLateSt.setObjectName("pushButton_statLateSt") self.horizontalLayout_4.addWidget(self.splitter_11) self.splitter_10 = QtWidgets.QSplitter(self.centralwidget) self.splitter_10.setOrientation(QtCore.Qt.Horizontal) self.splitter_10.setObjectName("splitter_10") self.pushButton_statBestSit = QtWidgets.QPushButton(self.splitter_10) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.pushButton_statBestSit.sizePolicy().hasHeightForWidth()) self.pushButton_statBestSit.setSizePolicy(sizePolicy) self.pushButton_statBestSit.setStyleSheet( "background-color: #ffb162;\n" "border: 1px solid #684a25;\n" "color: black;\n" "padding: 5px;\n" "text-align: center;\n" "text-decoration: none;\n" "display: inline-block;\n" "font-size: 16px;\n" "cursor: pointer;\n" "border-radius: 8px;") self.pushButton_statBestSit.setObjectName("pushButton_statBestSit") self.pushButton_statAllGradedAsig = QtWidgets.QPushButton( self.splitter_10) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.pushButton_statAllGradedAsig.sizePolicy().hasHeightForWidth()) self.pushButton_statAllGradedAsig.setSizePolicy(sizePolicy) self.pushButton_statAllGradedAsig.setStyleSheet( "background-color: #ffb162;\n" "border: 1px solid #684a25;\n" "color: black;\n" "padding: 5px;\n" "text-align: center;\n" "text-decoration: none;\n" "display: inline-block;\n" "font-size: 16px;\n" "cursor: pointer;\n" "border-radius: 8px;\n" "hover\n" "{\n" " border: 1px solid red;\n" "}") self.pushButton_statAllGradedAsig.setObjectName( "pushButton_statAllGradedAsig") self.horizontalLayout_4.addWidget(self.splitter_10) self.gridLayout.addLayout(self.horizontalLayout_4, 5, 0, 1, 2) spacerItem5 = QtWidgets.QSpacerItem(20, 10, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) self.gridLayout.addItem(spacerItem5, 1, 0, 1, 2) self.gridLayout_2.addLayout(self.gridLayout, 0, 0, 1, 1) StudentsManagement.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(StudentsManagement) self.menubar.setGeometry(QtCore.QRect(0, 0, 1010, 21)) self.menubar.setStyleSheet("background-color: rgb(230, 224, 215);") self.menubar.setObjectName("menubar") StudentsManagement.setMenuBar(self.menubar) self.undoAct = QAction("Undo") self.undoAct.setShortcut('Ctrl+Z') self.undoAct.triggered.connect(partial(self.doUndo)) self.menubar.addAction(self.undoAct) self.redoAct = QAction("Redo") self.redoAct.setShortcut('Ctrl+Y') self.redoAct.triggered.connect(partial(self.doRedo)) self.menubar.addAction(self.redoAct) self.retranslateUi(StudentsManagement) QtCore.QMetaObject.connectSlotsByName(StudentsManagement) self.events()
def __init__(self, pathtoebook=None, debug_javascript=False, open_at=None, start_in_fullscreen=False, continue_reading=False, listener=None): MainWindow.__init__(self, debug_javascript) self.view.magnification_changed.connect(self.magnification_changed) self.closed = False self.show_toc_on_open = False self.listener = listener if listener is not None: t = Thread(name='ConnListener', target=listen, args=(self,)) t.daemon = True t.start() self.msg_from_anotherinstance.connect(self.another_instance_wants_to_talk, type=Qt.QueuedConnection) self.current_book_has_toc = False self.iterator = None self.current_page = None self.pending_search = None self.pending_search_dir= None self.pending_anchor = None self.pending_reference = None self.pending_bookmark = None self.pending_restore = False self.pending_goto_page = None self.cursor_hidden = False self.existing_bookmarks= [] self.selected_text = None self.was_maximized = False self.page_position_on_footnote_toggle = [] self.read_settings() self.autosave_timer = t = QTimer(self) t.setInterval(self.AUTOSAVE_INTERVAL * 1000), t.setSingleShot(True) t.timeout.connect(self.autosave) self.pos.value_changed.connect(self.update_pos_label) self.pos.value_changed.connect(self.autosave_timer.start) self.pos.setMinimumWidth(150) self.setFocusPolicy(Qt.StrongFocus) self.view.set_manager(self) self.pi = ProgressIndicator(self) self.action_quit = QAction(_('&Quit'), self) self.addAction(self.action_quit) self.view_resized_timer = QTimer(self) self.view_resized_timer.timeout.connect(self.viewport_resize_finished) self.view_resized_timer.setSingleShot(True) self.resize_in_progress = False self.action_reload = QAction(_('&Reload book'), self) self.action_reload.triggered.connect(self.reload_book) self.action_quit.triggered.connect(self.quit) self.action_reference_mode.triggered[bool].connect(self.view.reference_mode) self.action_metadata.triggered[bool].connect(self.metadata.setVisible) self.action_table_of_contents.toggled[bool].connect(self.set_toc_visible) self.action_copy.triggered[bool].connect(self.copy) self.action_font_size_larger.triggered.connect(self.font_size_larger) self.action_font_size_smaller.triggered.connect(self.font_size_smaller) self.action_open_ebook.triggered[bool].connect(self.open_ebook) self.action_next_page.triggered.connect(self.view.next_page) self.action_previous_page.triggered.connect(self.view.previous_page) self.action_find_next.triggered.connect(self.find_next) self.action_find_previous.triggered.connect(self.find_previous) self.action_full_screen.triggered[bool].connect(self.toggle_fullscreen) self.action_back.triggered[bool].connect(self.back) self.action_forward.triggered[bool].connect(self.forward) self.action_preferences.triggered.connect(self.do_config) self.pos.editingFinished.connect(self.goto_page_num) self.vertical_scrollbar.valueChanged[int].connect(lambda x:self.goto_page(x/100.)) self.search.search.connect(self.find) self.search.focus_to_library.connect(lambda: self.view.setFocus(Qt.OtherFocusReason)) self.toc.pressed[QModelIndex].connect(self.toc_clicked) self.toc.searched.connect(partial(self.toc_clicked, force=True)) def toggle_toc(ev): try: key = self.view.shortcuts.get_match(ev) except AttributeError: pass if key == 'Table of Contents': ev.accept() self.action_table_of_contents.trigger() return True return False self.toc.handle_shortcuts = toggle_toc self.reference.goto.connect(self.goto) self.bookmarks.edited.connect(self.bookmarks_edited) self.bookmarks.activated.connect(self.goto_bookmark) self.bookmarks.create_requested.connect(self.bookmark) self.set_bookmarks([]) self.load_theme_menu() if pathtoebook is not None: f = functools.partial(self.load_ebook, pathtoebook, open_at=open_at) QTimer.singleShot(50, f) elif continue_reading: QTimer.singleShot(50, self.continue_reading) self.window_mode_changed = None self.toggle_toolbar_action = QAction(_('Show/hide controls'), self) self.toggle_toolbar_action.setCheckable(True) self.toggle_toolbar_action.triggered.connect(self.toggle_toolbars) self.toolbar_hidden = None self.addAction(self.toggle_toolbar_action) self.full_screen_label_anim = QPropertyAnimation( self.full_screen_label, b'size') self.clock_timer = QTimer(self) self.clock_timer.timeout.connect(self.update_clock) self.action_print.triggered.connect(self.print_book) self.clear_recent_history_action = QAction( _('Clear list of recently opened books'), self) self.clear_recent_history_action.triggered.connect(self.clear_recent_history) self.build_recent_menu() self.open_history_menu.triggered.connect(self.open_recent) for x in ('tool_bar', 'tool_bar2'): x = getattr(self, x) for action in x.actions(): # So that the keyboard shortcuts for these actions will # continue to function even when the toolbars are hidden self.addAction(action) for plugin in self.view.document.all_viewer_plugins: plugin.customize_ui(self) self.view.document.settings_changed.connect(self.settings_changed) self.restore_state() self.settings_changed() self.action_toggle_paged_mode.toggled[bool].connect(self.toggle_paged_mode) if (start_in_fullscreen or self.view.document.start_in_fullscreen): self.action_full_screen.trigger() self.hide_cursor_timer = t = QTimer(self) t.setSingleShot(True), t.setInterval(3000) t.timeout.connect(self.hide_cursor) t.start()
class Ui_StudentsManagement(QtWidgets.QMainWindow): def __init__(self, undoC, studC, assignC, gradeC, statistics): ''' To create a CliMenu you need to provide: undoController studentController assignmentController gradeController statisticsClass ''' self._undo = undoC self._stud = studC self._assign = assignC self._grade = gradeC self._stat = statistics def setupUi(self, StudentsManagement): StudentsManagement.setObjectName("StudentsManagement") StudentsManagement.resize(1010, 767) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( StudentsManagement.sizePolicy().hasHeightForWidth()) StudentsManagement.setSizePolicy(sizePolicy) StudentsManagement.setMinimumSize(QtCore.QSize(0, 604)) StudentsManagement.setMaximumSize(QtCore.QSize(16777215, 860)) StudentsManagement.setStyleSheet( "background-color: rgb(255, 248, 238);") self.centralwidget = QtWidgets.QWidget(StudentsManagement) self.centralwidget.setObjectName("centralwidget") self.gridLayout_2 = QtWidgets.QGridLayout(self.centralwidget) self.gridLayout_2.setObjectName("gridLayout_2") self.gridLayout = QtWidgets.QGridLayout() self.gridLayout.setObjectName("gridLayout") spacerItem = QtWidgets.QSpacerItem(20, 18, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) self.gridLayout.addItem(spacerItem, 6, 0, 1, 2) self.verticalLayout = QtWidgets.QVBoxLayout() self.verticalLayout.setObjectName("verticalLayout") self.horizontalLayout_2 = QtWidgets.QHBoxLayout() self.horizontalLayout_2.setSizeConstraint( QtWidgets.QLayout.SetFixedSize) self.horizontalLayout_2.setContentsMargins(30, -1, 30, -1) self.horizontalLayout_2.setSpacing(0) self.horizontalLayout_2.setObjectName("horizontalLayout_2") spacerItem1 = QtWidgets.QSpacerItem(80, 20, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem1) self.label_st_list = QtWidgets.QLabel(self.centralwidget) font = QtGui.QFont() font.setFamily("Monotype Corsiva") font.setPointSize(22) font.setBold(False) font.setItalic(True) font.setUnderline(False) font.setWeight(50) self.label_st_list.setFont(font) self.label_st_list.setObjectName("label_st_list") self.horizontalLayout_2.addWidget(self.label_st_list) spacerItem2 = QtWidgets.QSpacerItem(100, 20, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem2) self.label_as_list = QtWidgets.QLabel(self.centralwidget) font = QtGui.QFont() font.setFamily("Monotype Corsiva") font.setPointSize(22) font.setBold(False) font.setItalic(True) font.setUnderline(False) font.setWeight(50) self.label_as_list.setFont(font) self.label_as_list.setObjectName("label_as_list") self.horizontalLayout_2.addWidget(self.label_as_list) spacerItem3 = QtWidgets.QSpacerItem(150, 20, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem3) self.label_gr_list = QtWidgets.QLabel(self.centralwidget) font = QtGui.QFont() font.setFamily("Monotype Corsiva") font.setPointSize(22) font.setBold(False) font.setItalic(True) font.setUnderline(False) font.setWeight(50) self.label_gr_list.setFont(font) self.label_gr_list.setObjectName("label_gr_list") self.horizontalLayout_2.addWidget(self.label_gr_list) self.verticalLayout.addLayout(self.horizontalLayout_2) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) self.horizontalLayout.setContentsMargins(30, -1, 30, -1) self.horizontalLayout.setSpacing(10) self.horizontalLayout.setObjectName("horizontalLayout") self.listWidget_Students = QtWidgets.QListWidget(self.centralwidget) self.listWidget_Students.setStyleSheet( "background-color: rgb(255, 255, 255);\n" "border: 2px solid black;\n" "font-family: Arial;\n" "font-size: 12px;") self.listWidget_Students.setObjectName("listWidget_Students") self.horizontalLayout.addWidget(self.listWidget_Students) self.listWidget_Assignments = QtWidgets.QListWidget(self.centralwidget) self.listWidget_Assignments.setStyleSheet( "background-color: rgb(255, 255, 255);\n" "border: 2px solid black;") self.listWidget_Assignments.setObjectName("listWidget_Assignments") self.horizontalLayout.addWidget(self.listWidget_Assignments) self.listWidget_Grade = QtWidgets.QListWidget(self.centralwidget) self.listWidget_Grade.setStyleSheet( "background-color: rgb(255, 255, 255);\n" "border: 2px solid black;") self.listWidget_Grade.setObjectName("listWidget_Grade") self.horizontalLayout.addWidget(self.listWidget_Grade) self.verticalLayout.addLayout(self.horizontalLayout) self.gridLayout.addLayout(self.verticalLayout, 0, 0, 1, 2) spacerItem4 = QtWidgets.QSpacerItem(20, 18, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) self.gridLayout.addItem(spacerItem4, 4, 0, 1, 2) self.horizontalLayout_3 = QtWidgets.QHBoxLayout() self.horizontalLayout_3.setContentsMargins(50, -1, 50, -1) self.horizontalLayout_3.setSpacing(50) self.horizontalLayout_3.setObjectName("horizontalLayout_3") self.splitter_3 = QtWidgets.QSplitter(self.centralwidget) self.splitter_3.setOrientation(QtCore.Qt.Horizontal) self.splitter_3.setObjectName("splitter_3") self.splitter = QtWidgets.QSplitter(self.splitter_3) self.splitter.setOrientation(QtCore.Qt.Vertical) self.splitter.setObjectName("splitter") self.pushButton_addStud = QtWidgets.QPushButton(self.splitter) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.pushButton_addStud.sizePolicy().hasHeightForWidth()) self.pushButton_addStud.setSizePolicy(sizePolicy) font = QtGui.QFont() font.setPointSize(-1) font.setUnderline(False) font.setStrikeOut(False) self.pushButton_addStud.setFont(font) self.pushButton_addStud.setStyleSheet("background-color: #ffb162;\n" "border: 1px solid #684a25;\n" "color: black;\n" "padding: 5px;\n" "text-align: center;\n" "text-decoration: none;\n" "display: inline-block;\n" "font-size: 16px;\n" "cursor: pointer;\n" "border-radius: 8px;") self.pushButton_addStud.setObjectName("pushButton_addStud") self.pushButton_UpdSt = QtWidgets.QPushButton(self.splitter) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.pushButton_UpdSt.sizePolicy().hasHeightForWidth()) self.pushButton_UpdSt.setSizePolicy(sizePolicy) font = QtGui.QFont() font.setPointSize(-1) font.setUnderline(False) font.setStrikeOut(False) self.pushButton_UpdSt.setFont(font) self.pushButton_UpdSt.setStyleSheet("background-color: #ffb162;\n" "border: 1px solid #684a25;\n" "color: black;\n" "padding: 5px;\n" "text-align: center;\n" "text-decoration: none;\n" "display: inline-block;\n" "font-size: 16px;\n" "cursor: pointer;\n" "border-radius: 8px;") self.pushButton_UpdSt.setObjectName("pushButton_UpdSt") self.splitter_2 = QtWidgets.QSplitter(self.splitter_3) self.splitter_2.setOrientation(QtCore.Qt.Vertical) self.splitter_2.setObjectName("splitter_2") self.pushButton_RmvSt = QtWidgets.QPushButton(self.splitter_2) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.pushButton_RmvSt.sizePolicy().hasHeightForWidth()) self.pushButton_RmvSt.setSizePolicy(sizePolicy) font = QtGui.QFont() font.setPointSize(-1) font.setUnderline(False) font.setStrikeOut(False) self.pushButton_RmvSt.setFont(font) self.pushButton_RmvSt.setStyleSheet("background-color: #ffb162;\n" "border: 1px solid #684a25;\n" "color: black;\n" "padding: 5px;\n" "text-align: center;\n" "text-decoration: none;\n" "display: inline-block;\n" "font-size: 16px;\n" "cursor: pointer;\n" "border-radius: 8px;") self.pushButton_RmvSt.setObjectName("pushButton_RmvSt") self.pushButton_listSt = QtWidgets.QPushButton(self.splitter_2) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.pushButton_listSt.sizePolicy().hasHeightForWidth()) self.pushButton_listSt.setSizePolicy(sizePolicy) font = QtGui.QFont() font.setPointSize(-1) font.setUnderline(False) font.setStrikeOut(False) self.pushButton_listSt.setFont(font) self.pushButton_listSt.setStyleSheet("background-color: #ffb162;\n" "border: 1px solid #684a25;\n" "color: black;\n" "padding: 5px;\n" "text-align: center;\n" "text-decoration: none;\n" "display: inline-block;\n" "font-size: 16px;\n" "cursor: pointer;\n" "border-radius: 8px;") self.pushButton_listSt.setObjectName("pushButton_listSt") self.horizontalLayout_3.addWidget(self.splitter_3) self.splitter_6 = QtWidgets.QSplitter(self.centralwidget) self.splitter_6.setOrientation(QtCore.Qt.Horizontal) self.splitter_6.setObjectName("splitter_6") self.splitter_4 = QtWidgets.QSplitter(self.splitter_6) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.splitter_4.sizePolicy().hasHeightForWidth()) self.splitter_4.setSizePolicy(sizePolicy) self.splitter_4.setOrientation(QtCore.Qt.Vertical) self.splitter_4.setObjectName("splitter_4") self.pushButton_addAsig = QtWidgets.QPushButton(self.splitter_4) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.pushButton_addAsig.sizePolicy().hasHeightForWidth()) self.pushButton_addAsig.setSizePolicy(sizePolicy) font = QtGui.QFont() font.setPointSize(-1) font.setUnderline(False) font.setStrikeOut(False) self.pushButton_addAsig.setFont(font) self.pushButton_addAsig.setStyleSheet("background-color: #ffb162;\n" "border: 1px solid #684a25;\n" "color: black;\n" "padding: 5px;\n" "text-align: center;\n" "text-decoration: none;\n" "display: inline-block;\n" "font-size: 16px;\n" "cursor: pointer;\n" "border-radius: 8px;") self.pushButton_addAsig.setObjectName("pushButton_addAsig") self.pushButton_UpdAs = QtWidgets.QPushButton(self.splitter_4) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.pushButton_UpdAs.sizePolicy().hasHeightForWidth()) self.pushButton_UpdAs.setSizePolicy(sizePolicy) font = QtGui.QFont() font.setPointSize(-1) font.setUnderline(False) font.setStrikeOut(False) self.pushButton_UpdAs.setFont(font) self.pushButton_UpdAs.setStyleSheet("background-color: #ffb162;\n" "border: 1px solid #684a25;\n" "color: black;\n" "padding: 5px;\n" "text-align: center;\n" "text-decoration: none;\n" "display: inline-block;\n" "font-size: 16px;\n" "cursor: pointer;\n" "border-radius: 8px;") self.pushButton_UpdAs.setObjectName("pushButton_UpdAs") self.splitter_5 = QtWidgets.QSplitter(self.splitter_6) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.splitter_5.sizePolicy().hasHeightForWidth()) self.splitter_5.setSizePolicy(sizePolicy) self.splitter_5.setOrientation(QtCore.Qt.Vertical) self.splitter_5.setObjectName("splitter_5") self.pushButton_RmvAs = QtWidgets.QPushButton(self.splitter_5) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.pushButton_RmvAs.sizePolicy().hasHeightForWidth()) self.pushButton_RmvAs.setSizePolicy(sizePolicy) font = QtGui.QFont() font.setPointSize(-1) font.setUnderline(False) font.setStrikeOut(False) self.pushButton_RmvAs.setFont(font) self.pushButton_RmvAs.setStyleSheet("background-color: #ffb162;\n" "border: 1px solid #684a25;\n" "color: black;\n" "padding: 5px;\n" "text-align: center;\n" "text-decoration: none;\n" "display: inline-block;\n" "font-size: 16px;\n" "cursor: pointer;\n" "border-radius: 8px;") self.pushButton_RmvAs.setObjectName("pushButton_RmvAs") self.pushButton_listAs = QtWidgets.QPushButton(self.splitter_5) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.pushButton_listAs.sizePolicy().hasHeightForWidth()) self.pushButton_listAs.setSizePolicy(sizePolicy) font = QtGui.QFont() font.setPointSize(-1) font.setUnderline(False) font.setStrikeOut(False) self.pushButton_listAs.setFont(font) self.pushButton_listAs.setStyleSheet("background-color: #ffb162;\n" "border: 1px solid #684a25;\n" "color: black;\n" "padding: 5px;\n" "text-align: center;\n" "text-decoration: none;\n" "display: inline-block;\n" "font-size: 16px;\n" "cursor: pointer;\n" "border-radius: 8px;") self.pushButton_listAs.setObjectName("pushButton_listAs") self.horizontalLayout_3.addWidget(self.splitter_6) self.splitter_9 = QtWidgets.QSplitter(self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.splitter_9.sizePolicy().hasHeightForWidth()) self.splitter_9.setSizePolicy(sizePolicy) self.splitter_9.setOrientation(QtCore.Qt.Horizontal) self.splitter_9.setObjectName("splitter_9") self.splitter_7 = QtWidgets.QSplitter(self.splitter_9) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.splitter_7.sizePolicy().hasHeightForWidth()) self.splitter_7.setSizePolicy(sizePolicy) self.splitter_7.setOrientation(QtCore.Qt.Vertical) self.splitter_7.setObjectName("splitter_7") self.pushButton_addAsigToSt = QtWidgets.QPushButton(self.splitter_7) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.pushButton_addAsigToSt.sizePolicy().hasHeightForWidth()) self.pushButton_addAsigToSt.setSizePolicy(sizePolicy) font = QtGui.QFont() font.setPointSize(-1) font.setUnderline(False) font.setStrikeOut(False) self.pushButton_addAsigToSt.setFont(font) self.pushButton_addAsigToSt.setContextMenuPolicy( QtCore.Qt.CustomContextMenu) self.pushButton_addAsigToSt.setLayoutDirection(QtCore.Qt.LeftToRight) self.pushButton_addAsigToSt.setAutoFillBackground(False) self.pushButton_addAsigToSt.setStyleSheet( "background-color: #ffb162;\n" "border: 1px solid #684a25;\n" "color: black;\n" "padding: 5px;\n" "text-align: center;\n" "text-decoration: none;\n" "display: inline-block;\n" "font-size: 16px;\n" "cursor: pointer;\n" "border-radius: 8px;") self.pushButton_addAsigToSt.setIconSize(QtCore.QSize(40, 40)) self.pushButton_addAsigToSt.setAutoDefault(False) self.pushButton_addAsigToSt.setObjectName("pushButton_addAsigToSt") self.pushButton_addAsigToGr = QtWidgets.QPushButton(self.splitter_7) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.pushButton_addAsigToGr.sizePolicy().hasHeightForWidth()) self.pushButton_addAsigToGr.setSizePolicy(sizePolicy) font = QtGui.QFont() font.setPointSize(-1) font.setUnderline(False) font.setStrikeOut(False) self.pushButton_addAsigToGr.setFont(font) self.pushButton_addAsigToGr.setStyleSheet( "background-color: #ffb162;\n" "border: 1px solid #684a25;\n" "color: black;\n" "padding: 5px;\n" "text-align: center;\n" "text-decoration: none;\n" "display: inline-block;\n" "font-size: 16px;\n" "cursor: pointer;\n" "border-radius: 8px;") self.pushButton_addAsigToGr.setObjectName("pushButton_addAsigToGr") self.splitter_8 = QtWidgets.QSplitter(self.splitter_9) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.splitter_8.sizePolicy().hasHeightForWidth()) self.splitter_8.setSizePolicy(sizePolicy) self.splitter_8.setOrientation(QtCore.Qt.Vertical) self.splitter_8.setObjectName("splitter_8") self.pushButton_SetGr = QtWidgets.QPushButton(self.splitter_8) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.pushButton_SetGr.sizePolicy().hasHeightForWidth()) self.pushButton_SetGr.setSizePolicy(sizePolicy) font = QtGui.QFont() font.setPointSize(-1) font.setUnderline(False) font.setStrikeOut(False) self.pushButton_SetGr.setFont(font) self.pushButton_SetGr.setStyleSheet("background-color: #ffb162;\n" "border: 1px solid #684a25;\n" "color: black;\n" "padding: 5px;\n" "text-align: center;\n" "text-decoration: none;\n" "display: inline-block;\n" "font-size: 16px;\n" "cursor: pointer;\n" "border-radius: 8px;") self.pushButton_SetGr.setObjectName("pushButton_SetGr") self.pushButton_listGr = QtWidgets.QPushButton(self.splitter_8) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.pushButton_listGr.sizePolicy().hasHeightForWidth()) self.pushButton_listGr.setSizePolicy(sizePolicy) font = QtGui.QFont() font.setPointSize(-1) font.setUnderline(False) font.setStrikeOut(False) self.pushButton_listGr.setFont(font) self.pushButton_listGr.setStyleSheet("background-color: #ffb162;\n" "border: 1px solid #684a25;\n" "color: black;\n" "padding: 5px;\n" "text-align: center;\n" "text-decoration: none;\n" "display: inline-block;\n" "font-size: 16px;\n" "cursor: pointer;\n" "border-radius: 8px;") self.pushButton_listGr.setObjectName("pushButton_listGr") self.horizontalLayout_3.addWidget(self.splitter_9) self.gridLayout.addLayout(self.horizontalLayout_3, 2, 0, 2, 2) self.horizontalLayout_4 = QtWidgets.QHBoxLayout() self.horizontalLayout_4.setContentsMargins(100, -1, 100, -1) self.horizontalLayout_4.setSpacing(250) self.horizontalLayout_4.setObjectName("horizontalLayout_4") self.splitter_11 = QtWidgets.QSplitter(self.centralwidget) self.splitter_11.setOrientation(QtCore.Qt.Horizontal) self.splitter_11.setObjectName("splitter_11") self.pushButton_statStGivAs = QtWidgets.QPushButton(self.splitter_11) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.pushButton_statStGivAs.sizePolicy().hasHeightForWidth()) self.pushButton_statStGivAs.setSizePolicy(sizePolicy) self.pushButton_statStGivAs.setStyleSheet( "background-color: #ffb162;\n" "border: 1px solid #684a25;\n" "color: black;\n" "padding: 5px;\n" "text-align: center;\n" "text-decoration: none;\n" "display: inline-block;\n" "font-size: 16px;\n" "cursor: pointer;\n" "border-radius: 8px;") self.pushButton_statStGivAs.setObjectName("pushButton_statStGivAs") self.pushButton_statLateSt = QtWidgets.QPushButton(self.splitter_11) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.pushButton_statLateSt.sizePolicy().hasHeightForWidth()) self.pushButton_statLateSt.setSizePolicy(sizePolicy) self.pushButton_statLateSt.setStyleSheet("background-color: #ffb162;\n" "border: 1px solid #684a25;\n" "color: black;\n" "padding: 5px;\n" "text-align: center;\n" "text-decoration: none;\n" "display: inline-block;\n" "font-size: 16px;\n" "cursor: pointer;\n" "border-radius: 8px;") self.pushButton_statLateSt.setObjectName("pushButton_statLateSt") self.horizontalLayout_4.addWidget(self.splitter_11) self.splitter_10 = QtWidgets.QSplitter(self.centralwidget) self.splitter_10.setOrientation(QtCore.Qt.Horizontal) self.splitter_10.setObjectName("splitter_10") self.pushButton_statBestSit = QtWidgets.QPushButton(self.splitter_10) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.pushButton_statBestSit.sizePolicy().hasHeightForWidth()) self.pushButton_statBestSit.setSizePolicy(sizePolicy) self.pushButton_statBestSit.setStyleSheet( "background-color: #ffb162;\n" "border: 1px solid #684a25;\n" "color: black;\n" "padding: 5px;\n" "text-align: center;\n" "text-decoration: none;\n" "display: inline-block;\n" "font-size: 16px;\n" "cursor: pointer;\n" "border-radius: 8px;") self.pushButton_statBestSit.setObjectName("pushButton_statBestSit") self.pushButton_statAllGradedAsig = QtWidgets.QPushButton( self.splitter_10) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.pushButton_statAllGradedAsig.sizePolicy().hasHeightForWidth()) self.pushButton_statAllGradedAsig.setSizePolicy(sizePolicy) self.pushButton_statAllGradedAsig.setStyleSheet( "background-color: #ffb162;\n" "border: 1px solid #684a25;\n" "color: black;\n" "padding: 5px;\n" "text-align: center;\n" "text-decoration: none;\n" "display: inline-block;\n" "font-size: 16px;\n" "cursor: pointer;\n" "border-radius: 8px;\n" "hover\n" "{\n" " border: 1px solid red;\n" "}") self.pushButton_statAllGradedAsig.setObjectName( "pushButton_statAllGradedAsig") self.horizontalLayout_4.addWidget(self.splitter_10) self.gridLayout.addLayout(self.horizontalLayout_4, 5, 0, 1, 2) spacerItem5 = QtWidgets.QSpacerItem(20, 10, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) self.gridLayout.addItem(spacerItem5, 1, 0, 1, 2) self.gridLayout_2.addLayout(self.gridLayout, 0, 0, 1, 1) StudentsManagement.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(StudentsManagement) self.menubar.setGeometry(QtCore.QRect(0, 0, 1010, 21)) self.menubar.setStyleSheet("background-color: rgb(230, 224, 215);") self.menubar.setObjectName("menubar") StudentsManagement.setMenuBar(self.menubar) self.undoAct = QAction("Undo") self.undoAct.setShortcut('Ctrl+Z') self.undoAct.triggered.connect(partial(self.doUndo)) self.menubar.addAction(self.undoAct) self.redoAct = QAction("Redo") self.redoAct.setShortcut('Ctrl+Y') self.redoAct.triggered.connect(partial(self.doRedo)) self.menubar.addAction(self.redoAct) self.retranslateUi(StudentsManagement) QtCore.QMetaObject.connectSlotsByName(StudentsManagement) self.events() def retranslateUi(self, StudentsManagement): _translate = QtCore.QCoreApplication.translate StudentsManagement.setWindowTitle( _translate("StudentsManagement", "MainWindow")) self.label_st_list.setText( _translate("StudentsManagement", "Students list")) self.label_as_list.setText( _translate("StudentsManagement", "Assignments list")) self.label_gr_list.setText( _translate("StudentsManagement", "Grades list")) self.pushButton_addStud.setText( _translate("StudentsManagement", "Add Student")) self.pushButton_UpdSt.setText( _translate("StudentsManagement", "Update Student")) self.pushButton_RmvSt.setText( _translate("StudentsManagement", "Remove Student")) self.pushButton_listSt.setText( _translate("StudentsManagement", "List Students")) self.pushButton_addAsig.setText( _translate("StudentsManagement", "Add Assignment")) self.pushButton_UpdAs.setText( _translate("StudentsManagement", "Update Assignment")) self.pushButton_RmvAs.setText( _translate("StudentsManagement", "Remove Assignment")) self.pushButton_listAs.setText( _translate("StudentsManagement", "List Assignments")) self.pushButton_addAsigToSt.setText( _translate("StudentsManagement", "Add Assignment\n" "to Student")) self.pushButton_addAsigToGr.setText( _translate("StudentsManagement", "Add Assignment\n" "to Grop")) self.pushButton_SetGr.setText( _translate("StudentsManagement", "Set Grade")) self.pushButton_listGr.setText( _translate("StudentsManagement", "List Grades")) self.pushButton_statStGivAs.setText( _translate("StudentsManagement", "Students who\n" "received a given\n" " assignment")) self.pushButton_statLateSt.setText( _translate("StudentsManagement", "Students who\n" "are late with\n" "assignments")) self.pushButton_statBestSit.setText( _translate("StudentsManagement", "Students with\n" "best school\n" "situation")) self.pushButton_statAllGradedAsig.setText( _translate("StudentsManagement", "All assignments\n" "graded")) def events(self): self.pushButton_listSt.clicked.connect( partial(self.listObj, self._stud, self.listWidget_Students)) self.pushButton_listGr.clicked.connect( partial(self.listObj, self._grade, self.listWidget_Grade)) self.pushButton_listAs.clicked.connect( partial(self.listObj, self._assign, self.listWidget_Assignments)) self.pushButton_addStud.clicked.connect( partial(self.openWindow, Ui_w_AddSt, self._grade)) self.pushButton_addAsig.clicked.connect( partial(self.openWindow, Ui_w_AddAs, self._grade)) self.pushButton_RmvSt.clicked.connect( partial(self.rmvSt, self.listWidget_Students.currentItem, self._grade.removeStud)) self.pushButton_RmvAs.clicked.connect( partial(self.rmvAs, self.listWidget_Assignments.currentItem, self._grade.removeAssign)) self.pushButton_UpdSt.clicked.connect(partial(self.openUpdateSt)) self.pushButton_UpdAs.clicked.connect(partial(self.openUpdateAs)) self.pushButton_SetGr.clicked.connect( partial(self.openWindow, Ui_w_SetGrade, self._grade)) self.pushButton_addAsigToGr.clicked.connect( partial(self.openWindow, Ui_w_SetAsigToGr, self._grade)) self.pushButton_addAsigToSt.clicked.connect( partial(self.openWindow, Ui_w_SetAsigToSt, self._grade)) self.pushButton_statAllGradedAsig.clicked.connect( partial(self.statAllGradedAsig)) self.pushButton_statBestSit.clicked.connect(partial(self.statBestSit)) self.pushButton_statLateSt.clicked.connect(partial(self.statLateSt)) self.pushButton_statStGivAs.clicked.connect(partial(self.statStGivAs)) def listObj(self, obj, listW): listW.clear() for ob in obj.getAll(): listW.addItem(str(ob)) def rmv(self, selected, rmvFunc): selected = selected() if type(selected) != QtWidgets.QListWidgetItem: raise Exception("Plese select a student from the list!") strg = selected.text().strip() ind = "" i = 0 while (not strg[i].isdigit()): i += 1 while (strg[i].isdigit()): ind += strg[i] i += 1 rmvFunc(int(ind)) def rmvSt(self, selected, rmvFunc): try: self.rmv(selected, rmvFunc) self.createDialog("Student was removed!", "Operation succeded!", QtWidgets.QMessageBox.Information) self.refreshLists() except Exception as err: self.createDialog(str(err), "Error!", QtWidgets.QMessageBox.Critical) def rmvAs(self, selected, rmvFunc): try: self.rmv(selected, rmvFunc) self.createDialog("Assignment was removed!", "Operation succeded!", QtWidgets.QMessageBox.Information) self.refreshLists() except Exception as err: self.createDialog(str(err), "Error!", QtWidgets.QMessageBox.Critical) def openWindow(self, window, ctr): self.window = QtWidgets.QDialog() self.ui = window(ctr) self.ui.setupUi(self.window) self.window.show() def openUpdateSt(self): self.window = QtWidgets.QDialog() self.ui = Ui_w_AddSt(self._stud) self.ui.setupUi(self.window) self.ui.changeWindowName(self.window, "UpdateStudent") self.ui.changeAddBut("Update") self.window.show() def openUpdateAs(self): self.window = QtWidgets.QDialog() self.ui = Ui_w_AddAs(self._assign) self.ui.setupUi(self.window) self.ui.changeWindowName(self.window, "UpdateAssignment") self.ui.changeAddBut("Update") self.window.show() def createDialog(self, err, title, iccon): qDial = QtWidgets.QMessageBox() qDial.setIcon(iccon) qDial.setText(err) qDial.setWindowTitle(title) qDial.exec_() def doUndo(self): try: self._undo.undo() self.refreshLists() except Exception as err: self.createDialog(str(err), "Warning!", QtWidgets.QMessageBox.Information) def doRedo(self): try: self._undo.redo() self.refreshLists() except Exception as err: self.createDialog(str(err), "Warning!", QtWidgets.QMessageBox.Information) def statAllGradedAsig(self): try: x = self._stat.getAssigByGrade() if len(x) > 0: strg = "All students who received a given assignment, ordered alphabetically or\nby average grade for that assignment\n" for i in x: strg += str(i) + "\n" x = ScrollMessageBox(QtWidgets.QMessageBox.Information, "Students which received an assignment!", strg) else: raise Exception("No student satisffies this condition!") except Exception as err: self.createDialog(str(err), "Warning!", QtWidgets.QMessageBox.Information) def statBestSit(self): try: x = self._stat.getStudBestGrSchool() if len(x) > 0: strg = "Students with the best school situation, sorted in descending order of the average grade received for all assignments\n" for i in x: strg += str(i) + "\n" x = ScrollMessageBox(QtWidgets.QMessageBox.Information, "Students with bests results!", strg) else: raise Exception("No student satisffies this condition!") except Exception as err: self.createDialog(str(err), "Warning!", QtWidgets.QMessageBox.Information) def statLateSt(self): try: x = self._stat.getLateStuds() if len(x) > 0: strg = "All students who are late in handing in at least one assignment.\nThese are all the students who have an ungraded assignment for which the deadline has passed\n" for i in x: strg += str(i) + "\n" x = ScrollMessageBox(QtWidgets.QMessageBox.Information, "Students who are late!", strg) else: raise Exception("No student satisffies this condition!") except Exception as err: self.createDialog(str(err), "Warning!", QtWidgets.QMessageBox.Information) def statStGivAs(self): try: x = self.listWidget_Assignments.currentItem() if type(x) != QtWidgets.QListWidgetItem: raise Exception("Plese select the assignment from the list!") strg = x.text() aID = "" i = 0 while (not strg[i].isdigit() and i < len(strg)): i += 1 while (strg[i].isdigit() and i < len(strg)): aID += strg[i] i += 1 aID = int(aID) x = self._stat.getStudGivenAssig(aID) if len(x) > 0: strg = "All students who received a given assignment, ordered alphabetically or by average grade for that assignment\n" for i in x: strg += str(i) + "\n" x = ScrollMessageBox( QtWidgets.QMessageBox.Information, "Students who received the assignment with id " + str(aID), strg) else: raise Exception("No student satisffies this condition!") except Exception as err: self.createDialog(str(err), "Warning!", QtWidgets.QMessageBox.Information) def refreshLists(self): self.listObj(self._stud, self.listWidget_Students) self.listObj(self._grade, self.listWidget_Grade) self.listObj(self._assign, self.listWidget_Assignments)
def __init__(self, text, name, view): QAction.__init__(self, text, view) self._name = name self.triggered.connect(self.apply_style)
def __init__(self, text, key, ascending, parent): QAction.__init__(self, text, parent) self.key, self.ascending = key, ascending self.triggered.connect(self)
class EditorWidget(QWebView, LineEditECM): # {{{ def __init__(self, parent=None): QWebView.__init__(self, parent) self.base_url = None self._parent = weakref.ref(parent) self.readonly = False self.comments_pat = re.compile(r'<!--.*?-->', re.DOTALL) extra_shortcuts = { 'ToggleBold': 'Bold', 'ToggleItalic': 'Italic', 'ToggleUnderline': 'Underline', } for wac, name, icon, text, checkable in [ ('ToggleBold', 'bold', 'format-text-bold', _('Bold'), True), ('ToggleItalic', 'italic', 'format-text-italic', _('Italic'), True), ('ToggleUnderline', 'underline', 'format-text-underline', _('Underline'), True), ('ToggleStrikethrough', 'strikethrough', 'format-text-strikethrough', _('Strikethrough'), True), ('ToggleSuperscript', 'superscript', 'format-text-superscript', _('Superscript'), True), ('ToggleSubscript', 'subscript', 'format-text-subscript', _('Subscript'), True), ('InsertOrderedList', 'ordered_list', 'format-list-ordered', _('Ordered list'), True), ('InsertUnorderedList', 'unordered_list', 'format-list-unordered', _('Unordered list'), True), ('AlignLeft', 'align_left', 'format-justify-left', _('Align left'), False), ('AlignCenter', 'align_center', 'format-justify-center', _('Align center'), False), ('AlignRight', 'align_right', 'format-justify-right', _('Align right'), False), ('AlignJustified', 'align_justified', 'format-justify-fill', _('Align justified'), False), ('Undo', 'undo', 'edit-undo', _('Undo'), False), ('Redo', 'redo', 'edit-redo', _('Redo'), False), ('RemoveFormat', 'remove_format', 'edit-clear', _('Remove formatting'), False), ('Copy', 'copy', 'edit-copy', _('Copy'), False), ('Paste', 'paste', 'edit-paste', _('Paste'), False), ('Cut', 'cut', 'edit-cut', _('Cut'), False), ('Indent', 'indent', 'format-indent-more', _('Increase indentation'), False), ('Outdent', 'outdent', 'format-indent-less', _('Decrease indentation'), False), ('SelectAll', 'select_all', 'edit-select-all', _('Select all'), False), ]: ac = PageAction(wac, icon, text, checkable, self) setattr(self, 'action_'+name, ac) ss = extra_shortcuts.get(wac, None) if ss: ac.setShortcut(QKeySequence(getattr(QKeySequence, ss))) if wac == 'RemoveFormat': ac.triggered.connect(self.remove_format_cleanup, type=Qt.QueuedConnection) self.action_color = QAction(QIcon(I('format-text-color.png')), _('Foreground color'), self) self.action_color.triggered.connect(self.foreground_color) self.action_background = QAction(QIcon(I('format-fill-color.png')), _('Background color'), self) self.action_background.triggered.connect(self.background_color) self.action_block_style = QAction(QIcon(I('format-text-heading.png')), _('Style text block'), self) self.action_block_style.setToolTip( _('Style the selected text block')) self.block_style_menu = QMenu(self) self.action_block_style.setMenu(self.block_style_menu) self.block_style_actions = [] for text, name in [ (_('Normal'), 'p'), (_('Heading') +' 1', 'h1'), (_('Heading') +' 2', 'h2'), (_('Heading') +' 3', 'h3'), (_('Heading') +' 4', 'h4'), (_('Heading') +' 5', 'h5'), (_('Heading') +' 6', 'h6'), (_('Pre-formatted'), 'pre'), (_('Blockquote'), 'blockquote'), (_('Address'), 'address'), ]: ac = BlockStyleAction(text, name, self) self.block_style_menu.addAction(ac) self.block_style_actions.append(ac) self.action_insert_link = QAction(QIcon(I('insert-link.png')), _('Insert link or image'), self) self.action_insert_hr = QAction(QIcon(I('format-text-hr.png')), _('Insert separator'), self) self.action_insert_link.triggered.connect(self.insert_link) self.action_insert_hr.triggered.connect(self.insert_hr) self.pageAction(QWebPage.ToggleBold).changed.connect(self.update_link_action) self.action_insert_link.setEnabled(False) self.action_insert_hr.setEnabled(False) self.action_clear = QAction(QIcon(I('trash.png')), _('Clear'), self) self.action_clear.triggered.connect(self.clear_text) self.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks) self.page().linkClicked.connect(self.link_clicked) secure_web_page(self.page().settings()) self.setHtml('') self.set_readonly(False) def update_link_action(self): wac = self.pageAction(QWebPage.ToggleBold).isEnabled() self.action_insert_link.setEnabled(wac) self.action_insert_hr.setEnabled(wac) def set_readonly(self, what): self.readonly = what self.page().setContentEditable(not self.readonly) def clear_text(self, *args): us = self.page().undoStack() us.beginMacro('clear all text') self.action_select_all.trigger() self.action_remove_format.trigger() self.exec_command('delete') us.endMacro() self.set_font_style() self.setFocus(Qt.OtherFocusReason) def link_clicked(self, url): open_url(url) def foreground_color(self): col = QColorDialog.getColor(Qt.black, self, _('Choose foreground color'), QColorDialog.ShowAlphaChannel) if col.isValid(): self.exec_command('foreColor', unicode(col.name())) def background_color(self): col = QColorDialog.getColor(Qt.white, self, _('Choose background color'), QColorDialog.ShowAlphaChannel) if col.isValid(): self.exec_command('hiliteColor', unicode(col.name())) def insert_hr(self, *args): self.exec_command('insertHTML', '<hr>') def insert_link(self, *args): link, name, is_image = self.ask_link() if not link: return url = self.parse_link(link) if url.isValid(): url = unicode(url.toString(NO_URL_FORMATTING)) self.setFocus(Qt.OtherFocusReason) if is_image: self.exec_command('insertHTML', '<img src="%s" alt="%s"></img>'%(prepare_string_for_xml(url, True), prepare_string_for_xml(name or _('Image'), True))) elif name: self.exec_command('insertHTML', '<a href="%s">%s</a>'%(prepare_string_for_xml(url, True), prepare_string_for_xml(name))) else: self.exec_command('createLink', url) else: error_dialog(self, _('Invalid URL'), _('The url %r is invalid') % link, show=True) def ask_link(self): d = QDialog(self) d.setWindowTitle(_('Create link')) l = QFormLayout() l.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow) d.setLayout(l) d.url = QLineEdit(d) d.name = QLineEdit(d) d.treat_as_image = QCheckBox(d) d.setMinimumWidth(600) d.bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel) d.br = b = QPushButton(_('&Browse')) b.setIcon(QIcon(I('document_open.png'))) def cf(): files = choose_files(d, 'select link file', _('Choose file'), select_only_single_file=True) if files: path = files[0] d.url.setText(path) if path and os.path.exists(path): with lopen(path, 'rb') as f: q = what(f) is_image = q in {'jpeg', 'png', 'gif'} d.treat_as_image.setChecked(is_image) b.clicked.connect(cf) d.la = la = QLabel(_( 'Enter a URL. If you check the "Treat the URL as an image" box ' 'then the URL will be added as an image reference instead of as ' 'a link. You can also choose to create a link to a file on ' 'your computer. ' 'Note that if you create a link to a file on your computer, it ' 'will stop working if the file is moved.')) la.setWordWrap(True) la.setStyleSheet('QLabel { margin-bottom: 1.5ex }') l.setWidget(0, l.SpanningRole, la) l.addRow(_('Enter &URL:'), d.url) l.addRow(_('Treat the URL as an &image'), d.treat_as_image) l.addRow(_('Enter &name (optional):'), d.name) l.addRow(_('Choose a file on your computer:'), d.br) l.addRow(d.bb) d.bb.accepted.connect(d.accept) d.bb.rejected.connect(d.reject) d.resize(d.sizeHint()) link, name, is_image = None, None, False if d.exec_() == d.Accepted: link, name = unicode(d.url.text()).strip(), unicode(d.name.text()).strip() is_image = d.treat_as_image.isChecked() return link, name, is_image def parse_link(self, link): link = link.strip() if link and os.path.exists(link): return QUrl.fromLocalFile(link) has_schema = re.match(r'^[a-zA-Z]+:', link) if has_schema is not None: url = QUrl(link, QUrl.TolerantMode) if url.isValid(): return url if os.path.exists(link): return QUrl.fromLocalFile(link) if has_schema is None: first, _, rest = link.partition('.') prefix = 'http' if first == 'ftp': prefix = 'ftp' url = QUrl(prefix +'://'+link, QUrl.TolerantMode) if url.isValid(): return url return QUrl(link, QUrl.TolerantMode) def sizeHint(self): return QSize(150, 150) def exec_command(self, cmd, arg=None): frame = self.page().mainFrame() if arg is not None: js = 'document.execCommand("%s", false, %s);' % (cmd, json.dumps(unicode(arg))) else: js = 'document.execCommand("%s", false, null);' % cmd frame.evaluateJavaScript(js) def remove_format_cleanup(self): self.html = self.html @dynamic_property def html(self): def fget(self): ans = u'' try: if not self.page().mainFrame().documentElement().findFirst('meta[name="calibre-dont-sanitize"]').isNull(): # Bypass cleanup if special meta tag exists return unicode(self.page().mainFrame().toHtml()) check = unicode(self.page().mainFrame().toPlainText()).strip() raw = unicode(self.page().mainFrame().toHtml()) raw = xml_to_unicode(raw, strip_encoding_pats=True, resolve_entities=True)[0] raw = self.comments_pat.sub('', raw) if not check and '<img' not in raw.lower(): return ans try: root = html.fromstring(raw) except: root = fromstring(raw) elems = [] for body in root.xpath('//body'): if body.text: elems.append(body.text) elems += [html.tostring(x, encoding=unicode) for x in body if x.tag not in ('script', 'style')] if len(elems) > 1: ans = u'<div>%s</div>'%(u''.join(elems)) else: ans = u''.join(elems) if not ans.startswith('<'): ans = '<p>%s</p>'%ans ans = xml_replace_entities(ans) except: import traceback traceback.print_exc() return ans def fset(self, val): if self.base_url is None: self.setHtml(val) else: self.setHtml(val, self.base_url) self.set_font_style() return property(fget=fget, fset=fset) def set_base_url(self, qurl): self.base_url = qurl self.setHtml('', self.base_url) def set_html(self, val, allow_undo=True): if not allow_undo or self.readonly: self.html = val return mf = self.page().mainFrame() mf.evaluateJavaScript('document.execCommand("selectAll", false, null)') mf.evaluateJavaScript('document.execCommand("insertHTML", false, %s)' % json.dumps(unicode(val))) self.set_font_style() def set_font_style(self): fi = QFontInfo(QApplication.font(self)) f = fi.pixelSize() + 1 + int(tweaks['change_book_details_font_size_by']) fam = unicode(fi.family()).strip().replace('"', '') if not fam: fam = 'sans-serif' style = 'font-size: %fpx; font-family:"%s",sans-serif;' % (f, fam) # toList() is needed because PyQt on Debian is old/broken for body in self.page().mainFrame().documentElement().findAll('body').toList(): body.setAttribute('style', style) self.page().setContentEditable(not self.readonly) def event(self, ev): if ev.type() in (ev.KeyPress, ev.KeyRelease, ev.ShortcutOverride) and ev.key() in ( Qt.Key_Tab, Qt.Key_Escape, Qt.Key_Backtab): if (ev.key() == Qt.Key_Tab and ev.modifiers() & Qt.ControlModifier and ev.type() == ev.KeyPress): self.exec_command('insertHTML', '<span style="white-space:pre">\t</span>') ev.accept() return True ev.ignore() return False return QWebView.event(self, ev) def text(self): return self.page().selectedText() def setText(self, text): self.exec_command('insertText', text) def contextMenuEvent(self, ev): menu = self.page().createStandardContextMenu() paste = self.pageAction(QWebPage.Paste) for action in menu.actions(): if action == paste: menu.insertAction(action, self.pageAction(QWebPage.PasteAndMatchStyle)) st = self.text() if st and st.strip(): self.create_change_case_menu(menu) parent = self._parent() if hasattr(parent, 'toolbars_visible'): vis = parent.toolbars_visible menu.addAction(_('%s toolbars') % (_('Hide') if vis else _('Show')), parent.toggle_toolbars) menu.exec_(ev.globalPos())
class MatPlotLibBase(QWidget): def __init__(self, parent, file_dialog_service, h_margin=(0.8, 0.1), v_margin=(0.5, 0.15), h_axes=[Size.Scaled(1.0)], v_axes=[Size.Scaled(1.0)], nx_default=1, ny_default=1): QWidget.__init__(self, parent) self._file_dialog_service = file_dialog_service self._figure = Figure() self._canvas = FigureCanvas(self._figure) h = [Size.Fixed(h_margin[0]), *h_axes, Size.Fixed(h_margin[1])] v = [Size.Fixed(v_margin[0]), *v_axes, Size.Fixed(v_margin[1])] self._divider = Divider(self._figure, (0.0, 0.0, 1.0, 1.0), h, v, aspect=False) self._axes = LocatableAxes(self._figure, self._divider.get_position()) self._axes.set_axes_locator( self._divider.new_locator(nx=nx_default, ny=ny_default)) self._axes.set_zorder(2) self._axes.patch.set_visible(False) for spine in ['top', 'right']: self._axes.spines[spine].set_visible(False) self._figure.add_axes(self._axes) self._canvas.setParent(self) self._layout = QVBoxLayout(self) self._layout.setContentsMargins(0, 0, 0, 0) self._layout.addWidget(self._canvas) self.setLayout(self._layout) self._figure.canvas.mpl_connect('scroll_event', self._on_scroll) self._xy_extents = None self._background_cache = None self._decoration_artists = [] self._is_panning = False self._zoom_selector = _RectangleSelector(self._axes, self._zoom_selected) self._zoom_selector.set_active(False) self._x_extent_padding = 0.01 self._y_extent_padding = 0.01 self._axes.ticklabel_format(style='sci', axis='x', scilimits=(-4, 4)) self._axes.ticklabel_format(style='sci', axis='y', scilimits=(-4, 4)) self._active_tools = {} self._span = _SpanSeletor(self._axes, self._handle_span_select, 'horizontal', rectprops=dict(alpha=0.2, facecolor='red', edgecolor='k'), span_stays=True) self._span.set_on_select_none(self._handle_span_select_none) self.span = self._previous_span = None self._span_center_mouse_event = None self._span_left_mouse_event = None self._span_right_mouse_event = None self._figure.canvas.mpl_connect('button_press_event', self._handle_press) self._figure.canvas.mpl_connect('motion_notify_event', self._handle_move) self._figure.canvas.mpl_connect('button_release_event', self._handle_release) self._figure.canvas.mpl_connect('resize_event', self._handle_resize) self.activateTool(ToolType.span, self.isActiveDefault(ToolType.span)) self._pan_event = None self._pending_draw = None self._pending_artists_draw = None self._other_draw_events = [] self._draw_timer = QTimer(self) self._draw_timer.timeout.connect(self._do_draw_events) self._draw_timer.start(20) self._zoom_skew = None self._menu = QMenu(self) self._copy_image_action = QAction(self.tr('Copy To Clipboard'), self) self._copy_image_action.triggered.connect(self.copyToClipboard) self._copy_image_action.setShortcuts(QKeySequence.Copy) self._save_image_action = QAction(self.tr('Save As Image'), self) self._save_image_action.triggered.connect(self.saveAsImage) self._show_table_action = QAction(self.tr('Show Table'), self) self._show_table_action.triggered.connect(self.showTable) self._menu.addAction(self._copy_image_action) self._menu.addAction(self._save_image_action) self._menu.addAction(self._show_table_action) self.addAction(self._copy_image_action) self._table_view = None self._single_axis_zoom_enabled = True self._cached_label_width_height = None if hasattr(type(self), 'dataChanged'): self.dataChanged.connect(self._on_data_changed) self._options_view = None self._secondary_axes = self._secondary_y_extent = self._secondary_x_extent = None self._legend = None self._draggable_legend = None self._setting_axis_limits = False self.hasHiddenSeries = False enabledToolsChanged = pyqtSignal() spanChanged = pyqtSignal(SpanModel) hasHiddenSeriesChanged = pyqtSignal(bool) span = AutoProperty(SpanModel) hasHiddenSeries = AutoProperty(bool) def setOptionsView(self, options_view): self._options_view = options_view self._options_view.setSecondaryYLimitsEnabled( self._secondary_y_enabled()) self._options_view.setSecondaryXLimitsEnabled( self._secondary_x_enabled()) self._options_view.showGridLinesChanged.connect( self._update_grid_lines) self._options_view.xAxisLowerLimitChanged.connect( self._handle_options_view_limit_changed(x_min_changed=True)) self._options_view.xAxisUpperLimitChanged.connect( self._handle_options_view_limit_changed(x_max_changed=True)) self._options_view.yAxisLowerLimitChanged.connect( self._handle_options_view_limit_changed(y_min_changed=True)) self._options_view.yAxisUpperLimitChanged.connect( self._handle_options_view_limit_changed(y_max_changed=True)) self._options_view.xAxisLimitsChanged.connect( self._handle_options_view_limit_changed(x_min_changed=True, x_max_changed=True)) self._options_view.yAxisLimitsChanged.connect( self._handle_options_view_limit_changed(y_min_changed=True, y_max_changed=True)) self._options_view.secondaryXAxisLowerLimitChanged.connect( self._handle_options_view_secondary_limit_changed( x_min_changed=True)) self._options_view.secondaryXAxisUpperLimitChanged.connect( self._handle_options_view_secondary_limit_changed( x_max_changed=True)) self._options_view.secondaryYAxisLowerLimitChanged.connect( self._handle_options_view_secondary_limit_changed( y_min_changed=True)) self._options_view.secondaryYAxisUpperLimitChanged.connect( self._handle_options_view_secondary_limit_changed( y_max_changed=True)) self._options_view.secondaryXAxisLimitsChanged.connect( self._handle_options_view_secondary_limit_changed( x_min_changed=True, x_max_changed=True)) self._options_view.secondaryYAxisLimitsChanged.connect( self._handle_options_view_secondary_limit_changed( y_min_changed=True, y_max_changed=True)) def setLegendControl(self, legend_control): self._legend_control = legend_control self._legend_control.seriesUpdated.connect(self._legend_series_updated) self._legend_control.showLegendChanged.connect(self._show_legend) self._legend_control.seriesNameChanged.connect( self._handle_series_name_changed) self._legend_control.showSeriesChanged.connect( self._handle_show_series_changed) bind(self._legend_control, self, 'hasHiddenSeries', two_way=False) def _legend_series_updated(self): if self._legend is not None: self._show_legend(self._legend_control.showLegend) def _show_legend(self, show): if self._legend and not show: self._legend.remove() self._legend = None self.draw() elif show: if self._legend: self._legend.remove() show_series = self._legend_control.showSeries handles = [ h for h, s in zip(self._legend_control.seriesHandles, show_series) if s ] names = [ n for n, s in zip(self._legend_control.seriesNames, show_series) if s ] axes = (self._secondary_axes if self._secondary_axes and self._secondary_axes.get_visible() and self._secondary_axes.get_zorder() > self._axes.get_zorder() else self._axes) self._legend = self._create_legend( axes, handles, names, markerscale=self._get_legend_markerscale()) if self._get_legend_text_color() is not None: for text in self._legend.texts: text.set_color(self._get_legend_text_color()) self._draggable_legend = DraggableLegend(self._legend) self.draw() def _get_legend_markerscale(self): return 5 def _create_legend(self, axes, handles, names, **kwargs): return axes.legend(handles, names, **kwargs) def _get_legend_text_color(self): return None def _handle_series_name_changed(self, index, series_name): if self._legend is not None and index < len( self._legend_control.seriesHandles): visible_handles = [ h for h, s in zip(self._legend_control.seriesHandles, self._legend_control.showSeries) if s and h is not None ] try: legend_index = visible_handles.index( self._legend_control.seriesHandles[index]) except ValueError: return if legend_index < len(self._legend.texts): self._legend.texts[legend_index].set_text(series_name) self.draw() def _handle_show_series_changed(self, index, show_series): if index < len(self._legend_control.seriesHandles): self._set_series_visibility( self._legend_control.seriesHandles[index], show_series) if self._legend is not None: self._show_legend(self._legend_control.showLegend) else: self.draw() def _set_series_visibility(self, handle, visible): if not handle: return if hasattr(handle, 'set_visible'): handle.set_visible(visible) elif hasattr(handle, 'get_children'): for child in handle.get_children(): self._set_series_visibility(child, visible) def _update_grid_lines(self): show_grid_lines = False if self._options_view is None else self._options_view.showGridLines gridline_color = self._axes.spines['bottom'].get_edgecolor() gridline_color = gridline_color[0], gridline_color[1], gridline_color[ 2], 0.5 kwargs = dict(color=gridline_color, alpha=0.5) if show_grid_lines else {} self._axes.grid(show_grid_lines, **kwargs) self.draw() def _handle_options_view_limit_changed(self, x_min_changed=False, x_max_changed=False, y_min_changed=False, y_max_changed=False): def _(): if self._options_view is None or self._setting_axis_limits: return (x_min, x_max), (y_min, y_max) = (new_x_min, new_x_max), ( new_y_min, new_y_max) = self._get_xy_extents() (x_opt_min, x_opt_max), (y_opt_min, y_opt_max) = self._get_options_view_xy_extents() if x_min_changed: new_x_min = x_opt_min if x_max_changed: new_x_max = x_opt_max if y_min_changed: new_y_min = y_opt_min if y_max_changed: new_y_max = y_opt_max if [new_x_min, new_x_max, new_y_min, new_y_max ] != [x_min, x_max, y_min, y_max]: self._xy_extents = (new_x_min, new_x_max), (new_y_min, new_y_max) self._set_axes_limits() self.draw() return _ def _get_options_view_xy_extents(self): (x_data_min, x_data_max), (y_data_min, y_data_max) = self._get_data_xy_extents() x_min = x_data_min if np.isnan( self._options_view.xAxisLowerLimit ) else self._options_view.xAxisLowerLimit x_max = x_data_max if np.isnan( self._options_view.xAxisUpperLimit ) else self._options_view.xAxisUpperLimit y_min = y_data_min if np.isnan( self._options_view.yAxisLowerLimit ) else self._options_view.yAxisLowerLimit y_max = y_data_max if np.isnan( self._options_view.yAxisUpperLimit ) else self._options_view.yAxisUpperLimit return (x_min, x_max), (y_min, y_max) def _handle_options_view_secondary_limit_changed(self, x_min_changed=False, x_max_changed=False, y_min_changed=False, y_max_changed=False): def _(): if self._options_view is None or self._setting_axis_limits: return updated = False (x_opt_min, x_opt_max), ( y_opt_min, y_opt_max) = self._get_options_view_secondary_xy_extents() if self._has_secondary_y_extent() and (y_min_changed or y_max_changed): y_min, y_max = new_y_min, new_y_max = self._get_secondary_y_extent( ) if y_min_changed: new_y_min = y_opt_min if y_max_changed: new_y_max = y_opt_max if [new_y_min, new_y_max] != [y_min, y_max]: self._secondary_y_extent = (new_y_min, new_y_max) updated = True if self._has_secondary_x_extent() and (x_min_changed or x_max_changed): x_min, x_max = new_x_min, new_x_max = self._get_secondary_x_extent( ) if x_min_changed: new_x_min = x_opt_min if x_max_changed: new_x_max = x_opt_max if [new_x_min, new_x_max] != [x_min, x_max]: self._secondary_x_extent = (new_x_min, new_x_max) updated = True if updated: self._set_axes_limits() self.draw() return _ def _get_options_view_secondary_xy_extents(self): x_data_min, x_data_max = self._get_data_secondary_x_extent() y_data_min, y_data_max = self._get_data_secondary_y_extent() x_min = x_data_min if np.isnan( self._options_view.secondaryXAxisLowerLimit ) else self._options_view.secondaryXAxisLowerLimit x_max = x_data_max if np.isnan( self._options_view.secondaryXAxisUpperLimit ) else self._options_view.secondaryXAxisUpperLimit y_min = y_data_min if np.isnan( self._options_view.secondaryYAxisLowerLimit ) else self._options_view.secondaryYAxisLowerLimit y_max = y_data_max if np.isnan( self._options_view.secondaryYAxisUpperLimit ) else self._options_view.secondaryYAxisUpperLimit return (x_min, x_max), (y_min, y_max) def _on_data_changed(self): self._cached_label_width_height = None def closeEvent(self, event): QWidget.closeEvent(self, event) if event.isAccepted(): self._zoom_selector.onselect = self._span.onselect = self._span._select_none_handler = None def set_divider_h_margin(self, h_margin): h = [ Size.Fixed(h_margin[0]), Size.Scaled(1.0), Size.Fixed(h_margin[1]) ] self._divider.set_horizontal(h) def set_divider_v_margin(self, v_margin): v = [ Size.Fixed(v_margin[0]), Size.Scaled(1.0), Size.Fixed(v_margin[1]) ] self._divider.set_vertical(v) @property def x_extent_padding(self): return self._x_extent_padding @x_extent_padding.setter def x_extent_padding(self, value): self._x_extent_padding = value @property def y_extent_padding(self): return self._y_extent_padding @y_extent_padding.setter def y_extent_padding(self, value): self._y_extent_padding = value def _in_interval(self, value, interval): return interval[0] <= value <= interval[1] def _interval_skew(self, value, interval): return (value - interval[0]) / (interval[1] - interval[0]) def _in_x_scroll_zone(self, event): return self._in_interval(event.x, self._axes.bbox.intervalx ) and event.y <= self._axes.bbox.intervaly[1] def _in_y_scroll_zone(self, event): return self._in_interval(event.y, self._axes.bbox.intervaly ) and event.x <= self._axes.bbox.intervalx[1] def _on_scroll(self, event): if self._secondary_axes is not None: self._handle_scroll_secondary(event) in_x = self._in_x_scroll_zone(event) in_y = self._in_y_scroll_zone(event) if in_x or in_y and event.button in ['up', 'down']: (x_min, x_max), (y_min, y_max) = self._get_actual_xy_extents() if (in_x and self._single_axis_zoom_enabled) or (in_x and in_y): skew = self._zoom_skew and self._zoom_skew[0] skew = self._interval_skew( event.x, self._axes.bbox.intervalx) if skew is None else skew x_min, x_max = self._zoom(x_min, x_max, skew, event.button) if (in_y and self._single_axis_zoom_enabled) or (in_x and in_y): skew = self._zoom_skew and self._zoom_skew[1] skew = self._interval_skew( event.y, self._axes.bbox.intervaly) if skew is None else skew y_min, y_max = self._zoom(y_min, y_max, skew, event.button) self._xy_extents = (x_min, x_max), (y_min, y_max) self._set_axes_limits() self.draw() def _in_secondary_y_scroll_zone(self, event): return self._in_interval(event.y, self._axes.bbox.intervaly) and \ event.x >= self._axes.bbox.intervalx[1] def _in_secondary_x_scroll_zone(self, event): return self._in_interval(event.x, self._axes.bbox.intervalx) and \ event.y >= self._axes.bbox.intervaly[1] def _handle_scroll_secondary(self, event): if self._has_secondary_y_extent(): in_secondary_y = self._in_secondary_y_scroll_zone(event) if in_secondary_y and event.button in ['up', 'down']: self._secondary_y_extent = self._zoom( *self._get_secondary_y_extent(), self._interval_skew(event.y, self._axes.bbox.intervaly), event.button) if self._has_secondary_x_extent(): in_secondary_x = self._in_secondary_x_scroll_zone(event) if in_secondary_x and event.button in ['up', 'down']: self._secondary_x_extent = self._zoom( *self._get_secondary_x_extent(), self._interval_skew(event.x, self._axes.bbox.intervalx), event.button) def _get_zoom_multiplier(self): return 20 / 19 def _zoom(self, min_, max_, skew, direction): zoom_multiplier = self._get_zoom_multiplier( ) if direction == 'up' else 1 / self._get_zoom_multiplier() range_ = max_ - min_ diff = (range_ * (1 / zoom_multiplier)) - range_ max_ += diff * (1 - skew) min_ -= diff * skew return min_, max_ def _set_axes_limits(self): try: self._setting_axis_limits = True if self._secondary_axes is not None: self._set_secondary_axes_limits() self._update_ticks() (x_min, x_max), (y_min, y_max) = self._get_xy_extents() if self._options_view is not None: if self._options_view.x_limits: self._options_view.setXLimits(float(x_min), float(x_max)) if self._options_view.y_limits: self._options_view.setYLimits(float(y_min), float(y_max)) self._axes.set_xlim(*_safe_limits(x_min, x_max)) self._axes.set_ylim(*_safe_limits(y_min, y_max)) finally: self._setting_axis_limits = False def _set_secondary_axes_limits(self): if self._options_view is not None: if self._options_view.secondary_y_limits: enabled = self._secondary_y_enabled() secondary_y_min, secondary_y_max = self._get_secondary_y_extent( ) if enabled else (float('nan'), float('nan')) self._options_view.setSecondaryYLimitsEnabled(enabled) self._options_view.setSecondaryYLimits(float(secondary_y_min), float(secondary_y_max)) if self._options_view.secondary_x_limits: enabled = self._secondary_x_enabled() secondary_x_min, secondary_x_max = self._get_secondary_x_extent( ) if enabled else (float('nan'), float('nan')) self._options_view.setSecondaryXLimitsEnabled(enabled) self._options_view.setSecondaryXLimits(float(secondary_x_min), float(secondary_x_max)) if self._has_secondary_y_extent(): self._secondary_axes.set_ylim(*_safe_limits( *self._get_secondary_y_extent())) if self._has_secondary_x_extent(): self._secondary_axes.set_xlim(*_safe_limits( *self._get_secondary_x_extent())) def _secondary_y_enabled(self): return True if self._secondary_axes and self._secondary_axes.get_visible( ) and self._has_secondary_y_extent() else False def _secondary_x_enabled(self): return True if self._secondary_axes and self._secondary_axes.get_visible( ) and self._has_secondary_x_extent() else False def _set_axes_labels(self): self._axes.set_xlabel(self.data.xAxisTitle) self._axes.set_ylabel(self.data.yAxisTitle) def _set_center(self, center): if not all(c is not None for c in center): center = (0, 0) x_extent, y_extent = self._get_xy_extents() span = x_extent[1] - x_extent[0], y_extent[1] - y_extent[0] x_extent = center[0] - span[0] / 2, center[0] + span[0] / 2 y_extent = center[1] - span[1] / 2, center[1] + span[1] / 2 self._xy_extents = x_extent, y_extent def _get_xy_extents(self): if self.data is None: return (0, 0), (0, 0) if self._xy_extents is None: return self._get_data_xy_extents() return self._xy_extents def _get_data_xy_extents(self): if self.data is None: return (0, 0), (0, 0) (x_min, x_max), (y_min, y_max) = self.data.get_xy_extents() return self._pad_extent(x_min, x_max, self.x_extent_padding), self._pad_extent( y_min, y_max, self.y_extent_padding) def _has_secondary_y_extent(self): return hasattr(self.data, 'get_secondary_y_extent') def _get_secondary_y_extent(self): if self._secondary_y_extent is not None: return self._secondary_y_extent if self.data is not None: return self._get_data_secondary_y_extent() return (0, 0) def _get_data_secondary_y_extent(self): if self.data is None: return (0, 0) return self._pad_extent(*self.data.get_secondary_y_extent(), self.y_extent_padding) def _has_secondary_x_extent(self): return hasattr(self.data, 'get_secondary_x_extent') def _get_secondary_x_extent(self): if self._secondary_x_extent is not None: return self._secondary_x_extent if self.data is not None: return self._get_data_secondary_x_extent() return (0, 0) def _get_data_secondary_x_extent(self): if self.data is None or not hasattr(self.data, 'get_secondary_x_extent'): return (0, 0) return self._pad_extent(*self.data.get_secondary_x_extent(), self.x_extent_padding) def _get_actual_xy_extents(self): return self._axes.get_xlim(), self._axes.get_ylim() def _pad_extent(self, min_, max_, padding): min_, max_ = self._zero_if_nan(min_), self._zero_if_nan(max_) range_ = max_ - min_ return min_ - padding * range_, max_ + padding * range_ def _zoom_selected(self, start_pos, end_pos): x_min, x_max = min(start_pos.xdata, end_pos.xdata), max(start_pos.xdata, end_pos.xdata) y_min, y_max = min(start_pos.ydata, end_pos.ydata), max(start_pos.ydata, end_pos.ydata) self._xy_extents = (x_min, x_max), (y_min, y_max) self._set_axes_limits() self.draw() def _handle_span_select(self, x_min, x_max): x_min, x_max = self._round_to_bin_width(x_min, x_max) self._update_span_rect(x_min, x_max) self.span = SpanModel(self, x_min, x_max) self.draw() def _handle_span_select_none(self): self.span = None def _handle_press(self, event): if event.button == 1: if self._is_panning: self._pan_event = event elif self._span.active: self._handle_span_press(event) def _handle_move(self, event): if event.xdata and self._pan_event: self._handle_pan_move(event) elif event.xdata and any(self._span_events()): self._handle_span_move(event) def _handle_release(self, event): if self._pan_event: self._pan_event = None elif any(self._span_events()): self._handle_span_release(event) def _handle_pan_move(self, event): from_x, from_y = self._axes.transData.inverted().transform( (self._pan_event.x, self._pan_event.y)) to_x, to_y = self._axes.transData.inverted().transform( (event.x, event.y)) self._pan(from_x - to_x, from_y - to_y) self._pan_event = event def _pan(self, delta_x, delta_y): (x_min, x_max), (y_min, y_max) = self._get_xy_extents() self._xy_extents = (x_min + delta_x, x_max + delta_x), (y_min + delta_y, y_max + delta_y) self._set_axes_limits() self.draw() def _span_events(self): return self._span_center_mouse_event, self._span_left_mouse_event, self._span_right_mouse_event def _handle_span_press(self, event): if not event.xdata: return span_min, span_max = (self.span.left, self.span.right) if self.span else (0, 0) edge_tolerance = self._span_tolerance() if abs(span_min - event.xdata) < edge_tolerance: self._span.active = False self._span_left_mouse_event = event elif abs(span_max - event.xdata) < edge_tolerance: self._span.active = False self._span_right_mouse_event = event elif span_min < event.xdata < span_max: self._span.active = False self._span_center_mouse_event = event def _handle_span_move(self, event): if not self.span: return x_min, x_max = self.span.left, self.span.right last_event = next(x for x in self._span_events() if x) diff_x = event.xdata - last_event.xdata if self._span_center_mouse_event is not None: self._update_span_rect(x_min + diff_x) elif self._span_left_mouse_event is not None: self._update_span_rect(x_min + diff_x, x_max) elif self._span_right_mouse_event is not None: self._update_span_rect(x_min, x_max + diff_x) self.draw([self._span.rect]) def _handle_span_release(self, _event): x_min = self._span.rect.get_x() x_max = x_min + self._span.rect.get_width() x_min, x_max = self._round_to_bin_width(x_min, x_max) self._update_span_rect(x_min, x_max) self.span = SpanModel(self, x_min, x_max) self.draw() self._span.active = True self._span_center_mouse_event = self._span_left_mouse_event = self._span_right_mouse_event = None def _update_span_rect(self, x_min, x_max=None): self._span.rect.set_x(x_min) self._span.stay_rect.set_x(x_min) if x_max: self._span.rect.set_width(x_max - x_min) self._span.stay_rect.set_width(x_max - x_min) def _round_to_bin_width(self, x_min, x_max): return x_min, x_max def _span_tolerance(self): return 5 def toolEnabled(self, _tool_type): return False def toolAvailable(self, _tool_type): return False def activateTool(self, tool_type, active): if tool_type == ToolType.zoom: self._zoom_selector.set_active(active) elif tool_type == ToolType.span: if self._span.active and not active: self._previous_span = self.span self.span = None for r in [self._span.rect, self._span.stay_rect]: self._remove_artist(r) elif not self._span.active and active: self.span = self._previous_span for r in [self._span.rect, self._span.stay_rect]: self._add_artist(r) self._span.active = active self.draw() elif tool_type == ToolType.pan: self._is_panning = active self._active_tools[tool_type] = active def toolActive(self, tool_type): return self._active_tools.get(tool_type, False) def isActiveDefault(self, _tool_type): return False def _add_artist(self, artist): self._axes.add_artist(artist) self._decoration_artists.append(artist) def _remove_artist(self, artist): artist.remove() if artist in self._decoration_artists: self._decoration_artists.remove(artist) def _handle_resize(self, _event): self._update_ticks() return self.draw() def draw(self, artists=None): if artists is None: def _update(): for a in self._decoration_artists: a.remove() self._canvas.draw() self._background_cache = self._canvas.copy_from_bbox( self._figure.bbox) for a in self._decoration_artists: self._axes.add_artist(a) self._axes.draw_artist(a) self._canvas.update() self._pending_draw = _update else: def _update(): if self._background_cache is None: raise RuntimeError('Must run draw before drawing artists!') self._canvas.restore_region(self._background_cache) for a in artists: self._axes.draw_artist(a) self._canvas.update() self._pending_artists_draw = _update def _do_draw_events(self): if self._pending_draw is not None: self._pending_draw() self._pending_draw = None if self._pending_artists_draw is not None: self._pending_artists_draw() self._pending_artists_draw = None if self._other_draw_events: for draw_event in self._other_draw_events: draw_event() self._other_draw_events = [] def addDrawEvent(self, draw_event): self._other_draw_events.append(draw_event) def resetZoom(self): self._secondary_y_extent = self._secondary_x_extent = None self._xy_extents = None self._set_axes_limits() self.draw() def _twinx(self, ylabel): axes = self._axes.twinx() for spine in ['top', 'left']: axes.spines[spine].set_visible(False) axes.set_ylabel(ylabel) axes.set_zorder(1) return axes @property def axes(self): return self._axes @property def secondary_axes(self): if self._secondary_axes is None: self._set_secondary_axes(self._twinx('')) return self._secondary_axes def _set_secondary_axes(self, axes): self._secondary_axes = axes @staticmethod def sizeHint(): """function::sizeHint() Override the default sizeHint to ensure the plot has an initial size """ return QSize(600, 400) def minimumSizeHint(self): """function::sizeHint() Override the default sizeHint to ensure the plot does not shrink below minimum size """ return self.sizeHint() @staticmethod def _zero_if_nan(value): return value if not isinstance(value, float) or not np.isnan(value) else 0 def canShowTable(self): return hasattr(self, 'data') and self.data is not None and hasattr( self.data, 'table') def contextMenuEvent(self, event): self._show_table_action.setEnabled(self.canShowTable()) self._menu.exec_(event.globalPos()) def copyToClipboard(self): with BytesIO() as buffer: self._figure.savefig(buffer, facecolor=self._figure.get_facecolor()) QApplication.clipboard().setImage( QImage.fromData(buffer.getvalue())) def saveAsImage(self): filename = self._file_dialog_service.get_save_filename( self, self.tr('Portable Network Graphics (*.png)')) if filename: self._figure.savefig(filename, facecolor=self._figure.get_facecolor()) def showTable(self): if self.canShowTable(): self._table_view = TableView(None) self._table_view.pasteEnabled = False self._table_view.setModel(self.data.table) self._table_view.setMinimumSize(800, 600) self._table_view.show() def _update_ticks(self): if not self.data: return if hasattr(self.data, 'x_labels'): step = self.data.x_tick_interval if hasattr( self.data, 'x_tick_interval') else None x_ticks, x_labels = self._get_labels(self.data.x_labels, step, horizontal=True) self._axes.set_xticks(x_ticks) self._axes.set_xticklabels(x_labels) if hasattr(self.data, 'y_labels'): step = self.data.y_tick_interval if hasattr( self.data, 'y_tick_interval') else None y_ticks, y_labels = self._get_labels(self.data.y_labels, step, horizontal=False) self._axes.set_yticks(y_ticks) self._axes.set_yticklabels(y_labels) def _get_labels(self, labels, step, horizontal=True): (x0, x1), (y0, y1) = self._get_xy_extents() start, end = (int(x0), int(x1)) if horizontal else (int(y0), int(y1)) visible_points = end - start if not (step and step > 0): width, height = self._get_label_width_height(labels) axes_bbox = self._axes.get_window_extent( self._figure.canvas.get_renderer()).transformed( self._figure.dpi_scale_trans.inverted()) plot_size = (axes_bbox.width if horizontal else axes_bbox.height) * self._figure.dpi size = (width if horizontal else height) if plot_size == 0 or size == 0: n_labels = 16 else: n_labels = int(plot_size / size) if n_labels == 0: n_labels = 16 step = int(visible_points / n_labels) + 1 else: step = int(step) indexes = list(range(len(labels))) display_labels = list(labels) for i in indexes: if i % step: display_labels[i] = '' return indexes, display_labels def _get_label_width_height(self, labels): if not self._cached_label_width_height: font = MatPlotLibFont.default() width = 0 height = 0 for label in labels: next_width, next_height = font.get_size( str(label), matplotlib.rcParams['font.size'], self._figure.dpi) width = max(width, next_width) height = max(height, next_height) self._cached_label_width_height = width, height return self._cached_label_width_height def _create_new_axes(self, nx=1, ny=1) -> LocatableAxes: axes = LocatableAxes(self._figure, self._divider.get_position()) axes.set_axes_locator(self._divider.new_locator(nx=nx, ny=ny)) self._figure.add_axes(axes) return axes @staticmethod def _create_secondary_xy_axes(figure, divider, nx=1, ny=1, visible=False, z_order=1): axes = LocatableAxes(figure, divider.get_position()) axes.set_axes_locator(divider.new_locator(nx=nx, ny=ny)) axes.xaxis.tick_top() axes.xaxis.set_label_position('top') axes.yaxis.tick_right() axes.yaxis.set_label_position('right') axes.patch.set_visible(visible) axes.set_zorder(z_order) figure.add_axes(axes) axes.ticklabel_format(style='sci', axis='x', scilimits=(-4, 4)) axes.ticklabel_format(style='sci', axis='y', scilimits=(-4, 4)) return axes @staticmethod def _create_shared_axes(figure, divider, shared_axes, nx=1, ny=1, visible=False, z_order=1): axes = LocatableAxes(figure, divider.get_position(), sharex=shared_axes, sharey=shared_axes, frameon=False) axes.set_axes_locator(divider.new_locator(nx=nx, ny=ny)) for spine in axes.spines.values(): spine.set_visible(False) for axis in axes.axis.values(): axis.set_visible(False) axes.patch.set_visible(False) axes.set_visible(False) axes.set_zorder(z_order) figure.add_axes(axes) return axes
def __init__(self, parent=None): QWebView.__init__(self, parent) self.base_url = None self._parent = weakref.ref(parent) self.readonly = False self.comments_pat = re.compile(r'<!--.*?-->', re.DOTALL) extra_shortcuts = { 'ToggleBold': 'Bold', 'ToggleItalic': 'Italic', 'ToggleUnderline': 'Underline', } for wac, name, icon, text, checkable in [ ('ToggleBold', 'bold', 'format-text-bold', _('Bold'), True), ('ToggleItalic', 'italic', 'format-text-italic', _('Italic'), True), ('ToggleUnderline', 'underline', 'format-text-underline', _('Underline'), True), ('ToggleStrikethrough', 'strikethrough', 'format-text-strikethrough', _('Strikethrough'), True), ('ToggleSuperscript', 'superscript', 'format-text-superscript', _('Superscript'), True), ('ToggleSubscript', 'subscript', 'format-text-subscript', _('Subscript'), True), ('InsertOrderedList', 'ordered_list', 'format-list-ordered', _('Ordered list'), True), ('InsertUnorderedList', 'unordered_list', 'format-list-unordered', _('Unordered list'), True), ('AlignLeft', 'align_left', 'format-justify-left', _('Align left'), False), ('AlignCenter', 'align_center', 'format-justify-center', _('Align center'), False), ('AlignRight', 'align_right', 'format-justify-right', _('Align right'), False), ('AlignJustified', 'align_justified', 'format-justify-fill', _('Align justified'), False), ('Undo', 'undo', 'edit-undo', _('Undo'), False), ('Redo', 'redo', 'edit-redo', _('Redo'), False), ('RemoveFormat', 'remove_format', 'edit-clear', _('Remove formatting'), False), ('Copy', 'copy', 'edit-copy', _('Copy'), False), ('Paste', 'paste', 'edit-paste', _('Paste'), False), ('Cut', 'cut', 'edit-cut', _('Cut'), False), ('Indent', 'indent', 'format-indent-more', _('Increase indentation'), False), ('Outdent', 'outdent', 'format-indent-less', _('Decrease indentation'), False), ('SelectAll', 'select_all', 'edit-select-all', _('Select all'), False), ]: ac = PageAction(wac, icon, text, checkable, self) setattr(self, 'action_'+name, ac) ss = extra_shortcuts.get(wac, None) if ss: ac.setShortcut(QKeySequence(getattr(QKeySequence, ss))) if wac == 'RemoveFormat': ac.triggered.connect(self.remove_format_cleanup, type=Qt.QueuedConnection) self.action_color = QAction(QIcon(I('format-text-color.png')), _('Foreground color'), self) self.action_color.triggered.connect(self.foreground_color) self.action_background = QAction(QIcon(I('format-fill-color.png')), _('Background color'), self) self.action_background.triggered.connect(self.background_color) self.action_block_style = QAction(QIcon(I('format-text-heading.png')), _('Style text block'), self) self.action_block_style.setToolTip( _('Style the selected text block')) self.block_style_menu = QMenu(self) self.action_block_style.setMenu(self.block_style_menu) self.block_style_actions = [] for text, name in [ (_('Normal'), 'p'), (_('Heading') +' 1', 'h1'), (_('Heading') +' 2', 'h2'), (_('Heading') +' 3', 'h3'), (_('Heading') +' 4', 'h4'), (_('Heading') +' 5', 'h5'), (_('Heading') +' 6', 'h6'), (_('Pre-formatted'), 'pre'), (_('Blockquote'), 'blockquote'), (_('Address'), 'address'), ]: ac = BlockStyleAction(text, name, self) self.block_style_menu.addAction(ac) self.block_style_actions.append(ac) self.action_insert_link = QAction(QIcon(I('insert-link.png')), _('Insert link or image'), self) self.action_insert_hr = QAction(QIcon(I('format-text-hr.png')), _('Insert separator'), self) self.action_insert_link.triggered.connect(self.insert_link) self.action_insert_hr.triggered.connect(self.insert_hr) self.pageAction(QWebPage.ToggleBold).changed.connect(self.update_link_action) self.action_insert_link.setEnabled(False) self.action_insert_hr.setEnabled(False) self.action_clear = QAction(QIcon(I('trash.png')), _('Clear'), self) self.action_clear.triggered.connect(self.clear_text) self.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks) self.page().linkClicked.connect(self.link_clicked) secure_web_page(self.page().settings()) self.setHtml('') self.set_readonly(False)
class MenuBar(QObject): is_native_menubar = True @property def native_menubar(self): return self.gui.native_menubar def __init__(self, location_manager, parent): QObject.__init__(self, parent) self.gui = parent self.location_manager = location_manager self.added_actions = [] self.last_actions = [] self.donate_action = QAction(_('Donate'), self) self.donate_menu = QMenu() self.donate_menu.addAction(self.gui.donate_action) self.donate_action.setMenu(self.donate_menu) self.refresh_timer = t = QTimer(self) t.setInterval(200), t.setSingleShot(True), t.timeout.connect(self.refresh_bar) def init_bar(self, actions): self.last_actions = actions for ac in self.added_actions: m = ac.menu() if m is not None: m.setVisible(False) mb = self.native_menubar for ac in self.added_actions: mb.removeAction(ac) if ac is not self.donate_action: ac.setMenu(None) ac.deleteLater() self.added_actions = [] for what in actions: if what is None: continue elif what == 'Location Manager': for ac in self.location_manager.available_actions: self.build_menu(ac) elif what == 'Donate': mb.addAction(self.donate_action) elif what in self.gui.iactions: action = self.gui.iactions[what] self.build_menu(action.qaction) def build_menu(self, ac): ans = CloneAction(ac, self.native_menubar, is_top_level=True) if ans.menu() is None: m = QMenu() m.addAction(CloneAction(ac, self.native_menubar)) ans.setMenu(m) # Qt (as of 5.3.0) does not update global menubar entries # correctly, so we have to rebuild the global menubar. # Without this the Choose Library action shows the text # 'Untitled' and the Location Manager items do not work. ans.text_changed.connect(self.refresh_timer.start) ans.visibility_changed.connect(self.refresh_timer.start) self.native_menubar.addAction(ans) self.added_actions.append(ans) return ans def setVisible(self, yes): pass # no-op on OS X since menu bar is always visible def update_lm_actions(self): pass # no-op as this is taken care of by init_bar() def refresh_bar(self): self.init_bar(self.last_actions)
class Main( MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ TagBrowserMixin, CoverFlowMixin, LibraryViewMixin, SearchBoxMixin, SavedSearchBoxMixin, SearchRestrictionMixin, LayoutMixin, UpdateMixin, EbookDownloadMixin): 'The main GUI' proceed_requested = pyqtSignal(object, object) book_converted = pyqtSignal(object, object) shutting_down = False def __init__(self, opts, parent=None, gui_debug=None): global _gui MainWindow.__init__(self, opts, parent=parent, disable_automatic_gc=True) self.setWindowIcon(QApplication.instance().windowIcon()) self.jobs_pointer = Pointer(self) self.proceed_requested.connect(self.do_proceed, type=Qt.QueuedConnection) self.proceed_question = ProceedQuestion(self) self.job_error_dialog = JobError(self) self.keyboard = Manager(self) _gui = self self.opts = opts self.device_connected = None self.gui_debug = gui_debug self.iactions = OrderedDict() # Actions for action in interface_actions(): if opts.ignore_plugins and action.plugin_path is not None: continue try: ac = self.init_iaction(action) except: # Ignore errors in loading user supplied plugins import traceback traceback.print_exc() if action.plugin_path is None: raise continue ac.plugin_path = action.plugin_path ac.interface_action_base_plugin = action self.add_iaction(ac) self.load_store_plugins() def init_iaction(self, action): ac = action.load_actual_plugin(self) ac.plugin_path = action.plugin_path ac.interface_action_base_plugin = action action.actual_iaction_plugin_loaded = True return ac def add_iaction(self, ac): acmap = self.iactions if ac.name in acmap: if ac.priority >= acmap[ac.name].priority: acmap[ac.name] = ac else: acmap[ac.name] = ac def load_store_plugins(self): from calibre.gui2.store.loader import Stores self.istores = Stores() for store in available_store_plugins(): if self.opts.ignore_plugins and store.plugin_path is not None: continue try: st = self.init_istore(store) self.add_istore(st) except: # Ignore errors in loading user supplied plugins import traceback traceback.print_exc() if store.plugin_path is None: raise continue self.istores.builtins_loaded() def init_istore(self, store): st = store.load_actual_plugin(self) st.plugin_path = store.plugin_path st.base_plugin = store store.actual_istore_plugin_loaded = True return st def add_istore(self, st): stmap = self.istores if st.name in stmap: if st.priority >= stmap[st.name].priority: stmap[st.name] = st else: stmap[st.name] = st def initialize(self, library_path, db, listener, actions, show_gui=True): opts = self.opts self.preferences_action, self.quit_action = actions self.library_path = library_path self.content_server = None self._spare_pool = None self.must_restart_before_config = False self.listener = Listener(listener) self.check_messages_timer = QTimer() self.check_messages_timer.timeout.connect( self.another_instance_wants_to_talk) self.check_messages_timer.start(1000) for ac in self.iactions.values(): try: ac.do_genesis() except Exception: # Ignore errors in third party plugins import traceback traceback.print_exc() if getattr(ac, 'plugin_path', None) is None: raise self.donate_action = QAction(QIcon(I('donate.png')), _('&Donate to support calibre'), self) for st in self.istores.values(): st.do_genesis() MainWindowMixin.init_main_window_mixin(self, db) # Jobs Button {{{ self.job_manager = JobManager() self.jobs_dialog = JobsDialog(self, self.job_manager) self.jobs_button = JobsButton(horizontal=True, parent=self) self.jobs_button.initialize(self.jobs_dialog, self.job_manager) # }}} LayoutMixin.init_layout_mixin(self) DeviceMixin.init_device_mixin(self) self.progress_indicator = ProgressIndicator(self) self.progress_indicator.pos = (0, 20) self.verbose = opts.verbose self.get_metadata = GetMetadata() self.upload_memory = {} self.metadata_dialogs = [] self.default_thumbnail = None self.tb_wrapper = textwrap.TextWrapper(width=40) self.viewers = collections.deque() self.system_tray_icon = None if config['systray_icon']: self.system_tray_icon = factory( app_id='com.calibre-ebook.gui').create_system_tray_icon( parent=self, title='calibre') if self.system_tray_icon is not None: self.system_tray_icon.setIcon( QIcon(I('lt.png', allow_user_override=False))) if not (iswindows or isosx): self.system_tray_icon.setIcon( QIcon.fromTheme('calibre-gui', self.system_tray_icon.icon())) self.system_tray_icon.setToolTip(self.jobs_button.tray_tooltip()) self.system_tray_icon.setVisible(True) self.jobs_button.tray_tooltip_updated.connect( self.system_tray_icon.setToolTip) elif config['systray_icon']: prints( 'Failed to create system tray icon, your desktop environment probably does not support the StatusNotifier spec' ) self.system_tray_menu = QMenu(self) self.toggle_to_tray_action = self.system_tray_menu.addAction( QIcon(I('page.png')), '') self.toggle_to_tray_action.triggered.connect( self.system_tray_icon_activated) self.system_tray_menu.addAction(self.donate_action) self.donate_button.clicked.connect(self.donate_action.trigger) self.donate_button.setToolTip(self.donate_action.text().replace( '&', '')) self.donate_button.setIcon(self.donate_action.icon()) self.donate_button.setStatusTip(self.donate_button.toolTip()) self.eject_action = self.system_tray_menu.addAction( QIcon(I('eject.png')), _('&Eject connected device')) self.eject_action.setEnabled(False) self.addAction(self.quit_action) self.system_tray_menu.addAction(self.quit_action) self.keyboard.register_shortcut('quit calibre', _('Quit calibre'), default_keys=('Ctrl+Q', ), action=self.quit_action) if self.system_tray_icon is not None: self.system_tray_icon.setContextMenu(self.system_tray_menu) self.system_tray_icon.activated.connect( self.system_tray_icon_activated) self.quit_action.triggered[bool].connect(self.quit) self.donate_action.triggered[bool].connect(self.donate) self.minimize_action = QAction(_('Minimize the calibre window'), self) self.addAction(self.minimize_action) self.keyboard.register_shortcut('minimize calibre', self.minimize_action.text(), default_keys=(), action=self.minimize_action) self.minimize_action.triggered.connect(self.showMinimized) self.esc_action = QAction(self) self.addAction(self.esc_action) self.keyboard.register_shortcut('clear current search', _('Clear the current search'), default_keys=('Esc', ), action=self.esc_action) self.esc_action.triggered.connect(self.esc) self.shift_esc_action = QAction(self) self.addAction(self.shift_esc_action) self.keyboard.register_shortcut('focus book list', _('Focus the book list'), default_keys=('Shift+Esc', ), action=self.shift_esc_action) self.shift_esc_action.triggered.connect(self.shift_esc) self.ctrl_esc_action = QAction(self) self.addAction(self.ctrl_esc_action) self.keyboard.register_shortcut('clear virtual library', _('Clear the virtual library'), default_keys=('Ctrl+Esc', ), action=self.ctrl_esc_action) self.ctrl_esc_action.triggered.connect(self.ctrl_esc) self.alt_esc_action = QAction(self) self.addAction(self.alt_esc_action) self.keyboard.register_shortcut('clear additional restriction', _('Clear the additional restriction'), default_keys=('Alt+Esc', ), action=self.alt_esc_action) self.alt_esc_action.triggered.connect( self.clear_additional_restriction) # ###################### Start spare job server ######################## QTimer.singleShot(1000, self.create_spare_pool) # ###################### Location Manager ######################## self.location_manager.location_selected.connect(self.location_selected) self.location_manager.unmount_device.connect( self.device_manager.umount_device) self.location_manager.configure_device.connect( self.configure_connected_device) self.location_manager.update_device_metadata.connect( self.update_metadata_on_device) self.eject_action.triggered.connect(self.device_manager.umount_device) # ################### Update notification ################### UpdateMixin.init_update_mixin(self, opts) # ###################### Search boxes ######################## SearchRestrictionMixin.init_search_restriction_mixin(self) SavedSearchBoxMixin.init_saved_seach_box_mixin(self) # ###################### Library view ######################## LibraryViewMixin.init_library_view_mixin(self, db) SearchBoxMixin.init_search_box_mixin(self) # Requires current_db if self.system_tray_icon is not None and self.system_tray_icon.isVisible( ) and opts.start_in_tray: self.hide_windows() self.library_view.model().count_changed_signal.connect( self.iactions['Choose Library'].count_changed) if not gprefs.get('quick_start_guide_added', False): try: add_quick_start_guide(self.library_view) except: import traceback traceback.print_exc() for view in ('library', 'memory', 'card_a', 'card_b'): v = getattr(self, '%s_view' % view) v.selectionModel().selectionChanged.connect(self.update_status_bar) v.model().count_changed_signal.connect(self.update_status_bar) self.library_view.model().count_changed() self.bars_manager.database_changed(self.library_view.model().db) self.library_view.model().database_changed.connect( self.bars_manager.database_changed, type=Qt.QueuedConnection) # ########################## Tags Browser ############################## TagBrowserMixin.init_tag_browser_mixin(self, db) self.library_view.model().database_changed.connect( self.populate_tb_manage_menu, type=Qt.QueuedConnection) # ######################## Search Restriction ########################## if db.prefs['virtual_lib_on_startup']: self.apply_virtual_library(db.prefs['virtual_lib_on_startup']) self.rebuild_vl_tabs() # ########################## Cover Flow ################################ CoverFlowMixin.init_cover_flow_mixin(self) self._calculated_available_height = min(max_available_height() - 15, self.height()) self.resize(self.width(), self._calculated_available_height) self.build_context_menus() for ac in self.iactions.values(): try: ac.gui_layout_complete() except: import traceback traceback.print_exc() if ac.plugin_path is None: raise if config['autolaunch_server']: self.start_content_server() self.read_settings() self.finalize_layout() if self.bars_manager.showing_donate: self.donate_button.start_animation() self.set_window_title() for ac in self.iactions.values(): try: ac.initialization_complete() except: import traceback traceback.print_exc() if ac.plugin_path is None: raise self.set_current_library_information(current_library_name(), db.library_id, db.field_metadata) register_keyboard_shortcuts() self.keyboard.finalize() if show_gui: # Note this has to come after restoreGeometry() because of # https://bugreports.qt.io/browse/QTBUG-56831 self.show() self.auto_adder = AutoAdder(gprefs['auto_add_path'], self) self.save_layout_state() # Collect cycles now gc.collect() QApplication.instance().shutdown_signal_received.connect(self.quit) if show_gui and self.gui_debug is not None: QTimer.singleShot(10, self.show_gui_debug_msg) self.iactions['Connect Share'].check_smartdevice_menus() QTimer.singleShot(1, self.start_smartdevice) QTimer.singleShot(100, self.update_toggle_to_tray_action) def show_gui_debug_msg(self): info_dialog(self, _('Debug mode'), '<p>' + _('You have started calibre in debug mode. After you ' 'quit calibre, the debug log will be available in ' 'the file: %s<p>The ' 'log will be displayed automatically.') % self.gui_debug, show=True) def esc(self, *args): self.clear_button.click() def shift_esc(self): self.current_view().setFocus(Qt.OtherFocusReason) def ctrl_esc(self): self.apply_virtual_library() self.current_view().setFocus(Qt.OtherFocusReason) def start_smartdevice(self): message = None if self.device_manager.get_option('smartdevice', 'autostart'): try: message = self.device_manager.start_plugin('smartdevice') except: message = 'start smartdevice unknown exception' prints(message) import traceback traceback.print_exc() if message: if not self.device_manager.is_running('Wireless Devices'): error_dialog( self, _('Problem starting the wireless device'), _('The wireless device driver had problems starting. ' 'It said "%s"') % message, show=True) self.iactions['Connect Share'].set_smartdevice_action_state() def start_content_server(self, check_started=True): from calibre.library.server.main import start_threaded_server from calibre.library.server import server_config self.content_server = start_threaded_server( self.library_view.model().db, server_config().parse()) self.content_server.state_callback = Dispatcher( self.iactions['Connect Share'].content_server_state_changed) if check_started: self.content_server.start_failure_callback = \ Dispatcher(self.content_server_start_failed) def content_server_start_failed(self, msg): error_dialog(self, _('Failed to start Content Server'), _('Could not start the content server. Error:\n\n%s') % msg, show=True) def resizeEvent(self, ev): MainWindow.resizeEvent(self, ev) self.search.setMaximumWidth(self.width() - 150) def create_spare_pool(self, *args): if self._spare_pool is None: num = min(detect_ncpus(), int(config['worker_limit'] / 2.0)) self._spare_pool = Pool(max_workers=num, name='GUIPool') def spare_pool(self): ans, self._spare_pool = self._spare_pool, None QTimer.singleShot(1000, self.create_spare_pool) return ans def do_proceed(self, func, payload): if callable(func): func(payload) def no_op(self, *args): pass def system_tray_icon_activated(self, r=False): if r in (QSystemTrayIcon.Trigger, QSystemTrayIcon.MiddleClick, False): if self.isVisible(): if self.isMinimized(): self.showNormal() else: self.hide_windows() else: self.show_windows() if self.isMinimized(): self.showNormal() @property def is_minimized_to_tray(self): return getattr(self, '__systray_minimized', False) def ask_a_yes_no_question(self, title, msg, det_msg='', show_copy_button=False, ans_when_user_unavailable=True, skip_dialog_name=None, skipped_value=True): if self.is_minimized_to_tray: return ans_when_user_unavailable return question_dialog(self, title, msg, det_msg=det_msg, show_copy_button=show_copy_button, skip_dialog_name=skip_dialog_name, skip_dialog_skipped_value=skipped_value) def update_toggle_to_tray_action(self, *args): if hasattr(self, 'toggle_to_tray_action'): self.toggle_to_tray_action.setText( _('Hide main window') if self.isVisible( ) else _('Show main window')) def hide_windows(self): for window in QApplication.topLevelWidgets(): if isinstance(window, (MainWindow, QDialog)) and \ window.isVisible(): window.hide() setattr(window, '__systray_minimized', True) self.update_toggle_to_tray_action() def show_windows(self, *args): for window in QApplication.topLevelWidgets(): if getattr(window, '__systray_minimized', False): window.show() setattr(window, '__systray_minimized', False) self.update_toggle_to_tray_action() def test_server(self, *args): if self.content_server is not None and \ self.content_server.exception is not None: error_dialog(self, _('Failed to start content server'), unicode(self.content_server.exception)).exec_() @property def current_db(self): return self.library_view.model().db def another_instance_wants_to_talk(self): try: msg = self.listener.queue.get_nowait() except Empty: return if msg.startswith('launched:'): import json try: argv = json.loads(msg[len('launched:'):]) except ValueError: prints('Failed to decode message from other instance: %r' % msg) if DEBUG: error_dialog( self, 'Invalid message', 'Received an invalid message from other calibre instance.' ' Do you have multiple versions of calibre installed?', det_msg='Invalid msg: %r' % msg, show=True) argv = () if isinstance(argv, (list, tuple)) and len(argv) > 1: files = [ os.path.abspath(p) for p in argv[1:] if not os.path.isdir(p) and os.access(p, os.R_OK) ] if files: self.iactions['Add Books'].add_filesystem_book(files) self.setWindowState(self.windowState() & ~Qt.WindowMinimized | Qt.WindowActive) self.show_windows() self.raise_() self.activateWindow() elif msg.startswith('refreshdb:'): m = self.library_view.model() m.db.new_api.reload_from_db() m.db.data.refresh(clear_caches=False, do_search=False) m.resort() m.research() self.tags_view.recount() elif msg.startswith('shutdown:'): self.quit(confirm_quit=False) elif msg.startswith('bookedited:'): parts = msg.split(':')[1:] try: book_id, fmt, library_id = parts[:3] book_id = int(book_id) m = self.library_view.model() db = m.db.new_api if m.db.library_id == library_id and db.has_id(book_id): db.format_metadata(book_id, fmt, allow_cache=False, update_db=True) db.update_last_modified((book_id, )) m.refresh_ids((book_id, )) except Exception: import traceback traceback.print_exc() else: print msg def current_view(self): '''Convenience method that returns the currently visible view ''' idx = self.stack.currentIndex() if idx == 0: return self.library_view if idx == 1: return self.memory_view if idx == 2: return self.card_a_view if idx == 3: return self.card_b_view def booklists(self): return self.memory_view.model().db, self.card_a_view.model( ).db, self.card_b_view.model().db def library_moved(self, newloc, copy_structure=False, call_close=True, allow_rebuild=False): if newloc is None: return default_prefs = None try: olddb = self.library_view.model().db if copy_structure: default_prefs = olddb.prefs except: olddb = None if copy_structure and olddb is not None and default_prefs is not None: default_prefs[ 'field_metadata'] = olddb.new_api.field_metadata.all_metadata( ) try: db = LibraryDatabase(newloc, default_prefs=default_prefs) except apsw.Error: if not allow_rebuild: raise import traceback repair = question_dialog( self, _('Corrupted database'), _('The library database at %s appears to be corrupted. Do ' 'you want calibre to try and rebuild it automatically? ' 'The rebuild may not be completely successful.') % force_unicode(newloc, filesystem_encoding), det_msg=traceback.format_exc()) if repair: from calibre.gui2.dialogs.restore_library import repair_library_at if repair_library_at(newloc, parent=self): db = LibraryDatabase(newloc, default_prefs=default_prefs) else: return else: return if self.content_server is not None: self.content_server.set_database(db) self.library_path = newloc prefs['library_path'] = self.library_path self.book_on_device(None, reset=True) db.set_book_on_device_func(self.book_on_device) self.library_view.set_database(db) self.tags_view.set_database(db, self.alter_tb) self.library_view.model().set_book_on_device_func(self.book_on_device) self.status_bar.clear_message() self.search.clear() self.saved_search.clear() self.book_details.reset_info() # self.library_view.model().count_changed() db = self.library_view.model().db self.iactions['Choose Library'].count_changed(db.count()) self.set_window_title() self.apply_named_search_restriction('') # reset restriction to null self.saved_searches_changed( recount=False) # reload the search restrictions combo box if db.prefs['virtual_lib_on_startup']: self.apply_virtual_library(db.prefs['virtual_lib_on_startup']) self.rebuild_vl_tabs() for action in self.iactions.values(): action.library_changed(db) if olddb is not None: try: if call_close: olddb.close() except: import traceback traceback.print_exc() olddb.break_cycles() if self.device_connected: self.set_books_in_library(self.booklists(), reset=True) self.refresh_ondevice() self.memory_view.reset() self.card_a_view.reset() self.card_b_view.reset() self.set_current_library_information(current_library_name(), db.library_id, db.field_metadata) self.library_view.set_current_row(0) # Run a garbage collection now so that it does not freeze the # interface later gc.collect() def set_window_title(self): db = self.current_db restrictions = [ x for x in (db.data.get_base_restriction_name(), db.data.get_search_restriction_name()) if x ] restrictions = ' :: '.join(restrictions) font = QFont() if restrictions: restrictions = ' :: ' + restrictions font.setBold(True) font.setItalic(True) self.virtual_library.setFont(font) title = u'{0} - || {1}{2} ||'.format( __appname__, self.iactions['Choose Library'].library_name(), restrictions) self.setWindowTitle(title) def location_selected(self, location): ''' Called when a location icon is clicked (e.g. Library) ''' page = 0 if location == 'library' else 1 if location == 'main' else 2 if location == 'carda' else 3 self.stack.setCurrentIndex(page) self.book_details.reset_info() for x in ('tb', 'cb'): splitter = getattr(self, x + '_splitter') splitter.button.setEnabled(location == 'library') for action in self.iactions.values(): action.location_selected(location) if location == 'library': self.virtual_library_menu.setEnabled(True) self.highlight_only_button.setEnabled(True) self.vl_tabs.setEnabled(True) else: self.virtual_library_menu.setEnabled(False) self.highlight_only_button.setEnabled(False) self.vl_tabs.setEnabled(False) # Reset the view in case something changed while it was invisible self.current_view().reset() self.set_number_of_books_shown() self.update_status_bar() def job_exception(self, job, dialog_title=_('Conversion Error'), retry_func=None): if not hasattr(self, '_modeless_dialogs'): self._modeless_dialogs = [] minz = self.is_minimized_to_tray if self.isVisible(): for x in list(self._modeless_dialogs): if not x.isVisible(): self._modeless_dialogs.remove(x) try: if 'calibre.ebooks.DRMError' in job.details: if not minz: from calibre.gui2.dialogs.drm_error import DRMErrorMessage d = DRMErrorMessage( self, _('Cannot convert') + ' ' + job.description.split(':')[-1].partition('(')[-1][:-1]) d.setModal(False) d.show() self._modeless_dialogs.append(d) return if 'calibre.ebooks.oeb.transforms.split.SplitError' in job.details: title = job.description.split(':')[-1].partition('(')[-1][:-1] msg = _('<p><b>Failed to convert: %s') % title msg += '<p>' + _(''' Many older ebook reader devices are incapable of displaying EPUB files that have internal components over a certain size. Therefore, when converting to EPUB, calibre automatically tries to split up the EPUB into smaller sized pieces. For some files that are large undifferentiated blocks of text, this splitting fails. <p>You can <b>work around the problem</b> by either increasing the maximum split size under EPUB Output in the conversion dialog, or by turning on Heuristic Processing, also in the conversion dialog. Note that if you make the maximum split size too large, your ebook reader may have trouble with the EPUB. ''') if not minz: d = error_dialog(self, _('Conversion Failed'), msg, det_msg=job.details) d.setModal(False) d.show() self._modeless_dialogs.append(d) return if 'calibre.web.feeds.input.RecipeDisabled' in job.details: if not minz: msg = job.details msg = msg[msg. find('calibre.web.feeds.input.RecipeDisabled:'):] msg = msg.partition(':')[-1] d = error_dialog(self, _('Recipe Disabled'), '<p>%s</p>' % msg) d.setModal(False) d.show() self._modeless_dialogs.append(d) return if 'calibre.ebooks.conversion.ConversionUserFeedBack:' in job.details: if not minz: import json payload = job.details.rpartition( 'calibre.ebooks.conversion.ConversionUserFeedBack:' )[-1] payload = json.loads('{' + payload.partition('{')[-1]) d = { 'info': info_dialog, 'warn': warning_dialog, 'error': error_dialog }.get(payload['level'], error_dialog) d = d(self, payload['title'], '<p>%s</p>' % payload['msg'], det_msg=payload['det_msg']) d.setModal(False) d.show() self._modeless_dialogs.append(d) return except: pass if job.killed: return try: prints(job.details, file=sys.stderr) except: pass if not minz: self.job_error_dialog.show_error(dialog_title, _('<b>Failed</b>') + ': ' + unicode(job.description), det_msg=job.details, retry_func=retry_func) def read_settings(self): geometry = config['main_window_geometry'] if geometry is not None: self.restoreGeometry(geometry) self.read_layout_settings() def write_settings(self): with gprefs: # Only write to gprefs once config.set('main_window_geometry', self.saveGeometry()) dynamic.set('sort_history', self.library_view.model().sort_history) self.save_layout_state() def quit(self, checked=True, restart=False, debug_on_restart=False, confirm_quit=True): if self.shutting_down: return if confirm_quit and not self.confirm_quit(): return try: self.shutdown() except: pass self.restart_after_quit = restart self.debug_on_restart = debug_on_restart QApplication.instance().quit() def donate(self, *args): open_url(QUrl('https://calibre-ebook.com/donate')) def confirm_quit(self): if self.job_manager.has_jobs(): msg = _('There are active jobs. Are you sure you want to quit?') if self.job_manager.has_device_jobs(): msg = '<p>'+__appname__ + \ _(''' is communicating with the device!<br> Quitting may cause corruption on the device.<br> Are you sure you want to quit?''')+'</p>' if not question_dialog(self, _('Active jobs'), msg): return False if self.proceed_question.questions: msg = _( 'There are library updates waiting. Are you sure you want to quit?' ) if not question_dialog(self, _('Library Updates Waiting'), msg): return False from calibre.db.delete_service import has_jobs if has_jobs(): msg = _('Some deleted books are still being moved to the Recycle ' 'Bin, if you quit now, they will be left behind. Are you ' 'sure you want to quit?') if not question_dialog(self, _('Active jobs'), msg): return False return True def shutdown(self, write_settings=True): self.shutting_down = True self.show_shutdown_message() from calibre.customize.ui import has_library_closed_plugins if has_library_closed_plugins(): self.show_shutdown_message( _('Running database shutdown plugins. This could take a few seconds...' )) self.grid_view.shutdown() db = None try: db = self.library_view.model().db cf = db.clean except: pass else: cf() # Save the current field_metadata for applications like calibre2opds # Goes here, because if cf is valid, db is valid. db.new_api.set_pref('field_metadata', db.field_metadata.all_metadata()) db.commit_dirty_cache() db.prefs.write_serialized(prefs['library_path']) for action in self.iactions.values(): if not action.shutting_down(): return if write_settings: self.write_settings() self.check_messages_timer.stop() if hasattr(self, 'update_checker'): self.update_checker.shutdown() self.listener.close() self.job_manager.server.close() self.job_manager.threaded_server.close() self.device_manager.keep_going = False self.auto_adder.stop() # Do not report any errors that happen after the shutdown # We cannot restore the original excepthook as that causes PyQt to # call abort() on unhandled exceptions import traceback def eh(t, v, tb): try: traceback.print_exception(t, v, tb, file=sys.stderr) except: pass sys.excepthook = eh mb = self.library_view.model().metadata_backup if mb is not None: mb.stop() if db is not None: db.close() try: try: if self.content_server is not None: # If the content server has any sockets being closed then # this can take quite a long time (minutes). Tell the user that it is # happening. self.show_shutdown_message( _('Shutting down the content server. This could take a while ...' )) s = self.content_server self.content_server = None s.exit() except: pass except KeyboardInterrupt: pass self.hide_windows() if self._spare_pool is not None: self._spare_pool.shutdown() from calibre.db.delete_service import shutdown shutdown() time.sleep(2) self.istores.join() return True def run_wizard(self, *args): if self.confirm_quit(): self.run_wizard_b4_shutdown = True self.restart_after_quit = True try: self.shutdown(write_settings=False) except: pass QApplication.instance().quit() def closeEvent(self, e): if self.shutting_down: return self.write_settings() if self.system_tray_icon is not None and self.system_tray_icon.isVisible( ): if not dynamic['systray_msg'] and not isosx: info_dialog( self, 'calibre', 'calibre ' + _('will keep running in the system tray. To close it, ' 'choose <b>Quit</b> in the context menu of the ' 'system tray.'), show_copy_button=False).exec_() dynamic['systray_msg'] = True self.hide_windows() e.ignore() else: if self.confirm_quit(): try: self.shutdown(write_settings=False) except: import traceback traceback.print_exc() e.accept() else: e.ignore()
def __init__(self, parent, view, row, link_delegate): QDialog.__init__(self, parent) self.normal_brush = QBrush(Qt.GlobalColor.white) self.marked_brush = QBrush(Qt.GlobalColor.lightGray) self.marked = None self.gui = parent self.splitter = QSplitter(self) self._l = l = QVBoxLayout(self) self.setLayout(l) l.addWidget(self.splitter) self.cover = Cover(self, show_size=gprefs['bd_overlay_cover_size']) self.cover.resizeEvent = self.cover_view_resized self.cover.cover_changed.connect(self.cover_changed) self.cover.open_with_requested.connect(self.open_with) self.cover.choose_open_with_requested.connect(self.choose_open_with) self.cover_pixmap = None self.cover.sizeHint = self.details_size_hint self.splitter.addWidget(self.cover) self.details = Details(parent.book_details.book_info, self) self.details.anchor_clicked.connect(self.on_link_clicked) self.link_delegate = link_delegate self.details.setAttribute(Qt.WidgetAttribute.WA_OpaquePaintEvent, False) palette = self.details.palette() self.details.setAcceptDrops(False) palette.setBrush(QPalette.ColorRole.Base, Qt.GlobalColor.transparent) self.details.setPalette(palette) self.c = QWidget(self) self.c.l = l2 = QGridLayout(self.c) l2.setContentsMargins(0, 0, 0, 0) self.c.setLayout(l2) l2.addWidget(self.details, 0, 0, 1, -1) self.splitter.addWidget(self.c) self.fit_cover = QCheckBox(_('Fit &cover within view'), self) self.fit_cover.setChecked( gprefs.get('book_info_dialog_fit_cover', True)) self.hl = hl = QHBoxLayout() hl.setContentsMargins(0, 0, 0, 0) l2.addLayout(hl, l2.rowCount(), 0, 1, -1) hl.addWidget(self.fit_cover), hl.addStretch() self.clabel = QLabel( '<div style="text-align: right"><a href="calibre:conf" title="{}" style="text-decoration: none">{}</a>' .format(_('Configure this view'), _('Configure'))) self.clabel.linkActivated.connect(self.configure) hl.addWidget(self.clabel) self.previous_button = QPushButton(QIcon(I('previous.png')), _('&Previous'), self) self.previous_button.clicked.connect(self.previous) l2.addWidget(self.previous_button, l2.rowCount(), 0) self.next_button = QPushButton(QIcon(I('next.png')), _('&Next'), self) self.next_button.clicked.connect(self.next) l2.addWidget(self.next_button, l2.rowCount() - 1, 1) self.view = view self.path_to_book = None self.current_row = None self.refresh(row) self.view.model().new_bookdisplay_data.connect(self.slave) self.fit_cover.stateChanged.connect(self.toggle_cover_fit) self.ns = QShortcut(QKeySequence('Alt+Right'), self) self.ns.activated.connect(self.next) self.ps = QShortcut(QKeySequence('Alt+Left'), self) self.ps.activated.connect(self.previous) self.next_button.setToolTip( _('Next [%s]') % unicode_type(self.ns.key().toString( QKeySequence.SequenceFormat.NativeText))) self.previous_button.setToolTip( _('Previous [%s]') % unicode_type(self.ps.key().toString( QKeySequence.SequenceFormat.NativeText))) geom = QCoreApplication.instance().desktop().availableGeometry(self) screen_height = geom.height() - 100 screen_width = geom.width() - 100 self.resize(max(int(screen_width / 2), 700), screen_height) saved_layout = gprefs.get('book_info_dialog_layout', None) if saved_layout is not None: try: QApplication.instance().safe_restore_geometry( self, saved_layout[0]) self.splitter.restoreState(saved_layout[1]) except Exception: pass from calibre.gui2.ui import get_gui ema = get_gui().iactions['Edit Metadata'].menuless_qaction a = self.ema = QAction('edit metadata', self) a.setShortcut(ema.shortcut()) self.addAction(a) a.triggered.connect(self.edit_metadata)
def initialize(self, library_path, db, listener, actions, show_gui=True): opts = self.opts self.preferences_action, self.quit_action = actions self.library_path = library_path self.content_server = None self._spare_pool = None self.must_restart_before_config = False self.listener = Listener(listener) self.check_messages_timer = QTimer() self.check_messages_timer.timeout.connect( self.another_instance_wants_to_talk) self.check_messages_timer.start(1000) for ac in self.iactions.values(): try: ac.do_genesis() except Exception: # Ignore errors in third party plugins import traceback traceback.print_exc() if getattr(ac, 'plugin_path', None) is None: raise self.donate_action = QAction(QIcon(I('donate.png')), _('&Donate to support calibre'), self) for st in self.istores.values(): st.do_genesis() MainWindowMixin.init_main_window_mixin(self, db) # Jobs Button {{{ self.job_manager = JobManager() self.jobs_dialog = JobsDialog(self, self.job_manager) self.jobs_button = JobsButton(horizontal=True, parent=self) self.jobs_button.initialize(self.jobs_dialog, self.job_manager) # }}} LayoutMixin.init_layout_mixin(self) DeviceMixin.init_device_mixin(self) self.progress_indicator = ProgressIndicator(self) self.progress_indicator.pos = (0, 20) self.verbose = opts.verbose self.get_metadata = GetMetadata() self.upload_memory = {} self.metadata_dialogs = [] self.default_thumbnail = None self.tb_wrapper = textwrap.TextWrapper(width=40) self.viewers = collections.deque() self.system_tray_icon = None if config['systray_icon']: self.system_tray_icon = factory( app_id='com.calibre-ebook.gui').create_system_tray_icon( parent=self, title='calibre') if self.system_tray_icon is not None: self.system_tray_icon.setIcon( QIcon(I('lt.png', allow_user_override=False))) if not (iswindows or isosx): self.system_tray_icon.setIcon( QIcon.fromTheme('calibre-gui', self.system_tray_icon.icon())) self.system_tray_icon.setToolTip(self.jobs_button.tray_tooltip()) self.system_tray_icon.setVisible(True) self.jobs_button.tray_tooltip_updated.connect( self.system_tray_icon.setToolTip) elif config['systray_icon']: prints( 'Failed to create system tray icon, your desktop environment probably does not support the StatusNotifier spec' ) self.system_tray_menu = QMenu(self) self.toggle_to_tray_action = self.system_tray_menu.addAction( QIcon(I('page.png')), '') self.toggle_to_tray_action.triggered.connect( self.system_tray_icon_activated) self.system_tray_menu.addAction(self.donate_action) self.donate_button.clicked.connect(self.donate_action.trigger) self.donate_button.setToolTip(self.donate_action.text().replace( '&', '')) self.donate_button.setIcon(self.donate_action.icon()) self.donate_button.setStatusTip(self.donate_button.toolTip()) self.eject_action = self.system_tray_menu.addAction( QIcon(I('eject.png')), _('&Eject connected device')) self.eject_action.setEnabled(False) self.addAction(self.quit_action) self.system_tray_menu.addAction(self.quit_action) self.keyboard.register_shortcut('quit calibre', _('Quit calibre'), default_keys=('Ctrl+Q', ), action=self.quit_action) if self.system_tray_icon is not None: self.system_tray_icon.setContextMenu(self.system_tray_menu) self.system_tray_icon.activated.connect( self.system_tray_icon_activated) self.quit_action.triggered[bool].connect(self.quit) self.donate_action.triggered[bool].connect(self.donate) self.minimize_action = QAction(_('Minimize the calibre window'), self) self.addAction(self.minimize_action) self.keyboard.register_shortcut('minimize calibre', self.minimize_action.text(), default_keys=(), action=self.minimize_action) self.minimize_action.triggered.connect(self.showMinimized) self.esc_action = QAction(self) self.addAction(self.esc_action) self.keyboard.register_shortcut('clear current search', _('Clear the current search'), default_keys=('Esc', ), action=self.esc_action) self.esc_action.triggered.connect(self.esc) self.shift_esc_action = QAction(self) self.addAction(self.shift_esc_action) self.keyboard.register_shortcut('focus book list', _('Focus the book list'), default_keys=('Shift+Esc', ), action=self.shift_esc_action) self.shift_esc_action.triggered.connect(self.shift_esc) self.ctrl_esc_action = QAction(self) self.addAction(self.ctrl_esc_action) self.keyboard.register_shortcut('clear virtual library', _('Clear the virtual library'), default_keys=('Ctrl+Esc', ), action=self.ctrl_esc_action) self.ctrl_esc_action.triggered.connect(self.ctrl_esc) self.alt_esc_action = QAction(self) self.addAction(self.alt_esc_action) self.keyboard.register_shortcut('clear additional restriction', _('Clear the additional restriction'), default_keys=('Alt+Esc', ), action=self.alt_esc_action) self.alt_esc_action.triggered.connect( self.clear_additional_restriction) # ###################### Start spare job server ######################## QTimer.singleShot(1000, self.create_spare_pool) # ###################### Location Manager ######################## self.location_manager.location_selected.connect(self.location_selected) self.location_manager.unmount_device.connect( self.device_manager.umount_device) self.location_manager.configure_device.connect( self.configure_connected_device) self.location_manager.update_device_metadata.connect( self.update_metadata_on_device) self.eject_action.triggered.connect(self.device_manager.umount_device) # ################### Update notification ################### UpdateMixin.init_update_mixin(self, opts) # ###################### Search boxes ######################## SearchRestrictionMixin.init_search_restriction_mixin(self) SavedSearchBoxMixin.init_saved_seach_box_mixin(self) # ###################### Library view ######################## LibraryViewMixin.init_library_view_mixin(self, db) SearchBoxMixin.init_search_box_mixin(self) # Requires current_db if self.system_tray_icon is not None and self.system_tray_icon.isVisible( ) and opts.start_in_tray: self.hide_windows() self.library_view.model().count_changed_signal.connect( self.iactions['Choose Library'].count_changed) if not gprefs.get('quick_start_guide_added', False): try: add_quick_start_guide(self.library_view) except: import traceback traceback.print_exc() for view in ('library', 'memory', 'card_a', 'card_b'): v = getattr(self, '%s_view' % view) v.selectionModel().selectionChanged.connect(self.update_status_bar) v.model().count_changed_signal.connect(self.update_status_bar) self.library_view.model().count_changed() self.bars_manager.database_changed(self.library_view.model().db) self.library_view.model().database_changed.connect( self.bars_manager.database_changed, type=Qt.QueuedConnection) # ########################## Tags Browser ############################## TagBrowserMixin.init_tag_browser_mixin(self, db) self.library_view.model().database_changed.connect( self.populate_tb_manage_menu, type=Qt.QueuedConnection) # ######################## Search Restriction ########################## if db.prefs['virtual_lib_on_startup']: self.apply_virtual_library(db.prefs['virtual_lib_on_startup']) self.rebuild_vl_tabs() # ########################## Cover Flow ################################ CoverFlowMixin.init_cover_flow_mixin(self) self._calculated_available_height = min(max_available_height() - 15, self.height()) self.resize(self.width(), self._calculated_available_height) self.build_context_menus() for ac in self.iactions.values(): try: ac.gui_layout_complete() except: import traceback traceback.print_exc() if ac.plugin_path is None: raise if config['autolaunch_server']: self.start_content_server() self.read_settings() self.finalize_layout() if self.bars_manager.showing_donate: self.donate_button.start_animation() self.set_window_title() for ac in self.iactions.values(): try: ac.initialization_complete() except: import traceback traceback.print_exc() if ac.plugin_path is None: raise self.set_current_library_information(current_library_name(), db.library_id, db.field_metadata) register_keyboard_shortcuts() self.keyboard.finalize() if show_gui: # Note this has to come after restoreGeometry() because of # https://bugreports.qt.io/browse/QTBUG-56831 self.show() self.auto_adder = AutoAdder(gprefs['auto_add_path'], self) self.save_layout_state() # Collect cycles now gc.collect() QApplication.instance().shutdown_signal_received.connect(self.quit) if show_gui and self.gui_debug is not None: QTimer.singleShot(10, self.show_gui_debug_msg) self.iactions['Connect Share'].check_smartdevice_menus() QTimer.singleShot(1, self.start_smartdevice) QTimer.singleShot(100, self.update_toggle_to_tray_action)
class MessageBox(QDialog): # {{{ ERROR = 0 WARNING = 1 INFO = 2 QUESTION = 3 resize_needed = pyqtSignal() def setup_ui(self): self.setObjectName("Dialog") self.resize(497, 235) self.gridLayout = l = QGridLayout(self) l.setObjectName("gridLayout") self.icon_widget = Icon(self) l.addWidget(self.icon_widget) self.msg = la = QLabel(self) la.setWordWrap(True), la.setMinimumWidth(400) la.setOpenExternalLinks(True) la.setObjectName("msg") l.addWidget(la, 0, 1, 1, 1) self.det_msg = dm = QPlainTextEdit(self) dm.setReadOnly(True) dm.setObjectName("det_msg") l.addWidget(dm, 1, 0, 1, 2) self.bb = bb = QDialogButtonBox(self) bb.setStandardButtons(QDialogButtonBox.StandardButton.Ok) bb.setObjectName("bb") bb.accepted.connect(self.accept) bb.rejected.connect(self.reject) l.addWidget(bb, 3, 0, 1, 2) self.toggle_checkbox = tc = QCheckBox(self) tc.setObjectName("toggle_checkbox") l.addWidget(tc, 2, 0, 1, 2) def __init__(self, type_, title, msg, det_msg='', q_icon=None, show_copy_button=True, parent=None, default_yes=True, yes_text=None, no_text=None, yes_icon=None, no_icon=None): QDialog.__init__(self, parent) if q_icon is None: icon = { self.ERROR: 'error', self.WARNING: 'warning', self.INFO: 'information', self.QUESTION: 'question', }[type_] icon = 'dialog_%s.png' % icon self.icon = QIcon(I(icon)) else: self.icon = q_icon if isinstance(q_icon, QIcon) else QIcon( I(q_icon)) self.setup_ui() self.setWindowTitle(title) self.setWindowIcon(self.icon) self.icon_widget.set_icon(self.icon) self.msg.setText(msg) self.det_msg.setPlainText(det_msg) self.det_msg.setVisible(False) self.toggle_checkbox.setVisible(False) if show_copy_button: self.ctc_button = self.bb.addButton( _('&Copy to clipboard'), QDialogButtonBox.ButtonRole.ActionRole) self.ctc_button.clicked.connect(self.copy_to_clipboard) self.show_det_msg = _('Show &details') self.hide_det_msg = _('Hide &details') self.det_msg_toggle = self.bb.addButton( self.show_det_msg, QDialogButtonBox.ButtonRole.ActionRole) self.det_msg_toggle.clicked.connect(self.toggle_det_msg) self.det_msg_toggle.setToolTip( _('Show detailed information about this error')) self.copy_action = QAction(self) self.addAction(self.copy_action) self.copy_action.setShortcuts(QKeySequence.StandardKey.Copy) self.copy_action.triggered.connect(self.copy_to_clipboard) self.is_question = type_ == self.QUESTION if self.is_question: self.bb.setStandardButtons(QDialogButtonBox.StandardButton.Yes | QDialogButtonBox.StandardButton.No) self.bb.button(QDialogButtonBox.StandardButton.Yes if default_yes else QDialogButtonBox.StandardButton.No).setDefault( True) self.default_yes = default_yes if yes_text is not None: self.bb.button( QDialogButtonBox.StandardButton.Yes).setText(yes_text) if no_text is not None: self.bb.button( QDialogButtonBox.StandardButton.No).setText(no_text) if yes_icon is not None: self.bb.button(QDialogButtonBox.StandardButton.Yes).setIcon( yes_icon if isinstance(yes_icon, QIcon ) else QIcon(I(yes_icon))) if no_icon is not None: self.bb.button(QDialogButtonBox.StandardButton.No).setIcon( no_icon if isinstance(no_icon, QIcon) else QIcon(I(no_icon) )) else: self.bb.button(QDialogButtonBox.StandardButton.Ok).setDefault(True) if not det_msg: self.det_msg_toggle.setVisible(False) self.resize_needed.connect(self.do_resize, type=Qt.ConnectionType.QueuedConnection) self.do_resize() def sizeHint(self): ans = QDialog.sizeHint(self) ans.setWidth( max(min(ans.width(), 500), self.bb.sizeHint().width() + 100)) ans.setHeight(min(ans.height(), 500)) return ans def toggle_det_msg(self, *args): vis = self.det_msg.isVisible() self.det_msg.setVisible(not vis) self.det_msg_toggle.setText( self.show_det_msg if vis else self.hide_det_msg) self.resize_needed.emit() def do_resize(self): self.resize(self.sizeHint()) def copy_to_clipboard(self, *args): QApplication.clipboard().setText( 'calibre, version %s\n%s: %s\n\n%s' % (__version__, unicode_type( self.windowTitle()), unicode_type(self.msg.text()), unicode_type(self.det_msg.toPlainText()))) if hasattr(self, 'ctc_button'): self.ctc_button.setText(_('Copied')) def showEvent(self, ev): ret = QDialog.showEvent(self, ev) if self.is_question: try: self.bb.button(QDialogButtonBox.StandardButton.Yes if self. default_yes else QDialogButtonBox. StandardButton.No).setFocus( Qt.FocusReason.OtherFocusReason) except: pass # Buttons were changed else: self.bb.button(QDialogButtonBox.StandardButton.Ok).setFocus( Qt.FocusReason.OtherFocusReason) return ret def set_details(self, msg): if not msg: msg = '' self.det_msg.setPlainText(msg) self.det_msg_toggle.setText(self.show_det_msg) self.det_msg_toggle.setVisible(bool(msg)) self.det_msg.setVisible(False) self.resize_needed.emit()
def __init__(self, id_, title, parent): QAction.__init__(self, title, parent) self.id = id_ self.triggered.connect(self._triggered)
class Splitter(QSplitter): state_changed = pyqtSignal(object) def __init__(self, name, label, icon, initial_show=True, initial_side_size=120, connect_button=True, orientation=Qt.Horizontal, side_index=0, parent=None, shortcut=None, hide_handle_on_single_panel=True): QSplitter.__init__(self, parent) if hide_handle_on_single_panel: self.state_changed.connect(self.update_handle_width) self.original_handle_width = self.handleWidth() self.resize_timer = QTimer(self) self.resize_timer.setSingleShot(True) self.desired_side_size = initial_side_size self.desired_show = initial_show self.resize_timer.setInterval(5) self.resize_timer.timeout.connect(self.do_resize) self.setOrientation(orientation) self.side_index = side_index self._name = name self.label = label self.initial_side_size = initial_side_size self.initial_show = initial_show self.splitterMoved.connect(self.splitter_moved, type=Qt.QueuedConnection) self.button = LayoutButton(icon, label, self, shortcut=shortcut) if connect_button: self.button.clicked.connect(self.double_clicked) if shortcut is not None: self.action_toggle = QAction(QIcon(icon), _('Toggle') + ' ' + label, self) self.action_toggle.changed.connect(self.update_shortcut) self.action_toggle.triggered.connect(self.toggle_triggered) if parent is not None: parent.addAction(self.action_toggle) if hasattr(parent, 'keyboard'): parent.keyboard.register_shortcut( 'splitter %s %s' % (name, label), unicode_type(self.action_toggle.text()), default_keys=(shortcut, ), action=self.action_toggle) else: self.action_toggle.setShortcut(shortcut) else: self.action_toggle.setShortcut(shortcut) def update_shortcut(self): self.button.update_shortcut(self.action_toggle) def toggle_triggered(self, *args): self.toggle_side_pane() def createHandle(self): return SplitterHandle(self.orientation(), self) def initialize(self): for i in range(self.count()): h = self.handle(i) if h is not None: h.splitter_moved() self.state_changed.emit(not self.is_side_index_hidden) def splitter_moved(self, *args): self.desired_side_size = self.side_index_size self.state_changed.emit(not self.is_side_index_hidden) def update_handle_width(self, not_one_panel): self.setHandleWidth(self.original_handle_width if not_one_panel else 0) @property def is_side_index_hidden(self): sizes = list(self.sizes()) try: return sizes[self.side_index] == 0 except IndexError: return True @property def save_name(self): ori = 'horizontal' if self.orientation() == Qt.Horizontal \ else 'vertical' return self._name + '_' + ori def print_sizes(self): if self.count() > 1: print(self.save_name, 'side:', self.side_index_size, 'other:', end=' ') print(list(self.sizes())[self.other_index]) @dynamic_property def side_index_size(self): def fget(self): if self.count() < 2: return 0 return self.sizes()[self.side_index] def fset(self, val): if self.count() < 2: return if val == 0 and not self.is_side_index_hidden: self.save_state() sizes = list(self.sizes()) for i in range(len(sizes)): sizes[i] = val if i == self.side_index else 10 self.setSizes(sizes) total = sum(self.sizes()) sizes = list(self.sizes()) for i in range(len(sizes)): sizes[i] = val if i == self.side_index else total - val self.setSizes(sizes) self.initialize() return property(fget=fget, fset=fset) def do_resize(self, *args): orig = self.desired_side_size QSplitter.resizeEvent(self, self._resize_ev) if orig > 20 and self.desired_show: c = 0 while abs(self.side_index_size - orig) > 10 and c < 5: self.apply_state(self.get_state(), save_desired=False) c += 1 def resizeEvent(self, ev): if self.resize_timer.isActive(): self.resize_timer.stop() self._resize_ev = ev self.resize_timer.start() def get_state(self): if self.count() < 2: return (False, 200) return (self.desired_show, self.desired_side_size) def apply_state(self, state, save_desired=True): if state[0]: self.side_index_size = state[1] if save_desired: self.desired_side_size = self.side_index_size else: self.side_index_size = 0 self.desired_show = state[0] def default_state(self): return (self.initial_show, self.initial_side_size) # Public API {{{ def update_desired_state(self): self.desired_show = not self.is_side_index_hidden def save_state(self): if self.count() > 1: gprefs[self.save_name + '_state'] = self.get_state() @property def other_index(self): return (self.side_index + 1) % 2 def restore_state(self): if self.count() > 1: state = gprefs.get(self.save_name + '_state', self.default_state()) self.apply_state(state, save_desired=False) self.desired_side_size = state[1] def toggle_side_pane(self, hide=None): if hide is None: action = 'show' if self.is_side_index_hidden else 'hide' else: action = 'hide' if hide else 'show' getattr(self, action + '_side_pane')() def show_side_pane(self): if self.count() < 2 or not self.is_side_index_hidden: return if self.desired_side_size == 0: self.desired_side_size = self.initial_side_size self.apply_state((True, self.desired_side_size)) def hide_side_pane(self): if self.count() < 2 or self.is_side_index_hidden: return self.apply_state((False, self.desired_side_size)) def double_clicked(self, *args): self.toggle_side_pane()
def genesis(self): self.prev_lname = self.last_lname = '' self.count_changed(0) self.action_choose = self.menuless_qaction self.action_exim = ac = QAction(_('Export/import all calibre data'), self.gui) ac.triggered.connect(self.exim_data) self.stats = LibraryUsageStats() self.popup_type = (QToolButton.InstantPopup if len(self.stats.stats) > 1 else QToolButton.MenuButtonPopup) if len(self.stats.stats) > 1: self.action_choose.triggered.connect(self.choose_library) else: self.qaction.triggered.connect(self.choose_library) self.choose_menu = self.qaction.menu() ac = self.create_action(spec=(_('Pick a random book'), 'random.png', None, None), attr='action_pick_random') ac.triggered.connect(self.pick_random) if not os.environ.get('CALIBRE_OVERRIDE_DATABASE_PATH', None): self.choose_menu.addAction(self.action_choose) self.quick_menu = QMenu(_('Quick switch')) self.quick_menu_action = self.choose_menu.addMenu(self.quick_menu) self.rename_menu = QMenu(_('Rename library')) self.rename_menu_action = self.choose_menu.addMenu( self.rename_menu) self.choose_menu.addAction(ac) self.delete_menu = QMenu(_('Remove library')) self.delete_menu_action = self.choose_menu.addMenu( self.delete_menu) self.choose_menu.addAction(self.action_exim) else: self.choose_menu.addAction(ac) self.rename_separator = self.choose_menu.addSeparator() self.switch_actions = [] for i in range(5): ac = self.create_action(spec=('', None, None, None), attr='switch_action%d' % i) ac.setObjectName(str(i)) self.switch_actions.append(ac) ac.setVisible(False) connect_lambda( ac.triggered, self, lambda self: self.switch_requested(self.qs_locations[int( self.gui.sender().objectName())]), type=Qt.QueuedConnection) self.choose_menu.addAction(ac) self.rename_separator = self.choose_menu.addSeparator() self.maintenance_menu = QMenu(_('Library maintenance')) ac = self.create_action(spec=(_('Library metadata backup status'), 'lt.png', None, None), attr='action_backup_status') ac.triggered.connect(self.backup_status, type=Qt.QueuedConnection) self.maintenance_menu.addAction(ac) ac = self.create_action(spec=(_('Check library'), 'lt.png', None, None), attr='action_check_library') ac.triggered.connect(self.check_library, type=Qt.QueuedConnection) self.maintenance_menu.addAction(ac) ac = self.create_action(spec=(_('Restore database'), 'lt.png', None, None), attr='action_restore_database') ac.triggered.connect(self.restore_database, type=Qt.QueuedConnection) self.maintenance_menu.addAction(ac) self.choose_menu.addMenu(self.maintenance_menu) self.view_state_map = {} self.restore_view_state.connect(self._restore_view_state, type=Qt.QueuedConnection)
class EbookViewer(MainWindow): STATE_VERSION = 2 FLOW_MODE_TT = _('Switch to paged mode - where the text is broken up ' 'into pages like a paper book') PAGED_MODE_TT = _('Switch to flow mode - where the text is not broken up ' 'into pages') AUTOSAVE_INTERVAL = 10 # seconds msg_from_anotherinstance = pyqtSignal(object) def __init__(self, pathtoebook=None, debug_javascript=False, open_at=None, start_in_fullscreen=False, continue_reading=False, listener=None): MainWindow.__init__(self, debug_javascript) self.view.magnification_changed.connect(self.magnification_changed) self.closed = False self.show_toc_on_open = False self.listener = listener if listener is not None: t = Thread(name='ConnListener', target=listen, args=(self,)) t.daemon = True t.start() self.msg_from_anotherinstance.connect(self.another_instance_wants_to_talk, type=Qt.QueuedConnection) self.current_book_has_toc = False self.iterator = None self.current_page = None self.pending_search = None self.pending_search_dir= None self.pending_anchor = None self.pending_reference = None self.pending_bookmark = None self.pending_restore = False self.pending_goto_page = None self.cursor_hidden = False self.existing_bookmarks= [] self.selected_text = None self.was_maximized = False self.page_position_on_footnote_toggle = [] self.read_settings() self.autosave_timer = t = QTimer(self) t.setInterval(self.AUTOSAVE_INTERVAL * 1000), t.setSingleShot(True) t.timeout.connect(self.autosave) self.pos.value_changed.connect(self.update_pos_label) self.pos.value_changed.connect(self.autosave_timer.start) self.pos.setMinimumWidth(150) self.setFocusPolicy(Qt.StrongFocus) self.view.set_manager(self) self.pi = ProgressIndicator(self) self.action_quit = QAction(_('&Quit'), self) self.addAction(self.action_quit) self.view_resized_timer = QTimer(self) self.view_resized_timer.timeout.connect(self.viewport_resize_finished) self.view_resized_timer.setSingleShot(True) self.resize_in_progress = False self.action_reload = QAction(_('&Reload book'), self) self.action_reload.triggered.connect(self.reload_book) self.action_quit.triggered.connect(self.quit) self.action_reference_mode.triggered[bool].connect(self.view.reference_mode) self.action_metadata.triggered[bool].connect(self.metadata.setVisible) self.action_table_of_contents.toggled[bool].connect(self.set_toc_visible) self.action_copy.triggered[bool].connect(self.copy) self.action_font_size_larger.triggered.connect(self.font_size_larger) self.action_font_size_smaller.triggered.connect(self.font_size_smaller) self.action_open_ebook.triggered[bool].connect(self.open_ebook) self.action_next_page.triggered.connect(self.view.next_page) self.action_previous_page.triggered.connect(self.view.previous_page) self.action_find_next.triggered.connect(self.find_next) self.action_find_previous.triggered.connect(self.find_previous) self.action_full_screen.triggered[bool].connect(self.toggle_fullscreen) self.action_back.triggered[bool].connect(self.back) self.action_forward.triggered[bool].connect(self.forward) self.action_preferences.triggered.connect(self.do_config) self.pos.editingFinished.connect(self.goto_page_num) self.vertical_scrollbar.valueChanged[int].connect(lambda x:self.goto_page(x/100.)) self.search.search.connect(self.find) self.search.focus_to_library.connect(lambda: self.view.setFocus(Qt.OtherFocusReason)) self.toc.pressed[QModelIndex].connect(self.toc_clicked) self.toc.searched.connect(partial(self.toc_clicked, force=True)) def toggle_toc(ev): try: key = self.view.shortcuts.get_match(ev) except AttributeError: pass if key == 'Table of Contents': ev.accept() self.action_table_of_contents.trigger() return True return False self.toc.handle_shortcuts = toggle_toc self.reference.goto.connect(self.goto) self.bookmarks.edited.connect(self.bookmarks_edited) self.bookmarks.activated.connect(self.goto_bookmark) self.bookmarks.create_requested.connect(self.bookmark) self.set_bookmarks([]) self.load_theme_menu() if pathtoebook is not None: f = functools.partial(self.load_ebook, pathtoebook, open_at=open_at) QTimer.singleShot(50, f) elif continue_reading: QTimer.singleShot(50, self.continue_reading) self.window_mode_changed = None self.toggle_toolbar_action = QAction(_('Show/hide controls'), self) self.toggle_toolbar_action.setCheckable(True) self.toggle_toolbar_action.triggered.connect(self.toggle_toolbars) self.toolbar_hidden = None self.addAction(self.toggle_toolbar_action) self.full_screen_label_anim = QPropertyAnimation( self.full_screen_label, b'size') self.clock_timer = QTimer(self) self.clock_timer.timeout.connect(self.update_clock) self.action_print.triggered.connect(self.print_book) self.clear_recent_history_action = QAction( _('Clear list of recently opened books'), self) self.clear_recent_history_action.triggered.connect(self.clear_recent_history) self.build_recent_menu() self.open_history_menu.triggered.connect(self.open_recent) for x in ('tool_bar', 'tool_bar2'): x = getattr(self, x) for action in x.actions(): # So that the keyboard shortcuts for these actions will # continue to function even when the toolbars are hidden self.addAction(action) for plugin in self.view.document.all_viewer_plugins: plugin.customize_ui(self) self.view.document.settings_changed.connect(self.settings_changed) self.restore_state() self.settings_changed() self.action_toggle_paged_mode.toggled[bool].connect(self.toggle_paged_mode) if (start_in_fullscreen or self.view.document.start_in_fullscreen): self.action_full_screen.trigger() self.hide_cursor_timer = t = QTimer(self) t.setSingleShot(True), t.setInterval(3000) t.timeout.connect(self.hide_cursor) t.start() def eventFilter(self, obj, ev): if ev.type() == ev.MouseMove: if self.cursor_hidden: self.cursor_hidden = False QApplication.instance().restoreOverrideCursor() self.hide_cursor_timer.start() return False def hide_cursor(self): self.cursor_hidden = True QApplication.instance().setOverrideCursor(Qt.BlankCursor) def toggle_paged_mode(self, checked, at_start=False): in_paged_mode = not self.action_toggle_paged_mode.isChecked() self.view.document.in_paged_mode = in_paged_mode self.action_toggle_paged_mode.setToolTip(self.FLOW_MODE_TT if self.action_toggle_paged_mode.isChecked() else self.PAGED_MODE_TT) if at_start: return self.reload() def settings_changed(self): for x in ('', '2'): x = getattr(self, 'tool_bar'+x) x.setVisible(self.view.document.show_controls) def reload(self): if hasattr(self, 'current_index') and self.current_index > -1: self.view.document.page_position.save(overwrite=False) self.pending_restore = True self.load_path(self.view.last_loaded_path) def set_toc_visible(self, yes): self.toc_dock.setVisible(yes) if not yes: self.show_toc_on_open = False def clear_recent_history(self, *args): vprefs.set('viewer_open_history', []) self.build_recent_menu() def build_recent_menu(self): m = self.open_history_menu m.clear() recent = vprefs.get('viewer_open_history', []) if recent: m.addAction(self.clear_recent_history_action) m.addSeparator() count = 0 for path in recent: if count > 9: break if os.path.exists(path): m.addAction(RecentAction(path, m)) count += 1 def continue_reading(self): actions = self.open_history_menu.actions()[2:] if actions: actions[0].trigger() def shutdown(self): if self.isFullScreen() and not self.view.document.start_in_fullscreen: self.action_full_screen.trigger() return False self.save_state() if self.listener is not None: self.listener.close() return True def quit(self): if self.shutdown(): QApplication.instance().quit() def closeEvent(self, e): if self.closed: e.ignore() return if self.shutdown(): self.closed = True return MainWindow.closeEvent(self, e) else: e.ignore() def toggle_toolbars(self): for x in ('tool_bar', 'tool_bar2'): x = getattr(self, x) x.setVisible(not x.isVisible()) def save_state(self): state = bytearray(self.saveState(self.STATE_VERSION)) vprefs['main_window_state'] = state if not self.isFullScreen(): vprefs.set('viewer_window_geometry', bytearray(self.saveGeometry())) if self.current_book_has_toc: vprefs.set('viewer_toc_isvisible', self.show_toc_on_open or bool(self.toc_dock.isVisible())) vprefs['multiplier'] = self.view.multiplier vprefs['in_paged_mode'] = not self.action_toggle_paged_mode.isChecked() def restore_state(self): state = vprefs.get('main_window_state', None) if state is not None: try: state = QByteArray(state) self.restoreState(state, self.STATE_VERSION) except: pass self.initialize_dock_state() mult = vprefs.get('multiplier', None) if mult: self.view.multiplier = mult # On windows Qt lets the user hide toolbars via a right click in a very # specific location, ensure they are visible. self.tool_bar.setVisible(True) self.tool_bar2.setVisible(True) self.toc_dock.close() # This will be opened on book open, if the book has a toc and it was previously opened self.action_toggle_paged_mode.setChecked(not vprefs.get('in_paged_mode', True)) self.toggle_paged_mode(self.action_toggle_paged_mode.isChecked(), at_start=True) def lookup(self, word): from urllib import quote word = quote(word.encode('utf-8')) try: url = lookup_website(canonicalize_lang(self.view.current_language) or 'en').format(word=word) except Exception: traceback.print_exc() url = default_lookup_website(canonicalize_lang(self.view.current_language) or 'en').format(word=word) open_url(url) def print_book(self): if self.iterator is None: return error_dialog(self, _('No book opened'), _( 'Cannot print as no book is opened'), show=True) from calibre.gui2.viewer.printing import print_book print_book(self.iterator.pathtoebook, self, self.current_title) def toggle_fullscreen(self): if self.isFullScreen(): self.showNormal() else: self.showFullScreen() def showFullScreen(self): self.view.document.page_position.save() self.window_mode_changed = 'fullscreen' self.tool_bar.setVisible(False) self.tool_bar2.setVisible(False) self.was_maximized = self.isMaximized() if not self.view.document.fullscreen_scrollbar: self.vertical_scrollbar.setVisible(False) super(EbookViewer, self).showFullScreen() def show_full_screen_label(self): f = self.full_screen_label height = f.final_height width = int(0.7*self.view.width()) f.resize(width, height) if self.view.document.show_fullscreen_help: f.setVisible(True) a = self.full_screen_label_anim a.setDuration(500) a.setStartValue(QSize(width, 0)) a.setEndValue(QSize(width, height)) a.start() QTimer.singleShot(3500, self.full_screen_label.hide) if self.view.document.fullscreen_clock: self.show_clock() if self.view.document.fullscreen_pos: self.show_pos_label() self.relayout_fullscreen_labels() def show_clock(self): self.clock_label.setVisible(True) self.clock_label.setText(QTime(22, 33, 33).toString(Qt.SystemLocaleShortDate)) self.clock_timer.start(1000) self.clock_label.setStyleSheet(self.info_label_style%( 'rgba(0, 0, 0, 0)', self.view.document.colors()[1])) self.clock_label.resize(self.clock_label.sizeHint()) self.update_clock() def show_pos_label(self): self.pos_label.setVisible(True) self.pos_label.setStyleSheet(self.info_label_style%( 'rgba(0, 0, 0, 0)', self.view.document.colors()[1])) self.update_pos_label() def relayout_fullscreen_labels(self): vswidth = (self.vertical_scrollbar.width() if self.vertical_scrollbar.isVisible() else 0) p = self.pos_label p.move(15, p.parent().height() - p.height()-10) c = self.clock_label c.move(c.parent().width() - vswidth - 15 - c.width(), c.parent().height() - c.height() - 10) f = self.full_screen_label f.move((f.parent().width() - f.width())//2, (f.parent().height() - f.final_height)//2) def update_clock(self): self.clock_label.setText(QTime.currentTime().toString(Qt.SystemLocaleShortDate)) def update_pos_label(self, *args): if self.pos_label.isVisible(): try: value, maximum = args except: value, maximum = self.pos.value(), self.pos.maximum() text = '%g/%g'%(value, maximum) self.pos_label.setText(text) self.pos_label.resize(self.pos_label.sizeHint()) def showNormal(self): self.view.document.page_position.save() self.clock_label.setVisible(False) self.pos_label.setVisible(False) self.clock_timer.stop() self.vertical_scrollbar.setVisible(True) self.window_mode_changed = 'normal' self.settings_changed() self.full_screen_label.setVisible(False) if self.was_maximized: super(EbookViewer, self).showMaximized() else: super(EbookViewer, self).showNormal() def goto(self, ref): if ref: tokens = ref.split('.') if len(tokens) > 1: spine_index = int(tokens[0]) -1 if spine_index == self.current_index: self.view.goto(ref) else: self.pending_reference = ref self.load_path(self.iterator.spine[spine_index]) def goto_bookmark(self, bm): spine_index = bm['spine'] if spine_index > -1 and self.current_index == spine_index: if self.resize_in_progress: self.view.document.page_position.set_pos(bm['pos']) else: self.view.goto_bookmark(bm) # Going to a bookmark does not call scrolled() so we update the # page position explicitly. Use a timer to ensure it is # accurate. QTimer.singleShot(100, self.update_page_number) else: self.pending_bookmark = bm if spine_index < 0 or spine_index >= len(self.iterator.spine): spine_index = 0 self.pending_bookmark = None self.load_path(self.iterator.spine[spine_index]) def toc_clicked(self, index, force=False): if force or QApplication.mouseButtons() & Qt.LeftButton: item = self.toc_model.itemFromIndex(index) if item.abspath is not None: if not os.path.exists(item.abspath): return error_dialog(self, _('No such location'), _('The location pointed to by this item' ' does not exist.'), det_msg=item.abspath, show=True) url = QUrl.fromLocalFile(item.abspath) if item.fragment: url.setFragment(item.fragment) self.link_clicked(url) self.view.setFocus(Qt.OtherFocusReason) def selection_changed(self, selected_text): self.selected_text = selected_text.strip() self.action_copy.setEnabled(bool(self.selected_text)) def copy(self, x): if self.selected_text: QApplication.clipboard().setText(self.selected_text) def back(self, x): pos = self.history.back(self.pos.value()) if pos is not None: self.goto_page(pos) def goto_page_num(self): num = self.pos.value() self.goto_page(num) def forward(self, x): pos = self.history.forward(self.pos.value()) if pos is not None: self.goto_page(pos) def goto_start(self): self.goto_page(1) def goto_end(self): self.goto_page(self.pos.maximum()) def goto_page(self, new_page, loaded_check=True): if self.current_page is not None or not loaded_check: for page in self.iterator.spine: if new_page >= page.start_page and new_page <= page.max_page: try: frac = float(new_page-page.start_page)/(page.pages-1) except ZeroDivisionError: frac = 0 if page == self.current_page: self.view.scroll_to(frac) else: self.load_path(page, pos=frac) def open_ebook(self, checked): files = choose_files(self, 'ebook viewer open dialog', _('Choose ebook'), [(_('Ebooks'), available_input_formats())], all_files=False, select_only_single_file=True) if files: self.load_ebook(files[0]) def open_recent(self, action): self.load_ebook(action.path) def font_size_larger(self): self.view.magnify_fonts() def font_size_smaller(self): self.view.shrink_fonts() def magnification_changed(self, val): tt = '%(action)s [%(sc)s]\n'+_('Current magnification: %(mag).1f') sc = _(' or ').join(self.view.shortcuts.get_shortcuts('Font larger')) self.action_font_size_larger.setToolTip( tt %dict(action=unicode(self.action_font_size_larger.text()), mag=val, sc=sc)) sc = _(' or ').join(self.view.shortcuts.get_shortcuts('Font smaller')) self.action_font_size_smaller.setToolTip( tt %dict(action=unicode(self.action_font_size_smaller.text()), mag=val, sc=sc)) self.action_font_size_larger.setEnabled(self.view.multiplier < 3) self.action_font_size_smaller.setEnabled(self.view.multiplier > 0.2) def find(self, text, repeat=False, backwards=False): if not text: self.view.search('') return self.search.search_done(False) if self.view.search(text, backwards=backwards): self.scrolled(self.view.scroll_fraction) return self.search.search_done(True) index = self.iterator.search(text, self.current_index, backwards=backwards) if index is None: if self.current_index > 0: index = self.iterator.search(text, 0) if index is None: info_dialog(self, _('No matches found'), _('No matches found for: %s')%text).exec_() return self.search.search_done(True) return self.search.search_done(True) self.pending_search = text self.pending_search_dir = 'backwards' if backwards else 'forwards' self.load_path(self.iterator.spine[index]) def find_next(self): self.find(unicode(self.search.text()), repeat=True) def find_previous(self): self.find(unicode(self.search.text()), repeat=True, backwards=True) def do_search(self, text, backwards): self.pending_search = None self.pending_search_dir = None if self.view.search(text, backwards=backwards): self.scrolled(self.view.scroll_fraction) def internal_link_clicked(self, prev_pos): self.history.add(prev_pos) def link_clicked(self, url): path = os.path.abspath(unicode(url.toLocalFile())) frag = None if path in self.iterator.spine: self.update_page_number() # Ensure page number is accurate as it is used for history self.history.add(self.pos.value()) path = self.iterator.spine[self.iterator.spine.index(path)] if url.hasFragment(): frag = unicode(url.fragment()) if path != self.current_page: self.pending_anchor = frag self.load_path(path) else: oldpos = self.view.document.ypos if frag: self.view.scroll_to(frag) else: # Scroll to top self.view.scroll_to(0) if self.view.document.ypos == oldpos: # If we are coming from goto_next_section() call this will # cause another goto next section call with the next toc # entry, since this one did not cause any scrolling at all. QTimer.singleShot(10, self.update_indexing_state) else: open_url(url) def load_started(self): self.open_progress_indicator(_('Loading flow...')) def load_finished(self, ok): self.close_progress_indicator() path = self.view.path() try: index = self.iterator.spine.index(path) except (ValueError, AttributeError): return -1 self.current_page = self.iterator.spine[index] self.current_index = index self.set_page_number(self.view.scroll_fraction) QTimer.singleShot(100, self.update_indexing_state) if self.pending_search is not None: self.do_search(self.pending_search, self.pending_search_dir=='backwards') self.pending_search = None self.pending_search_dir = None if self.pending_anchor is not None: self.view.scroll_to(self.pending_anchor) self.pending_anchor = None if self.pending_reference is not None: self.view.goto(self.pending_reference) self.pending_reference = None if self.pending_bookmark is not None: self.goto_bookmark(self.pending_bookmark) self.pending_bookmark = None if self.pending_restore: self.view.document.page_position.restore() return self.current_index def goto_next_section(self): if hasattr(self, 'current_index'): entry = self.toc_model.next_entry(self.current_index, self.view.document.read_anchor_positions(), self.view.viewport_rect, self.view.document.in_paged_mode) if entry is not None: self.pending_goto_next_section = ( self.toc_model.currently_viewed_entry, entry, False) self.toc_clicked(entry.index(), force=True) def goto_previous_section(self): if hasattr(self, 'current_index'): entry = self.toc_model.next_entry(self.current_index, self.view.document.read_anchor_positions(), self.view.viewport_rect, self.view.document.in_paged_mode, backwards=True) if entry is not None: self.pending_goto_next_section = ( self.toc_model.currently_viewed_entry, entry, True) self.toc_clicked(entry.index(), force=True) def update_indexing_state(self, anchor_positions=None): pgns = getattr(self, 'pending_goto_next_section', None) if hasattr(self, 'current_index'): if anchor_positions is None: anchor_positions = self.view.document.read_anchor_positions() items = self.toc_model.update_indexing_state(self.current_index, self.view.viewport_rect, anchor_positions, self.view.document.in_paged_mode) if items: self.toc.scrollTo(items[-1].index()) if pgns is not None: self.pending_goto_next_section = None # Check that we actually progressed if pgns[0] is self.toc_model.currently_viewed_entry: entry = self.toc_model.next_entry(self.current_index, self.view.document.read_anchor_positions(), self.view.viewport_rect, self.view.document.in_paged_mode, backwards=pgns[2], current_entry=pgns[1]) if entry is not None: self.pending_goto_next_section = ( self.toc_model.currently_viewed_entry, entry, pgns[2]) self.toc_clicked(entry.index(), force=True) def load_path(self, path, pos=0.0): self.open_progress_indicator(_('Laying out %s')%self.current_title) self.view.load_path(path, pos=pos) def footnote_visibility_changed(self, is_visible): if self.view.document.in_paged_mode: pp = namedtuple('PagePosition', 'time is_visible page_dimensions multiplier last_loaded_path page_number after_resize_page_number') self.page_position_on_footnote_toggle.append(pp( time.time(), is_visible, self.view.document.page_dimensions, self.view.multiplier, self.view.last_loaded_path, self.view.document.page_number, None)) def pre_footnote_toggle_position(self): num = len(self.page_position_on_footnote_toggle) if self.view.document.in_paged_mode and num > 1 and num % 2 == 0: two, one = self.page_position_on_footnote_toggle.pop(), self.page_position_on_footnote_toggle.pop() if ( time.time() - two.time < 1 and not two.is_visible and one.is_visible and one.last_loaded_path == two.last_loaded_path and two.last_loaded_path == self.view.last_loaded_path and one.page_dimensions == self.view.document.page_dimensions and one.multiplier == self.view.multiplier and one.after_resize_page_number == self.view.document.page_number ): return one.page_number def viewport_resize_started(self, event): if not self.resize_in_progress: # First resize, so save the current page position self.resize_in_progress = True if not self.window_mode_changed: # The special handling for window mode changed will already # have saved page position, so only save it if this is not a # mode change self.view.document.page_position.save() if self.resize_in_progress: self.view_resized_timer.start(75) def viewport_resize_finished(self): # There hasn't been a resize event for some time # restore the current page position. self.resize_in_progress = False wmc, self.window_mode_changed = self.window_mode_changed, None fs = wmc == 'fullscreen' if wmc: # Sets up body text margins, which can be limited in fs mode by a # separate config option, so must be done before relayout of text (self.view.document.switch_to_fullscreen_mode if fs else self.view.document.switch_to_window_mode)() # Re-layout text, must be done before restoring page position self.view.document.after_resize() if wmc: # This resize is part of a window mode change, special case it if fs: self.show_full_screen_label() self.view.document.page_position.restore() self.scrolled(self.view.scroll_fraction) else: if self.isFullScreen(): self.relayout_fullscreen_labels() pre_footnote_pos = self.pre_footnote_toggle_position() if pre_footnote_pos is not None: self.view.document.page_number = pre_footnote_pos else: self.view.document.page_position.restore() self.update_page_number() if len(self.page_position_on_footnote_toggle) % 2 == 1: self.page_position_on_footnote_toggle[-1] = self.page_position_on_footnote_toggle[-1]._replace( after_resize_page_number=self.view.document.page_number) if self.pending_goto_page is not None: pos, self.pending_goto_page = self.pending_goto_page, None self.goto_page(pos, loaded_check=False) def update_page_number(self): self.set_page_number(self.view.document.scroll_fraction) return self.pos.value() def close_progress_indicator(self): self.pi.stop() for o in ('tool_bar', 'tool_bar2', 'view', 'horizontal_scrollbar', 'vertical_scrollbar'): getattr(self, o).setEnabled(True) self.unsetCursor() self.view.setFocus(Qt.PopupFocusReason) def open_progress_indicator(self, msg=''): self.pi.start(msg) for o in ('tool_bar', 'tool_bar2', 'view', 'horizontal_scrollbar', 'vertical_scrollbar'): getattr(self, o).setEnabled(False) self.setCursor(Qt.BusyCursor) def load_theme_menu(self): from calibre.gui2.viewer.config import load_themes self.themes_menu.clear() for key in load_themes(): title = key[len('theme_'):] self.themes_menu.addAction(title, partial(self.load_theme, key)) def load_theme(self, theme_id): self.view.load_theme(theme_id) def do_config(self): self.view.config(self) self.load_theme_menu() if self.iterator is not None: self.iterator.copy_bookmarks_to_file = self.view.document.copy_bookmarks_to_file from calibre.gui2 import config if not config['viewer_search_history']: self.search.clear_history() def bookmark(self, *args): num = 1 bm = None while True: bm = _('Bookmark #%d')%num if bm not in self.existing_bookmarks: break num += 1 title, ok = QInputDialog.getText(self, _('Add bookmark'), _('Enter title for bookmark:'), text=bm) title = unicode(title).strip() if ok and title: bm = self.view.bookmark() bm['spine'] = self.current_index bm['title'] = title self.iterator.add_bookmark(bm) self.set_bookmarks(self.iterator.bookmarks) self.bookmarks.set_current_bookmark(bm) def autosave(self): self.save_current_position(no_copy_to_file=True) def bookmarks_edited(self, bookmarks): self.build_bookmarks_menu(bookmarks) self.iterator.set_bookmarks(bookmarks) self.iterator.save_bookmarks() def build_bookmarks_menu(self, bookmarks): self.bookmarks_menu.clear() sc = _(' or ').join(self.view.shortcuts.get_shortcuts('Bookmark')) self.bookmarks_menu.addAction(_("Bookmark this location [%s]") % sc, self.bookmark) self.bookmarks_menu.addAction(_("Show/hide Bookmarks"), self.bookmarks_dock.toggleViewAction().trigger) self.bookmarks_menu.addSeparator() current_page = None self.existing_bookmarks = [] for bm in bookmarks: if bm['title'] == 'calibre_current_page_bookmark': if self.view.document.remember_current_page: current_page = bm else: self.existing_bookmarks.append(bm['title']) self.bookmarks_menu.addAction(bm['title'], partial(self.goto_bookmark, bm)) return current_page def set_bookmarks(self, bookmarks): self.bookmarks.set_bookmarks(bookmarks) return self.build_bookmarks_menu(bookmarks) @property def current_page_bookmark(self): bm = self.view.bookmark() bm['spine'] = self.current_index bm['title'] = 'calibre_current_page_bookmark' return bm def save_current_position(self, no_copy_to_file=False): if not self.view.document.remember_current_page: return if hasattr(self, 'current_index'): try: self.iterator.add_bookmark(self.current_page_bookmark, no_copy_to_file=no_copy_to_file) except: traceback.print_exc() def another_instance_wants_to_talk(self, msg): try: path, open_at = msg except Exception: return self.load_ebook(path, open_at=open_at) self.raise_() def load_ebook(self, pathtoebook, open_at=None, reopen_at=None): if self.iterator is not None: self.save_current_position() self.iterator.__exit__() self.iterator = EbookIterator(pathtoebook, copy_bookmarks_to_file=self.view.document.copy_bookmarks_to_file) self.history.clear() self.open_progress_indicator(_('Loading ebook...')) worker = Worker(target=partial(self.iterator.__enter__, view_kepub=True)) worker.path_to_ebook = pathtoebook worker.start() while worker.isAlive(): worker.join(0.1) QApplication.processEvents() if worker.exception is not None: tb = worker.traceback.strip() if tb and tb.splitlines()[-1].startswith('DRMError:'): from calibre.gui2.dialogs.drm_error import DRMErrorMessage DRMErrorMessage(self).exec_() else: r = getattr(worker.exception, 'reason', worker.exception) error_dialog(self, _('Could not open ebook'), as_unicode(r) or _('Unknown error'), det_msg=tb, show=True) self.close_progress_indicator() else: self.metadata.show_opf(self.iterator.opf, self.iterator.book_format) self.view.current_language = self.iterator.language title = self.iterator.opf.title if not title: title = os.path.splitext(os.path.basename(pathtoebook))[0] if self.iterator.toc: self.toc_model = TOC(self.iterator.spine, self.iterator.toc) self.toc.setModel(self.toc_model) if self.show_toc_on_open: self.action_table_of_contents.setChecked(True) else: self.toc_model = TOC(self.iterator.spine) self.toc.setModel(self.toc_model) self.action_table_of_contents.setChecked(False) if isbytestring(pathtoebook): pathtoebook = force_unicode(pathtoebook, filesystem_encoding) vh = vprefs.get('viewer_open_history', []) try: vh.remove(pathtoebook) except: pass vh.insert(0, pathtoebook) vprefs.set('viewer_open_history', vh[:50]) self.build_recent_menu() self.footnotes_dock.close() self.action_table_of_contents.setDisabled(not self.iterator.toc) self.current_book_has_toc = bool(self.iterator.toc) self.current_title = title self.setWindowTitle(title + ' [%s]'%self.iterator.book_format + ' - ' + self.base_window_title) self.pos.setMaximum(sum(self.iterator.pages)) self.pos.setSuffix(' / %d'%sum(self.iterator.pages)) self.vertical_scrollbar.setMinimum(100) self.vertical_scrollbar.setMaximum(100*sum(self.iterator.pages)) self.vertical_scrollbar.setSingleStep(10) self.vertical_scrollbar.setPageStep(100) self.set_vscrollbar_value(1) self.current_index = -1 QApplication.instance().alert(self, 5000) previous = self.set_bookmarks(self.iterator.bookmarks) if reopen_at is not None: previous = reopen_at if open_at is None and previous is not None: self.goto_bookmark(previous) else: if open_at is None: self.next_document() else: if open_at > self.pos.maximum(): open_at = self.pos.maximum() if open_at < self.pos.minimum(): open_at = self.pos.minimum() if self.resize_in_progress: self.pending_goto_page = open_at else: self.goto_page(open_at, loaded_check=False) def set_vscrollbar_value(self, pagenum): self.vertical_scrollbar.blockSignals(True) self.vertical_scrollbar.setValue(int(pagenum*100)) self.vertical_scrollbar.blockSignals(False) def set_page_number(self, frac): if getattr(self, 'current_page', None) is not None: page = self.current_page.start_page + frac*float(self.current_page.pages-1) self.pos.set_value(page) self.set_vscrollbar_value(page) def scrolled(self, frac, onload=False): self.set_page_number(frac) if not onload: ap = self.view.document.read_anchor_positions() self.update_indexing_state(ap) def next_document(self): if (hasattr(self, 'current_index') and self.current_index < len(self.iterator.spine) - 1): self.load_path(self.iterator.spine[self.current_index+1]) def previous_document(self): if hasattr(self, 'current_index') and self.current_index > 0: self.load_path(self.iterator.spine[self.current_index-1], pos=1.0) def keyPressEvent(self, event): if event.key() == Qt.Key_Escape: if self.metadata.isVisible(): self.metadata.setVisible(False) event.accept() return if self.isFullScreen(): self.action_full_screen.trigger() event.accept() return try: key = self.view.shortcuts.get_match(event) except AttributeError: return MainWindow.keyPressEvent(self, event) try: bac = self.bookmarks_menu.actions()[0] except (AttributeError, TypeError, IndexError, KeyError): bac = None action = { 'Quit':self.action_quit, 'Show metadata':self.action_metadata, 'Copy':self.view.copy_action, 'Font larger': self.action_font_size_larger, 'Font smaller': self.action_font_size_smaller, 'Fullscreen': self.action_full_screen, 'Find next': self.action_find_next, 'Find previous': self.action_find_previous, 'Search online': self.view.search_online_action, 'Lookup word': self.view.dictionary_action, 'Next occurrence': self.view.search_action, 'Bookmark': bac, 'Reload': self.action_reload, 'Table of Contents': self.action_table_of_contents, 'Print': self.action_print, }.get(key, None) if action is not None: event.accept() action.trigger() return if key == 'Focus Search': self.search.setFocus(Qt.OtherFocusReason) return if not self.view.handle_key_press(event): event.ignore() def reload_book(self): if getattr(self.iterator, 'pathtoebook', None): try: reopen_at = self.current_page_bookmark except Exception: reopen_at = None self.history.clear() self.load_ebook(self.iterator.pathtoebook, reopen_at=reopen_at) return def __enter__(self): return self def __exit__(self, *args): if self.iterator is not None: self.save_current_position() self.iterator.__exit__(*args) def read_settings(self): c = config().parse() if c.remember_window_size: wg = vprefs.get('viewer_window_geometry', None) if wg is not None: self.restoreGeometry(wg) self.show_toc_on_open = vprefs.get('viewer_toc_isvisible', False) desktop = QApplication.instance().desktop() av = desktop.availableGeometry(self).height() - 30 if self.height() > av: self.resize(self.width(), av) def show_footnote_view(self): self.footnotes_dock.show()
def _init(self): # Standard actions - needed on Mac to be placed correctly in # "application" menu action = QAction(QIcon.fromTheme('help-about'), '&About App', self) action.setMenuRole(QAction.AboutRole) action.triggered.connect(self._showAboutDialog) self._actions['Standard/About'] = action action = QAction(IconProvider.settingsIcon(), 'Pr&eferences', self) action.setMenuRole(QAction.PreferencesRole) action.setShortcut(QKeySequence(QKeySequence.Preferences)) action.triggered.connect(self._showPreferences) self._actions['Standard/Preferences'] = action action = QAction(QIcon.fromTheme('application-exit'), 'Quit', self) action.setMenuRole(QAction.QuitRole) # Shortcut set from browserWindow action.triggered.connect(self._quitApplication) self._actions['Standard/Quit'] = action # File menu self._menuFile = QMenu('&File', self) self._menuFile.aboutToShow.connect(self._aboutToShowFileMenu) self._ADD_ACTION('File/NewTab', self._menuFile, IconProvider.newTabIcon(), 'New Tab', self._newTab, 'Ctrl+T') self._ADD_ACTION('File/NewWindow', self._menuFile, IconProvider.newWindowIcon(), '&New Window', self._newWindow, 'Ctrl+N') self._ADD_ACTION('File/NewPrivateWindow', self._menuFile, IconProvider.privateBrowsingIcon(), 'New &Private Window', self._newPrivateWindow, 'Ctrl+Shift+P') self._ADD_ACTION('File/OpenLocation', self._menuFile, QIcon.fromTheme('document-open-remote'), 'Open Location', self._openLocation, 'Ctrl+L') self._ADD_ACTION('File/OpenFile', self._menuFile, QIcon.fromTheme('document-open'), 'Open &File...', self._openFile, 'Ctrl+O') self._ADD_ACTION('File/CloseWindow', self._menuFile, QIcon.fromTheme('window-close'), 'Close Window', self._closeWindow, 'Ctrl+Shift+W') self._menuFile.addSeparator() sessionManager = gVar.app.sessionManager() if sessionManager: sessionsSubmenu = QMenu('Sessions', self) sessionsSubmenu.aboutToShow.connect(sessionManager._aboutToShowSessionsMenu) self._menuFile.addMenu(sessionsSubmenu) action = QAction('Session Manager', self) action.triggered.connect(sessionManager.openSessionManagerDialog) self._actions['File/SessionManager'] = action self._menuFile.addAction(action) self._menuFile.addSeparator() self._ADD_ACTION('File/SavePageAs', self._menuFile, QIcon.fromTheme('document-save'), '&Save Page As...', self._savePageAs, 'Ctrl+S') self._ADD_ACTION('File/SendLink', self._menuFile, QIcon.fromTheme('mail-message-new'), 'Send Link...', self._sendLink, '') self._ADD_ACTION('File/Print', self._menuFile, QIcon.fromTheme('document-print'), '&Print...', self._printPage, 'Ctrl+P') self._menuFile.addSeparator() self._menuFile.addAction(self._actions['Standard/Quit']) # Edit Menu self._menuEdit = QMenu('&Edit', self) self._menuEdit.aboutToShow.connect(self._aboutToShowEditMenu) action = self._ADD_ACTION('Edit/Undo', self._menuEdit, QIcon.fromTheme('edit-undo'), '&Undo', self._editUndo, 'Ctrl+Z') action.setShortcutContext(Qt.WidgetShortcut) action = self._ADD_ACTION('Edit/Redo', self._menuEdit, QIcon.fromTheme('edit-redo'), '&Redo', self._editRedo, 'Ctrl+Shift+Z') action.setShortcutContext(Qt.WidgetShortcut) self._menuEdit.addSeparator() action = self._ADD_ACTION('Edit/Cut', self._menuEdit, QIcon.fromTheme('edit-cut'), '&Cut', self._editCut, 'Ctrl+X') action.setShortcutContext(Qt.WidgetShortcut) action = self._ADD_ACTION('Edit/Copy', self._menuEdit, QIcon.fromTheme('edit-copy'), 'C&opy', self._editCopy, 'Ctrl+C') action.setShortcutContext(Qt.WidgetShortcut) action = self._ADD_ACTION('Edit/Paste', self._menuEdit, QIcon.fromTheme('edit-paste'), '&Paste', self._editPaste, 'Ctrl+V') action.setShortcutContext(Qt.WidgetShortcut) self._menuEdit.addSeparator() action = self._ADD_ACTION('Edit/SelectAll', self._menuEdit, QIcon.fromTheme('edit-select-all'), 'Select &All', self._editSelectAll, 'Ctrl+A') action.setShortcutContext(Qt.WidgetShortcut) action = self._ADD_ACTION('Edit/Find', self._menuEdit, QIcon.fromTheme('edit-find'), '&Find', self._editFind, 'Ctrl+F') action.setShortcutContext(Qt.WidgetShortcut) self._menuFile.addSeparator() # View menu self._menuView = QMenu('&View', self) self._menuView.aboutToShow.connect(self._aboutToShowViewMenu) toolbarsMenu = QMenu('Toolbars', self._menuView) toolbarsMenu.aboutToShow.connect(self._aboutToShowtoolbarsMenu) sidebarMenu = QMenu('Sidebar', self._menuView) sidebarMenu.aboutToShow.connect(self._aboutToShowSidebarsMenu) encodingMenu = QMenu('Character &Encoding', self._menuView) encodingMenu.aboutToShow.connect(self._aboutToShowEncodingMenu) # Create menus to make shortcuts available event before first showing # the menu self._window.createToolbarsMenu(toolbarsMenu) self._window.createSidebarsMenu(sidebarMenu) self._menuView.addMenu(toolbarsMenu) self._menuView.addMenu(sidebarMenu) self._ADD_CHECKABLE_ACTION('View/ShowStatusBar', self._menuView, QIcon(), 'Sta&tus Bar', self._showStatusBar, '') self._menuView.addSeparator() self._ADD_ACTION('View/Stop', self._menuView, QIcon.fromTheme('process-stop'), '&Stop', self._stop, 'Esc') self._ADD_ACTION('View/Reload', self._menuView, QIcon.fromTheme('view-refresh'), '&Reload', self._reload, 'F5') self._menuView.addSeparator() self._ADD_ACTION('View/ZoomIn', self._menuView, QIcon.fromTheme('zoom-in'), 'Zoom &In', self._zoomIn, 'Ctrl++') self._ADD_ACTION('View/ZoomOut', self._menuView, QIcon.fromTheme('zoom-out'), 'Zoom &Out', self._zoomOut, 'Ctrl+-') self._ADD_ACTION('View/ZoomReset', self._menuView, QIcon.fromTheme('zoom-original'), 'Reset', self._zoomReset, 'Ctrl+0') self._menuView.addSeparator() self._menuView.addMenu(encodingMenu) self._menuView.addSeparator() action = self._ADD_ACTION('View/PageSource', self._menuView, QIcon.fromTheme('text-html'), '&Page Source', self._showPageSource, 'Ctrl+U') action.setShortcutContext(Qt.WidgetShortcut) self._ADD_CHECKABLE_ACTION('View/FullScreen', self._menuView, QIcon.fromTheme('view-fullscreen'), '&FullScreen', self._showFullScreen, 'F11') # Tool menu self._menuTools = QMenu('&Tools', self) self._menuTools.aboutToShow.connect(self._aboutToShowToolsMenu) self._ADD_ACTION('Tools/WebSearch', self._menuTools, QIcon.fromTheme('edit-find'), '&Web Search', self._webSearch, 'Ctrl+K') action = self._ADD_ACTION('Tools/SiteInfo', self._menuTools, QIcon.fromTheme('dialog-information'), 'Site &Info', self._showSiteInfo, 'Ctrl+I') action.setShortcutContext(Qt.WidgetShortcut) self._menuTools.addSeparator() self._ADD_ACTION('Tools/DownloadManager', self._menuTools, QIcon.fromTheme('download'), '&Download Manager', self._showDownloadManager, 'Ctrl+Y') self._ADD_ACTION('Tools/CookiesManager', self._menuTools, QIcon(), '&Cookies Manager', self._showCookieManager, '') self._ADD_ACTION('Tools/WebInspector', self._menuTools, QIcon(), 'Web In&spector', self._toggleWebInspector, 'Ctrl+Shift+I') self._ADD_ACTION('Tools/ClearRecentHistory', self._menuTools, QIcon.fromTheme('edit-clear'), 'Clear Recent &History', self._showClearRecentHistoryDialog, 'Ctrl+Shift+Del') if not WebInspector.isEnabled(): self._actions['Tools/WebInspector'].setVisible(False) self._submenuExtensions = QMenu('&Extensions', self) self._submenuExtensions.menuAction().setVisible(False) self._menuTools.addMenu(self._submenuExtensions) self._menuTools.addSeparator() # Help menu self._menuHelp = QMenu('&Help', self) # ifndef Q_OS_MACOS self._ADD_ACTION('Help/AboutQt', self._menuHelp, QIcon(), 'About &Qt', self._aboutQt, '') self._menuHelp.addAction(self._actions['Standard/About']) self._menuHelp.addSeparator() # endif self._ADD_ACTION('Help/InfoAboutApp', self._menuHelp, QIcon.fromTheme('help-contents'), 'Information about application', self._showInfoAboutApp, '') self._ADD_ACTION('Help/ConfigInfo', self._menuHelp, QIcon(), 'Configuration Information', self._showConfigInfo, '') self._ADD_ACTION('Help/ReportIssue', self._menuHelp, QIcon(), 'Report &Issue', self._reportIssue, '') self._actions['Help/InfoAboutApp'].setShortcut(QKeySequence(QKeySequence.HelpContents)) # History menu self._menuHistory = HistoryMenu() self._menuHistory.setMainWindow(self._window) # Bookmarks menu self._menuBookmarks = BookmarksMenu() self._menuBookmarks.setMainWindow(self._window) # Other actions action = QAction(QIcon.fromTheme('user-trash'), 'Restore &Closed Tab', self) action.setShortcut(QKeySequence('Ctrl+Shift+T')) action.triggered.connect(self._restoreClosedTab) self._actions['Other/RestoreClosedTab'] = action # # ifdef Q_OS_MACOS # self._actions['View/FullScreen'].setShortcut(QKeySequence('Ctrl+Meta+F')) # # Add standard actions to File Menu (as it won't be ever cleared) and # # Mac menubar should move them to "Application" menu # self._menuFile.addAction(self._actions['Standard/About']) # self._menuFile.addAction(self._actions['Standard/Preferences']) # # Prevent ConfigInfo action to be detected as "Preferences..." action in # # Mac # self._actions['Help/ConfigInfo'].setMenuRole(QAction.NoRole) # # Create Dock Menu # dockMenu = QMenu(0) # dockMenu.addAction(self._actions['File/NewTab']) # dockMenu.addAction(self._actions['File/NewWindow']) # dockMenu.addAction(self._actions['File/NewPrivateWindow']) # qt_mac_set_dock_menu(dockMenu) # # endif if const.OS_UNIX and not const.OS_MACOS: self._menuEdit.addAction(self._actions['Standard/Preferences']) elif not const.OS_MACOS: self._menuTools.addAction(self._actions['Standard/Preferences']) self._addActionsToWindow()
def __init__(self, parent): QFrame.__init__(self, parent) self.setFrameStyle(QFrame.NoFrame if gprefs['tag_browser_old_look'] else QFrame.StyledPanel) self._parent = parent self._layout = QVBoxLayout(self) self._layout.setContentsMargins(0, 0, 0, 0) # Set up the find box & button self.tb_bar = tbb = TagBrowserBar(self) self.alter_tb, self.item_search, self.search_button = tbb.alter_tb, tbb.item_search, tbb.search_button self.toggle_search_button = tbb.toggle_search_button self._layout.addWidget(tbb) self.current_find_position = None self.search_button.clicked.connect(self.find) self.item_search.lineEdit().returnPressed.connect(self.do_find) self.item_search.lineEdit().textEdited.connect(self.find_text_changed) self.item_search.activated[str].connect(self.do_find) # The tags view parent.tags_view = TagsView(parent) self.tags_view = parent.tags_view self._layout.insertWidget(0, parent.tags_view) # Now the floating 'not found' box l = QLabel(self.tags_view) self.not_found_label = l l.setFrameStyle(QFrame.StyledPanel) l.setAutoFillBackground(True) l.setText( '<p><b>' + _('No More Matches.</b><p> Click Find again to go to first match')) l.setAlignment(Qt.AlignVCenter) l.setWordWrap(True) l.resize(l.sizeHint()) l.move(10, 20) l.setVisible(False) self.not_found_label_timer = QTimer() self.not_found_label_timer.setSingleShot(True) self.not_found_label_timer.timeout.connect( self.not_found_label_timer_event, type=Qt.QueuedConnection) # The Alter Tag Browser button l = self.alter_tb self.collapse_all_action = ac = QAction(parent) parent.addAction(ac) parent.keyboard.register_shortcut('tag browser collapse all', _('Collapse all'), default_keys=(), action=ac, group=_('Tag browser')) ac.triggered.connect(lambda: self.tags_view.collapseAll()) ac = QAction(parent) parent.addAction(ac) parent.keyboard.register_shortcut('tag browser alter', _('Configure Tag browser'), default_keys=(), action=ac, group=_('Tag browser')) ac.triggered.connect(l.showMenu) sb = l.m.addAction(_('Sort by')) sb.m = l.sort_menu = QMenu(l.m) sb.setMenu(sb.m) sb.bg = QActionGroup(sb) # Must be in the same order as db2.CATEGORY_SORTS for i, x in enumerate( (_('Name'), _('Number of books'), _('Average rating'))): a = sb.m.addAction(x) sb.bg.addAction(a) a.setCheckable(True) if i == 0: a.setChecked(True) sb.setToolTip(_('Set the sort order for entries in the Tag browser')) sb.setStatusTip(sb.toolTip()) ma = l.m.addAction(_('Search type when selecting multiple items')) ma.m = l.match_menu = QMenu(l.m) ma.setMenu(ma.m) ma.ag = QActionGroup(ma) # Must be in the same order as db2.MATCH_TYPE for i, x in enumerate( (_('Match any of the items'), _('Match all of the items'))): a = ma.m.addAction(x) ma.ag.addAction(a) a.setCheckable(True) if i == 0: a.setChecked(True) ma.setToolTip( _('When selecting multiple entries in the Tag browser ' 'match any or all of them')) ma.setStatusTip(ma.toolTip()) mt = l.m.addAction(_('Manage authors, tags, etc.')) mt.setToolTip( _('All of these category_managers are available by right-clicking ' 'on items in the tag browser above')) mt.m = l.manage_menu = QMenu(l.m) mt.setMenu(mt.m) ac = QAction(parent) parent.addAction(ac) parent.keyboard.register_shortcut('tag browser toggle item', _("'Click' found item"), default_keys=(), action=ac, group=_('Tag browser')) ac.triggered.connect(self.toggle_item)
class EbookViewer(MainWindow): STATE_VERSION = 2 FLOW_MODE_TT = _('Switch to paged mode - where the text is broken up ' 'into pages like a paper book') PAGED_MODE_TT = _('Switch to flow mode - where the text is not broken up ' 'into pages') def __init__(self, pathtoebook=None, debug_javascript=False, open_at=None, start_in_fullscreen=False): MainWindow.__init__(self, debug_javascript) self.view.magnification_changed.connect(self.magnification_changed) self.closed = False self.show_toc_on_open = False self.current_book_has_toc = False self.iterator = None self.current_page = None self.pending_search = None self.pending_search_dir= None self.pending_anchor = None self.pending_reference = None self.pending_bookmark = None self.pending_restore = False self.cursor_hidden = False self.existing_bookmarks= [] self.selected_text = None self.was_maximized = False self.page_position_on_footnote_toggle = [] self.read_settings() self.pos.value_changed.connect(self.update_pos_label) self.pos.setMinimumWidth(150) self.setFocusPolicy(Qt.StrongFocus) self.view.set_manager(self) self.pi = ProgressIndicator(self) self.action_quit = QAction(_('&Quit'), self) self.addAction(self.action_quit) self.view_resized_timer = QTimer(self) self.view_resized_timer.timeout.connect(self.viewport_resize_finished) self.view_resized_timer.setSingleShot(True) self.resize_in_progress = False self.action_reload = QAction(_('&Reload book'), self) self.action_reload.triggered.connect(self.reload_book) self.action_quit.triggered.connect(self.quit) self.action_reference_mode.triggered[bool].connect(self.view.reference_mode) self.action_metadata.triggered[bool].connect(self.metadata.setVisible) self.action_table_of_contents.toggled[bool].connect(self.set_toc_visible) self.action_copy.triggered[bool].connect(self.copy) self.action_font_size_larger.triggered.connect(self.font_size_larger) self.action_font_size_smaller.triggered.connect(self.font_size_smaller) self.action_open_ebook.triggered[bool].connect(self.open_ebook) self.action_next_page.triggered.connect(self.view.next_page) self.action_previous_page.triggered.connect(self.view.previous_page) self.action_find_next.triggered.connect(self.find_next) self.action_find_previous.triggered.connect(self.find_previous) self.action_full_screen.triggered[bool].connect(self.toggle_fullscreen) self.action_back.triggered[bool].connect(self.back) self.action_forward.triggered[bool].connect(self.forward) self.action_preferences.triggered.connect(self.do_config) self.pos.editingFinished.connect(self.goto_page_num) self.vertical_scrollbar.valueChanged[int].connect(lambda x:self.goto_page(x/100.)) self.search.search.connect(self.find) self.search.focus_to_library.connect(lambda: self.view.setFocus(Qt.OtherFocusReason)) self.toc.pressed[QModelIndex].connect(self.toc_clicked) self.toc.searched.connect(partial(self.toc_clicked, force=True)) self.reference.goto.connect(self.goto) self.bookmarks.edited.connect(self.bookmarks_edited) self.bookmarks.activated.connect(self.goto_bookmark) self.bookmarks.create_requested.connect(self.bookmark) self.set_bookmarks([]) self.load_theme_menu() if pathtoebook is not None: f = functools.partial(self.load_ebook, pathtoebook, open_at=open_at) QTimer.singleShot(50, f) self.window_mode_changed = None self.toggle_toolbar_action = QAction(_('Show/hide controls'), self) self.toggle_toolbar_action.setCheckable(True) self.toggle_toolbar_action.triggered.connect(self.toggle_toolbars) self.toolbar_hidden = None self.addAction(self.toggle_toolbar_action) self.full_screen_label_anim = QPropertyAnimation( self.full_screen_label, 'size') self.clock_timer = QTimer(self) self.clock_timer.timeout.connect(self.update_clock) self.action_print.triggered.connect(self.print_book) self.clear_recent_history_action = QAction( _('Clear list of recently opened books'), self) self.clear_recent_history_action.triggered.connect(self.clear_recent_history) self.build_recent_menu() self.open_history_menu.triggered.connect(self.open_recent) for x in ('tool_bar', 'tool_bar2'): x = getattr(self, x) for action in x.actions(): # So that the keyboard shortcuts for these actions will # continue to function even when the toolbars are hidden self.addAction(action) for plugin in self.view.document.all_viewer_plugins: plugin.customize_ui(self) self.view.document.settings_changed.connect(self.settings_changed) self.restore_state() self.settings_changed() self.action_toggle_paged_mode.toggled[bool].connect(self.toggle_paged_mode) if (start_in_fullscreen or self.view.document.start_in_fullscreen): self.action_full_screen.trigger() self.hide_cursor_timer = t = QTimer(self) t.setSingleShot(True), t.setInterval(3000) t.timeout.connect(self.hide_cursor) t.start() def eventFilter(self, obj, ev): if ev.type() == ev.MouseMove: if self.cursor_hidden: self.cursor_hidden = False QApplication.instance().restoreOverrideCursor() self.hide_cursor_timer.start() return False def hide_cursor(self): self.cursor_hidden = True QApplication.instance().setOverrideCursor(Qt.BlankCursor) def toggle_paged_mode(self, checked, at_start=False): in_paged_mode = not self.action_toggle_paged_mode.isChecked() self.view.document.in_paged_mode = in_paged_mode self.action_toggle_paged_mode.setToolTip(self.FLOW_MODE_TT if self.action_toggle_paged_mode.isChecked() else self.PAGED_MODE_TT) if at_start: return self.reload() def settings_changed(self): for x in ('', '2'): x = getattr(self, 'tool_bar'+x) x.setVisible(self.view.document.show_controls) def reload(self): if hasattr(self, 'current_index') and self.current_index > -1: self.view.document.page_position.save(overwrite=False) self.pending_restore = True self.load_path(self.view.last_loaded_path) def set_toc_visible(self, yes): self.toc_dock.setVisible(yes) if not yes: self.show_toc_on_open = False def clear_recent_history(self, *args): vprefs.set('viewer_open_history', []) self.build_recent_menu() def build_recent_menu(self): m = self.open_history_menu m.clear() recent = vprefs.get('viewer_open_history', []) if recent: m.addAction(self.clear_recent_history_action) m.addSeparator() count = 0 for path in recent: if count > 9: break if os.path.exists(path): m.addAction(RecentAction(path, m)) count += 1 def shutdown(self): if self.isFullScreen() and not self.view.document.start_in_fullscreen: self.action_full_screen.trigger() return False self.save_state() return True def quit(self): if self.shutdown(): QApplication.instance().quit() def closeEvent(self, e): if self.closed: e.ignore() return if self.shutdown(): self.closed = True return MainWindow.closeEvent(self, e) else: e.ignore() def toggle_toolbars(self): for x in ('tool_bar', 'tool_bar2'): x = getattr(self, x) x.setVisible(not x.isVisible()) def save_state(self): state = bytearray(self.saveState(self.STATE_VERSION)) vprefs['main_window_state'] = state if not self.isFullScreen(): vprefs.set('viewer_window_geometry', bytearray(self.saveGeometry())) if self.current_book_has_toc: vprefs.set('viewer_toc_isvisible', self.show_toc_on_open or bool(self.toc_dock.isVisible())) vprefs['multiplier'] = self.view.multiplier vprefs['in_paged_mode'] = not self.action_toggle_paged_mode.isChecked() def restore_state(self): state = vprefs.get('main_window_state', None) if state is not None: try: state = QByteArray(state) self.restoreState(state, self.STATE_VERSION) except: pass self.initialize_dock_state() mult = vprefs.get('multiplier', None) if mult: self.view.multiplier = mult # On windows Qt lets the user hide toolbars via a right click in a very # specific location, ensure they are visible. self.tool_bar.setVisible(True) self.tool_bar2.setVisible(True) self.toc_dock.close() # This will be opened on book open, if the book has a toc and it was previously opened self.action_toggle_paged_mode.setChecked(not vprefs.get('in_paged_mode', True)) self.toggle_paged_mode(self.action_toggle_paged_mode.isChecked(), at_start=True) def lookup(self, word): from urllib import quote word = quote(word.encode('utf-8')) try: url = lookup_website(canonicalize_lang(self.view.current_language) or 'en').format(word=word) except Exception: traceback.print_exc() url = default_lookup_website(canonicalize_lang(self.view.current_language) or 'en').format(word=word) open_url(url) def print_book(self): from calibre.gui2.viewer.printing import print_book print_book(self.iterator.pathtoebook, self, self.current_title) def toggle_fullscreen(self): if self.isFullScreen(): self.showNormal() else: self.showFullScreen() def showFullScreen(self): self.view.document.page_position.save() self.window_mode_changed = 'fullscreen' self.tool_bar.setVisible(False) self.tool_bar2.setVisible(False) self.was_maximized = self.isMaximized() if not self.view.document.fullscreen_scrollbar: self.vertical_scrollbar.setVisible(False) super(EbookViewer, self).showFullScreen() def show_full_screen_label(self): f = self.full_screen_label height = f.final_height width = int(0.7*self.view.width()) f.resize(width, height) if self.view.document.show_fullscreen_help: f.setVisible(True) a = self.full_screen_label_anim a.setDuration(500) a.setStartValue(QSize(width, 0)) a.setEndValue(QSize(width, height)) a.start() QTimer.singleShot(3500, self.full_screen_label.hide) self.view.document.switch_to_fullscreen_mode() if self.view.document.fullscreen_clock: self.show_clock() if self.view.document.fullscreen_pos: self.show_pos_label() self.relayout_fullscreen_labels() def show_clock(self): self.clock_label.setVisible(True) self.clock_label.setText(QTime(22, 33, 33).toString(Qt.SystemLocaleShortDate)) self.clock_timer.start(1000) self.clock_label.setStyleSheet(self.info_label_style%( 'rgba(0, 0, 0, 0)', self.view.document.colors()[1])) self.clock_label.resize(self.clock_label.sizeHint()) self.update_clock() def show_pos_label(self): self.pos_label.setVisible(True) self.pos_label.setStyleSheet(self.info_label_style%( 'rgba(0, 0, 0, 0)', self.view.document.colors()[1])) self.update_pos_label() def relayout_fullscreen_labels(self): vswidth = (self.vertical_scrollbar.width() if self.vertical_scrollbar.isVisible() else 0) p = self.pos_label p.move(15, p.parent().height() - p.height()-10) c = self.clock_label c.move(c.parent().width() - vswidth - 15 - c.width(), c.parent().height() - c.height() - 10) f = self.full_screen_label f.move((f.parent().width() - f.width())//2, (f.parent().height() - f.final_height)//2) def update_clock(self): self.clock_label.setText(QTime.currentTime().toString(Qt.SystemLocaleShortDate)) def update_pos_label(self, *args): if self.pos_label.isVisible(): try: value, maximum = args except: value, maximum = self.pos.value(), self.pos.maximum() text = '%g/%g'%(value, maximum) self.pos_label.setText(text) self.pos_label.resize(self.pos_label.sizeHint()) def showNormal(self): self.view.document.page_position.save() self.clock_label.setVisible(False) self.pos_label.setVisible(False) self.clock_timer.stop() self.vertical_scrollbar.setVisible(True) self.window_mode_changed = 'normal' self.settings_changed() self.full_screen_label.setVisible(False) if self.was_maximized: super(EbookViewer, self).showMaximized() else: super(EbookViewer, self).showNormal() def handle_window_mode_toggle(self): if self.window_mode_changed: fs = self.window_mode_changed == 'fullscreen' self.window_mode_changed = None if fs: self.show_full_screen_label() else: self.view.document.switch_to_window_mode() self.view.document.page_position.restore() self.scrolled(self.view.scroll_fraction) def goto(self, ref): if ref: tokens = ref.split('.') if len(tokens) > 1: spine_index = int(tokens[0]) -1 if spine_index == self.current_index: self.view.goto(ref) else: self.pending_reference = ref self.load_path(self.iterator.spine[spine_index]) def goto_bookmark(self, bm): spine_index = bm['spine'] if spine_index > -1 and self.current_index == spine_index: if self.resize_in_progress: self.view.document.page_position.set_pos(bm['pos']) else: self.view.goto_bookmark(bm) # Going to a bookmark does not call scrolled() so we update the # page position explicitly. Use a timer to ensure it is # accurate. QTimer.singleShot(100, self.update_page_number) else: self.pending_bookmark = bm if spine_index < 0 or spine_index >= len(self.iterator.spine): spine_index = 0 self.pending_bookmark = None self.load_path(self.iterator.spine[spine_index]) def toc_clicked(self, index, force=False): if force or QApplication.mouseButtons() & Qt.LeftButton: item = self.toc_model.itemFromIndex(index) if item.abspath is not None: if not os.path.exists(item.abspath): return error_dialog(self, _('No such location'), _('The location pointed to by this item' ' does not exist.'), det_msg=item.abspath, show=True) url = QUrl.fromLocalFile(item.abspath) if item.fragment: url.setFragment(item.fragment) self.link_clicked(url) self.view.setFocus(Qt.OtherFocusReason) def selection_changed(self, selected_text): self.selected_text = selected_text.strip() self.action_copy.setEnabled(bool(self.selected_text)) def copy(self, x): if self.selected_text: QApplication.clipboard().setText(self.selected_text) def back(self, x): pos = self.history.back(self.pos.value()) if pos is not None: self.goto_page(pos) def goto_page_num(self): num = self.pos.value() self.goto_page(num) def forward(self, x): pos = self.history.forward(self.pos.value()) if pos is not None: self.goto_page(pos) def goto_start(self): self.goto_page(1) def goto_end(self): self.goto_page(self.pos.maximum()) def goto_page(self, new_page, loaded_check=True): if self.current_page is not None or not loaded_check: for page in self.iterator.spine: if new_page >= page.start_page and new_page <= page.max_page: try: frac = float(new_page-page.start_page)/(page.pages-1) except ZeroDivisionError: frac = 0 if page == self.current_page: self.view.scroll_to(frac) else: self.load_path(page, pos=frac) def open_ebook(self, checked): files = choose_files(self, 'ebook viewer open dialog', _('Choose ebook'), [(_('Ebooks'), available_input_formats())], all_files=False, select_only_single_file=True) if files: self.load_ebook(files[0]) def open_recent(self, action): self.load_ebook(action.path) def font_size_larger(self): self.view.magnify_fonts() def font_size_smaller(self): self.view.shrink_fonts() def magnification_changed(self, val): tt = '%(action)s [%(sc)s]\n'+_('Current magnification: %(mag).1f') sc = _(' or ').join(self.view.shortcuts.get_shortcuts('Font larger')) self.action_font_size_larger.setToolTip( tt %dict(action=unicode(self.action_font_size_larger.text()), mag=val, sc=sc)) sc = _(' or ').join(self.view.shortcuts.get_shortcuts('Font smaller')) self.action_font_size_smaller.setToolTip( tt %dict(action=unicode(self.action_font_size_smaller.text()), mag=val, sc=sc)) self.action_font_size_larger.setEnabled(self.view.multiplier < 3) self.action_font_size_smaller.setEnabled(self.view.multiplier > 0.2) def find(self, text, repeat=False, backwards=False): if not text: self.view.search('') return self.search.search_done(False) if self.view.search(text, backwards=backwards): self.scrolled(self.view.scroll_fraction) return self.search.search_done(True) index = self.iterator.search(text, self.current_index, backwards=backwards) if index is None: if self.current_index > 0: index = self.iterator.search(text, 0) if index is None: info_dialog(self, _('No matches found'), _('No matches found for: %s')%text).exec_() return self.search.search_done(True) return self.search.search_done(True) self.pending_search = text self.pending_search_dir = 'backwards' if backwards else 'forwards' self.load_path(self.iterator.spine[index]) def find_next(self): self.find(unicode(self.search.text()), repeat=True) def find_previous(self): self.find(unicode(self.search.text()), repeat=True, backwards=True) def do_search(self, text, backwards): self.pending_search = None self.pending_search_dir = None if self.view.search(text, backwards=backwards): self.scrolled(self.view.scroll_fraction) def internal_link_clicked(self, prev_pos): self.history.add(prev_pos) def link_clicked(self, url): path = os.path.abspath(unicode(url.toLocalFile())) frag = None if path in self.iterator.spine: self.update_page_number() # Ensure page number is accurate as it is used for history self.history.add(self.pos.value()) path = self.iterator.spine[self.iterator.spine.index(path)] if url.hasFragment(): frag = unicode(url.fragment()) if path != self.current_page: self.pending_anchor = frag self.load_path(path) else: oldpos = self.view.document.ypos if frag: self.view.scroll_to(frag) else: # Scroll to top self.view.scroll_to(0) if self.view.document.ypos == oldpos: # If we are coming from goto_next_section() call this will # cause another goto next section call with the next toc # entry, since this one did not cause any scrolling at all. QTimer.singleShot(10, self.update_indexing_state) else: open_url(url) def load_started(self): self.open_progress_indicator(_('Loading flow...')) def load_finished(self, ok): self.close_progress_indicator() path = self.view.path() try: index = self.iterator.spine.index(path) except (ValueError, AttributeError): return -1 self.current_page = self.iterator.spine[index] self.current_index = index self.set_page_number(self.view.scroll_fraction) QTimer.singleShot(100, self.update_indexing_state) if self.pending_search is not None: self.do_search(self.pending_search, self.pending_search_dir=='backwards') self.pending_search = None self.pending_search_dir = None if self.pending_anchor is not None: self.view.scroll_to(self.pending_anchor) self.pending_anchor = None if self.pending_reference is not None: self.view.goto(self.pending_reference) self.pending_reference = None if self.pending_bookmark is not None: self.goto_bookmark(self.pending_bookmark) self.pending_bookmark = None if self.pending_restore: self.view.document.page_position.restore() return self.current_index def goto_next_section(self): if hasattr(self, 'current_index'): entry = self.toc_model.next_entry(self.current_index, self.view.document.read_anchor_positions(), self.view.viewport_rect, self.view.document.in_paged_mode) if entry is not None: self.pending_goto_next_section = ( self.toc_model.currently_viewed_entry, entry, False) self.toc_clicked(entry.index(), force=True) def goto_previous_section(self): if hasattr(self, 'current_index'): entry = self.toc_model.next_entry(self.current_index, self.view.document.read_anchor_positions(), self.view.viewport_rect, self.view.document.in_paged_mode, backwards=True) if entry is not None: self.pending_goto_next_section = ( self.toc_model.currently_viewed_entry, entry, True) self.toc_clicked(entry.index(), force=True) def update_indexing_state(self, anchor_positions=None): pgns = getattr(self, 'pending_goto_next_section', None) if hasattr(self, 'current_index'): if anchor_positions is None: anchor_positions = self.view.document.read_anchor_positions() items = self.toc_model.update_indexing_state(self.current_index, self.view.viewport_rect, anchor_positions, self.view.document.in_paged_mode) if items: self.toc.scrollTo(items[-1].index()) if pgns is not None: self.pending_goto_next_section = None # Check that we actually progressed if pgns[0] is self.toc_model.currently_viewed_entry: entry = self.toc_model.next_entry(self.current_index, self.view.document.read_anchor_positions(), self.view.viewport_rect, self.view.document.in_paged_mode, backwards=pgns[2], current_entry=pgns[1]) if entry is not None: self.pending_goto_next_section = ( self.toc_model.currently_viewed_entry, entry, pgns[2]) self.toc_clicked(entry.index(), force=True) def load_path(self, path, pos=0.0): self.open_progress_indicator(_('Laying out %s')%self.current_title) self.view.load_path(path, pos=pos) def footnote_visibility_changed(self, is_visible): if self.view.document.in_paged_mode: pp = namedtuple('PagePosition', 'time is_visible page_dimensions multiplier last_loaded_path page_number after_resize_page_number') self.page_position_on_footnote_toggle.append(pp( time.time(), is_visible, self.view.document.page_dimensions, self.view.multiplier, self.view.last_loaded_path, self.view.document.page_number, None)) def pre_footnote_toggle_position(self): num = len(self.page_position_on_footnote_toggle) if self.view.document.in_paged_mode and num > 1 and num % 2 == 0: two, one = self.page_position_on_footnote_toggle.pop(), self.page_position_on_footnote_toggle.pop() if ( time.time() - two.time < 1 and not two.is_visible and one.is_visible and one.last_loaded_path == two.last_loaded_path and two.last_loaded_path == self.view.last_loaded_path and one.page_dimensions == self.view.document.page_dimensions and one.multiplier == self.view.multiplier and one.after_resize_page_number == self.view.document.page_number ): return one.page_number def viewport_resize_started(self, event): if not self.resize_in_progress: # First resize, so save the current page position self.resize_in_progress = True if not self.window_mode_changed: # The special handling for window mode changed will already # have saved page position, so only save it if this is not a # mode change self.view.document.page_position.save() if self.resize_in_progress: self.view_resized_timer.start(75) def viewport_resize_finished(self): # There hasn't been a resize event for some time # restore the current page position. self.resize_in_progress = False wmc = self.window_mode_changed if self.window_mode_changed: # This resize is part of a window mode change, special case it self.handle_window_mode_toggle() else: if self.isFullScreen(): self.relayout_fullscreen_labels() self.view.document.after_resize() if not wmc: pre_footnote_pos = self.pre_footnote_toggle_position() if pre_footnote_pos is not None: self.view.document.page_number = pre_footnote_pos else: self.view.document.page_position.restore() self.update_page_number() if len(self.page_position_on_footnote_toggle) % 2 == 1: self.page_position_on_footnote_toggle[-1] = self.page_position_on_footnote_toggle[-1]._replace( after_resize_page_number=self.view.document.page_number) def update_page_number(self): self.set_page_number(self.view.document.scroll_fraction) return self.pos.value() def close_progress_indicator(self): self.pi.stop() for o in ('tool_bar', 'tool_bar2', 'view', 'horizontal_scrollbar', 'vertical_scrollbar'): getattr(self, o).setEnabled(True) self.unsetCursor() self.view.setFocus(Qt.PopupFocusReason) def open_progress_indicator(self, msg=''): self.pi.start(msg) for o in ('tool_bar', 'tool_bar2', 'view', 'horizontal_scrollbar', 'vertical_scrollbar'): getattr(self, o).setEnabled(False) self.setCursor(Qt.BusyCursor) def load_theme_menu(self): from calibre.gui2.viewer.config import load_themes self.themes_menu.clear() for key in load_themes(): title = key[len('theme_'):] self.themes_menu.addAction(title, partial(self.load_theme, key)) def load_theme(self, theme_id): self.view.load_theme(theme_id) def do_config(self): self.view.config(self) self.load_theme_menu() if self.iterator is not None: self.iterator.copy_bookmarks_to_file = self.view.document.copy_bookmarks_to_file from calibre.gui2 import config if not config['viewer_search_history']: self.search.clear_history() def bookmark(self, *args): num = 1 bm = None while True: bm = _('Bookmark #%d')%num if bm not in self.existing_bookmarks: break num += 1 title, ok = QInputDialog.getText(self, _('Add bookmark'), _('Enter title for bookmark:'), text=bm) title = unicode(title).strip() if ok and title: bm = self.view.bookmark() bm['spine'] = self.current_index bm['title'] = title self.iterator.add_bookmark(bm) self.set_bookmarks(self.iterator.bookmarks) self.bookmarks.set_current_bookmark(bm) def bookmarks_edited(self, bookmarks): self.build_bookmarks_menu(bookmarks) self.iterator.set_bookmarks(bookmarks) self.iterator.save_bookmarks() def build_bookmarks_menu(self, bookmarks): self.bookmarks_menu.clear() sc = _(' or ').join(self.view.shortcuts.get_shortcuts('Bookmark')) self.bookmarks_menu.addAction(_("Bookmark this location [%s]") % sc, self.bookmark) self.bookmarks_menu.addAction(_("Show/hide Bookmarks"), self.bookmarks_dock.toggleViewAction().trigger) self.bookmarks_menu.addSeparator() current_page = None self.existing_bookmarks = [] for bm in bookmarks: if bm['title'] == 'calibre_current_page_bookmark': if self.view.document.remember_current_page: current_page = bm else: self.existing_bookmarks.append(bm['title']) self.bookmarks_menu.addAction(bm['title'], partial(self.goto_bookmark, bm)) return current_page def set_bookmarks(self, bookmarks): self.bookmarks.set_bookmarks(bookmarks) return self.build_bookmarks_menu(bookmarks) @property def current_page_bookmark(self): bm = self.view.bookmark() bm['spine'] = self.current_index bm['title'] = 'calibre_current_page_bookmark' return bm def save_current_position(self): if not self.view.document.remember_current_page: return if hasattr(self, 'current_index'): try: self.iterator.add_bookmark(self.current_page_bookmark) except: traceback.print_exc() def load_ebook(self, pathtoebook, open_at=None, reopen_at=None): if self.iterator is not None: self.save_current_position() self.iterator.__exit__() self.iterator = EbookIterator(pathtoebook, copy_bookmarks_to_file=self.view.document.copy_bookmarks_to_file) self.history.clear() self.open_progress_indicator(_('Loading ebook...')) worker = Worker(target=partial(self.iterator.__enter__, view_kepub=True)) worker.start() while worker.isAlive(): worker.join(0.1) QApplication.processEvents() if worker.exception is not None: if isinstance(worker.exception, DRMError): from calibre.gui2.dialogs.drm_error import DRMErrorMessage DRMErrorMessage(self).exec_() else: r = getattr(worker.exception, 'reason', worker.exception) error_dialog(self, _('Could not open ebook'), as_unicode(r) or _('Unknown error'), det_msg=worker.traceback, show=True) self.close_progress_indicator() else: self.metadata.show_opf(self.iterator.opf, self.iterator.book_format) self.view.current_language = self.iterator.language title = self.iterator.opf.title if not title: title = os.path.splitext(os.path.basename(pathtoebook))[0] if self.iterator.toc: self.toc_model = TOC(self.iterator.spine, self.iterator.toc) self.toc.setModel(self.toc_model) if self.show_toc_on_open: self.action_table_of_contents.setChecked(True) else: self.toc_model = TOC(self.iterator.spine) self.toc.setModel(self.toc_model) self.action_table_of_contents.setChecked(False) if isbytestring(pathtoebook): pathtoebook = force_unicode(pathtoebook, filesystem_encoding) vh = vprefs.get('viewer_open_history', []) try: vh.remove(pathtoebook) except: pass vh.insert(0, pathtoebook) vprefs.set('viewer_open_history', vh[:50]) self.build_recent_menu() self.footnotes_dock.close() self.action_table_of_contents.setDisabled(not self.iterator.toc) self.current_book_has_toc = bool(self.iterator.toc) self.current_title = title self.setWindowTitle(title + ' [%s]'%self.iterator.book_format + ' - ' + self.base_window_title) self.pos.setMaximum(sum(self.iterator.pages)) self.pos.setSuffix(' / %d'%sum(self.iterator.pages)) self.vertical_scrollbar.setMinimum(100) self.vertical_scrollbar.setMaximum(100*sum(self.iterator.pages)) self.vertical_scrollbar.setSingleStep(10) self.vertical_scrollbar.setPageStep(100) self.set_vscrollbar_value(1) self.current_index = -1 QApplication.instance().alert(self, 5000) previous = self.set_bookmarks(self.iterator.bookmarks) if reopen_at is not None: previous = reopen_at if open_at is None and previous is not None: self.goto_bookmark(previous) else: if open_at is None: self.next_document() else: if open_at > self.pos.maximum(): open_at = self.pos.maximum() if open_at < self.pos.minimum(): open_at = self.pos.minimum() self.goto_page(open_at, loaded_check=False) def set_vscrollbar_value(self, pagenum): self.vertical_scrollbar.blockSignals(True) self.vertical_scrollbar.setValue(int(pagenum*100)) self.vertical_scrollbar.blockSignals(False) def set_page_number(self, frac): if getattr(self, 'current_page', None) is not None: page = self.current_page.start_page + frac*float(self.current_page.pages-1) self.pos.set_value(page) self.set_vscrollbar_value(page) def scrolled(self, frac, onload=False): self.set_page_number(frac) if not onload: ap = self.view.document.read_anchor_positions() self.update_indexing_state(ap) def next_document(self): if (hasattr(self, 'current_index') and self.current_index < len(self.iterator.spine) - 1): self.load_path(self.iterator.spine[self.current_index+1]) def previous_document(self): if hasattr(self, 'current_index') and self.current_index > 0: self.load_path(self.iterator.spine[self.current_index-1], pos=1.0) def keyPressEvent(self, event): if event.key() == Qt.Key_Escape: if self.metadata.isVisible(): self.metadata.setVisible(False) event.accept() return if self.isFullScreen(): self.action_full_screen.trigger() event.accept() return try: key = self.view.shortcuts.get_match(event) except AttributeError: return MainWindow.keyPressEvent(self, event) try: bac = self.bookmarks_menu.actions()[0] except (AttributeError, TypeError, IndexError, KeyError): bac = None action = { 'Quit':self.action_quit, 'Show metadata':self.action_metadata, 'Copy':self.view.copy_action, 'Font larger': self.action_font_size_larger, 'Font smaller': self.action_font_size_smaller, 'Fullscreen': self.action_full_screen, 'Find next': self.action_find_next, 'Find previous': self.action_find_previous, 'Search online': self.view.search_online_action, 'Lookup word': self.view.dictionary_action, 'Next occurrence': self.view.search_action, 'Bookmark': bac, 'Reload': self.action_reload, 'Table of Contents': self.action_table_of_contents, }.get(key, None) if action is not None: event.accept() action.trigger() return if key == 'Focus Search': self.search.setFocus(Qt.OtherFocusReason) return if not self.view.handle_key_press(event): event.ignore() def reload_book(self): if getattr(self.iterator, 'pathtoebook', None): try: reopen_at = self.current_page_bookmark except Exception: reopen_at = None self.history.clear() self.load_ebook(self.iterator.pathtoebook, reopen_at=reopen_at) return def __enter__(self): return self def __exit__(self, *args): if self.iterator is not None: self.save_current_position() self.iterator.__exit__(*args) def read_settings(self): c = config().parse() if c.remember_window_size: wg = vprefs.get('viewer_window_geometry', None) if wg is not None: self.restoreGeometry(wg) self.show_toc_on_open = vprefs.get('viewer_toc_isvisible', False) desktop = QApplication.instance().desktop() av = desktop.availableGeometry(self).height() - 30 if self.height() > av: self.resize(self.width(), av) def show_footnote_view(self): self.footnotes_dock.show()
def _create_context_menu(self): self.plugin_view.setContextMenuPolicy(Qt.ActionsContextMenu) self.install_action = QAction(QIcon(I('plugins/plugin_upgrade_ok.png')), _('&Install'), self) self.install_action.setToolTip(_('Install the selected plugin')) self.install_action.triggered.connect(self._install_clicked) self.install_action.setEnabled(False) self.plugin_view.addAction(self.install_action) self.history_action = QAction(QIcon(I('chapters.png')), _('Version &history'), self) self.history_action.setToolTip(_('Show history of changes to this plugin')) self.history_action.triggered.connect(self._history_clicked) self.history_action.setEnabled(False) self.plugin_view.addAction(self.history_action) self.forum_action = QAction(QIcon(I('plugins/mobileread.png')), _('Plugin &forum thread'), self) self.forum_action.triggered.connect(self._forum_label_activated) self.forum_action.setEnabled(False) self.plugin_view.addAction(self.forum_action) sep1 = QAction(self) sep1.setSeparator(True) self.plugin_view.addAction(sep1) self.toggle_enabled_action = QAction(_('Enable/&disable plugin'), self) self.toggle_enabled_action.setToolTip(_('Enable or disable this plugin')) self.toggle_enabled_action.triggered.connect(self._toggle_enabled_clicked) self.toggle_enabled_action.setEnabled(False) self.plugin_view.addAction(self.toggle_enabled_action) self.uninstall_action = QAction(_('&Remove plugin'), self) self.uninstall_action.setToolTip(_('Uninstall the selected plugin')) self.uninstall_action.triggered.connect(self._uninstall_clicked) self.uninstall_action.setEnabled(False) self.plugin_view.addAction(self.uninstall_action) sep2 = QAction(self) sep2.setSeparator(True) self.plugin_view.addAction(sep2) self.donate_enabled_action = QAction(QIcon(I('donate.png')), _('Donate to developer'), self) self.donate_enabled_action.setToolTip(_('Donate to the developer of this plugin')) self.donate_enabled_action.triggered.connect(self._donate_clicked) self.donate_enabled_action.setEnabled(False) self.plugin_view.addAction(self.donate_enabled_action) sep3 = QAction(self) sep3.setSeparator(True) self.plugin_view.addAction(sep3) self.configure_action = QAction(QIcon(I('config.png')), _('&Customize plugin'), self) self.configure_action.setToolTip(_('Customize the options for this plugin')) self.configure_action.triggered.connect(self._configure_clicked) self.configure_action.setEnabled(False) self.plugin_view.addAction(self.configure_action)
def __init__(self, pathtoebook=None, debug_javascript=False, open_at=None, start_in_fullscreen=False): MainWindow.__init__(self, debug_javascript) self.view.magnification_changed.connect(self.magnification_changed) self.closed = False self.show_toc_on_open = False self.current_book_has_toc = False self.iterator = None self.current_page = None self.pending_search = None self.pending_search_dir= None self.pending_anchor = None self.pending_reference = None self.pending_bookmark = None self.pending_restore = False self.cursor_hidden = False self.existing_bookmarks= [] self.selected_text = None self.was_maximized = False self.page_position_on_footnote_toggle = [] self.read_settings() self.pos.value_changed.connect(self.update_pos_label) self.pos.setMinimumWidth(150) self.setFocusPolicy(Qt.StrongFocus) self.view.set_manager(self) self.pi = ProgressIndicator(self) self.action_quit = QAction(_('&Quit'), self) self.addAction(self.action_quit) self.view_resized_timer = QTimer(self) self.view_resized_timer.timeout.connect(self.viewport_resize_finished) self.view_resized_timer.setSingleShot(True) self.resize_in_progress = False self.action_reload = QAction(_('&Reload book'), self) self.action_reload.triggered.connect(self.reload_book) self.action_quit.triggered.connect(self.quit) self.action_reference_mode.triggered[bool].connect(self.view.reference_mode) self.action_metadata.triggered[bool].connect(self.metadata.setVisible) self.action_table_of_contents.toggled[bool].connect(self.set_toc_visible) self.action_copy.triggered[bool].connect(self.copy) self.action_font_size_larger.triggered.connect(self.font_size_larger) self.action_font_size_smaller.triggered.connect(self.font_size_smaller) self.action_open_ebook.triggered[bool].connect(self.open_ebook) self.action_next_page.triggered.connect(self.view.next_page) self.action_previous_page.triggered.connect(self.view.previous_page) self.action_find_next.triggered.connect(self.find_next) self.action_find_previous.triggered.connect(self.find_previous) self.action_full_screen.triggered[bool].connect(self.toggle_fullscreen) self.action_back.triggered[bool].connect(self.back) self.action_forward.triggered[bool].connect(self.forward) self.action_preferences.triggered.connect(self.do_config) self.pos.editingFinished.connect(self.goto_page_num) self.vertical_scrollbar.valueChanged[int].connect(lambda x:self.goto_page(x/100.)) self.search.search.connect(self.find) self.search.focus_to_library.connect(lambda: self.view.setFocus(Qt.OtherFocusReason)) self.toc.pressed[QModelIndex].connect(self.toc_clicked) self.toc.searched.connect(partial(self.toc_clicked, force=True)) self.reference.goto.connect(self.goto) self.bookmarks.edited.connect(self.bookmarks_edited) self.bookmarks.activated.connect(self.goto_bookmark) self.bookmarks.create_requested.connect(self.bookmark) self.set_bookmarks([]) self.load_theme_menu() if pathtoebook is not None: f = functools.partial(self.load_ebook, pathtoebook, open_at=open_at) QTimer.singleShot(50, f) self.window_mode_changed = None self.toggle_toolbar_action = QAction(_('Show/hide controls'), self) self.toggle_toolbar_action.setCheckable(True) self.toggle_toolbar_action.triggered.connect(self.toggle_toolbars) self.toolbar_hidden = None self.addAction(self.toggle_toolbar_action) self.full_screen_label_anim = QPropertyAnimation( self.full_screen_label, 'size') self.clock_timer = QTimer(self) self.clock_timer.timeout.connect(self.update_clock) self.action_print.triggered.connect(self.print_book) self.clear_recent_history_action = QAction( _('Clear list of recently opened books'), self) self.clear_recent_history_action.triggered.connect(self.clear_recent_history) self.build_recent_menu() self.open_history_menu.triggered.connect(self.open_recent) for x in ('tool_bar', 'tool_bar2'): x = getattr(self, x) for action in x.actions(): # So that the keyboard shortcuts for these actions will # continue to function even when the toolbars are hidden self.addAction(action) for plugin in self.view.document.all_viewer_plugins: plugin.customize_ui(self) self.view.document.settings_changed.connect(self.settings_changed) self.restore_state() self.settings_changed() self.action_toggle_paged_mode.toggled[bool].connect(self.toggle_paged_mode) if (start_in_fullscreen or self.view.document.start_in_fullscreen): self.action_full_screen.trigger() self.hide_cursor_timer = t = QTimer(self) t.setSingleShot(True), t.setInterval(3000) t.timeout.connect(self.hide_cursor) t.start()