Example #1
0
 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)
Example #2
0
 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)
Example #3
0
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)
Example #4
0
 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)
Example #5
0
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)
Example #6
0
 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()
Example #7
0
    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)
Example #8
0
 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
Example #9
0
    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)
Example #10
0
    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)
Example #11
0
File: jobs.py Project: kba/calibre
    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)
Example #12
0
    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)
Example #13
0
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)))
Example #14
0
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()
Example #15
0
 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)
Example #16
0
 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)
Example #17
0
        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)
Example #18
0
        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)
Example #19
0
        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)
Example #20
0
    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
Example #21
0
 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)
Example #22
0
File: ui.py Project: Cykooz/calibre
 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
Example #23
0
    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)
Example #24
0
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)
Example #25
0
 def __init__(self, path, parent):
     self.path = path
     QAction.__init__(self, os.path.basename(path), parent)
Example #26
0
    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)
Example #27
0
    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()
Example #28
0
 def __init__(self, clone, parent):
     QAction.__init__(self, clone.text(), parent)
     self.clone = clone
     clone.changed.connect(self.clone_changed)
Example #29
0
    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)
Example #30
0
    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
Example #31
0
    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)
Example #32
0
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
Example #33
0
    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()
Example #34
0
    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()
Example #35
0
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())
Example #36
0
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 &lt;%s&gt;') % 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())
Example #37
0
    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()
Example #38
0
    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()
Example #39
0
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)
Example #40
0
 def __init__(self, text, name, view):
     QAction.__init__(self, text, view)
     self._name = name
     self.triggered.connect(self.apply_style)
Example #41
0
 def __init__(self, text, key, ascending, parent):
     QAction.__init__(self, text, parent)
     self.key, self.ascending = key, ascending
     self.triggered.connect(self)
Example #42
0
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())
Example #43
0
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
Example #44
0
    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)
Example #45
0
    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)
Example #46
0
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()
Example #47
0
    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)
Example #48
0
    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)
Example #49
0
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()
Example #50
0
 def __init__(self, id_, title, parent):
     QAction.__init__(self, title, parent)
     self.id = id_
     self.triggered.connect(self._triggered)
Example #51
0
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()
Example #52
0
    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)
Example #53
0
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()
Example #54
0
    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()
Example #55
0
 def __init__(self, path, parent):
     self.path = path
     QAction.__init__(self, os.path.basename(path), parent)
Example #56
0
    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)
Example #57
0
 def __init__(self, id_, title, parent):
     QAction.__init__(self, title, parent)
     self.id = id_
     self.triggered.connect(self._triggered)
Example #58
0
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()
Example #59
0
    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)
Example #60
0
    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()