Beispiel #1
0
class Comments(HTMLDisplay):  # {{{

    def __init__(self, parent=None):
        HTMLDisplay.__init__(self, parent)
        self.setAcceptDrops(False)
        self.wait_timer = QTimer(self)
        self.wait_timer.timeout.connect(self.update_wait)
        self.wait_timer.setInterval(800)
        self.dots_count = 0
        self.anchor_clicked.connect(self.link_activated)

    def link_activated(self, url):
        from calibre.gui2 import open_url
        if url.scheme() in {'http', 'https'}:
            open_url(url)

    def show_wait(self):
        self.dots_count = 0
        self.wait_timer.start()
        self.update_wait()

    def update_wait(self):
        self.dots_count += 1
        self.dots_count %= 10
        self.dots_count = self.dots_count or 1
        self.setHtml(
            '<h2>'+_('Please wait')+
            '<br><span id="dots">{}</span></h2>'.format('.' * self.dots_count))

    def show_data(self, html):
        self.wait_timer.stop()

        def color_to_string(col):
            ans = '#000000'
            if col.isValid():
                col = col.toRgb()
                if col.isValid():
                    ans = unicode_type(col.name())
            return ans

        c = color_to_string(QApplication.palette().color(QPalette.ColorGroup.Normal,
                        QPalette.ColorRole.WindowText))
        templ = '''\
        <html>
            <head>
            <style type="text/css">
                body, td {background-color: transparent; color: %s }
                a { text-decoration: none; }
                div.description { margin-top: 0; padding-top: 0; text-indent: 0 }
                table { margin-bottom: 0; padding-bottom: 0; }
            </style>
            </head>
            <body>
            <div class="description">
            %%s
            </div>
            </body>
        <html>
        '''%(c,)
        self.setHtml(templ%html)
Beispiel #2
0
 def get(self, qurl, html=None, num_retries=1, delay = 10, timeout = 10):
     t1 = time()
     
     loop = QEventLoop()
     timer = QTimer()
     timer.setSingleShot(True)
     timer.timeout.connect(loop.quit)
     self.loadFinished.connect(loop.quit)
     if qurl:
         if html:
             self.setHtml(html, qurl)
         else: 
             self.mainFrame().load(QUrl(qurl))
     timer.start(timeout * 1000)
     loop.exec_() # delay here until download finished or timeout
 
     if timer.isActive():
         # downloaded successfully
         timer.stop()
         self._wait(delay - (time() - t1))
         parsed_html = self.mainFrame().toHtml()
     else:
         # did not download in time
         if num_retries > 0:
             logging.debug('Timeout - retrying')
             parsed_html = self.get(qurl, num_retries=num_retries-1, timerout=timeout, delay=delay)
         else:
             logging.debug('Timed out')
             parsed_html = ''
     self.mainFrame().setHtml(None)
     return parsed_html
Beispiel #3
0
    def get(self, qurl, html=None, num_retries=1, delay=10, timeout=10):
        t1 = time()

        loop = QEventLoop()
        timer = QTimer()
        timer.setSingleShot(True)
        timer.timeout.connect(loop.quit)
        self.loadFinished.connect(loop.quit)
        if qurl:
            if html:
                self.setHtml(html, qurl)
            else:
                self.mainFrame().load(QUrl(qurl))
        timer.start(timeout * 1000)
        loop.exec_()  # delay here until download finished or timeout

        if timer.isActive():
            # downloaded successfully
            timer.stop()
            self._wait(delay - (time() - t1))
            parsed_html = self.mainFrame().toHtml()
        else:
            # did not download in time
            if num_retries > 0:
                logging.debug('Timeout - retrying')
                parsed_html = self.get(qurl,
                                       num_retries=num_retries - 1,
                                       timerout=timeout,
                                       delay=delay)
            else:
                logging.debug('Timed out')
                parsed_html = ''
        self.mainFrame().setHtml(None)
        return parsed_html
Beispiel #4
0
 def qt_step():
     loop.call_later(period, qt_step)
     if not stack:
         qloop = QEventLoop()
         timer = QTimer()
         timer.timeout.connect(qloop.quit)
         stack.append((qloop, timer))
     qloop, timer = stack.pop()
     timer.start(0)
     qloop.exec_()
     timer.stop()
     stack.append((qloop, timer))
class Clock(QWidget):
    
    WIDTH = 140
    HEIGHT = 45
    
    TIMER_STEP = 25
    
    def __init__(self, parent):
        super().__init__(parent)
        self.initUI()
        
    def initUI(self):
        clock_stylesheet = """
                                .QLabel {
                                    padding-top: 10px;
                                    font-weight: bold;
                                    font-size: 25px;
                                    color:#ff5e5e;
                                }
                            """  
        
        self.accumulator = 0
        
        self.time = QLabel("0:00.0", self)
        self.time.resize(Clock.WIDTH, Clock.HEIGHT)
        self.time.setStyleSheet(clock_stylesheet)
        self.time.setAlignment(Qt.AlignHCenter)
        
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.updateTime)        
        self.timer.task = None
        
    def startClock(self):
        self.timer.start(Clock.TIMER_STEP)
        
    def stopClock(self):
        self.timer.stop()
    
    def reset(self):
        self.accumulator = 0
        self.time.setText("0:00.0")
    
    def updateTime(self):
        self.accumulator = self.accumulator + Clock.TIMER_STEP/100
        self.time.setText(str(int(self.accumulator%6000/600)) +":"+str(int(self.accumulator%600/100))+str(int(self.accumulator%100/10))+"."+str(int(self.accumulator)%10))
        if self.task != None:
            self.task()
Beispiel #6
0
class CoverDelegate(QStyledItemDelegate):  # {{{

    ICON_SIZE = 150, 200

    needs_redraw = pyqtSignal()

    def __init__(self, parent):
        QStyledItemDelegate.__init__(self, parent)

        self.angle = 0
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.frame_changed)
        self.dark_color = parent.palette().color(QPalette.ColorRole.WindowText)
        self.light_color = parent.palette().color(QPalette.ColorRole.Window)
        self.spinner_width = 64

    def frame_changed(self, *args):
        self.angle = (self.angle - 2) % 360
        self.needs_redraw.emit()

    def start_animation(self):
        self.angle = 0
        self.timer.start(10)

    def stop_animation(self):
        self.timer.stop()

    def paint(self, painter, option, index):
        QStyledItemDelegate.paint(self, painter, option, index)
        style = QApplication.style()
        waiting = self.timer.isActive() and bool(
            index.data(Qt.ItemDataRole.UserRole))
        if waiting:
            rect = QRect(0, 0, self.spinner_width, self.spinner_width)
            rect.moveCenter(option.rect.center())
            draw_snake_spinner(painter, rect, self.angle, self.light_color,
                               self.dark_color)
        else:
            # Ensure the cover is rendered over any selection rect
            style.drawItemPixmap(
                painter, option.rect,
                Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignHCenter,
                QPixmap(index.data(Qt.ItemDataRole.DecorationRole)))
Beispiel #7
0
class MoveMonitor(QObject):
    def __init__(self, worker, rq, callback, parent):
        QObject.__init__(self, parent)
        self.worker = worker
        self.rq = rq
        self.callback = callback
        self.parent = parent

        self.worker.start()
        self.dialog = ProgressDialog(_('Moving library...'),
                                     '',
                                     max=self.worker.total,
                                     parent=parent)
        self.dialog.button_box.setDisabled(True)
        self.dialog.setModal(True)
        self.dialog.show()
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.check)
        self.timer.start(200)

    def check(self):
        if self.worker.is_alive():
            self.update()
        else:
            self.timer.stop()
            self.dialog.hide()
            if self.worker.failed:
                error_dialog(self.parent,
                             _('Failed to move library'),
                             _('Failed to move library'),
                             self.worker.details,
                             show=True)
                return self.callback(None)
            else:
                return self.callback(self.worker.to)

    def update(self):
        try:
            title = self.rq.get_nowait()[-1]
            self.dialog.value += 1
            self.dialog.set_msg(_('Copied') + ' ' + title)
        except Empty:
            pass
Beispiel #8
0
class Brick(ElemMap):
    def __init__(self):
        super().__init__()
        self._live_brick = 0
        self._transparency = False
        self._pic = []
        self._pos_show_pic = 0
        for i in range(6):
            self._pic.append(QPixmap(resource_path("PIC/1_" + str(i) +
                                                   ".png")))

        self.timer_brick = QTimer()
        self.timer_brick.timeout.connect(self.updateValues)

    def dig(self):
        self._live_brick = 6
        self._transparency = True
        self.timer_brick.start(1500)
        self.updateValues()

    def updateValues(self):
        self._live_brick -= 1
        self._pos_show_pic = self._live_brick
        if self._live_brick == 0:
            self._transparency = False
            self.timer_brick.stop()
            if type(
                    game.map.get_elem_xy(int(game.hero.get_x()),
                                         int(round(
                                             game.hero.get_y())))) == Brick:
                game.you_lose()
            for enemy in game.enemies:
                if type(
                        game.map.get_elem_xy(int(enemy.get_x()),
                                             int(round(
                                                 enemy.get_y())))) == Brick:
                    enemy.set_xy(enemy.get_start_x(), enemy.get_start_y())

    def get_pic(self):
        return self._pic[self._pos_show_pic]
Beispiel #9
0
class CoverDelegate(QStyledItemDelegate):  # {{{

    ICON_SIZE = 150, 200

    needs_redraw = pyqtSignal()

    def __init__(self, parent):
        QStyledItemDelegate.__init__(self, parent)

        self.angle = 0
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.frame_changed)
        self.dark_color = parent.palette().color(QPalette.WindowText)
        self.light_color = parent.palette().color(QPalette.Window)
        self.spinner_width = 64

    def frame_changed(self, *args):
        self.angle = (self.angle-2)%360
        self.needs_redraw.emit()

    def start_animation(self):
        self.angle = 0
        self.timer.start(10)

    def stop_animation(self):
        self.timer.stop()

    def paint(self, painter, option, index):
        QStyledItemDelegate.paint(self, painter, option, index)
        style = QApplication.style()
        waiting = self.timer.isActive() and bool(index.data(Qt.UserRole))
        if waiting:
            rect = QRect(0, 0, self.spinner_width, self.spinner_width)
            rect.moveCenter(option.rect.center())
            draw_snake_spinner(painter, rect, self.angle, self.light_color, self.dark_color)
        else:
            # Ensure the cover is rendered over any selection rect
            style.drawItemPixmap(painter, option.rect, Qt.AlignTop|Qt.AlignHCenter,
                QPixmap(index.data(Qt.DecorationRole)))
Beispiel #10
0
class MoveMonitor(QObject):

    def __init__(self, worker, rq, callback, parent):
        QObject.__init__(self, parent)
        self.worker = worker
        self.rq = rq
        self.callback = callback
        self.parent = parent

        self.worker.start()
        self.dialog = ProgressDialog(_('Moving library...'), '',
                max=self.worker.total, parent=parent)
        self.dialog.button_box.setDisabled(True)
        self.dialog.setModal(True)
        self.dialog.show()
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.check)
        self.timer.start(200)

    def check(self):
        if self.worker.is_alive():
            self.update()
        else:
            self.timer.stop()
            self.dialog.hide()
            if self.worker.failed:
                error_dialog(self.parent, _('Failed to move library'),
                    _('Failed to move library'), self.worker.details, show=True)
                return self.callback(None)
            else:
                return self.callback(self.worker.to)

    def update(self):
        try:
            title = self.rq.get_nowait()[-1]
            self.dialog.value += 1
            self.dialog.set_msg(_('Copied') + ' '+title)
        except Empty:
            pass
Beispiel #11
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)

    def __init__(self, opts, parent=None, gui_debug=None):
        global _gui
        MainWindow.__init__(self, opts, parent=parent, disable_automatic_gc=True)
        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')))
            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.setDefaultAction(self.donate_action)
        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.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_restirction_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 show_gui:
            self.show()

        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)

        # ######################## 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.keyboard_interrupt.connect(self.quit, type=Qt.QueuedConnection)

        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()
        self.auto_adder = AutoAdder(gprefs['auto_add_path'], self)

        self.save_layout_state()

        # Collect cycles now
        gc.collect()

        if show_gui and self.gui_debug is not None:
            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)

        self.iactions['Connect Share'].check_smartdevice_menus()
        QTimer.singleShot(1, self.start_smartdevice)
        QTimer.singleShot(100, self.update_toggle_to_tray_action)

    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

            from calibre.utils.formatter_functions import unload_user_template_functions
            unload_user_template_functions(olddb.library_id)
        except:
            olddb = None
        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 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('http://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
        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.grid_view.shutdown()
        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()
        mb = self.library_view.model().metadata_backup
        if mb is not None:
            mb.stop()

        self.hide_windows()
        try:
            try:
                if self.content_server is not None:
                    s = self.content_server
                    self.content_server = None
                    s.exit()
            except:
                pass
        except KeyboardInterrupt:
            pass
        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()
        self.hide_windows()
        # Do not report any errors that happen after the shutdown
        sys.excepthook = sys.__excepthook__
        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):
        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()
Beispiel #12
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.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.print_menu.actions()[0].triggered.connect(self.print_preview)
        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.shutdown():
            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):
        p = Printing(self.iterator, self)
        p.start_print()

    def print_preview(self):
        p = Printing(self.iterator, self)
        p.start_preview()

    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()
Beispiel #13
0
class SearchBox2(QComboBox):  # {{{

    '''
    To use this class:

        * Call initialize()
        * Connect to the search() and cleared() signals from this widget.
        * Connect to the changed() signal to know when the box content changes
        * Connect to focus_to_library() signal to be told to manually change focus
        * Call search_done() after every search is complete
        * Call set_search_string() to perform a search programmatically
        * You can use the current_text property to get the current search text
          Be aware that if you are using it in a slot connected to the
          changed() signal, if the connection is not queued it will not be
          accurate.
    '''

    INTERVAL = 1500  #: Time to wait before emitting search signal
    MAX_COUNT = 25

    search  = pyqtSignal(object)
    cleared = pyqtSignal()
    changed = pyqtSignal()
    focus_to_library = pyqtSignal()

    def __init__(self, parent=None):
        QComboBox.__init__(self, parent)
        self.normal_background = 'rgb(255, 255, 255, 0%)'
        self.line_edit = SearchLineEdit(self)
        self.setLineEdit(self.line_edit)

        c = self.line_edit.completer()
        c.setCompletionMode(c.PopupCompletion)
        c.highlighted[str].connect(self.completer_used)

        self.line_edit.key_pressed.connect(self.key_pressed, type=Qt.DirectConnection)
        # QueuedConnection as workaround for https://bugreports.qt-project.org/browse/QTBUG-40807
        self.activated[str].connect(self.history_selected, type=Qt.QueuedConnection)
        self.setEditable(True)
        self.as_you_type = True
        self.timer = QTimer()
        self.timer.setSingleShot(True)
        self.timer.timeout.connect(self.timer_event, type=Qt.QueuedConnection)
        self.setInsertPolicy(self.NoInsert)
        self.setMaxCount(self.MAX_COUNT)
        self.setSizeAdjustPolicy(self.AdjustToMinimumContentsLengthWithIcon)
        self.setMinimumContentsLength(25)
        self._in_a_search = False
        self.tool_tip_text = self.toolTip()

    def initialize(self, opt_name, colorize=False, help_text=_('Search')):
        self.as_you_type = config['search_as_you_type']
        self.opt_name = opt_name
        items = []
        for item in config[opt_name]:
            if item not in items:
                items.append(item)
        self.addItems(items)
        self.line_edit.setPlaceholderText(help_text)
        self.colorize = colorize
        self.clear()

    def hide_completer_popup(self):
        try:
            self.lineEdit().completer().popup().setVisible(False)
        except:
            pass

    def normalize_state(self):
        self.setToolTip(self.tool_tip_text)
        self.line_edit.setStyleSheet(
            'QLineEdit{color:none;background-color:%s;}' % self.normal_background)

    def text(self):
        return self.currentText()

    def clear_history(self, *args):
        QComboBox.clear(self)

    def clear(self, emit_search=True):
        self.normalize_state()
        self.setEditText('')
        if emit_search:
            self.search.emit('')
        self._in_a_search = False
        self.cleared.emit()

    def clear_clicked(self, *args):
        self.clear()

    def search_done(self, ok):
        if isinstance(ok, basestring):
            self.setToolTip(ok)
            ok = False
        if not unicode(self.currentText()).strip():
            self.clear(emit_search=False)
            return
        self._in_a_search = ok
        col = 'rgba(0,255,0,20%)' if ok else 'rgb(255,0,0,20%)'
        if not self.colorize:
            col = self.normal_background
        self.line_edit.setStyleSheet('QLineEdit{color:black;background-color:%s;}' % col)

    # Comes from the lineEdit control
    def key_pressed(self, event):
        k = event.key()
        if k in (Qt.Key_Left, Qt.Key_Right, Qt.Key_Up, Qt.Key_Down,
                Qt.Key_Home, Qt.Key_End, Qt.Key_PageUp, Qt.Key_PageDown,
                Qt.Key_unknown):
            return
        self.normalize_state()
        if self._in_a_search:
            self.changed.emit()
            self._in_a_search = False
        if event.key() in (Qt.Key_Return, Qt.Key_Enter):
            self.do_search()
            self.focus_to_library.emit()
        elif self.as_you_type and unicode(event.text()):
            self.timer.start(1500)

    # Comes from the combobox itself
    def keyPressEvent(self, event):
        k = event.key()
        if k in (Qt.Key_Enter, Qt.Key_Return):
            return self.do_search()
        if k not in (Qt.Key_Up, Qt.Key_Down):
            QComboBox.keyPressEvent(self, event)
        else:
            self.blockSignals(True)
            self.normalize_state()
            QComboBox.keyPressEvent(self, event)
            self.blockSignals(False)

    def completer_used(self, text):
        self.timer.stop()
        self.normalize_state()

    def timer_event(self):
        self.do_search()

    def history_selected(self, text):
        self.changed.emit()
        self.do_search()

    def _do_search(self, store_in_history=True):
        self.hide_completer_popup()
        text = unicode(self.currentText()).strip()
        if not text:
            return self.clear()
        self.search.emit(text)

        if store_in_history:
            idx = self.findText(text, Qt.MatchFixedString|Qt.MatchCaseSensitive)
            self.block_signals(True)
            if idx < 0:
                self.insertItem(0, text)
            else:
                t = self.itemText(idx)
                self.removeItem(idx)
                self.insertItem(0, t)
            self.setCurrentIndex(0)
            self.block_signals(False)
            history = [unicode(self.itemText(i)) for i in
                    range(self.count())]
            config[self.opt_name] = history

    def do_search(self, *args):
        self._do_search()

    def block_signals(self, yes):
        self.blockSignals(yes)
        self.line_edit.blockSignals(yes)

    def set_search_string(self, txt, store_in_history=False, emit_changed=True):
        if not store_in_history:
            self.activated[str].disconnect()
        try:
            self.setFocus(Qt.OtherFocusReason)
            if not txt:
                self.clear()
            else:
                self.normalize_state()
                # must turn on case sensitivity here so that tag browser strings
                # are not case-insensitively replaced from history
                self.line_edit.completer().setCaseSensitivity(Qt.CaseSensitive)
                self.setEditText(txt)
                self.line_edit.end(False)
                if emit_changed:
                    self.changed.emit()
                self._do_search(store_in_history=store_in_history)
                self.line_edit.completer().setCaseSensitivity(Qt.CaseInsensitive)
            self.focus_to_library.emit()
        finally:
            if not store_in_history:
                # QueuedConnection as workaround for https://bugreports.qt-project.org/browse/QTBUG-40807
                self.activated[str].connect(self.history_selected, type=Qt.QueuedConnection)

    def search_as_you_type(self, enabled):
        self.as_you_type = enabled

    def in_a_search(self):
        return self._in_a_search

    @property
    def current_text(self):
        return unicode(self.lineEdit().text())
Beispiel #14
0
class GridView(QListView):

    update_item = pyqtSignal(object)
    files_dropped = pyqtSignal(object)

    def __init__(self, parent):
        QListView.__init__(self, parent)
        self._ncols = None
        self.gesture_manager = GestureManager(self)
        setup_dnd_interface(self)
        self.setUniformItemSizes(True)
        self.setWrapping(True)
        self.setFlow(self.LeftToRight)
        # We cannot set layout mode to batched, because that breaks
        # restore_vpos()
        # self.setLayoutMode(self.Batched)
        self.setResizeMode(self.Adjust)
        self.setSelectionMode(self.ExtendedSelection)
        self.setVerticalScrollMode(self.ScrollPerPixel)
        self.delegate = CoverDelegate(self)
        self.delegate.animation.valueChanged.connect(
            self.animation_value_changed)
        self.delegate.animation.finished.connect(self.animation_done)
        self.setItemDelegate(self.delegate)
        self.setSpacing(self.delegate.spacing)
        self.set_color()
        self.ignore_render_requests = Event()
        dpr = self.device_pixel_ratio
        self.thumbnail_cache = ThumbnailCache(
            max_size=gprefs['cover_grid_disk_cache_size'],
            thumbnail_size=(int(dpr * self.delegate.cover_size.width()),
                            int(dpr * self.delegate.cover_size.height())))
        self.render_thread = None
        self.update_item.connect(self.re_render, type=Qt.QueuedConnection)
        self.doubleClicked.connect(self.double_clicked)
        self.setCursor(Qt.PointingHandCursor)
        self.gui = parent
        self.context_menu = None
        self.update_timer = QTimer(self)
        self.update_timer.setInterval(200)
        self.update_timer.timeout.connect(self.update_viewport)
        self.update_timer.setSingleShot(True)
        self.resize_timer = t = QTimer(self)
        t.setInterval(200), t.setSingleShot(True)
        t.timeout.connect(self.update_memory_cover_cache_size)

    def viewportEvent(self, ev):
        try:
            ret = self.gesture_manager.handle_event(ev)
        except AttributeError:
            ret = None
        if ret is not None:
            return ret
        return QListView.viewportEvent(self, ev)

    @property
    def device_pixel_ratio(self):
        try:
            return self.devicePixelRatioF()
        except AttributeError:
            return self.devicePixelRatio()

    @property
    def first_visible_row(self):
        geom = self.viewport().geometry()
        for y in xrange(geom.top(), (self.spacing() * 2) + geom.top(), 5):
            for x in xrange(geom.left(), (self.spacing() * 2) + geom.left(),
                            5):
                ans = self.indexAt(QPoint(x, y)).row()
                if ans > -1:
                    return ans

    @property
    def last_visible_row(self):
        geom = self.viewport().geometry()
        for y in xrange(geom.bottom(), geom.bottom() - 2 * self.spacing(), -5):
            for x in xrange(geom.left(), (self.spacing() * 2) + geom.left(),
                            5):
                ans = self.indexAt(QPoint(x, y)).row()
                if ans > -1:
                    item_width = self.delegate.item_size.width(
                    ) + 2 * self.spacing()
                    return ans + (geom.width() // item_width)

    def update_viewport(self):
        self.ignore_render_requests.clear()
        self.update_timer.stop()
        m = self.model()
        for r in xrange(self.first_visible_row or 0, self.last_visible_row
                        or (m.count() - 1)):
            self.update(m.index(r, 0))

    def start_view_animation(self, index):
        d = self.delegate
        if d.animating is None and not config['disable_animations']:
            d.animating = index
            d.animation.start()

    def double_clicked(self, index):
        self.start_view_animation(index)
        if tweaks['doubleclick_on_library_view'] == 'open_viewer':
            self.gui.iactions['View'].view_triggered(index)
        elif tweaks['doubleclick_on_library_view'] in {
                'edit_metadata', 'edit_cell'
        }:
            self.gui.iactions['Edit Metadata'].edit_metadata(False, False)

    def animation_value_changed(self, value):
        if self.delegate.animating is not None:
            self.update(self.delegate.animating)

    def animation_done(self):
        if self.delegate.animating is not None:
            idx = self.delegate.animating
            self.delegate.animating = None
            self.update(idx)

    def set_color(self):
        r, g, b = gprefs['cover_grid_color']
        pal = QPalette()
        col = QColor(r, g, b)
        pal.setColor(pal.Base, col)
        tex = gprefs['cover_grid_texture']
        if tex:
            from calibre.gui2.preferences.texture_chooser import texture_path
            path = texture_path(tex)
            if path:
                pm = QPixmap(path)
                if not pm.isNull():
                    val = pm.scaled(1, 1).toImage().pixel(0, 0)
                    r, g, b = qRed(val), qGreen(val), qBlue(val)
                    pal.setBrush(pal.Base, QBrush(pm))
        dark = (r + g + b) / 3.0 < 128
        pal.setColor(pal.Text, QColor(Qt.white if dark else Qt.black))
        self.setPalette(pal)
        self.delegate.highlight_color = pal.color(pal.Text)

    def refresh_settings(self):
        size_changed = (
            gprefs['cover_grid_width'] != self.delegate.original_width
            or gprefs['cover_grid_height'] != self.delegate.original_height)
        if (size_changed or gprefs['cover_grid_show_title'] !=
                self.delegate.original_show_title or
                gprefs['show_emblems'] != self.delegate.original_show_emblems
                or gprefs['emblem_size'] != self.delegate.orginal_emblem_size
                or gprefs['emblem_position'] !=
                self.delegate.orginal_emblem_position):
            self.delegate.set_dimensions()
            self.setSpacing(self.delegate.spacing)
            if size_changed:
                self.delegate.cover_cache.clear()
        if gprefs['cover_grid_spacing'] != self.delegate.original_spacing:
            self.delegate.calculate_spacing()
            self.setSpacing(self.delegate.spacing)
        self.set_color()
        if size_changed:
            dpr = self.device_pixel_ratio
            self.thumbnail_cache.set_thumbnail_size(
                int(dpr * self.delegate.cover_size.width()),
                int(dpr * self.delegate.cover_size.height()))
        cs = gprefs['cover_grid_disk_cache_size']
        if (cs * (1024**2)) != self.thumbnail_cache.max_size:
            self.thumbnail_cache.set_size(cs)
        self.update_memory_cover_cache_size()

    def resizeEvent(self, ev):
        self._ncols = None
        self.resize_timer.start()
        return QListView.resizeEvent(self, ev)

    def update_memory_cover_cache_size(self):
        try:
            sz = self.delegate.item_size
        except AttributeError:
            return
        rows, cols = self.width() // sz.width(), self.height() // sz.height()
        num = (rows + 1) * (cols + 1)
        limit = max(100,
                    num * max(2, gprefs['cover_grid_cache_size_multiple']))
        if limit != self.delegate.cover_cache.limit:
            self.delegate.cover_cache.set_limit(limit)

    def shown(self):
        self.update_memory_cover_cache_size()
        if self.render_thread is None:
            self.thumbnail_cache.set_database(self.gui.current_db)
            self.render_thread = Thread(target=self.render_covers)
            self.render_thread.daemon = True
            self.render_thread.start()

    def render_covers(self):
        q = self.delegate.render_queue
        while True:
            book_id = q.get()
            try:
                if book_id is None:
                    return
                if self.ignore_render_requests.is_set():
                    continue
                try:
                    self.render_cover(book_id)
                except:
                    import traceback
                    traceback.print_exc()
            finally:
                q.task_done()

    def render_cover(self, book_id):
        if self.ignore_render_requests.is_set():
            return
        tcdata, timestamp = self.thumbnail_cache[book_id]
        use_cache = False
        if timestamp is None:
            # Not in cache
            has_cover, cdata, timestamp = self.model(
            ).db.new_api.cover_or_cache(book_id, 0)
        else:
            has_cover, cdata, timestamp = self.model(
            ).db.new_api.cover_or_cache(book_id, timestamp)
            if has_cover and cdata is None:
                # The cached cover is fresh
                cdata = tcdata
                use_cache = True

        if has_cover:
            p = QImage()
            p.loadFromData(cdata, CACHE_FORMAT if cdata is tcdata else 'JPEG')
            dpr = self.device_pixel_ratio
            p.setDevicePixelRatio(dpr)
            if p.isNull() and cdata is tcdata:
                # Invalid image in cache
                self.thumbnail_cache.invalidate((book_id, ))
                self.update_item.emit(book_id)
                return
            cdata = None if p.isNull() else p
            if not use_cache:  # cache is stale
                if cdata is not None:
                    width, height = p.width(), p.height()
                    scaled, nwidth, nheight = fit_image(
                        width, height,
                        int(dpr * self.delegate.cover_size.width()),
                        int(dpr * self.delegate.cover_size.height()))
                    if scaled:
                        if self.ignore_render_requests.is_set():
                            return
                        p = p.scaled(nwidth, nheight, Qt.IgnoreAspectRatio,
                                     Qt.SmoothTransformation)
                        p.setDevicePixelRatio(dpr)
                    cdata = p
                # update cache
                if cdata is None:
                    self.thumbnail_cache.invalidate((book_id, ))
                else:
                    try:
                        self.thumbnail_cache.insert(book_id, timestamp,
                                                    image_to_data(cdata))
                    except EncodeError as err:
                        self.thumbnail_cache.invalidate((book_id, ))
                        prints(err)
                    except Exception:
                        import traceback
                        traceback.print_exc()
        elif tcdata is not None:
            # Cover was removed, but it exists in cache, remove from cache
            self.thumbnail_cache.invalidate((book_id, ))
        self.delegate.cover_cache.set(book_id, cdata)
        self.update_item.emit(book_id)

    def re_render(self, book_id):
        self.delegate.cover_cache.clear_staging()
        m = self.model()
        try:
            index = m.db.row(book_id)
        except (IndexError, ValueError, KeyError):
            return
        self.update(m.index(index, 0))

    def shutdown(self):
        self.ignore_render_requests.set()
        self.delegate.render_queue.put(None)
        self.thumbnail_cache.shutdown()

    def set_database(self, newdb, stage=0):
        if stage == 0:
            self.ignore_render_requests.set()
            try:
                for x in (self.delegate.cover_cache, self.thumbnail_cache):
                    self.model().db.new_api.remove_cover_cache(x)
            except AttributeError:
                pass  # db is None
            for x in (self.delegate.cover_cache, self.thumbnail_cache):
                newdb.new_api.add_cover_cache(x)
            try:
                # Use a timeout so that if, for some reason, the render thread
                # gets stuck, we dont deadlock, future covers wont get
                # rendered, but this is better than a deadlock
                join_with_timeout(self.delegate.render_queue)
            except RuntimeError:
                print('Cover rendering thread is stuck!')
            finally:
                self.ignore_render_requests.clear()
        else:
            self.delegate.cover_cache.clear()

    def select_rows(self, rows):
        sel = QItemSelection()
        sm = self.selectionModel()
        m = self.model()
        # Create a range based selector for each set of contiguous rows
        # as supplying selectors for each individual row causes very poor
        # performance if a large number of rows has to be selected.
        for k, g in itertools.groupby(enumerate(rows), lambda (i, x): i - x):
            group = list(map(operator.itemgetter(1), g))
            sel.merge(
                QItemSelection(m.index(min(group), 0), m.index(max(group), 0)),
                sm.Select)
        sm.select(sel, sm.ClearAndSelect)
Beispiel #15
0
class GridView(QListView):

    update_item = pyqtSignal(object)
    files_dropped = pyqtSignal(object)

    def __init__(self, parent):
        QListView.__init__(self, parent)
        setup_gestures(self)
        setup_dnd_interface(self)
        self.setUniformItemSizes(True)
        self.setWrapping(True)
        self.setFlow(self.LeftToRight)
        # We cannot set layout mode to batched, because that breaks
        # restore_vpos()
        # self.setLayoutMode(self.Batched)
        self.setResizeMode(self.Adjust)
        self.setSelectionMode(self.ExtendedSelection)
        self.setVerticalScrollMode(self.ScrollPerPixel)
        self.delegate = CoverDelegate(self)
        self.delegate.animation.valueChanged.connect(self.animation_value_changed)
        self.delegate.animation.finished.connect(self.animation_done)
        self.setItemDelegate(self.delegate)
        self.setSpacing(self.delegate.spacing)
        self.padding_left = 0
        self.set_color()
        self.ignore_render_requests = Event()
        dpr = self.device_pixel_ratio
        self.thumbnail_cache = ThumbnailCache(max_size=gprefs['cover_grid_disk_cache_size'],
            thumbnail_size=(int(dpr * self.delegate.cover_size.width()), int(dpr * self.delegate.cover_size.height())))
        self.render_thread = None
        self.update_item.connect(self.re_render, type=Qt.QueuedConnection)
        self.doubleClicked.connect(self.double_clicked)
        self.setCursor(Qt.PointingHandCursor)
        self.gui = parent
        self.context_menu = None
        self.update_timer = QTimer(self)
        self.update_timer.setInterval(200)
        self.update_timer.timeout.connect(self.update_viewport)
        self.update_timer.setSingleShot(True)
        self.resize_timer = t = QTimer(self)
        t.setInterval(200), t.setSingleShot(True)
        t.timeout.connect(self.update_memory_cover_cache_size)

    def viewportEvent(self, ev):
        ret = gesture_viewport_event(self, ev)
        if ret is not None:
            return ret
        return QListView.viewportEvent(self, ev)

    @property
    def device_pixel_ratio(self):
        try:
            return self.devicePixelRatioF()
        except AttributeError:
            return self.devicePixelRatio()

    @property
    def first_visible_row(self):
        geom = self.viewport().geometry()
        for y in xrange(geom.top(), (self.spacing()*2) + geom.top(), 5):
            for x in xrange(geom.left(), (self.spacing()*2) + geom.left(), 5):
                ans = self.indexAt(QPoint(x, y)).row()
                if ans > -1:
                    return ans

    @property
    def last_visible_row(self):
        geom = self.viewport().geometry()
        for y in xrange(geom.bottom(), geom.bottom() - 2 * self.spacing(), -5):
            for x in xrange(geom.left(), (self.spacing()*2) + geom.left(), 5):
                ans = self.indexAt(QPoint(x, y)).row()
                if ans > -1:
                    item_width = self.delegate.item_size.width() + 2*self.spacing()
                    return ans + (geom.width() // item_width)

    def update_viewport(self):
        self.ignore_render_requests.clear()
        self.update_timer.stop()
        m = self.model()
        for r in xrange(self.first_visible_row or 0, self.last_visible_row or (m.count() - 1)):
            self.update(m.index(r, 0))

    def double_clicked(self, index):
        d = self.delegate
        if d.animating is None and not config['disable_animations']:
            d.animating = index
            d.animation.start()
        if tweaks['doubleclick_on_library_view'] == 'open_viewer':
            self.gui.iactions['View'].view_triggered(index)
        elif tweaks['doubleclick_on_library_view'] in {'edit_metadata', 'edit_cell'}:
            self.gui.iactions['Edit Metadata'].edit_metadata(False, False)

    def animation_value_changed(self, value):
        if self.delegate.animating is not None:
            self.update(self.delegate.animating)

    def animation_done(self):
        if self.delegate.animating is not None:
            idx = self.delegate.animating
            self.delegate.animating = None
            self.update(idx)

    def set_color(self):
        r, g, b = gprefs['cover_grid_color']
        pal = QPalette()
        col = QColor(r, g, b)
        pal.setColor(pal.Base, col)
        tex = gprefs['cover_grid_texture']
        if tex:
            from calibre.gui2.preferences.texture_chooser import texture_path
            path = texture_path(tex)
            if path:
                pm = QPixmap(path)
                if not pm.isNull():
                    val = pm.scaled(1, 1).toImage().pixel(0, 0)
                    r, g, b = qRed(val), qGreen(val), qBlue(val)
                    pal.setBrush(pal.Base, QBrush(pm))
        dark = (r + g + b)/3.0 < 128
        pal.setColor(pal.Text, QColor(Qt.white if dark else Qt.black))
        self.setPalette(pal)
        self.delegate.highlight_color = pal.color(pal.Text)

    def refresh_settings(self):
        size_changed = (
            gprefs['cover_grid_width'] != self.delegate.original_width or
            gprefs['cover_grid_height'] != self.delegate.original_height
        )
        if (size_changed or gprefs['cover_grid_show_title'] != self.delegate.original_show_title or
                gprefs['show_emblems'] != self.delegate.original_show_emblems or
                gprefs['emblem_size'] != self.delegate.orginal_emblem_size or
                gprefs['emblem_position'] != self.delegate.orginal_emblem_position):
            self.delegate.set_dimensions()
            self.setSpacing(self.delegate.spacing)
            if size_changed:
                self.delegate.cover_cache.clear()
        if gprefs['cover_grid_spacing'] != self.delegate.original_spacing:
            self.delegate.calculate_spacing()
            self.setSpacing(self.delegate.spacing)
        self.set_color()
        if size_changed:
            dpr = self.device_pixel_ratio
            self.thumbnail_cache.set_thumbnail_size(int(dpr * self.delegate.cover_size.width()), int(dpr*self.delegate.cover_size.height()))
        cs = gprefs['cover_grid_disk_cache_size']
        if (cs*(1024**2)) != self.thumbnail_cache.max_size:
            self.thumbnail_cache.set_size(cs)
        self.update_memory_cover_cache_size()

    def resizeEvent(self, ev):
        self.resize_timer.start()
        return QListView.resizeEvent(self, ev)

    def update_memory_cover_cache_size(self):
        try:
            sz = self.delegate.item_size
        except AttributeError:
            return
        rows, cols = self.width() // sz.width(), self.height() // sz.height()
        num = (rows + 1) * (cols + 1)
        limit = max(100, num * max(2, gprefs['cover_grid_cache_size_multiple']))
        if limit != self.delegate.cover_cache.limit:
            self.delegate.cover_cache.set_limit(limit)

    def shown(self):
        self.update_memory_cover_cache_size()
        if self.render_thread is None:
            self.thumbnail_cache.set_database(self.gui.current_db)
            self.render_thread = Thread(target=self.render_covers)
            self.render_thread.daemon = True
            self.render_thread.start()

    def render_covers(self):
        q = self.delegate.render_queue
        while True:
            book_id = q.get()
            try:
                if book_id is None:
                    return
                if self.ignore_render_requests.is_set():
                    continue
                try:
                    self.render_cover(book_id)
                except:
                    import traceback
                    traceback.print_exc()
            finally:
                q.task_done()

    def render_cover(self, book_id):
        if self.ignore_render_requests.is_set():
            return
        tcdata, timestamp = self.thumbnail_cache[book_id]
        use_cache = False
        if timestamp is None:
            # Not in cache
            has_cover, cdata, timestamp = self.model().db.new_api.cover_or_cache(book_id, 0)
        else:
            has_cover, cdata, timestamp = self.model().db.new_api.cover_or_cache(book_id, timestamp)
            if has_cover and cdata is None:
                # The cached cover is fresh
                cdata = tcdata
                use_cache = True

        if has_cover:
            p = QImage()
            p.loadFromData(cdata, CACHE_FORMAT if cdata is tcdata else 'JPEG')
            dpr = self.device_pixel_ratio
            p.setDevicePixelRatio(dpr)
            if p.isNull() and cdata is tcdata:
                # Invalid image in cache
                self.thumbnail_cache.invalidate((book_id,))
                self.update_item.emit(book_id)
                return
            cdata = None if p.isNull() else p
            if not use_cache:  # cache is stale
                if cdata is not None:
                    width, height = p.width(), p.height()
                    scaled, nwidth, nheight = fit_image(
                        width, height, int(dpr * self.delegate.cover_size.width()), int(dpr * self.delegate.cover_size.height()))
                    if scaled:
                        if self.ignore_render_requests.is_set():
                            return
                        p = p.scaled(nwidth, nheight, Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
                        p.setDevicePixelRatio(dpr)
                    cdata = p
                # update cache
                if cdata is None:
                    self.thumbnail_cache.invalidate((book_id,))
                else:
                    try:
                        self.thumbnail_cache.insert(book_id, timestamp, image_to_data(cdata))
                    except EncodeError as err:
                        self.thumbnail_cache.invalidate((book_id,))
                        prints(err)
                    except Exception:
                        import traceback
                        traceback.print_exc()
        elif tcdata is not None:
            # Cover was removed, but it exists in cache, remove from cache
            self.thumbnail_cache.invalidate((book_id,))
        self.delegate.cover_cache.set(book_id, cdata)
        self.update_item.emit(book_id)

    def re_render(self, book_id):
        self.delegate.cover_cache.clear_staging()
        m = self.model()
        try:
            index = m.db.row(book_id)
        except (IndexError, ValueError, KeyError):
            return
        self.update(m.index(index, 0))

    def shutdown(self):
        self.ignore_render_requests.set()
        self.delegate.render_queue.put(None)
        self.thumbnail_cache.shutdown()

    def set_database(self, newdb, stage=0):
        if stage == 0:
            self.ignore_render_requests.set()
            try:
                for x in (self.delegate.cover_cache, self.thumbnail_cache):
                    self.model().db.new_api.remove_cover_cache(x)
            except AttributeError:
                pass  # db is None
            for x in (self.delegate.cover_cache, self.thumbnail_cache):
                newdb.new_api.add_cover_cache(x)
            try:
                # Use a timeout so that if, for some reason, the render thread
                # gets stuck, we dont deadlock, future covers wont get
                # rendered, but this is better than a deadlock
                join_with_timeout(self.delegate.render_queue)
            except RuntimeError:
                print ('Cover rendering thread is stuck!')
            finally:
                self.ignore_render_requests.clear()
        else:
            self.delegate.cover_cache.clear()

    def select_rows(self, rows):
        sel = QItemSelection()
        sm = self.selectionModel()
        m = self.model()
        # Create a range based selector for each set of contiguous rows
        # as supplying selectors for each individual row causes very poor
        # performance if a large number of rows has to be selected.
        for k, g in itertools.groupby(enumerate(rows), lambda (i,x):i-x):
            group = list(map(operator.itemgetter(1), g))
            sel.merge(QItemSelection(m.index(min(group), 0), m.index(max(group), 0)), sm.Select)
        sm.select(sel, sm.ClearAndSelect)
Beispiel #16
0
class Widget(QWidget, ScreenWidget):
    name = "timeSetup"

    def __init__(self):
        QWidget.__init__(self)
        self.ui = Ui_DateTimeWidget()
        self.ui.setupUi(self)
        self.timer = QTimer(self)
        self.from_time_updater = True
        self.is_date_changed = False

        self.current_zone = ""

        self.tz_dict = {}
        self.continents = []
        self.countries = []

        for country, data in yali.localedata.locales.items():
            if country == ctx.consts.lang:
                if data.has_key("timezone"):
                    ctx.installData.timezone = data["timezone"]

        # Append continents and countries the time zone dictionary
        self.createTZDictionary()

        # Sort continent list
        self.sortContinents()

        # Append sorted continents to combobox
        self.loadContinents()

        # Load current continents country list
        self.getCountries(self.current_zone["continent"])

        # Highlight the current zone
        self.index = self.ui.continentList.findText(self.current_zone["continent"])
        self.ui.continentList.setCurrentIndex(self.index)

        self.index = self.ui.countryList.findText(self.current_zone["country"])
        self.ui.countryList.setCurrentIndex(self.index)

        # Initialize widget signal and slots
        self.__initSignals__()

        self.ui.calendarWidget.setDate(QDate.currentDate())

        self.pthread = None
        self.pds_messagebox = PMessageBox(self)
        self.pds_messagebox.enableOverlay()

        self.timer.start(1000)

    def __initSignals__(self):
        self.ui.timeEdit.timeChanged[QTime].connect(self.timerStop)
        self.ui.calendarWidget.dateChanged.connect(self.dateChanged)
        self.timer.timeout.connect(self.updateClock)
        self.ui.continentList.activated[str].connect(self.getCountries)

    def createTZDictionary(self):
        tz = TimeZoneList()
        zones = [ x.timeZone for x in tz.getEntries() ]
        zones.sort()

        for zone in zones:
            split = zone.split("/")

            # Human readable continent names
            continent_pretty_name = split[0].replace("_", " ")
            continent_pretty_name = continent_pretty_name

            # Some country names can be like Argentina/Catamarca so this fixes the splitting problem
            # caused by zone.split("/")
            #
            # Remove continent info and take the rest as the country name
            split.pop(0)
            country_pretty_name = " / ".join(split)

            # Human readable country names
            country_pretty_name = country_pretty_name.replace("_", " ")

            # Get current zone
            if zone == ctx.installData.timezone:
                self.current_zone = { "continent":continent_pretty_name, "country":country_pretty_name}

            # Append to dictionary
            if self.tz_dict.has_key(continent_pretty_name):
                self.tz_dict[continent_pretty_name].append([country_pretty_name, zone])
            else:
                self.tz_dict[continent_pretty_name] = [[country_pretty_name, zone]]


    def sortContinents(self):
        for continent in self.tz_dict.keys():
            self.continents.append(continent)
        self.continents.sort()

    def loadContinents(self):
        for continent in self.continents:
            self.ui.continentList.addItem(continent)

    def getCountries(self, continent):
        # Countries of the selected continent
        countries = self.tz_dict[str(continent)]

        self.ui.countryList.clear()

        for country, zone in countries:
            self.ui.countryList.addItem(country, zone)
            self.countries.append(country)



    def dateChanged(self):
        self.is_date_changed = True

    def timerStop(self):
        if self.from_time_updater:
            return
        # Human action detected; stop the timer.
        self.timer.stop()

    def updateClock(self):

        # What time is it ?
        cur = QTime.currentTime()

        self.from_time_updater = True
        self.ui.timeEdit.setTime(cur)
        self.from_time_updater = False

    def shown(self):
        self.timer.start(1000)

        if ctx.flags.install_type == ctx.STEP_BASE:
            self.pthread = PThread(self, self.startInit, self.dummy)

    def dummy(self):
        pass

    def setTime(self):
        ctx.interface.informationWindow.update(_("Adjusting time settings"))
        date = self.ui.calendarWidget.date()
        time = self.ui.timeEdit.time()
        args = "%02d%02d%02d%02d%04d.%02d" % (date.month(), date.day(),
                                              time.hour(), time.minute(),
                                              date.year(), time.second())


        # Set current date and time
        ctx.logger.debug("Date/Time setting to %s" % args)
        yali.util.run_batch("date", [args])

        # Sync date time with hardware
        ctx.logger.debug("YALI's time is syncing with the system.")
        yali.util.run_batch("hwclock", ["--systohc"])
        ctx.interface.informationWindow.hide()

    def execute(self):
        if not self.timer.isActive() or self.is_date_changed:
            QTimer.singleShot(500, self.setTime)
            self.timer.stop()

        index = self.ui.countryList.currentIndex()
        ctx.installData.timezone = self.ui.countryList.itemData(index)
        ctx.logger.debug("Time zone selected as %s " % ctx.installData.timezone)

        if ctx.flags.install_type == ctx.STEP_BASE:
            #FIXME:Refactor hacky code
            ctx.installData.rootPassword = ctx.consts.default_password
            ctx.installData.hostName = yali.util.product_release()
            if ctx.storageInitialized:
                disks = filter(lambda d: not d.format.hidden, ctx.storage.disks)
                if len(disks) == 1:
                    ctx.storage.clearPartDisks = [disks[0].name]
                    ctx.mainScreen.step_increment = 2
                else:
                    ctx.mainScreen.step_increment = 1
                return True
            else:
                self.pds_messagebox.setMessage(_("Storage Devices initialising..."))
                self.pds_messagebox.animate(start=MIDCENTER, stop=MIDCENTER)
                ctx.mainScreen.step_increment = 0
                self.pthread.start()
                QTimer.singleShot(2, self.startStorageInitialize)
                return False

        return True

    def startInit(self):
        self.pds_messagebox.animate(start=MIDCENTER, stop=MIDCENTER)

    def startStorageInitialize(self):
        ctx.storageInitialized = yali.storage.initialize(ctx.storage, ctx.interface)
        self.initFinished()

    def initFinished(self):
        self.pds_messagebox.animate(start=CURRENT, stop=CURRENT, direction=OUT)
        disks = filter(lambda d: not d.format.hidden, ctx.storage.disks)
        if ctx.storageInitialized:
            if len(disks) == 1:
                ctx.storage.clearPartDisks = [disks[0].name]
                ctx.mainScreen.step_increment = 2
            else:
                ctx.mainScreen.step_increment = 1
            ctx.mainScreen.slotNext(dry_run=True)
        else:
            ctx.mainScreen.enableBack()
Beispiel #17
0
class LiveCSS(QWidget):

    goto_declaration = pyqtSignal(object)

    def __init__(self, preview, parent=None):
        QWidget.__init__(self, parent)
        self.preview = preview
        self.preview_is_refreshing = False
        self.refresh_needed = False
        preview.refresh_starting.connect(self.preview_refresh_starting)
        preview.refreshed.connect(self.preview_refreshed)
        self.apply_theme()
        self.setAutoFillBackground(True)
        self.update_timer = QTimer(self)
        self.update_timer.timeout.connect(self.update_data)
        self.update_timer.setSingleShot(True)
        self.update_timer.setInterval(500)
        self.now_showing = (None, None, None)

        self.stack = s = QStackedLayout(self)
        self.setLayout(s)

        self.clear_label = la = QLabel('<h3>' + _(
            'No style information found') + '</h3><p>' + _(
                'Move the cursor inside a HTML tag to see what styles'
                ' apply to that tag.'))
        la.setWordWrap(True)
        la.setAlignment(Qt.AlignTop | Qt.AlignLeft)
        s.addWidget(la)

        self.box = box = Box(self)
        box.hyperlink_activated.connect(self.goto_declaration, type=Qt.QueuedConnection)
        self.scroll = sc = QScrollArea(self)
        sc.setWidget(box)
        sc.setWidgetResizable(True)
        s.addWidget(sc)

    def preview_refresh_starting(self):
        self.preview_is_refreshing = True

    def preview_refreshed(self):
        self.preview_is_refreshing = False
        # We must let the event loop run otherwise the webview will return
        # stale data in read_data()
        self.refresh_needed = True
        self.start_update_timer()

    def apply_theme(self):
        f = self.font()
        f.setFamily(tprefs['editor_font_family'] or default_font_family())
        f.setPointSize(tprefs['editor_font_size'])
        self.setFont(f)
        theme = get_theme(tprefs['editor_theme'])
        pal = self.palette()
        pal.setColor(pal.Window, theme_color(theme, 'Normal', 'bg'))
        pal.setColor(pal.WindowText, theme_color(theme, 'Normal', 'fg'))
        pal.setColor(pal.AlternateBase, theme_color(theme, 'HighlightRegion', 'bg'))
        pal.setColor(pal.Link, theme_color(theme, 'Link', 'fg'))
        pal.setColor(pal.LinkVisited, theme_color(theme, 'Keyword', 'fg'))
        self.setPalette(pal)
        if hasattr(self, 'box'):
            self.box.relayout()
        self.update()

    def clear(self):
        self.stack.setCurrentIndex(0)

    def show_data(self, editor_name, sourceline, tags):
        if self.preview_is_refreshing:
            return
        if sourceline is None:
            self.clear()
        else:
            data = self.read_data(sourceline, tags)
            if data is None or len(data['computed_css']) < 1:
                if editor_name == self.current_name and (editor_name, sourceline, tags) == self.now_showing:
                    # Try again in a little while in case there was a transient
                    # error in the web view
                    self.start_update_timer()
                    return
                if self.now_showing == (None, None, None) or self.now_showing[0] != self.current_name:
                    self.clear()
                    return
                # Try to refresh the data for the currently shown tag instead
                # of clearing
                editor_name, sourceline, tags = self.now_showing
                data = self.read_data(sourceline, tags)
                if data is None or len(data['computed_css']) < 1:
                    self.clear()
                    return
            self.now_showing = (editor_name, sourceline, tags)
            data['html_name'] = editor_name
            self.box.show_data(data)
            self.refresh_needed = False
            self.stack.setCurrentIndex(1)

    def read_data(self, sourceline, tags):
        mf = self.preview.view.page().mainFrame()
        tags = [x.lower() for x in tags]
        result = unicode_type(mf.evaluateJavaScript(
            'window.calibre_preview_integration.live_css(%s, %s)' % (
                json.dumps(sourceline), json.dumps(tags))) or '')
        try:
            result = json.loads(result)
        except ValueError:
            result = None
        if result is not None:
            maximum_specificities = {}
            for node in result['nodes']:
                is_ancestor = node['is_ancestor']
                for rule in node['css']:
                    self.process_rule(rule, is_ancestor, maximum_specificities)
            for node in result['nodes']:
                for rule in node['css']:
                    for prop in rule['properties']:
                        if prop.specificity < maximum_specificities[prop.name]:
                            prop.is_overriden = True

        return result

    def process_rule(self, rule, is_ancestor, maximum_specificities):
        selector = rule['selector']
        sheet_index = rule['sheet_index']
        rule_address = rule['rule_address'] or ()
        if selector is not None:
            try:
                specificity = [0] + list(parse(selector)[0].specificity())
            except (AttributeError, TypeError, SelectorError):
                specificity = [0, 0, 0, 0]
        else:  # style attribute
            specificity = [1, 0, 0, 0]
        specificity.extend((sheet_index, tuple(rule_address)))
        ancestor_specificity = 0 if is_ancestor else 1
        properties = []
        for prop in rule['properties']:
            important = 1 if prop[-1] == 'important' else 0
            p = Property(prop, [ancestor_specificity] + [important] + specificity)
            properties.append(p)
            if p.specificity > maximum_specificities.get(p.name, (0,0,0,0,0,0)):
                maximum_specificities[p.name] = p.specificity
        rule['properties'] = properties

        href = rule['href']
        if hasattr(href, 'startswith') and href.startswith('%s://%s' % (FAKE_PROTOCOL, FAKE_HOST)):
            qurl = QUrl(href)
            name = qurl.path()[1:]
            if name:
                rule['href'] = name

    @property
    def current_name(self):
        return self.preview.current_name

    @property
    def is_visible(self):
        return self.isVisible()

    def showEvent(self, ev):
        self.update_timer.start()
        actions['auto-reload-preview'].setEnabled(True)
        return QWidget.showEvent(self, ev)

    def sync_to_editor(self):
        self.update_data()

    def update_data(self):
        if not self.is_visible or self.preview_is_refreshing:
            return
        editor_name = self.current_name
        ed = editors.get(editor_name, None)
        if self.update_timer.isActive() or (ed is None and editor_name is not None):
            return QTimer.singleShot(100, self.update_data)
        if ed is not None:
            sourceline, tags = ed.current_tag(for_position_sync=False)
            if self.refresh_needed or self.now_showing != (editor_name, sourceline, tags):
                self.show_data(editor_name, sourceline, tags)

    def start_update_timer(self):
        if self.is_visible:
            self.update_timer.start()

    def stop_update_timer(self):
        self.update_timer.stop()

    def navigate_to_declaration(self, data, editor):
        if data['type'] == 'inline':
            sourceline, tags = data['sourceline_address']
            editor.goto_sourceline(sourceline, tags, attribute='style')
        elif data['type'] == 'sheet':
            editor.goto_css_rule(data['rule_address'])
        elif data['type'] == 'elem':
            editor.goto_css_rule(data['rule_address'], sourceline_address=data['sourceline_address'])
Beispiel #18
0
class Pyqt5_Serial(QWidget, Ui_Form):
    def __init__(self, *args, **kwargs):
        super(Pyqt5_Serial, self).__init__(*args, **kwargs)
        self.setupUi(self)
        self.ctrl_init()
        self.show_init()
        self.ser_init()
        self.centre()

    def ser_init(self):
        self.ser = serial.Serial()
        self.port_check()

    def ctrl_init(self):
        # 串口检测按钮
        self.s1__box_1.clicked.connect(self.port_check)
        # 串口信息显示
        self.s1__box_2.currentTextChanged.connect(self.port_imf)
        # 打开串口按钮
        self.open_button.clicked.connect(self.port_open)
        # 关闭串口按钮
        self.close_button.clicked.connect(self.port_close)
        # 发送数据按钮
        self.s3__send_button.clicked.connect(self.data_send)
        # 定时发送数据
        self.timer_send = QTimer()
        self.timer_send.timeout.connect(self.data_send)
        self.timer_send_cb.stateChanged.connect(self.data_send_timer)
        # 定时器接收数据
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.data_receive)
        # 定时器检查串口
        self.chktimer = QTimer(self)
        self.chktimer.timeout.connect(self.port_check)
        #self.chktimer.start(5000)

        # 清除发送窗口
        self.s3__clear_button.clicked.connect(self.send_data_clear)
        # 清除接收窗口
        self.s2__clear_button.clicked.connect(self.receive_data_clear)
        # 保存文件
        self.s2__save_button_2.clicked.connect(self.save_data_file)
        # 定时打印
        self.pushButton_9.clicked.connect(self.timer_print)
        # 加速度设置
        self.pushButton_6.clicked.connect(self.kinco_set_accle)
        # 减速度设置
        self.pushButton_7.clicked.connect(self.kinco_set_decel)
        # 超声波id设置
        self.pushButton_5.clicked.connect(self.ks103_set_id)

        # 复位
        self.s4__send_5.clicked.connect(lambda: self.send_cmd("reset"))
        # 恢复出厂信息
        self.s4__send_3.clicked.connect(lambda: self.send_cmd("restore"))
        # 版本信息
        self.s4__send_7.clicked.connect(lambda: self.send_cmd("version"))
        # 系统信息
        self.s4__send_8.clicked.connect(lambda: self.send_cmd("sys-msg"))
        # 底盘信息
        self.s4__send_4.clicked.connect(lambda: self.send_cmd("kinco-msg"))
        # BMS信息
        self.s4__send_6.clicked.connect(lambda: self.send_cmd("bms-msg"))
        # 传感器信息
        self.s4__send_11.clicked.connect(lambda: self.send_cmd("sen-msg"))
        # 手掌信息
        self.s4__send_10.clicked.connect(lambda: self.send_cmd("plam-msg"))
        # 舵机ID设置使能
        self.s4__send_2.clicked.connect(lambda: self.send_cmd('ctldbg:1'))
        # 舵机ID设置
        self.s4__send_1.clicked.connect(self.servo_set_id)
        # 循环运动开
        self.pushButton_4.clicked.connect(lambda: self.send_cmd('ctlsrvo:1'))
        # 循环运动关
        self.pushButton_8.clicked.connect(lambda: self.send_cmd('ctlsrvo:0'))
        # 全部松轴
        self.pushButton.clicked.connect(lambda: self.send_cmd('srvo-torque:0'))
        # 全部锁轴
        self.pushButton_3.clicked.connect(
            lambda: self.send_cmd('srvo-torque:1'))
        # 舵机归中
        self.pushButton_2.clicked.connect(lambda: self.send_cmd('ctldef'))
        # 底盘锁轴
        self.pushButton_17.clicked.connect(
            lambda: self.send_cmd('set-kinco:lock:15'))
        # 底盘松轴
        self.pushButton_16.clicked.connect(
            lambda: self.send_cmd('set-kinco:lock:06'))
        # 打印超声波数据
        self.pushButton_20.clicked.connect(
            lambda: self.send_cmd('ks103-dbg:1'))

    def show_init(self):
        # 窗口初始化设置
        self.setWindowTitle("YYD测试工具V1.0(试用版)")
        #self.setWindowFlags(QtCore.Qt.CustomizeWindowHint) #隐藏标题栏
        #self.setWindowOpacity(0.96)
        #self.setMinimumHeight(720)
        #self.move(500, 200)
        #self.setFixedSize(900, 800)
        # self.showMaximized()
        # print(self.pos())
        self.adjustSize()
        #print(self.sizePolicy())

        # 按键状态
        self.close_button.setEnabled(False)
        self.open_button.setStyleSheet("background-color : cyan")
        #self.formGroupBox.setStyleSheet("background-color : yellow")
        # self.label_3.setStyleSheet("background-color : yellow")

        # 接收数据和发送数据数目置零
        self.data_num_received = 0
        self.lineEdit.setText(str(self.data_num_received))
        self.data_num_sended = 0
        self.lineEdit_2.setText(str(self.data_num_sended))
        self.serail_open_flag = False  #串口打开标志

    def centre(self):
        screen = QDesktopWidget().screenGeometry()
        print(screen.x(), screen.y(), screen.width(), screen.height())
        size = self.geometry()
        self.move((screen.width() - size.width()) / 2,
                  (screen.height() - size.height()) / 2)

    # 发送串口指令
    def send_cmd(self, str_cmd):
        if self.serail_open_flag == True:
            sendOrder = str(str_cmd + '$\r\n').encode('ascii')
            self.ser.write(sendOrder)
            print(sendOrder, type(sendOrder))

    # 设置舵机ID
    def servo_set_id(self):
        servoId = self.s1__box_7.currentText()
        sendOrder = "setid:%s" % (servoId)
        self.send_cmd(sendOrder)

    # 发送定时打印
    def timer_print(self):
        servoId = self.s1__box_9.currentText()
        sendOrder = "setprint:1," + servoId
        self.send_cmd(sendOrder)

    # 加速度设置
    def kinco_set_accle(self):
        input_s = "set-kinco:accel:" + self.LE_7.text()
        if input_s != "":
            self.send_cmd(input_s)

    # 减速度设置
    def kinco_set_decel(self):
        input_s = "set-kinco:decel:" + self.LE_8.text()
        if input_s != "":
            self.send_cmd(input_s)

    # 超声波ID设置
    def ks103_set_id(self):
        OldId = self.s1__box_8.currentText()
        NewId = self.s1__box_10.currentText()
        sendOrder = "setid:" + OldId + ',' + NewId
        self.send_cmd(sendOrder)

    def save_data_file(self):
        if self.s2__receive_text.toPlainText() != '':
            localtime = time.strftime("%Y-%m-%d_%H-%M-%S",
                                      time.localtime()) + '.txt'
            print(localtime, type(localtime))
            fp = open(localtime, 'a+', encoding='utf-8')
            fp.write(self.s2__receive_text.toPlainText())
            fp.close()
            QMessageBox.about(None, '提示', '保存成功!')

    # 串口检测
    def port_check(self):
        if self.serail_open_flag != True:
            # 检测所有存在的串口,将信息存储在字典中
            self.Com_Dict = {}
            port_list = list(serial.tools.list_ports.comports())
            self.s1__box_2.clear()
            for port in port_list:
                self.Com_Dict["%s" % port[0]] = "%s" % port[1]
                self.s1__box_2.addItem(port[0])
            if len(self.Com_Dict) == 0:
                self.state_label.setText(" 无串口")

    # 串口信息
    def port_imf(self):
        # 显示选定的串口的详细信息
        imf_s = self.s1__box_2.currentText()
        if imf_s != "":
            self.state_label.setText(
                self.Com_Dict[self.s1__box_2.currentText()])

    # 打开串口
    def port_open(self):
        self.serail_open_flag = True
        self.ser.port = self.s1__box_2.currentText()
        self.ser.baudrate = int(self.s1__box_3.currentText())
        self.ser.bytesize = int(self.s1__box_4.currentText())
        self.ser.stopbits = int(self.s1__box_6.currentText())
        self.ser.parity = self.s1__box_5.currentText()
        try:
            self.ser.open()
        except:
            QMessageBox.critical(self, "Port Error", "此串口不能被打开!")
            return None
        # 打开串口接收定时器,周期为10ms
        self.timer.start(20)

        if self.ser.isOpen():
            self.open_button.setEnabled(False)
            self.close_button.setEnabled(True)
            self.formGroupBox1.setTitle("串口状态(已开启)")

    # 关闭串口
    def port_close(self):
        self.serail_open_flag = False
        self.timer.stop()
        self.timer_send.stop()
        try:
            self.ser.close()
        except:
            pass
        self.open_button.setEnabled(True)
        self.close_button.setEnabled(False)
        self.lineEdit_3.setEnabled(True)
        # 接收数据和发送数据数目置零
        self.data_num_received = 0
        self.lineEdit.setText(str(self.data_num_received))
        self.data_num_sended = 0
        self.lineEdit_2.setText(str(self.data_num_sended))
        self.formGroupBox1.setTitle("串口状态(已关闭)")

    # 发送数据
    def data_send(self):
        if self.serail_open_flag == True:
            input_s = self.s3__send_text.toPlainText()
            if input_s != "":
                # 非空字符串
                if self.hex_send.isChecked():
                    # hex发送
                    input_s = input_s.strip()
                    send_list = []
                    while input_s != '':
                        try:
                            num = int(input_s[0:2], 16)
                        except ValueError:
                            QMessageBox.critical(self, 'wrong data',
                                                 '请输入十六进制数据,以空格分开!')
                            return None
                        input_s = input_s[2:].strip()
                        send_list.append(num)
                    input_s = bytes(send_list)
                else:
                    # ascii发送
                    input_s = (input_s + '\r\n').encode('utf-8')
                #print(type(input_s))
                num = self.ser.write(input_s)
                self.data_num_sended += num
                self.lineEdit_2.setText(str(self.data_num_sended))
        else:
            pass

    # 接收数据
    def data_receive(self):
        try:
            num = self.ser.inWaiting()
        except:
            self.port_close()
            return None
        if num > 0:
            data = self.ser.read(num)
            num = len(data)
            # hex显示
            if self.hex_receive.checkState():
                out_s = ''
                for i in range(0, len(data)):
                    out_s = out_s + '{:02X}'.format(data[i]) + ' '
                print(type(out_s))
                self.s2__receive_text.insertPlainText(out_s)
            else:
                print(type(data))
                try:
                    tempStr = data.decode('GB2312')
                    self.s2__receive_text.insertPlainText(str(tempStr))
                except:
                    print('decode err')
                    return None
                # 串口接收到的字符串为b'123',要转化成unicode字符串才能输出到窗口中去
                #self.s2__receive_text.insertPlainText(data.decode('iso-8859-1'))
                # self.s2__receive_text.insertPlainText(data.decode('gbk'))
            # 统计接收字符的数量
            self.data_num_received += num
            self.lineEdit.setText(str(self.data_num_received))

            # 获取到text光标
            textCursor = self.s2__receive_text.textCursor()
            # 滚动到底部
            textCursor.movePosition(textCursor.End)
            # 设置光标到text中去
            self.s2__receive_text.setTextCursor(textCursor)
        else:
            pass

    # 定时发送数据
    def data_send_timer(self):
        if self.timer_send_cb.isChecked():
            self.timer_send.start(int(self.lineEdit_3.text()))
            self.lineEdit_3.setEnabled(False)
        else:
            self.timer_send.stop()
            self.lineEdit_3.setEnabled(True)

    # 清除显示
    def send_data_clear(self):
        self.s3__send_text.setText("")

    # 接收清除
    def receive_data_clear(self):
        self.s2__receive_text.setText("")
        self.lineEdit.setText("0")
        self.lineEdit_2.setText("0")
        self.data_num_received = 0
        self.data_num_sended = 0
Beispiel #19
0
class Comments(HTMLDisplay):  # {{{
    def __init__(self, parent=None):
        HTMLDisplay.__init__(self, parent)
        self.setAcceptDrops(False)
        self.setMaximumWidth(300)
        self.setMinimumWidth(300)
        self.wait_timer = QTimer(self)
        self.wait_timer.timeout.connect(self.update_wait)
        self.wait_timer.setInterval(800)
        self.dots_count = 0
        self.anchor_clicked.connect(self.link_activated)

    def link_activated(self, url):
        from calibre.gui2 import open_url
        if url.scheme() in {'http', 'https'}:
            open_url(url)

    def show_wait(self):
        self.dots_count = 0
        self.wait_timer.start()
        self.update_wait()

    def update_wait(self):
        self.dots_count += 1
        self.dots_count %= 10
        self.dots_count = self.dots_count or 1
        self.setHtml(
            '<h2>' + _('Please wait') +
            '<br><span id="dots">{}</span></h2>'.format('.' * self.dots_count))

    def show_data(self, html):
        self.wait_timer.stop()

        def color_to_string(col):
            ans = '#000000'
            if col.isValid():
                col = col.toRgb()
                if col.isValid():
                    ans = unicode_type(col.name())
            return ans

        c = color_to_string(QApplication.palette().color(
            QPalette.Normal, QPalette.WindowText))
        templ = '''\
        <html>
            <head>
            <style type="text/css">
                body, td {background-color: transparent; color: %s }
                a { text-decoration: none; color: blue }
                div.description { margin-top: 0; padding-top: 0; text-indent: 0 }
                table { margin-bottom: 0; padding-bottom: 0; }
            </style>
            </head>
            <body>
            <div class="description">
            %%s
            </div>
            </body>
        <html>
        ''' % (c, )
        self.setHtml(templ % html)

    def sizeHint(self):
        # This is needed, because on windows the dialog cannot be resized to
        # so that this widgets height become < sizeHint().height(). Qt sets the
        # sizeHint to (800, 600), which makes the dialog unusable on smaller
        # screens.
        return QSize(800, 300)
Beispiel #20
0
class Widget(QWidget, ScreenWidget):
    name = "summary"

    def __init__(self):
        QWidget.__init__(self)
        self.ui = Ui_SummaryWidget()
        self.ui.setupUi(self)

        self.ui.content.setText("")
        self.timer = QTimer()
        self.start_time = 0

        try:
            self.timer.timeout.connect(self.updateCounter)
        except:
            pass

    def slotReboot(self):
        reply = QuestionDialog(_("Restart"),
                               _('''<b><p>Are you sure you want to restart the computer?</p></b>'''))
        if reply == "yes":
            yali.util.reboot()

    def startBombCounter(self):
        self.start_time = int(time.time())
        self.timer.start(1000)

    def backCheck(self):
        self.timer.stop()
        ctx.interface.informationWindow.hide()
        if ctx.flags.install_type == ctx.STEP_BASE or ctx.flags.install_type == ctx.STEP_DEFAULT:
            ctx.mainScreen.ui.buttonNext.setText(_("Next"))

        if (ctx.flags.install_type == ctx.STEP_BASE or ctx.flags.install_type == ctx.STEP_DEFAULT) \
           and not ctx.flags.collection:
            ctx.mainScreen.step_increment = 2
        return True

    def updateCounter(self):
        remain = 20 - (int(time.time()) - self.start_time)
        ctx.interface.informationWindow.update(_("Installation starts in <b>%s</b> seconds") % remain)
        if remain <= 0:
            self.timer.stop()
            ctx.mainScreen.slotNext()

    def shown(self):
        #ctx.mainScreen.disableNext()
        if ctx.flags.install_type == ctx.STEP_BASE or ctx.flags.install_type == ctx.STEP_DEFAULT:
            ctx.mainScreen.ui.buttonNext.setText(_("Start Installation"))
        if ctx.flags.install_type == ctx.STEP_FIRST_BOOT:
            ctx.mainScreen.ui.buttonNext.setText(_("Apply Settings"))

        if ctx.installData.isKahyaUsed:
            self.startBombCounter()

        self.fillContent()

    def fillContent(self):
        subject = "<p><li><b>%s</b></li><ul>"
        item    = "<li>%s</li>"
        end     = "</ul></p>"
        content = []

        content.append("""<html><body><ul>""")

        # Keyboard Layout
        if ctx.installData.keyData:
            content.append(subject % _("Keyboard Settings"))
            content.append(item %
                           _("Selected keyboard layout is <b>%s</b>") %
                           ctx.installData.keyData["name"])
            content.append(end)

        # TimeZone
        if ctx.installData.timezone:
            content.append(subject % _("Date/Time Settings"))
            content.append(item %
                           _("Selected TimeZone is <b>%s</b>") %
                           ctx.installData.timezone)
            content.append(end)

        # Users
        if len(yali.users.PENDING_USERS) > 0:
            content.append(subject % _("User Settings"))
            for user in yali.users.PENDING_USERS:
                state = _("User %(username)s (<b>%(realname)s</b>) added.")
                if "wheel" in user.groups:
                    state = _("User %(username)s (<b>%(realname)s</b>) added with <u>administrator privileges</u>.")
                content.append(item % state % {"username":user.realname, "realname":user.username})
            content.append(end)

        # HostName
        if ctx.installData.hostName:
            content.append(subject % _("Hostname Settings"))
            content.append(item %
                           _("Hostname is set as <b>%s</b>") %
                           ctx.installData.hostName)
            content.append(end)

        # Partition
        if ctx.storage.clearPartType is not None:
            content.append(subject % _("Partition Settings"))
            devices = ""
            for disk in ctx.storage.clearPartDisks:
                device = ctx.storage.devicetree.getDeviceByName(disk)
                devices += "(%s on %s)" % (device.model, device.name)

            if ctx.storage.doAutoPart:
                content.append(item % _("Automatic Partitioning selected."))
                if ctx.storage.clearPartType == CLEARPART_TYPE_ALL:
                    content.append(item % _("Use All Space"))
                    content.append(item % _("Removes all partitions on the selected "\
                                            "%s device(s). <b><u>This includes partitions "\
                                            "created by other operating systems.</u></b>") % devices)
                elif ctx.storage.clearPartType == CLEARPART_TYPE_LINUX:
                    content.append(item % _("Replace Existing Linux System(s)"))
                    content.append(item % _("Removes all Linux partitions on the selected" \
                                            "%s device(s). This does not remove "\
                                            "other partitions you may have on your " \
                                            "storage device(s) (such as VFAT or FAT32)") % devices)
                elif ctx.storage.clearPartType == CLEARPART_TYPE_NONE:
                    content.append(item % _("Use Free Space"))
                    content.append(item % _("Retains your current data and partitions" \
                                            " and uses only the unpartitioned space on " \
                                            "the selected %s device(s), assuming you have "\
                                            "enough free space available.") % devices)

            else:
                content.append(item % _("Manual Partitioning selected."))
                for operation in ctx.storage.devicetree.operations:
                    content.append(item % operation)

            content.append(end)

        # Bootloader
        if ctx.bootloader.stage1Device:
            content.append(subject % _("Bootloader Settings"))
            grubstr = _("GRUB will be installed to <b>%s</b>.")
            if ctx.bootloader.bootType == BOOT_TYPE_NONE:
                content.append(item % _("GRUB will not be installed."))
            else:
                content.append(item % grubstr % ctx.bootloader.stage1Device)

            content.append(end)

        if ctx.flags.collection and ctx.installData.autoCollection:
            content.append(subject % _("Package Installation Settings"))
            content.append(item % _("Collection <b>%s</b> selected") %
                           ctx.installData.autoCollection.title)

            content.append(end)

        content.append("""</ul></body></html>""")
        cnt= "\n".join(content)
        self.ui.content.setHtml(cnt)

    def execute(self):
        self.timer.stop()

        if ctx.flags.dryRun:
            ctx.logger.debug("dryRun activated Yali stopped")
            ctx.mainScreen.enableBack()
            return False

        if ctx.flags.install_type == ctx.STEP_BASE or ctx.flags.install_type == ctx.STEP_DEFAULT:
            self.createPackageList()

            rc = ctx.interface.messageWindow(_("Confirm"),
                                        _("The partitioning options you have selected "
                                          "will now be written to disk. Any "
                                          "data on deleted or reformatted partitions "
                                          "will be lost."),
                                          type = "custom", customIcon="question",
                                          customButtons=[_("Write Changes to Disk"), _("Go Back")],
                                          default=1)

            ctx.mainScreen.processEvents()

            if not rc:
                if yali.storage.complete(ctx.storage, ctx.interface):
                    ctx.storage.turnOnSwap()
                    ctx.storage.mountFilesystems(readOnly=False, skipRoot=False)
                    ctx.mainScreen.step_increment = 1
                    ctx.mainScreen.ui.buttonNext.setText(_("Next"))
                    return True

            ctx.mainScreen.enableBack()
            return False

        elif ctx.flags.install_type == ctx.STEP_FIRST_BOOT:
            ctx.mainScreen.ui.buttonNext.setText(_("Next"))
            return True


        # Auto Partitioning
        #if not ctx.storage.storageset.swapDevices:
        #    size = 0
        #    if yali.util.memInstalled() > 512:
        #        size = 300
        #    else:
        #        size = 600
        #    ctx.storage.storageset.createSwapFile(ctx.storage.storageset.rootDevice, ctx.consts.target_dir, size)

    def createPackageList(self):
        ctx.logger.debug("Generating package list...")

        if ctx.flags.collection:
            # Get only collection packages with collection Name
            packages = yali.pisiiface.getAllPackagesWithPaths(
                        collectionIndex=ctx.installData.autoCollection.index)
        else:
            # Check for just installing system.base packages
            if ctx.flags.baseonly:
                packages = yali.pisiiface.getBasePackages()
            else:
                packages = yali.pisiiface.getAllPackagesWithPaths()

        # Check for extra languages
        packages = list(set(packages) - set(yali.pisiiface.getNotNeededLanguagePackages()))
        ctx.logger.debug("Not needed lang packages will not be installing...")

        packages = self.filterDriverPacks(packages)
        packages.sort()

        # Place baselayout package on the top of package list
        baselayout = None
        for path in packages:
            if "/baselayout-" in path:
                baselayout = packages.index(path)
                break

        if baselayout:
            packages.insert(0, packages.pop(baselayout))

        ctx.packagesToInstall = packages

    def filterDriverPacks(self, paths):
        try:
            from panda import Panda
        except ImportError:
            ctx.logger.debug("Installing all driver packages since panda module is not installed.")
            return paths

        panda = Panda()

        # filter all driver packages
        foundDriverPackages = set(yali.pisiiface.getPathsByPackageName(panda.get_all_driver_packages()))
        ctx.logger.debug("Found driver packages: %s" % foundDriverPackages)

        allPackages = set(paths)
        packages = allPackages - foundDriverPackages

        # detect hardware
        neededDriverPackages = set(yali.pisiiface.getPathsByPackageName(panda.get_needed_driver_packages()))
        ctx.logger.debug("Known driver packages for this hardware: %s" % neededDriverPackages)

        # if alternatives are available ask to user, otherwise return
        if neededDriverPackages and neededDriverPackages.issubset(allPackages):
            answer = ctx.interface.messageWindow(
                    _("Proprietary Hardware Drivers"),
                    _("<qt>Proprietary drivers are available which may be required "
                      "to utilize the full capabilities of your video card. "
                      "These drivers are developed by the hardware manufacturer "
                      "and not supported by Pisi Linux developers since their "
                      "source code is not publicly available."
                      "<br><br>"
                      "<b>Do you want to install and use these proprietary drivers "
                      "instead of the default drivers?</b></qt>"),
                      type="custom", customIcon="question",
                      customButtons=[_("Yes"), _("No")])

            if answer == 0:
                packages.update(neededDriverPackages)
                ctx.blacklistedKernelModules.append(panda.get_blacklisted_module())
                ctx.logger.debug("These driver packages will be installed: %s" % neededDriverPackages)

        return list(packages)
Beispiel #21
0
class QuestForm(QtWidgets.QWidget, quest.Ui_Form):
    def __init__(self, nextStepFunc, img):
        super(QuestForm, self).__init__()
        self.setupUi(self)
        self.setWindowFlags(Qt.FramelessWindowHint)
        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)

        self.nextStepFunc = nextStepFunc
        self.img = img

        self.first_ans.toggled.connect(self.onSelect)
        self.first_ans.name = 0  #"first_ans"
        self.second_ans.toggled.connect(self.onSelect)
        self.second_ans.name = 1  #"second_ans"
        self.third_ans.toggled.connect(self.onSelect)
        self.third_ans.name = 2  #"third_ans"
        self.fourth_ans.toggled.connect(self.onSelect)
        self.fourth_ans.name = 3  #"fourth_ans

        self.btn_group = QtWidgets.QButtonGroup()
        self.btn_group.addButton(self.first_ans)
        self.btn_group.addButton(self.second_ans)
        self.btn_group.addButton(self.third_ans)
        self.btn_group.addButton(self.fourth_ans)
        self.pushButton.clicked.connect(self.nextStep)
        self.pushButton_2.clicked.connect(self.prevStep)

        self.VAS.setText("")

        self.Qtimer = None
        self.testData = []
        self.choice = None
        self.phrases = None
        self.step = 0
        self.pitchSize = None
        self.time = None
        self.stopCount = None
        self.currTime = 0

        self.not_finished = True

        self.localSetupUi()

    def timeOut(self):
        if -1 in self.choice:
            print("OMG")
            ans = []
            for sample in self.testData:
                ans.append(int(sample["ans"]))
            for i, el in enumerate(self.choice):
                if el == -1:
                    self.choice[i] = 0
                    if ans[i] == 0: self.choice[i] = 1

        self.nextStepFunc(self.choice)

    def setupTimer(self):
        if self.time > 0:
            self.stopCount = self.time * 60

    def timerStart(self):
        self.timer.setText("0:00")
        self.Qtimer = QTimer(self)
        self.Qtimer.timeout.connect(self.loop)
        self.Qtimer.start(1000)

    def loop(self):
        self.currTime += 1
        if self.not_finished:
            if not self.stopCount is None:
                if self.currTime == self.stopCount:
                    self.Qtimer.stop()
                    self.timeOut()
                if self.stopCount - self.currTime < 30:
                    self.timer.setStyleSheet("color: rgb(219, 35, 35);")

            time = str(self.currTime // 60) + ":"
            if self.currTime % 60 < 10: time += "0" + str(self.currTime % 60)
            else: time += str(self.currTime % 60)
            self.timer.setText(time)

        else:
            self.Qtimer.stop()

    def setPhrases(self, phr):
        self.phrases = phr

    def setData(self, data):
        self.pitchSize = len(data)
        self.choice = [-1 for el in range(self.pitchSize)]

        dataOrder = np.random.permutation(self.pitchSize)
        for ind in dataOrder:
            self.testData.append(data[ind])

        setup = self.testData[0]
        self.time = int(setup["time"])
        self.test_name.setText(setup["name"])
        self.setupTimer()

        self.guiUpdate(step=0)

    def nextStep(self):
        if self.step == self.pitchSize - 1:
            self.not_finished = False
            self.nextStepFunc(self.choice)
        else:
            self.step += 1
            self.guiUpdate(step=self.step)

    def prevStep(self):
        self.step -= 1
        self.guiUpdate(step=self.step)

    def onSelect(self):
        radioBtn = self.sender()
        if radioBtn.isChecked():
            self.choice[self.step] = radioBtn.name

        if self.step == self.pitchSize - 1:
            if not -1 in self.choice:
                self.pushButton.setEnabled(True)

    def guiUpdate(self, step=None):
        sample = self.testData[step]

        self.quest.setText(sample["text"])
        self.phrase.setText(np.random.choice(self.phrases))
        self.first_ans.setText(sample["q_1"])
        self.second_ans.setText(sample["q_2"])
        self.third_ans.setText(sample["q_3"])
        self.fourth_ans.setText(sample["q_4"])

        self.btn_group.setExclusive(
            False
        )  # переводим группу из екслюзивного ржима, в нем возможен множественный выбор/не выбрать ни одной
        for radio in self.btn_group.buttons():
            radio.setChecked(False)  # снимаем отметки
        self.btn_group.setExclusive(True)

        if self.choice[step] != -1:
            buttons = self.btn_group.buttons()
            button = buttons[self.choice[step]]
            button.setChecked(True)

        self.pushButton.setEnabled(True)
        self.pushButton_2.setEnabled(True)
        self.pushButton.setText("следующий")

        if step == 0:
            self.pushButton_2.setEnabled(False)
        elif step == self.pitchSize - 1:
            self.pushButton.setText("завершить")
            if -1 in self.choice:
                self.pushButton.setEnabled(False)

        self.quest_numb.setText("Вопрос №" + str(self.step + 1))

    def localSetupUi(self):
        self.palette = QtGui.QPalette()
        self.palette.setBrush(QtGui.QPalette.Window, QtGui.QBrush(self.img))
        self.setPalette(self.palette)
Beispiel #22
0
class CoverDelegate(QStyledItemDelegate):  # {{{

    needs_redraw = pyqtSignal()

    def __init__(self, parent):
        QStyledItemDelegate.__init__(self, parent)

        self.angle = 0
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.frame_changed)
        self.color = parent.palette().color(QPalette.WindowText)
        self.spinner_width = 64

    def frame_changed(self, *args):
        self.angle = (self.angle + 30) % 360
        self.needs_redraw.emit()

    def start_animation(self):
        self.angle = 0
        self.timer.start(200)

    def stop_animation(self):
        self.timer.stop()

    def draw_spinner(self, painter, rect):
        width = rect.width()

        outer_radius = (width - 1) * 0.5
        inner_radius = (width - 1) * 0.5 * 0.38

        capsule_height = outer_radius - inner_radius
        capsule_width = int(capsule_height * (0.23 if width > 32 else 0.35))
        capsule_radius = capsule_width // 2

        painter.save()
        painter.setRenderHint(painter.Antialiasing)

        for i in xrange(12):
            color = QColor(self.color)
            color.setAlphaF(1.0 - (i / 12.0))
            painter.setPen(Qt.NoPen)
            painter.setBrush(color)
            painter.save()
            painter.translate(rect.center())
            painter.rotate(self.angle - i * 30.0)
            painter.drawRoundedRect(-capsule_width * 0.5,
                                    -(inner_radius + capsule_height),
                                    capsule_width, capsule_height,
                                    capsule_radius, capsule_radius)
            painter.restore()
        painter.restore()

    def paint(self, painter, option, index):
        QStyledItemDelegate.paint(self, painter, option, index)
        style = QApplication.style()
        waiting = self.timer.isActive() and bool(index.data(Qt.UserRole))
        if waiting:
            rect = QRect(0, 0, self.spinner_width, self.spinner_width)
            rect.moveCenter(option.rect.center())
            self.draw_spinner(painter, rect)
        else:
            # Ensure the cover is rendered over any selection rect
            style.drawItemPixmap(painter, option.rect,
                                 Qt.AlignTop | Qt.AlignHCenter,
                                 QPixmap(index.data(Qt.DecorationRole)))
Beispiel #23
0
class SequencePlayback():
    def __init__(self):
        self.imageSequence = []
        self.framerate = 0
        self.currentFrame = -1
        self.currentFrameIdx = -1
        self.endFrameIdx = 1
        self.state = ''

        self.timer = QTimer()
        self.timer.timerEvent = self.onTick
        Event.add(AudioPlaybackEvent.TICK, self.onTick)
        Event.add(PlaybackEvent.STATE, self.onState)
        self.setFramerate(24)
        pass

    def onState(self, state):
        self.state = state
        if state == PlayStateType.PLAY:
            self.play()
        elif state == PlayStateType.PAUSE:
            self.pause()
            pass
        pass

    def onTick(self, time):
        self.render()
        pass

    # def load(self, imagesPath=None):
    #     if imagesPath:
    #         for root, dirs, files in os.walk(imagesPath):
    #             for filespath in files:
    #                 filename = os.path.join(root, filespath).replace('\\', '/')
    #                 # todo support image ext
    #                 if filename.find('.png') < 0:
    #                     continue
    #                 simage = SImage(filename)
    #                 self.imageSequence.append(simage)
    #                 simage.frameIdx = len(self.imageSequence)
    #                 self.endFrameIdx = simage.frameIdx
    #                 print('[load img]: ', filename)
    #         Event.dis(ActionEvent.LOAD_SEQ, self.imageSequence)
    #         pass

    def play(self):
        if not self.timer.isActive():
            self.timer.start()
            pass
        pass

    def pause(self):
        if self.timer.isActive():
            self.timer.stop()
            pass
        pass

    def render(self):
        self.currentFrameIdx = (self.currentFrameIdx + 1) % self.endFrameIdx
        event = SequencePlaybackEvent()
        event.type = SequencePlaybackEvent.RENDER_FRAME
        event.frameIdx = self.currentFrameIdx
        Event.dis(SequencePlaybackEvent.RENDER_FRAME, event)

    def setFramerate(self, framerate):
        self.framerate = framerate
        self.timer.setInterval(1000 / self.framerate)
        pass
Beispiel #24
0
class Guess(QMainWindow):
    def __init__(self):
        super().__init__()
        uic.loadUi('QTUI.ui', self)
        self.setWindowTitle('Игра «Угадай песню»')
        self.conn = sqlite3.connect('music_db.db')
        self.state_game = False
        self.played = []
        self.used = set()
        self.score = 0
        self.corr_answ = {'', ''}
        self.load_base.clicked.connect(self.load_own_musicdb)
        self.player = QMediaPlayer()
        self.timer = QTimer()
        self.selected_level = None
        self.answ_btn.clicked.connect(self.check_answ)
        self.easy_lvl_btn.clicked.connect(self.time_for_lvl)
        self.med_lvl_btn.clicked.connect(self.time_for_lvl)
        self.hard_lvl_btn.clicked.connect(self.time_for_lvl)
        self.load_own_sg.clicked.connect(self.load_own_song)
        self.start_btn.clicked.connect(self.start_game)
        self.stop_btn.clicked.connect(self.stop_game)
        self.volume_state.setValue(50)
        self.volume_state.valueChanged[int].connect(self.player.setVolume)
        self.btn_scorebrd.clicked.connect(self.show_scoreboard)

    def start_game(self):
        print('start')
        self.state_game = True
        if self.selected_level:
            self.able_btn(False, 'Игра началась, слушайте и пишите ответ в ввод ниже!')
            song_name, art_id, path_to_song = self.get_info_rnd_song()
            q = f'''SELECT Artist FROM Artist WHERE id={art_id}'''
            artist = sqlite3.connect('music_db.db').cursor().execute(q).fetchall()[0][0]
            set_name = set(song_name.lower().split())
            self.corr_answ = {artist.lower()} | set_name
            file = QUrl.fromLocalFile(path_to_song)
            cont = QMediaContent(file)
            self.player.setMedia(cont)
            self.player.play()
            self.timer.start()
            self.timer.timeout.connect(self.time_to_answ)
        else:
            self.verdict.setText('Не выбран уровень!')

    def stop_game(self):
        if self.player.state() == QMediaPlayer.PlayingState:
            self.player.stop()
        self.timer.stop()
        self.able_btn(True, 'Игра прервана')
        if self.score:
            valid = QMessageBox.question(
                self, '', f"Сохранить реультат score: {self.score}",
                QMessageBox.Yes, QMessageBox.No)
            if valid == QMessageBox.Yes:
                valid_name = QMessageBox.question(
                    self, '', f"Хотите ли вы добавить имя к результату?",
                    QMessageBox.Yes, QMessageBox.No)
                if valid_name == QMessageBox.Yes:
                    playername = QInputDialog.getText(self, "Добавление имени к результату", "Введите имя игрока:",
                                                      QLineEdit.Normal)[0]
                    self.load_score(playername)
                else:
                    self.load_score()
        self.succs_sg.display(0)
        self.score = 0
        self.state_game = False

    def load_score(self, playername='NOT STATED'):
        self.conn.cursor().execute(f'''INSERT INTO Scoreboard(score, name) VALUES({self.score}, "{playername}")''')
        self.conn.commit()
        print('loaded')

    def show_scoreboard(self):
        self.score_brd = ScoreBoard()
        self.score_brd.show()

    def time_to_answ(self):
        self.player.stop()
        self.verdict.setText('У вас есть 7 сек для ответа')
        self.timer.setInterval(7000)
        self.timer.start()
        self.timer.timeout.connect(self.check_answ)

    def check_answ(self):
        if self.state_game:
            answer_text = set([e.lower() for e in self.answ_line.text().split()])
            print(answer_text, self.corr_answ)
            self.timer.stop()
            res = True if len(self.corr_answ & answer_text) >= len(self.corr_answ) // 2 else False
            if res:
                self.score += 1
                self.succs_sg.display(str(self.score))
                self.answ_line.setText('')
                self.start_game()
            else:
                self.answ_line.setText('')
                self.stop_game()
                self.selected_level = None
                self.sel_lvl.setText('Вы проиграли')

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Return and self.state_game:
            self.check_answ()
            self.timer.stop()

    def time_for_lvl(self):
        lvl = self.sender().text()
        self.sel_lvl.setText(f'Выбранный уровень: {lvl}')
        self.selected_level = lvl
        if lvl == 'Легкий':
            self.timer.setInterval(15000)
        elif lvl == 'Средний':
            self.timer.setInterval(10000)
        elif lvl == 'Сложный':
            self.timer.setInterval(5000)

    def able_btn(self, state, verdict):
        self.verdict.setText(verdict)
        self.easy_lvl_btn.setEnabled(state)
        self.med_lvl_btn.setEnabled(state)
        self.hard_lvl_btn.setEnabled(state)
        self.start_btn.setEnabled(state)
        self.load_own_sg.setEnabled(state)
        self.load_base.setEnabled(state)

    def load_own_song(self):
        art_name = QInputDialog.getText(self, "Добавление песни", "Введите имя исполнителя:", QLineEdit.Normal)[0]
        track_name = QInputDialog.getText(self, "Добавление песни", "Введите название трека:", QLineEdit.Normal)[0]
        path_file = QFileDialog.getOpenFileName(self, 'Open File')[0]  # 'Музыка (*.mp3);;Музыка (*.wav);;Все файлы (*)'
        ex_art, trks = self.check_ex_art()
        track_name = ' '.join(list(filter(lambda x: x.isalpha(), track_name.split())))
        if path_file and art_name and track_name:
            self.add_song(art_name, track_name, path_file, ex_art)
        else:
            print('Ошибка')

    def add_song(self, artist, track_name, path_file, ex_art):
        if artist.lower() in list(map(lambda x: x.lower(), ex_art)):
            query1 = f'''SELECT id FROM Artist WHERE Artist="{artist.capitalize()}"'''
            art_id = self.conn.cursor().execute(query1).fetchall()[0][0]
            trks = self.check_ex_art(art_id)[1]
            if track_name.lower() not in trks:
                query = f'''INSERT INTO Tracks(name, artist_id, path) VALUES("{track_name.capitalize()}",
                "{art_id}", "{path_file}")'''
                self.conn.cursor().execute(query)
                self.conn.commit()
            else:
                print('Трек уже есть в коллекции')
        else:
            query = f'''INSERT INTO Artist(Artist) VALUES("{artist.capitalize()}")'''
            self.conn.cursor().execute(query)
            self.conn.commit()
            query1 = f'''SELECT id FROM Artist WHERE Artist="{artist.capitalize()}"'''
            id_a = self.conn.cursor().execute(query1).fetchall()[0][0]
            query = f'''INSERT INTO Tracks(name, artist_id, path) VALUES("{track_name.capitalize()}",
            "{id_a}", "{path_file}")'''
            self.conn.cursor().execute(query)
            self.conn.commit()

    def load_own_musicdb(self):
        directory = QFileDialog.getExistingDirectory(self, 'Open Music Folder')
        if directory:
            art_fold = os.listdir(directory)
            tracks = []
            for art in art_fold:
                if os.path.isdir(directory + '/' + art):
                    tracks += self.deep_in_fold(directory + '/' + art)
            ex_art, trks = self.check_ex_art()
            for track in tracks:
                artist = track[0]
                track_name = track[1].split('/')[-1].split('.')[0]
                track_name = ' '.join(list(filter(lambda x: x.isalpha(), track_name.split())))
                path_file = track[1]
                self.add_song(artist, track_name, path_file, ex_art)

    def deep_in_fold(self, directory):
        elems = os.listdir(directory)
        tracks = []
        for e in elems:
            if os.path.isdir(directory + '/' + e):
                tracks += self.deep_in_fold(directory + '/' + e)
            else:
                tracks = [(directory.split('/')[-2], directory + '/' + e) for e in elems]
                return tracks
        return tracks

    def get_info_rnd_song(self):
        tracks = self.check_ex_art()[1]
        rnd_num = rnd.randint(1, len(tracks))
        q = f'''SELECT * FROM Tracks WHERE id={rnd_num}'''
        res = self.conn.cursor().execute(q).fetchall()[0]
        song_name, id_artist, path = res[1], res[2], res[3]
        if (song_name, id_artist) in self.used:
            self.get_info_rnd_song()
        else:
            self.used.add((song_name, id_artist))
            return song_name, id_artist, path

    def check_ex_art(self, art_id=''):
        exist_art = [e[0] for e in sqlite3.connect('music_db.db').cursor().execute(
            '''SELECT Artist FROM Artist''').fetchall()]
        if art_id != '':
            tracks = [e[0] for e in sqlite3.connect('music_db.db').cursor().execute(
                f'''SELECT name FROM Tracks WHERE artist_id={art_id}''').fetchall()]
        else:
            tracks = [e[0] for e in sqlite3.connect('music_db.db').cursor().execute(
                '''SELECT name FROM Tracks''').fetchall()]
        return exist_art, tracks

    def Volume(self):
        self.player.setVolume(self.volume_state.Value)

    def closeEvent(self, event):
        self.conn.close()
Beispiel #25
0
class ScudCloud(QtWidgets.QMainWindow):

    forceClose = False
    messages = 0
    speller = Speller()
    title = 'ScudCloud'

    def __init__(self,
                 debug=False,
                 minimized=None,
                 urgent_hint=None,
                 settings_path='',
                 cache_path=''):
        super(ScudCloud, self).__init__(None)
        self.debug = debug
        self.minimized = minimized
        self.urgent_hint = urgent_hint
        self.setWindowTitle(self.title)
        self.settings_path = settings_path
        self.cache_path = cache_path
        self.notifier = Notifier(Resources.APP_NAME,
                                 Resources.get_path('scudcloud.png'))
        self.settings = QSettings(self.settings_path + '/scudcloud_qt5.cfg',
                                  QSettings.IniFormat)
        self.notifier.enabled = self.settings.value('Notifications',
                                                    defaultValue=True,
                                                    type=bool)
        self.identifier = self.settings.value("Domain")
        if Unity is not None:
            self.launcher = Unity.LauncherEntry.get_for_desktop_id(
                "scudcloud.desktop")
        else:
            self.launcher = DummyLauncher(self)
        self.webSettings()
        self.snippetsSettings()
        self.leftPane = LeftPane(self)
        self.stackedWidget = QtWidgets.QStackedWidget()
        centralWidget = QtWidgets.QWidget(self)
        layout = QtWidgets.QHBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        layout.addWidget(self.leftPane)
        layout.addWidget(self.stackedWidget)
        centralWidget.setLayout(layout)
        self.setCentralWidget(centralWidget)
        self.startURL = Resources.SIGNIN_URL
        if self.identifier is not None:
            if isinstance(self.identifier, str):
                self.domains = self.identifier.split(",")
            else:
                self.domains = self.identifier
            self.startURL = self.normalize(self.domains[0])
        else:
            self.domains = []
        self.addWrapper(self.startURL)
        self.addMenu()
        self.tray = Systray(self)
        self.systray(self.minimized)
        self.installEventFilter(self)
        self.statusBar().showMessage('Loading Slack...')
        self.tickler = QTimer(self)
        self.tickler.setInterval(1800000)
        # Watch for ScreenLock events
        if DBusQtMainLoop is not None:
            DBusQtMainLoop(set_as_default=True)
            sessionBus = dbus.SessionBus()
            # Ubuntu 12.04 and other distros
            sessionBus.add_match_string(
                "type='signal',interface='org.gnome.ScreenSaver'")
            # Ubuntu 14.04
            sessionBus.add_match_string(
                "type='signal',interface='com.ubuntu.Upstart0_6'")
            # Ubuntu 16.04 and KDE
            sessionBus.add_match_string(
                "type='signal',interface='org.freedesktop.ScreenSaver'")
            # Cinnamon
            sessionBus.add_match_string(
                "type='signal',interface='org.cinnamon.ScreenSaver'")
            sessionBus.add_message_filter(self.screenListener)
            self.tickler.timeout.connect(self.sendTickle)
        # If dbus is not present, tickler timer will act like a blocker to not send tickle too often
        else:
            self.tickler.setSingleShot(True)
        self.tickler.start()

    def screenListener(self, bus, message):
        event = message.get_member()
        # "ActiveChanged" for Ubuntu 12.04 and other distros. "EventEmitted" for Ubuntu 14.04 and above
        if event == "ActiveChanged" or event == "EventEmitted":
            arg = message.get_args_list()[0]
            # True for Ubuntu 12.04 and other distros. "desktop-lock" for Ubuntu 14.04 and above
            if (arg == True
                    or arg == "desktop-lock") and self.tickler.isActive():
                self.tickler.stop()
            elif (arg == False
                  or arg == "desktop-unlock") and not self.tickler.isActive():
                self.sendTickle()
                self.tickler.start()

    def sendTickle(self):
        for i in range(0, self.stackedWidget.count()):
            self.stackedWidget.widget(i).sendTickle()

    def addWrapper(self, url):
        webView = Wrapper(self)
        webView.load(QtCore.QUrl(url))
        webView.show()
        webView.setZoomFactor(self.zoom)
        self.stackedWidget.addWidget(webView)
        self.stackedWidget.setCurrentWidget(webView)
        self.clearMemory()

    def webSettings(self):
        self.cookiesjar = PersistentCookieJar(self)
        self.zoom = self.readZoom()
        # We don't want Flash (it causes a lot of trouble in some distros)
        QWebSettings.globalSettings().setAttribute(QWebSettings.PluginsEnabled,
                                                   False)
        # We don't need Java
        QWebSettings.globalSettings().setAttribute(QWebSettings.JavaEnabled,
                                                   False)
        # Enabling Local Storage (now required by Slack)
        QWebSettings.globalSettings().setAttribute(
            QWebSettings.LocalStorageEnabled, True)
        # We need browsing history (required to not limit LocalStorage)
        QWebSettings.globalSettings().setAttribute(
            QWebSettings.PrivateBrowsingEnabled, False)
        # Enabling Cache
        self.diskCache = QNetworkDiskCache(self)
        self.diskCache.setCacheDirectory(self.cache_path)
        # Required for copy and paste clipboard integration
        QWebSettings.globalSettings().setAttribute(
            QWebSettings.JavascriptCanAccessClipboard, True)
        # Enabling Inspeclet only when --debug=True (requires more CPU usage)
        QWebSettings.globalSettings().setAttribute(
            QWebSettings.DeveloperExtrasEnabled, self.debug)
        # Sharing the same networkAccessManager
        self.networkAccessManager = QNetworkAccessManager(self)
        self.networkAccessManager.setCookieJar(self.cookiesjar)
        self.networkAccessManager.setCache(self.diskCache)

    def snippetsSettings(self):
        self.disable_snippets = self.settings.value("Snippets")
        if self.disable_snippets is not None:
            self.disable_snippets = self.disable_snippets == "False"
        else:
            self.disable_snippets = False
        if self.disable_snippets:
            disable_snippets_css = ''
            with open(Resources.get_path('disable_snippets.css'), 'r') as f:
                disable_snippets_css = f.read()
            with open(os.path.join(self.cache_path, 'resources.css'),
                      'a') as f:
                f.write(disable_snippets_css)

    def toggleFullScreen(self):
        if self.isFullScreen():
            self.showMaximized()
        else:
            self.showFullScreen()

    def toggleMenuBar(self):
        menu = self.menuBar()
        state = menu.isHidden()
        menu.setVisible(state)
        if state:
            self.settings.setValue("Menu", "True")
        else:
            self.settings.setValue("Menu", "False")

    def restore(self):
        geometry = self.settings.value("geometry")
        if geometry is not None:
            self.restoreGeometry(geometry)
        windowState = self.settings.value("windowState")
        if windowState is not None:
            self.restoreState(windowState)
        else:
            self.setWindowState(QtCore.Qt.WindowMaximized)

    def systray(self, show=None):
        if show is None:
            show = self.settings.value("Systray") == "True"
        if show:
            self.tray.show()
            self.menus["file"]["close"].setEnabled(True)
            self.settings.setValue("Systray", "True")
        else:
            self.tray.setVisible(False)
            self.menus["file"]["close"].setEnabled(False)
            self.settings.setValue("Systray", "False")

    def readZoom(self):
        default = 1
        if self.settings.value("Zoom") is not None:
            default = float(self.settings.value("Zoom"))
        return default

    def setZoom(self, factor=1):
        if factor > 0:
            for i in range(0, self.stackedWidget.count()):
                widget = self.stackedWidget.widget(i)
                widget.setZoomFactor(factor)
            self.settings.setValue("Zoom", factor)

    def zoomIn(self):
        self.setZoom(self.current().zoomFactor() + 0.1)

    def zoomOut(self):
        self.setZoom(self.current().zoomFactor() - 0.1)

    def zoomReset(self):
        self.setZoom()

    def addTeam(self):
        self.switchTo(Resources.SIGNIN_URL)

    def addMenu(self):
        # We'll register the webpage shorcuts with the window too (Fixes #338)
        undo = self.current().pageAction(QWebPage.Undo)
        redo = self.current().pageAction(QWebPage.Redo)
        cut = self.current().pageAction(QWebPage.Cut)
        copy = self.current().pageAction(QWebPage.Copy)
        paste = self.current().pageAction(QWebPage.Paste)
        back = self.current().pageAction(QWebPage.Back)
        forward = self.current().pageAction(QWebPage.Forward)
        reload = self.current().pageAction(QWebPage.Reload)
        self.menus = {
            "file": {
                "preferences":
                self.createAction("Preferences",
                                  lambda: self.current().preferences()),
                "systray":
                self.createAction("Close to Tray", self.systray, None, True),
                "addTeam":
                self.createAction("Sign in to Another Team",
                                  lambda: self.addTeam()),
                "signout":
                self.createAction("Signout", lambda: self.current().logout()),
                "close":
                self.createAction("Close", self.close, QKeySequence.Close),
                "exit":
                self.createAction("Quit", self.exit, QKeySequence.Quit)
            },
            "edit": {
                "undo":
                self.createAction(
                    undo.text(),
                    lambda: self.current().page().triggerAction(QWebPage.Undo),
                    undo.shortcut()),
                "redo":
                self.createAction(
                    redo.text(),
                    lambda: self.current().page().triggerAction(QWebPage.Redo),
                    redo.shortcut()),
                "cut":
                self.createAction(
                    cut.text(),
                    lambda: self.current().page().triggerAction(QWebPage.Cut),
                    cut.shortcut()),
                "copy":
                self.createAction(
                    copy.text(),
                    lambda: self.current().page().triggerAction(QWebPage.Copy),
                    copy.shortcut()),
                "paste":
                self.createAction(
                    paste.text(), lambda: self.current().page().triggerAction(
                        QWebPage.Paste), paste.shortcut()),
                "back":
                self.createAction(
                    back.text(),
                    lambda: self.current().page().triggerAction(QWebPage.Back),
                    back.shortcut()),
                "forward":
                self.createAction(
                    forward.text(), lambda: self.current().page().
                    triggerAction(QWebPage.Forward), forward.shortcut()),
                "reload":
                self.createAction(
                    reload.text(), lambda: self.current().page().triggerAction(
                        QWebPage.Reload), reload.shortcut()),
            },
            "view": {
                "zoomin":
                self.createAction("Zoom In", self.zoomIn, QKeySequence.ZoomIn),
                "zoomout":
                self.createAction("Zoom Out", self.zoomOut,
                                  QKeySequence.ZoomOut),
                "reset":
                self.createAction("Reset", self.zoomReset,
                                  QtCore.Qt.CTRL + QtCore.Qt.Key_0),
                "fullscreen":
                self.createAction("Toggle Full Screen", self.toggleFullScreen,
                                  QtCore.Qt.Key_F11),
                "hidemenu":
                self.createAction("Toggle Menubar", self.toggleMenuBar,
                                  QtCore.Qt.Key_F12)
            },
            "help": {
                "help":
                self.createAction("Help and Feedback",
                                  lambda: self.current().help(),
                                  QKeySequence.HelpContents),
                "center":
                self.createAction("Slack Help Center",
                                  lambda: self.current().helpCenter()),
                "about":
                self.createAction("About", lambda: self.current().about())
            }
        }
        menu = self.menuBar()
        fileMenu = menu.addMenu("&File")
        fileMenu.addAction(self.menus["file"]["preferences"])
        fileMenu.addAction(self.menus["file"]["systray"])
        fileMenu.addSeparator()
        fileMenu.addAction(self.menus["file"]["addTeam"])
        fileMenu.addAction(self.menus["file"]["signout"])
        fileMenu.addSeparator()
        fileMenu.addAction(self.menus["file"]["close"])
        fileMenu.addAction(self.menus["file"]["exit"])
        editMenu = menu.addMenu("&Edit")
        editMenu.addAction(self.menus["edit"]["undo"])
        editMenu.addAction(self.menus["edit"]["redo"])
        editMenu.addSeparator()
        editMenu.addAction(self.menus["edit"]["cut"])
        editMenu.addAction(self.menus["edit"]["copy"])
        editMenu.addAction(self.menus["edit"]["paste"])
        editMenu.addSeparator()
        editMenu.addAction(self.menus["edit"]["back"])
        editMenu.addAction(self.menus["edit"]["forward"])
        editMenu.addAction(self.menus["edit"]["reload"])
        viewMenu = menu.addMenu("&View")
        viewMenu.addAction(self.menus["view"]["zoomin"])
        viewMenu.addAction(self.menus["view"]["zoomout"])
        viewMenu.addAction(self.menus["view"]["reset"])
        viewMenu.addSeparator()
        viewMenu.addAction(self.menus["view"]["fullscreen"])
        viewMenu.addAction(self.menus["view"]["hidemenu"])
        helpMenu = menu.addMenu("&Help")
        helpMenu.addAction(self.menus["help"]["help"])
        helpMenu.addAction(self.menus["help"]["center"])
        helpMenu.addSeparator()
        helpMenu.addAction(self.menus["help"]["about"])
        self.enableMenus(False)
        showSystray = self.settings.value("Systray") == "True"
        self.menus["file"]["systray"].setChecked(showSystray)
        self.menus["file"]["close"].setEnabled(showSystray)
        # Restore menu visibility
        visible = self.settings.value("Menu")
        if visible is not None and visible == "False":
            menu.setVisible(False)

    def enableMenus(self, enabled):
        self.menus["file"]["preferences"].setEnabled(enabled == True)
        self.menus["file"]["addTeam"].setEnabled(enabled == True)
        self.menus["file"]["signout"].setEnabled(enabled == True)
        self.menus["help"]["help"].setEnabled(enabled == True)

    def createAction(self, text, slot, shortcut=None, checkable=False):
        action = QtWidgets.QAction(text, self)
        action.triggered.connect(slot)
        if shortcut is not None:
            action.setShortcut(shortcut)
            self.addAction(action)
        if checkable:
            action.setCheckable(True)
        return action

    def normalize(self, url):
        if url.endswith(".slack.com"):
            url += "/"
        elif not url.endswith(".slack.com/"):
            url = "https://" + url + ".slack.com/"
        return url

    def current(self):
        return self.stackedWidget.currentWidget()

    def teams(self, teams):
        if len(self.domains) == 0:
            self.domains.append(teams[0]['team_url'])
        team_list = [t['team_url'] for t in teams]
        for t in teams:
            for i in range(0, len(self.domains)):
                self.domains[i] = self.normalize(self.domains[i])
                # When team_icon is missing, the team already exists (Fixes #381, #391)
                if 'team_icon' in t:
                    if self.domains[i] in team_list:
                        add = next(item for item in teams
                                   if item['team_url'] == self.domains[i])
                        if 'team_icon' in add:
                            self.leftPane.addTeam(add['id'], add['team_name'],
                                                  add['team_url'],
                                                  add['team_icon']['image_44'],
                                                  add == teams[0])
                            # Adding new teams and saving loading positions
                            if t['team_url'] not in self.domains:
                                self.leftPane.addTeam(
                                    t['id'], t['team_name'], t['team_url'],
                                    t['team_icon']['image_44'], t == teams[0])
                                self.domains.append(t['team_url'])
                                self.settings.setValue("Domain", self.domains)
        if len(teams) > 1:
            self.leftPane.show()

    def switchTo(self, url):
        exists = False
        for i in range(0, self.stackedWidget.count()):
            if self.stackedWidget.widget(i).url().toString().startswith(url):
                self.stackedWidget.setCurrentIndex(i)
                self.quicklist(self.current().listChannels())
                self.current().setFocus()
                self.leftPane.click(i)
                self.clearMemory()
                exists = True
                break
        if not exists:
            self.addWrapper(url)

    def eventFilter(self, obj, event):
        if event.type(
        ) == QtCore.QEvent.ActivationChange and self.isActiveWindow():
            self.focusInEvent(event)
        if event.type() == QtCore.QEvent.KeyPress:
            # Ctrl + <n>
            modifiers = QtWidgets.QApplication.keyboardModifiers()
            if modifiers == QtCore.Qt.ControlModifier:
                if event.key() == QtCore.Qt.Key_1: self.leftPane.click(0)
                elif event.key() == QtCore.Qt.Key_2: self.leftPane.click(1)
                elif event.key() == QtCore.Qt.Key_3: self.leftPane.click(2)
                elif event.key() == QtCore.Qt.Key_4: self.leftPane.click(3)
                elif event.key() == QtCore.Qt.Key_5: self.leftPane.click(4)
                elif event.key() == QtCore.Qt.Key_6: self.leftPane.click(5)
                elif event.key() == QtCore.Qt.Key_7: self.leftPane.click(6)
                elif event.key() == QtCore.Qt.Key_8: self.leftPane.click(7)
                elif event.key() == QtCore.Qt.Key_9:
                    self.leftPane.click(8)
                    # Ctrl + Tab
                elif event.key() == QtCore.Qt.Key_Tab:
                    self.leftPane.clickNext(1)
            # Ctrl + BackTab
            if (modifiers & QtCore.Qt.ControlModifier) and (
                    modifiers & QtCore.Qt.ShiftModifier):
                if event.key() == QtCore.Qt.Key_Backtab:
                    self.leftPane.clickNext(-1)
            # Ctrl + Shift + <key>
            if (modifiers & QtCore.Qt.ShiftModifier) and (
                    modifiers & QtCore.Qt.ShiftModifier):
                if event.key() == QtCore.Qt.Key_V:
                    self.current().createSnippet()
        return QtWidgets.QMainWindow.eventFilter(self, obj, event)

    def focusInEvent(self, event):
        self.launcher.set_property("urgent", False)
        self.tray.stopAlert()
        # Let's tickle all teams on window focus, but only if tickle was not fired in last 30 minutes
        if DBusQtMainLoop is None and not self.tickler.isActive():
            self.sendTickle()
            self.tickler.start()

    def titleChanged(self):
        self.setWindowTitle(self.current().title())

    def setForceClose(self):
        self.forceClose = True

    def closeEvent(self, event):
        if not self.forceClose and self.settings.value("Systray") == "True":
            self.hide()
            event.ignore()
        else:
            self.cookiesjar.save()
            self.settings.setValue("Domain", self.domains)
            self.settings.setValue("geometry", self.saveGeometry())
            self.settings.setValue("windowState", self.saveState())
            self.settings.setValue("Domain", self.domains)
        self.forceClose = False

    def show(self):
        self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized
                            | QtCore.Qt.WindowActive)
        self.activateWindow()
        self.setVisible(True)

    def exit(self):
        # Make sure tray is not visible (Fixes #513)
        self.tray.setVisible(False)
        self.setForceClose()
        self.close()

    def quicklist(self, channels):
        if Dbusmenu is not None:
            if channels is not None:
                ql = Dbusmenu.Menuitem.new()
                self.launcher.set_property("quicklist", ql)
                for c in channels:
                    if type(c) is dict and hasattr(
                            c, '__getitem__') and c['is_member']:
                        item = Dbusmenu.Menuitem.new()
                        item.property_set(Dbusmenu.MENUITEM_PROP_LABEL,
                                          "#" + c['name'])
                        item.property_set("id", c['name'])
                        item.property_set_bool(Dbusmenu.MENUITEM_PROP_VISIBLE,
                                               True)
                        item.connect(Dbusmenu.MENUITEM_SIGNAL_ITEM_ACTIVATED,
                                     self.current().openChannel)
                        ql.child_append(item)
                self.launcher.set_property("quicklist", ql)

    def notify(self, title, message, icon):
        if self.debug:
            print("Notification: title [{}] message [{}] icon [{}]".format(
                title, message, icon))
        self.notifier.notify(title, message, icon)
        self.alert()

    def alert(self):
        if not self.isActiveWindow():
            self.launcher.set_property("urgent", True)
            self.tray.alert()
        if self.urgent_hint is True:
            QApplication.alert(self)

    def count(self):
        total = 0
        unreads = 0
        for i in range(0, self.stackedWidget.count()):
            widget = self.stackedWidget.widget(i)
            highlights = widget.highlights
            unreads += widget.unreads
            total += highlights
        if total > self.messages:
            self.alert()
        if 0 == total:
            self.launcher.set_property("count_visible", False)
            self.tray.setCounter(0)
            if unreads > 0:
                self.setWindowTitle("*{}".format(self.title))
            else:
                self.setWindowTitle(self.title)
        else:
            self.tray.setCounter(total)
            self.launcher.set_property("count", total)
            self.launcher.set_property("count_visible", True)
            self.setWindowTitle("[{}]{}".format(str(total), self.title))
        self.messages = total

    def clearMemory(self):
        QWebSettings.globalSettings().clearMemoryCaches()
        QWebSettings.globalSettings().clearIconDatabase()
Beispiel #26
0
class SearchDialog(QDialog, Ui_Dialog):

    SEARCH_TEXT = _('&Search')
    STOP_TEXT = _('&Stop')

    def __init__(self, gui, parent=None, query=''):
        QDialog.__init__(self, parent)
        self.setupUi(self)
        s = self.style()
        self.close.setIcon(s.standardIcon(s.SP_DialogCloseButton))

        self.config = JSONConfig('store/search')
        self.search_title.initialize('store_search_search_title')
        self.search_author.initialize('store_search_search_author')
        self.search_edit.initialize('store_search_search')

        # Loads variables that store various settings.
        # This needs to be called soon in __init__ because
        # the variables it sets up are used later.
        self.load_settings()

        self.gui = gui

        # Setup our worker threads.
        self.search_pool = SearchThreadPool(self.search_thread_count)
        self.cache_pool = CacheUpdateThreadPool(self.cache_thread_count)
        self.results_view.model().cover_pool.set_thread_count(
            self.cover_thread_count)
        self.results_view.model().details_pool.set_thread_count(
            self.details_thread_count)
        self.results_view.setCursor(Qt.PointingHandCursor)

        # Check for results and hung threads.
        self.checker = QTimer()
        self.progress_checker = QTimer()
        self.hang_check = 0

        # Update store caches silently.
        for p in self.gui.istores.values():
            self.cache_pool.add_task(p, self.timeout)

        self.store_checks = {}
        self.setup_store_checks()

        # Set the search query
        if isinstance(query, (bytes, unicode_type)):
            self.search_edit.setText(query)
        elif isinstance(query, dict):
            if 'author' in query:
                self.search_author.setText(query['author'])
            if 'title' in query:
                self.search_title.setText(query['title'])

        # Create and add the progress indicator
        self.pi = ProgressIndicator(self, 24)
        self.button_layout.takeAt(0)
        self.button_layout.setAlignment(Qt.AlignCenter)
        self.button_layout.insertWidget(0, self.pi, 0, Qt.AlignCenter)

        self.adv_search_button.setIcon(QIcon(I('gear.png')))
        self.adv_search_button.setToolTip(_('Advanced search'))
        self.configure.setIcon(QIcon(I('config.png')))

        self.adv_search_button.clicked.connect(self.build_adv_search)
        self.search.clicked.connect(self.toggle_search)
        self.checker.timeout.connect(self.get_results)
        self.progress_checker.timeout.connect(self.check_progress)
        self.results_view.activated.connect(self.result_item_activated)
        self.results_view.download_requested.connect(self.download_book)
        self.results_view.open_requested.connect(self.open_store)
        self.results_view.model().total_changed.connect(self.update_book_total)
        self.select_all_stores.clicked.connect(self.stores_select_all)
        self.select_invert_stores.clicked.connect(self.stores_select_invert)
        self.select_none_stores.clicked.connect(self.stores_select_none)
        self.configure.clicked.connect(self.do_config)
        self.finished.connect(self.dialog_closed)
        self.searching = False

        self.progress_checker.start(100)

        self.restore_state()

    def setup_store_checks(self):
        first_run = self.config.get('first_run', True)

        # Add check boxes for each store so the user
        # can disable searching specific stores on a
        # per search basis.
        existing = {}
        for n in self.store_checks:
            existing[n] = self.store_checks[n].isChecked()

        self.store_checks = {}

        stores_check_widget = QWidget()
        store_list_layout = QGridLayout()
        stores_check_widget.setLayout(store_list_layout)

        icon = QIcon(I('donate.png'))
        for i, x in enumerate(
                sorted(self.gui.istores.keys(), key=lambda x: x.lower())):
            cbox = QCheckBox(x)
            cbox.setChecked(existing.get(x, first_run))
            store_list_layout.addWidget(cbox, i, 0, 1, 1)
            if self.gui.istores[x].base_plugin.affiliate:
                iw = QLabel(self)
                iw.setToolTip('<p>' + _(
                    'Buying from this store supports the calibre developer: %s</p>'
                ) % self.gui.istores[x].base_plugin.author + '</p>')
                iw.setPixmap(icon.pixmap(16, 16))
                store_list_layout.addWidget(iw, i, 1, 1, 1)
            self.store_checks[x] = cbox
        store_list_layout.setRowStretch(store_list_layout.rowCount(), 10)
        self.store_list.setWidget(stores_check_widget)

        self.config['first_run'] = False

    def build_adv_search(self):
        adv = AdvSearchBuilderDialog(self)
        if adv.exec_() == QDialog.Accepted:
            self.search_edit.setText(adv.search_string())

    def resize_columns(self):
        total = 600
        # Cover
        self.results_view.setColumnWidth(0, 85)
        total = total - 85
        # Title / Author
        self.results_view.setColumnWidth(1, int(total * .40))
        # Price
        self.results_view.setColumnWidth(2, int(total * .12))
        # DRM
        self.results_view.setColumnWidth(3, int(total * .15))
        # Store / Formats
        self.results_view.setColumnWidth(4, int(total * .25))
        # Download
        self.results_view.setColumnWidth(5, 20)
        # Affiliate
        self.results_view.setColumnWidth(6, 20)

    def toggle_search(self):
        if self.searching:
            self.search_pool.abort()
            m = self.results_view.model()
            m.details_pool.abort()
            m.cover_pool.abort()
            self.search.setText(self.SEARCH_TEXT)
            self.checker.stop()
            self.searching = False
        else:
            self.do_search()
        # Prevent hitting the enter key twice in quick succession causing
        # the search to start and stop
        self.search.setEnabled(False)
        QTimer.singleShot(1000, lambda: self.search.setEnabled(True))

    def do_search(self):
        # Stop all running threads.
        self.checker.stop()
        self.search_pool.abort()
        # Clear the visible results.
        self.results_view.model().clear_results()

        # Don't start a search if there is nothing to search for.
        query = []
        if self.search_title.text():
            query.append(
                u'title2:"~%s"' %
                unicode_type(self.search_title.text()).replace('"', ' '))
        if self.search_author.text():
            query.append(
                u'author2:"%s"' %
                unicode_type(self.search_author.text()).replace('"', ' '))
        if self.search_edit.text():
            query.append(unicode_type(self.search_edit.text()))
        query = " ".join(query)
        if not query.strip():
            error_dialog(self,
                         _('No query'),
                         _('You must enter a title, author or keyword to'
                           ' search for.'),
                         show=True)
            return
        self.searching = True
        self.search.setText(self.STOP_TEXT)
        # Give the query to the results model so it can do
        # futher filtering.
        self.results_view.model().set_query(query)

        # Plugins are in random order that does not change.
        # Randomize the ord of the plugin names every time
        # there is a search. This way plugins closer
        # to a don't have an unfair advantage over
        # plugins further from a.
        store_names = list(self.store_checks)
        if not store_names:
            return
        # Remove all of our internal filtering logic from the query.
        query = self.clean_query(query)
        shuffle(store_names)
        # Add plugins that the user has checked to the search pool's work queue.
        self.gui.istores.join(4.0)  # Wait for updated plugins to load
        for n in store_names:
            if self.store_checks[n].isChecked():
                self.search_pool.add_task(query, n, self.gui.istores[n],
                                          self.max_results, self.timeout)
        self.hang_check = 0
        self.checker.start(100)
        self.pi.startAnimation()

    def clean_query(self, query):
        query = query.lower()
        # Remove control modifiers.
        query = query.replace('\\', '')
        query = query.replace('!', '')
        query = query.replace('=', '')
        query = query.replace('~', '')
        query = query.replace('>', '')
        query = query.replace('<', '')
        # Remove the prefix.
        for loc in ('all', 'author', 'author2', 'authors', 'title', 'title2'):
            query = re.sub(r'%s:"(?P<a>[^\s"]+)"' % loc, r'\g<a>', query)
            query = query.replace('%s:' % loc, '')
        # Remove the prefix and search text.
        for loc in ('cover', 'download', 'downloads', 'drm', 'format',
                    'formats', 'price', 'store'):
            query = re.sub(r'%s:"[^"]"' % loc, '', query)
            query = re.sub(r'%s:[^\s]*' % loc, '', query)
        # Remove logic.
        query = re.sub(r'(^|\s|")(and|not|or|a|the|is|of)(\s|$|")', r' ',
                       query)
        # Remove "
        query = query.replace('"', '')
        # Remove excess whitespace.
        query = re.sub(r'\s+', ' ', query)
        query = query.strip()
        return query.encode('utf-8')

    def save_state(self):
        self.config['geometry'] = bytearray(self.saveGeometry())
        self.config['store_splitter_state'] = bytearray(
            self.store_splitter.saveState())
        self.config['results_view_column_width'] = [
            self.results_view.columnWidth(i)
            for i in range(self.results_view.model().columnCount())
        ]
        self.config['sort_col'] = self.results_view.model().sort_col
        self.config['sort_order'] = self.results_view.model().sort_order
        self.config['open_external'] = self.open_external.isChecked()

        store_check = {}
        for k, v in self.store_checks.items():
            store_check[k] = v.isChecked()
        self.config['store_checked'] = store_check

    def restore_state(self):
        geometry = self.config.get('geometry', None)
        if geometry:
            QApplication.instance().safe_restore_geometry(self, geometry)

        splitter_state = self.config.get('store_splitter_state', None)
        if splitter_state:
            self.store_splitter.restoreState(splitter_state)

        results_cwidth = self.config.get('results_view_column_width', None)
        if results_cwidth:
            for i, x in enumerate(results_cwidth):
                if i >= self.results_view.model().columnCount():
                    break
                self.results_view.setColumnWidth(i, x)
        else:
            self.resize_columns()

        self.open_external.setChecked(self.should_open_external)

        store_check = self.config.get('store_checked', None)
        if store_check:
            for n in store_check:
                if n in self.store_checks:
                    self.store_checks[n].setChecked(store_check[n])

        self.results_view.model().sort_col = self.config.get('sort_col', 2)
        self.results_view.model().sort_order = self.config.get(
            'sort_order', Qt.AscendingOrder)
        self.results_view.header().setSortIndicator(
            self.results_view.model().sort_col,
            self.results_view.model().sort_order)

    def load_settings(self):
        # Seconds
        self.timeout = self.config.get('timeout', 75)
        # Milliseconds
        self.hang_time = self.config.get('hang_time', 75) * 1000

        self.max_results = self.config.get('max_results', 15)
        self.should_open_external = self.config.get('open_external', True)

        # Number of threads to run for each type of operation
        self.search_thread_count = self.config.get('search_thread_count', 4)
        self.cache_thread_count = self.config.get('cache_thread_count', 2)
        self.cover_thread_count = self.config.get('cover_thread_count', 2)
        self.details_thread_count = self.config.get('details_thread_count', 4)

    def do_config(self):
        # Save values that need to be synced between the dialog and the
        # search widget.
        self.config['open_external'] = self.open_external.isChecked()

        # Create the config dialog. It's going to put two config widgets
        # into a QTabWidget for displaying all of the settings.
        d = QDialog(self)
        button_box = QDialogButtonBox(QDialogButtonBox.Close)
        v = QVBoxLayout(d)
        button_box.accepted.connect(d.accept)
        button_box.rejected.connect(d.reject)
        d.setWindowTitle(_('Customize Get books search'))

        tab_widget = QTabWidget(d)
        v.addWidget(tab_widget)
        v.addWidget(button_box)

        chooser_config_widget = StoreChooserWidget()
        search_config_widget = StoreConfigWidget(self.config)

        tab_widget.addTab(chooser_config_widget, _('Choose s&tores'))
        tab_widget.addTab(search_config_widget, _('Configure s&earch'))

        # Restore dialog state.
        geometry = self.config.get('config_dialog_geometry', None)
        if geometry:
            QApplication.instance().safe_restore_geometry(d, geometry)
        else:
            d.resize(800, 600)
        tab_index = self.config.get('config_dialog_tab_index', 0)
        tab_index = min(tab_index, tab_widget.count() - 1)
        tab_widget.setCurrentIndex(tab_index)

        d.exec_()

        # Save dialog state.
        self.config['config_dialog_geometry'] = bytearray(d.saveGeometry())
        self.config['config_dialog_tab_index'] = tab_widget.currentIndex()

        search_config_widget.save_settings()
        self.config_changed()
        self.gui.load_store_plugins()
        self.setup_store_checks()

    def config_changed(self):
        self.load_settings()

        self.open_external.setChecked(self.should_open_external)
        self.search_pool.set_thread_count(self.search_thread_count)
        self.cache_pool.set_thread_count(self.cache_thread_count)
        self.results_view.model().cover_pool.set_thread_count(
            self.cover_thread_count)
        self.results_view.model().details_pool.set_thread_count(
            self.details_thread_count)

    def get_results(self):
        # We only want the search plugins to run
        # a maximum set amount of time before giving up.
        self.hang_check += 1
        if self.hang_check >= self.hang_time:
            self.search_pool.abort()
            self.checker.stop()
        else:
            # Stop the checker if not threads are running.
            if not self.search_pool.threads_running(
            ) and not self.search_pool.has_tasks():
                self.checker.stop()

        while self.search_pool.has_results():
            res, store_plugin = self.search_pool.get_result()
            if res:
                self.results_view.model().add_result(res, store_plugin)

        if not self.search_pool.threads_running(
        ) and not self.results_view.model().has_results():
            info_dialog(self,
                        _('No matches'),
                        _('Couldn\'t find any books matching your query.'),
                        show=True,
                        show_copy_button=False)

    def update_book_total(self, total):
        self.total.setText('%s' % total)

    def result_item_activated(self, index):
        result = self.results_view.model().get_result(index)

        if result.downloads:
            self.download_book(result)
        else:
            self.open_store(result)

    def download_book(self, result):
        d = ChooseFormatDialog(self,
                               _('Choose format to download to your library.'),
                               list(result.downloads.keys()))
        if d.exec_() == d.Accepted:
            ext = d.format()
            fname = result.title[:60] + '.' + ext.lower()
            fname = ascii_filename(fname)
            show_download_info(result.title, parent=self)
            self.gui.download_ebook(result.downloads[ext],
                                    filename=fname,
                                    create_browser=result.create_browser)

    def open_store(self, result):
        self.gui.istores[result.store_name].open(
            self, result.detail_item, self.open_external.isChecked())

    def check_progress(self):
        m = self.results_view.model()
        if not self.search_pool.threads_running(
        ) and not m.cover_pool.threads_running(
        ) and not m.details_pool.threads_running():
            self.pi.stopAnimation()
            self.search.setText(self.SEARCH_TEXT)
            self.searching = False
        else:
            self.searching = True
            if unicode_type(self.search.text()) != self.STOP_TEXT:
                self.search.setText(self.STOP_TEXT)
            if not self.pi.isAnimated():
                self.pi.startAnimation()

    def stores_select_all(self):
        for check in self.store_checks.values():
            check.setChecked(True)

    def stores_select_invert(self):
        for check in self.store_checks.values():
            check.setChecked(not check.isChecked())

    def stores_select_none(self):
        for check in self.store_checks.values():
            check.setChecked(False)

    def dialog_closed(self, result):
        self.results_view.model().closing()
        self.search_pool.abort()
        self.cache_pool.abort()
        self.save_state()

    def exec_(self):
        if unicode_type(self.search_edit.text()).strip() or unicode_type(
                self.search_title.text()).strip() or unicode_type(
                    self.search_author.text()).strip():
            self.do_search()
        return QDialog.exec_(self)
Beispiel #27
0
class TabIcon(QWidget):
    class Data:
        def __init__(self):
            self.framesCount = 0
            self.animationInterval = 0
            self.animationPixmap = QPixmap()
            self.audioPlayingPixmap = QPixmap()
            self.audioMutedPixmap = QPixmap()

    def __init__(self, parent):
        '''
        @param parent QWidget
        '''
        super().__init__(parent)
        self._tab = None  # WebTab
        self._updateTimer = None  # QTimer
        self._hideTimer = None  # QTimer
        self._sitePixmap = QPixmap()
        self._currentFrame = 0
        self._animationRunning = False
        self._audioIconDisplayed = False
        self._audioIconRect = QRect()

        self.setObjectName('tab-icon')

        self._updateTimer = QTimer(self)
        self._updateTimer.setInterval(self.data().animationInterval)
        self._updateTimer.timeout.connect(self._updateAnimationFrame)

        self._hideTimer = QTimer(self)
        self._hideTimer.setInterval(250)
        self._hideTimer.timeout.connect(self._hide)

        self.resize(16, 16)

    def setWebTab(self, tab):
        '''
        @param: tab WebTab
        '''
        self._tab = tab

        self._tab.webView().loadStarted.connect(self._showLoadingAnimation)
        #self._tab.webView().loadFinished.connect(self._hideLoadingAnimation)
        self._tab.webView().loadProgress.connect(self._hideLoadingAnimation)
        self._tab.webView().iconChanged.connect(self.updateIcon)
        self._tab.webView().backgroundActivityChanged.connect(
            lambda: self.update())

        def pageChangedCb(page):
            '''
            @param: page WebPage
            '''
            page.recentlyAudibleChanged.connect(self._updateAudioIcon)

        pageChangedCb(self._tab.webView().page())
        self._tab.webView().pageChanged.connect(pageChangedCb)

        self.updateIcon()

    def updateIcon(self):
        self._sitePixmap = self._tab.icon(True).pixmap(16)  # allowNull
        if self._sitePixmap.isNull():
            if self._tab.url().isEmpty() or self._tab.url().scheme(
            ) == const.APP_SCHEME:
                self._hide()
            else:
                self._hideTimer.start()
        else:
            self._show()
        self.update()

    _s_data = None

    def data(self):
        '''
        @return: Data
        '''
        if self._s_data is None:
            self._s_data = data = self.Data()
            data.animationInterval = 70
            data.animationPixmap = QIcon(':/icons/other/loading.png').pixmap(
                288, 16)
            data.framesCount = data.animationPixmap.width(
            ) / data.animationPixmap.height()
            data.audioPlayingPixmap = QIcon.fromTheme(
                'audio-volume-high',
                QIcon(':/icons/other/audioplaying.svg')).pixmap(16)
            data.audioMutedPixmap = QIcon.fromTheme(
                'audio-volume-muted',
                QIcon(':/icons/other/audiomuted.svg')).pixmap(16)
        return self._s_data

    # Q_SIGNALS
    resized = pyqtSignal()

    # private Q_SLOTS:
    def _showLoadingAnimation(self):
        self._currentFrame = 0
        self._updateAnimationFrame()
        self._show()

    def _hideLoadingAnimation(self, progress):
        if progress == 100:
            self._animationRunning = False
            self._updateTimer.stop()
            self.updateIcon()

    def _updateAudioIcon(self, recentlyAudible):
        if self._tab.isMuted() or recentlyAudible:
            self._audioIconDisplayed = True
            self._show()
        else:
            self._audioIconDisplayed = False
            self._hide()

        self.update()

    def _updateAnimationFrame(self):
        if not self._animationRunning:
            self._updateTimer.start()
            self._animationRunning = True

        self.update()
        self._currentFrame = (self._currentFrame + 1) % self.data().framesCount

    # private:
    def _show(self):
        if not self._shouldBeVisible():
            return

        self._hideTimer.stop()

        if self.isVisible() and self.width() == 16:
            return

        self.setFixedSize(16, max(self.minimumHeight(), 16))
        self.resized.emit()
        super().show()

    def _hide(self):
        if self._shouldBeVisible():
            return

        if self.isHidden() and self.width() == 1:
            return

        self.setFixedSize(1, max(self.minimumHeight(), 16))
        self.resized.emit()
        super().hide()

    def _shouldBeVisible(self):
        return not self._sitePixmap.isNull() or self._animationRunning or \
            self._audioIconDisplayed or (self._tab and self._tab.isPinned())

    # override
    def event(self, event):
        '''
        @param: event QEvent
        '''
        if event.type() == QEvent.ToolTip:
            # QHelpEvent
            if self._audioIconDisplayed and self._audioIconRect.contains(
                    event.pos()):
                QToolTip.showText(
                    event.globalPos(),
                    self._tab.isMuted() and _('Unmute Tab') or _('Mute Tab'),
                    self)
                event.accept()
                return True
        return super().event(event)

    # override
    def paintEvent(self, event):
        '''
        @param event QPaintEvent
        '''
        p = QPainter(self)
        p.setRenderHint(QPainter.Antialiasing)

        size = 16
        pixmapSize = round(size *
                           self.data().animationPixmap.devicePixelRatioF())

        # Center the pixmap in rect
        r = QRect(self.rect())
        r.setX((r.width() - size) / 2)
        r.setY((r.height() - size) / 2)
        r.setWidth(size)
        r.setHeight(size)

        if self._animationRunning:
            p.drawPixmap(
                r,
                self.data().animationPixmap,
                QRect(self._currentFrame * pixmapSize, 0, pixmapSize,
                      pixmapSize))
        elif self._audioIconDisplayed and not self._tab.isPinned():
            self._audioIconRect = QRect(r)
            p.drawPixmap(
                r,
                self._tab.isMuted() and self.data().audioMutedPixmap
                or self.data().audioPlayingPixmap)
        elif not self._sitePixmap.isNull():
            p.drawPixmap(r, self._sitePixmap)
        elif self._tab and self._tab.isPinned():
            p.drawPixmap(r, IconProvider.emptyWebIcon().pixmap(size))

        # Draw audio icon on top of site icon for pinned tabs
        if not self._animationRunning and self._audioIconDisplayed and self._tab.isPinned(
        ):
            s = size - 4
            r0 = QRect(self.width() - 4, 0, s, s)
            self._audioIconRect = r0
            c = self.palette().color(QPalette.Window)
            c.setAlpha(180)
            p.setPen(c)
            p.setBrush(c)
            p.drawEllipse(r)
            p.drawPixmap(
                r,
                self._tab.isMuted() and self.data().audioMutedPixmap
                or self.data().audioPlayingPixmap)

        # Draw background activity indicator
        if self._tab and self._tab.isPinned() and self._tab.webView(
        ).backgroundActivity():
            s = 5
            # Background
            r1 = QRect(self.width() - s - 2,
                       self.height() - s - 2, s + 2, s + 2)
            c1 = self.palette().color(QPalette.Window)
            c1.setAlpha(180)
            p.setPen(Qt.transparent)
            p.setBrush(c1)
            p.drawEllipse(r1)
            # Forground
            r2 = QRect(self.width() - s - 1, self.height() - s - 1, s, s)
            c2 = self.palette().color(QPalette.Text)
            p.setPen(Qt.transparent)
            p.setBrush(c2)
            p.drawEllipse(r2)

    # override
    def mousePressEvent(self, event):
        '''
        @param event QMouseEvent
        '''
        if self._audioIconDisplayed and event.button() == Qt.LeftButton and \
                self._audioIconRect.contains(event.pos()):
            self._tab.toggleMuted()
            return

        super().mousePressEvent(event)
Beispiel #28
0
class DeviceBar(QWidget):
    """ DeviceBar

        Signals:
            onDeviceUpdated()
            onDeviceSelected(str) # str = id
            onDeviceChanged(str) # str = id

    """

    onDeviceUpdated = pyqtSignal(str, name="onDeviceUpdated")
    onDeviceSelected = pyqtSignal(str, name="onDeviceSelected")
    onDeviceChanged = pyqtSignal(str, name="onDeviceChanged")

    def __init__(self, parent=None, device_type='usb'):
        super().__init__(parent=parent)

        # dont show for local
        if device_type != 'usb':
            return

        self.parent = parent
        self.wait_for_devtype = device_type
        self.is_waiting = True
        self._adb = Adb()

        if not self._adb.min_required:
            raise Exception('Adb missing or no Device')

        self._git = Git()
        self.setAutoFillBackground(True)
        self.setStyleSheet(
            'background-color: crimson; color: white; font-weight: bold; margin: 0; padding: 10px;'
        )
        self.setup()
        self._timer = QTimer()
        self._timer.setInterval(500)
        self._timer.timeout.connect(self._on_timer)
        self._timer.start()
        self._timer_step = 0
        frida.get_device_manager().on('added', self._on_device)
        frida.get_device_manager().on('removed', self._on_device)
        self.devices_thread = DevicesUpdateThread(self)
        self.devices_thread.onAddDevice.connect(self.on_add_deviceitem)
        self.devices_thread.onDevicesUpdated.connect(self._on_devices_finished)
        self._update_thread = FridaUpdateThread(self)
        self._update_thread._adb = self._adb
        self._update_thread.onStatusUpdate.connect(self._update_statuslbl)
        self._update_thread.onFinished.connect(self._frida_updated)
        self._update_thread.onError.connect(self._on_download_error)
        self.updated_frida_version = ''
        self.updated_frida_assets_url = {}
        self._device_id = None
        self._devices = []
        remote_frida = self._git.get_frida_version()
        if remote_frida is None:
            self.updated_frida_version = ''
            self.updated_frida_assets_url.clear()
        else:
            remote_frida = remote_frida[0]
            self.updated_frida_version = remote_frida['tag_name']
            for asset in remote_frida['assets']:
                if 'name' not in asset:
                    continue
                if 'android-' not in asset['name']:
                    continue

                try:
                    name = asset['name']
                    tag_start = name.index('android-')
                    if name.index('server') >= 0:
                        tag = name[tag_start + 8:-3]
                        self.updated_frida_assets_url[tag] = asset[
                            'browser_download_url']
                except ValueError:
                    pass

    def setup(self):
        """ Setup ui
        """
        h_box = QHBoxLayout()
        h_box.setContentsMargins(0, 0, 0, 0)
        self.update_label = QLabel('Waiting for Device')
        self.update_label.setFixedWidth(self.parent.width())
        self.update_label.setOpenExternalLinks(True)
        self.update_label.setTextFormat(Qt.RichText)
        self.update_label.setFixedHeight(35)
        self.update_label.setTextInteractionFlags(Qt.TextBrowserInteraction)
        self._install_btn = QPushButton('Install Frida', self.update_label)
        self._install_btn.setStyleSheet('padding: 0; border-color: white;')
        self._install_btn.setGeometry(self.update_label.width() - 110, 5, 100,
                                      25)
        self._install_btn.clicked.connect(self._on_install_btn)
        self._install_btn.setVisible(False)
        self._start_btn = QPushButton('Start Frida', self.update_label)
        self._start_btn.setStyleSheet('padding: 0; border-color: white;')
        self._start_btn.setGeometry(self.update_label.width() - 110, 5, 100,
                                    25)
        self._start_btn.clicked.connect(self._on_start_btn)
        self._start_btn.setVisible(False)
        self._update_btn = QPushButton('Update Frida', self.update_label)
        self._update_btn.setStyleSheet('padding: 0; border-color: white;')
        self._update_btn.setGeometry(self.update_label.width() - 110, 5, 100,
                                     25)
        self._update_btn.clicked.connect(self._on_install_btn)
        self._update_btn.setVisible(False)
        self._restart_btn = QPushButton('Restart Frida', self.update_label)
        self._restart_btn.setStyleSheet('padding: 0; border-color: white;')
        self._restart_btn.setGeometry(self.update_label.width() - 110, 5, 100,
                                      25)
        self._restart_btn.clicked.connect(self._on_restart_btn)
        self._restart_btn.setVisible(False)
        self._devices_combobox = QComboBox(self.update_label)
        self._devices_combobox.setStyleSheet(
            'padding: 2px 5px; border-color: white;')
        self._devices_combobox.setGeometry(self.update_label.width() - 320, 5,
                                           200, 25)
        self._devices_combobox.currentIndexChanged.connect(
            self._on_device_changed)
        self._devices_combobox.setVisible(False)
        h_box.addWidget(self.update_label)
        self.setLayout(h_box)

    def on_add_deviceitem(self, device_ident):
        """ Adds an Item to the DeviceComboBox
        """
        if device_ident['type'] == self.wait_for_devtype:
            if device_ident['name'] not in self._devices:
                self._devices.append(device_ident)
            self._timer_step = -1
            self.is_waiting = False

    def _on_device_changed(self, index):
        device = None
        device_id = self._devices_combobox.itemData(index)
        if device_id:
            try:
                device = frida.get_device(device_id)
            except:
                return

            if device:
                self._device_id = device.id
                self._check_device(device)
                self.onDeviceChanged.emit(self._device_id)

    def _check_device(self, frida_device):
        self.update_label.setStyleSheet('background-color: crimson;')
        self._install_btn.setVisible(False)
        self._update_btn.setVisible(False)
        self._start_btn.setVisible(False)
        self._restart_btn.setVisible(False)
        self._adb.device = frida_device.id
        self._device_id = frida_device.id
        if self._adb.available():
            self.update_label.setText('Device: ' + frida_device.name)
            # try getting frida version
            device_frida = self._adb.get_frida_version()
            # frida not found show install button
            if device_frida is None:
                self._install_btn.setVisible(True)
            else:
                # frida is old show update button
                if self.updated_frida_version != device_frida:
                    self._start_btn.setVisible(True)
                    self._update_btn.setVisible(False)
                    # old frida is running allow use of this version
                    if self._adb.is_frida_running():
                        self._start_btn.setVisible(False)
                        if self.updated_frida_assets_url:
                            self._update_btn.setVisible(True)
                        self.update_label.setStyleSheet(
                            'background-color: yellowgreen;')
                        self.onDeviceUpdated.emit(frida_device.id)
                # frida not running show start button
                elif device_frida and not self._adb.is_frida_running():
                    self._start_btn.setVisible(True)
                # frida is running with last version show restart button
                elif device_frida and self._adb.is_frida_running():
                    self.update_label.setStyleSheet(
                        'background-color: yellowgreen;')
                    self._restart_btn.setVisible(True)
                    self.onDeviceUpdated.emit(frida_device.id)

        elif self._adb.non_root_available():
            self.update_label.setText('Device: ' + frida_device.name +
                                      ' (NOROOT!)')
            self.onDeviceUpdated.emit(frida_device.id)

    def _on_devices_finished(self):
        if self._devices:
            if len(self._devices) > 1:
                self._devices_combobox.clear()
                self._devices_combobox.setVisible(True)
                self.update_label.setText('Please select the Device: ')
                for device in self._devices:
                    self._devices_combobox.addItem(device['name'],
                                                   device['id'])
            else:
                self._devices_combobox.setVisible(False)
                try:
                    device = frida.get_device(self._devices[0]['id'])
                    self._check_device(device)
                except:
                    pass

    def _on_timer(self):
        if self._timer_step == -1:
            self._timer.stop()
            return

        if self._timer_step == 0:
            self.update_label.setText(self.update_label.text() + ' .')
            self._timer_step = 1
        elif self._timer_step == 1:
            self.update_label.setText(self.update_label.text() + '.')
            self._timer_step = 2
        elif self._timer_step == 2:
            self.update_label.setText(self.update_label.text() + '.')
            self._timer_step = 3
        else:
            self.update_label.setText(
                self.update_label.text()[:-self._timer_step])
            self._timer_step = 0
            if self.is_waiting and self.devices_thread is not None:
                if not self.devices_thread.isRunning():
                    self.devices_thread.start()

    def _on_download_error(self, text):
        self._timer_step = -1
        self.update_label.setStyleSheet('background-color: crimson;')
        self.update_label.setText(text)
        self._install_btn.setVisible(True)
        self._update_btn.setVisible(False)

    def _on_device(self):
        self.update_label.setText('Waiting for Device ...')
        self._timer_step = 3
        self.is_waiting = True
        self._on_timer()

    def _on_install_btn(self):
        # urls are empty
        if not self.updated_frida_assets_url:
            return

        arch = self._adb.get_device_arch()
        request_url = ''

        if arch is not None and len(arch) > 1:
            arch = arch.join(arch.split())

            if arch == 'arm64' or arch == 'arm64-v8a':
                request_url = self.updated_frida_assets_url['arm64']
            elif arch == 'armeabi-v7a':
                request_url = self.updated_frida_assets_url['arm']
            else:
                if arch in self.updated_frida_assets_url:
                    request_url = self.updated_frida_assets_url[arch]

            try:
                if self._adb.available() and request_url.index(
                        'https://') == 0:
                    self._install_btn.setVisible(False)
                    self._update_btn.setVisible(False)
                    qApp.processEvents()
                    if self._update_thread is not None:
                        if not self._update_thread.isRunning():
                            self._update_thread.frida_update_url = request_url
                            self._update_thread.adb = self._adb
                            self._update_thread.start()

            except ValueError:
                # something wrong in .git_cache folder
                print("request_url not set")

    def _update_statuslbl(self, text):
        self._timer.stop()
        self._timer_step = 0
        self._timer.start()
        self.update_label.setText(text)

    def _frida_updated(self):
        # self._timer_step = 3
        # self.is_waiting = True
        self._on_devices_finished()

    def _on_start_btn(self):
        if self._adb.available():
            self._start_btn.setVisible(False)
            qApp.processEvents()
            if self._adb.start_frida():
                # self.onDeviceUpdated.emit(self._device_id)
                self._on_devices_finished()
            else:
                self._start_btn.setVisible(True)

    def _on_restart_btn(self):
        if self._adb.available():
            self._restart_btn.setVisible(False)
            qApp.processEvents()
            if self._adb.start_frida(restart=True):
                self._restart_btn.setVisible(True)
                # self.onDeviceUpdated.emit(self._device_id)
                self._on_devices_finished()
Beispiel #29
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()
Beispiel #30
0
class SearchBox2(QComboBox):  # {{{

    '''
    To use this class:

        * Call initialize()
        * Connect to the search() and cleared() signals from this widget.
        * Connect to the changed() signal to know when the box content changes
        * Connect to focus_to_library() signal to be told to manually change focus
        * Call search_done() after every search is complete
        * Call set_search_string() to perform a search programmatically
        * You can use the current_text property to get the current search text
          Be aware that if you are using it in a slot connected to the
          changed() signal, if the connection is not queued it will not be
          accurate.
    '''

    INTERVAL = 1500  #: Time to wait before emitting search signal
    MAX_COUNT = 25

    search  = pyqtSignal(object)
    cleared = pyqtSignal()
    changed = pyqtSignal()
    focus_to_library = pyqtSignal()

    def __init__(self, parent=None):
        QComboBox.__init__(self, parent)
        self.normal_background = 'rgb(255, 255, 255, 0%)'
        self.line_edit = SearchLineEdit(self)
        self.setLineEdit(self.line_edit)

        c = self.line_edit.completer()
        c.setCompletionMode(c.PopupCompletion)
        c.highlighted[str].connect(self.completer_used)

        self.line_edit.key_pressed.connect(self.key_pressed, type=Qt.DirectConnection)
        # QueuedConnection as workaround for https://bugreports.qt-project.org/browse/QTBUG-40807
        self.activated[str].connect(self.history_selected, type=Qt.QueuedConnection)
        self.setEditable(True)
        self.as_you_type = True
        self.timer = QTimer()
        self.timer.setSingleShot(True)
        self.timer.timeout.connect(self.timer_event, type=Qt.QueuedConnection)
        self.setInsertPolicy(self.NoInsert)
        self.setMaxCount(self.MAX_COUNT)
        self.setSizeAdjustPolicy(self.AdjustToMinimumContentsLengthWithIcon)
        self.setMinimumContentsLength(25)
        self._in_a_search = False
        self.tool_tip_text = self.toolTip()

    def initialize(self, opt_name, colorize=False, help_text=_('Search')):
        self.as_you_type = config['search_as_you_type']
        self.opt_name = opt_name
        items = []
        for item in config[opt_name]:
            if item not in items:
                items.append(item)
        self.addItems(items)
        self.line_edit.setPlaceholderText(help_text)
        self.colorize = colorize
        self.clear()

    def hide_completer_popup(self):
        try:
            self.lineEdit().completer().popup().setVisible(False)
        except:
            pass

    def normalize_state(self):
        self.setToolTip(self.tool_tip_text)
        self.line_edit.setStyleSheet(
            'QLineEdit{color:none;background-color:%s;}' % self.normal_background)

    def text(self):
        return self.currentText()

    def clear_history(self, *args):
        QComboBox.clear(self)

    def clear(self, emit_search=True):
        self.normalize_state()
        self.setEditText('')
        if emit_search:
            self.search.emit('')
        self._in_a_search = False
        self.cleared.emit()

    def clear_clicked(self, *args):
        self.clear()

    def search_done(self, ok):
        if isinstance(ok, basestring):
            self.setToolTip(ok)
            ok = False
        if not unicode(self.currentText()).strip():
            self.clear(emit_search=False)
            return
        self._in_a_search = ok
        col = 'rgba(0,255,0,20%)' if ok else 'rgb(255,0,0,20%)'
        if not self.colorize:
            col = self.normal_background
        self.line_edit.setStyleSheet('QLineEdit{color:black;background-color:%s;}' % col)

    # Comes from the lineEdit control
    def key_pressed(self, event):
        k = event.key()
        if k in (Qt.Key_Left, Qt.Key_Right, Qt.Key_Up, Qt.Key_Down,
                Qt.Key_Home, Qt.Key_End, Qt.Key_PageUp, Qt.Key_PageDown,
                Qt.Key_unknown):
            return
        self.normalize_state()
        if self._in_a_search:
            self.changed.emit()
            self._in_a_search = False
        if event.key() in (Qt.Key_Return, Qt.Key_Enter):
            self.do_search()
            self.focus_to_library.emit()
        elif self.as_you_type and unicode(event.text()):
            self.timer.start(1500)

    # Comes from the combobox itself
    def keyPressEvent(self, event):
        k = event.key()
        if k in (Qt.Key_Enter, Qt.Key_Return):
            return self.do_search()
        if k not in (Qt.Key_Up, Qt.Key_Down):
            QComboBox.keyPressEvent(self, event)
        else:
            self.blockSignals(True)
            self.normalize_state()
            QComboBox.keyPressEvent(self, event)
            self.blockSignals(False)

    def completer_used(self, text):
        self.timer.stop()
        self.normalize_state()

    def timer_event(self):
        self.do_search()

    def history_selected(self, text):
        self.changed.emit()
        self.do_search()

    def _do_search(self, store_in_history=True):
        self.hide_completer_popup()
        text = unicode(self.currentText()).strip()
        if not text:
            return self.clear()
        self.search.emit(text)

        if store_in_history:
            idx = self.findText(text, Qt.MatchFixedString|Qt.MatchCaseSensitive)
            self.block_signals(True)
            if idx < 0:
                self.insertItem(0, text)
            else:
                t = self.itemText(idx)
                self.removeItem(idx)
                self.insertItem(0, t)
            self.setCurrentIndex(0)
            self.block_signals(False)
            history = [unicode(self.itemText(i)) for i in
                    range(self.count())]
            config[self.opt_name] = history

    def do_search(self, *args):
        self._do_search()

    def block_signals(self, yes):
        self.blockSignals(yes)
        self.line_edit.blockSignals(yes)

    def set_search_string(self, txt, store_in_history=False, emit_changed=True):
        if not store_in_history:
            self.activated[str].disconnect()
        try:
            self.setFocus(Qt.OtherFocusReason)
            if not txt:
                self.clear()
            else:
                self.normalize_state()
                # must turn on case sensitivity here so that tag browser strings
                # are not case-insensitively replaced from history
                self.line_edit.completer().setCaseSensitivity(Qt.CaseSensitive)
                self.setEditText(txt)
                self.line_edit.end(False)
                if emit_changed:
                    self.changed.emit()
                self._do_search(store_in_history=store_in_history)
                self.line_edit.completer().setCaseSensitivity(Qt.CaseInsensitive)
            self.focus_to_library.emit()
        finally:
            if not store_in_history:
                # QueuedConnection as workaround for https://bugreports.qt-project.org/browse/QTBUG-40807
                self.activated[str].connect(self.history_selected, type=Qt.QueuedConnection)

    def search_as_you_type(self, enabled):
        self.as_you_type = enabled

    def in_a_search(self):
        return self._in_a_search

    @property
    def current_text(self):
        return unicode(self.lineEdit().text())
Beispiel #31
0
class Model_View(Model):
    def __init__(self):
        super(Model_View, self).__init__(databasetype="QSQLITE",
                                         databasename="Sqlite_Sql/testcase.db",
                                         sqltablename="result")

    #讲所有测试用例的
    def modelview(self):
        self.view = QTableView()
        self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.view.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.view.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.view.setModel(self.model)
        for index in range(self.model.rowCount()):
            case_name = self.model.data(self.model.index(index, 2))
            self.view.setIndexWidget(self.model.index(index, 6),
                                     self.button(case_name))
            # self.view.setIndexWidget(self.model.index(index, 5), self.time(case_name))
        return self.view

    def button(self, id):
        widget = QWidget()
        Btn = QPushButton('执行')
        Btn.setStyleSheet(''' text-align : center;
                                          background-color : NavajoWhite;
                                          height : 30px;
                                          border-style: outset;
                                          font : 15px  ''')

        Btn.clicked.connect(lambda: self.Work(id))
        self.timer = QTimer()
        # self.timer.timeout.connect(self.CountTime)
        hLayout = QHBoxLayout()
        hLayout.addWidget(Btn)
        hLayout.setContentsMargins(0, 0, 0, 0)
        widget.setLayout(hLayout)
        return widget

    # def time(self,case_name):
    #
    #     return QLCDNumber()

    # def CountTime(self):
    #     self.t += 1
    #     self.time(self.name).display(self.t)
    def Work(self, index):
        self.name = index
        self.timer.start(1000)
        self.thread = RunThread(index)
        self.thread.start()
        self.thread.thread_signal.connect(self.TimeStop)

    # @staticmethod
    # def allrun():
    #
    #     thread = RunThread(0)
    #     thread.start()
    #     # thread.join()
    #     QCoreApplication.processEvents()
    def TimeStop(self):
        self.timer.stop()
        LOGER.loginfo(self.name + "用例执行完毕,邮件已发送")
        print("用例运行完毕,邮件已发送")
Beispiel #32
0
    class SliderControl(QObject):
        """This class implements a slider control for a colormap"""
        valueChanged = pyqtSignal(float)
        valueMoved = pyqtSignal(float)

        def __init__(self, name, value, minval, maxval, step, format="%s: %.1f"):
            QObject.__init__(self)
            self.name, self.value, self.minval, self.maxval, self.step, self.format = \
                name, value, minval, maxval, step, format
            self._default = value
            self._wlabel = None
            self._wreset = None
            self._wslider = None
            self._wslider_timer = None

        def makeControlWidgets(self, parent, gridlayout, row, column):
            toprow = QWidget(parent)
            gridlayout.addWidget(toprow, row * 2, column)
            top_lo = QHBoxLayout(toprow)
            top_lo.setContentsMargins(0, 0, 0, 0)
            self._wlabel = QLabel(self.format % (self.name, self.value), toprow)
            top_lo.addWidget(self._wlabel)
            self._wreset = QToolButton(toprow)
            self._wreset.setText("reset")
            self._wreset.setToolButtonStyle(Qt.ToolButtonTextOnly)
            self._wreset.setAutoRaise(True)
            self._wreset.setEnabled(self.value != self._default)
            self._wreset.clicked.connect(self._resetValue)
            top_lo.addWidget(self._wreset)
            self._wslider = QwtSlider(parent)
            self._wslider.setOrientation(Qt.Horizontal)
            # This works around a stupid bug in QwtSliders -- see comments on histogram zoom wheel above
            self._wslider_timer = QTimer(parent)
            self._wslider_timer.setSingleShot(True)
            self._wslider_timer.setInterval(500)
            self._wslider_timer.timeout.connect(self.setValue)
            gridlayout.addWidget(self._wslider, row * 2 + 1, column)
            self._wslider.setScale(self.minval, self.maxval)
            # self._wslider.setScaleStepSize(self.step)
            self._wslider.setValue(self.value)
            self._wslider.setTracking(False)
            self._wslider.valueChanged.connect(self.setValue)
            self._wslider.sliderMoved.connect(self._previewValue)

        def _resetValue(self):
            self._wslider.setValue(self._default)
            self.setValue(self._default)

        def setValue(self, value=None, notify=True):
            # only update widgets if already created
            self.value = value
            if self._wlabel is not None:
                if value is None:
                    self.value = value = self._wslider.value()
                self._wreset.setEnabled(value != self._default)
                self._wlabel.setText(self.format % (self.name, self.value))
                # stop timer if being called to finalize the change in value
                if notify:
                    self._wslider_timer.stop()
                    self.valueChanged.emit(self.value)

        def _previewValue(self, value):
            self.setValue(notify=False)
            self._wslider_timer.start(500)
            self.valueMoved.emit(self.value)
Beispiel #33
0
class Game(QWidget):
    def __init__(self):
        super().__init__()
        self._flag_1 = False
        self.media = QtCore.QUrl.fromLocalFile("MP3/music.mp3")
        content = QtMultimedia.QMediaContent(self.media)
        self.player = QtMultimedia.QMediaPlayer()
        self.player.setMedia(content)
        self.player.setVolume(50)
        self.initUI()

    def initUI(self):
        self.file_number = 2
        self._score = 0
        self.setMouseTracking(True)
        self.resize(500, 500)
        self.center()
        self.setWindowTitle('Игра')

        self.main_pic = QLabel(self)
        self.main_pic.setPixmap(
            QPixmap(resource_path("PIC/running_rabbit.jpg")))
        self.main_pic.resize(500, 500)

        self.button_start_game = QPushButton("НАЧАТЬ ИГРУ", self)
        self.button_start_game.resize(140, 50)
        self.button_start_game.move(70, 300)
        self.button_start_game.clicked.connect(self.start_game)

        self.button_edit_map = QPushButton("СОЗДАТЬ КАРТУ", self)
        self.button_edit_map.resize(140, 50)
        self.button_edit_map.move(70, 360)
        self.button_edit_map.clicked.connect(self.edit_map_menu)

        self.button_exit = QPushButton("ЗАКОНЧИТЬ СЕАНС", self)
        self.button_exit.resize(140, 50)
        self.button_exit.move(70, 420)
        self.button_exit.clicked.connect(self.exit)

        self.timer_game = QTimer()
        self.timer_game.timeout.connect(self.updateValues)
        self.timer_game.start(200)
        self.setFocusPolicy(Qt.StrongFocus)

        self.lable_score = QLabel(self)
        self.lable_score.resize(200, 35)
        self.lable_score.setFont(
            QFont("RetroComputer[RUS by Daymarius]", 26, QFont.Bold))
        self.lable_score.setVisible(False)

        self.button_menu = QPushButton("НАЗАД", self)
        self.button_menu.resize(120, 40)
        self.button_menu.clicked.connect(self.show_menu)
        self.button_menu.setVisible(False)

        self.lable_name = QLabel("ВВЕДИТЕ ВАШЕ ИМЯ:", self)
        self.lable_name.sizeHint()
        self.lable_name.setFont(
            QFont("RetroComputer[RUS by Daymarius]", 10, QFont.Bold))
        self.lable_name.move(230, 300)

        self.input_name = QLineEdit("Без имени", self)
        self.input_name.resize(200, 20)
        self.input_name.move(230, 325)

        self.table_score = QTableWidget(self)
        self.table_score.resize(200, 120)
        self.table_score.move(230, 350)

    def bd_score(self):
        con = sqlite3.connect(resource_path("DB/score.db"))
        # Создание курсора
        cur = con.cursor()
        # Выполнение запроса и получение всех результатов
        result = cur.execute(
            "SELECT * FROM main ORDER BY score DESC;").fetchmany(5)
        cur.execute("DROP TABLE main")
        cur.execute("CREATE TABLE main (name STRING, score INTEGER)")
        for item in result:
            cur.execute("INSERT INTO main VALUES(?, ?)", (item[0], item[1]))
            con.commit()
        # Вывод результатов на экран
        if result != []:
            self.table_score.setColumnCount(len(result[0]))
            self.table_score.setRowCount(0)
            for i, row in enumerate(result):
                self.table_score.setRowCount(self.table_score.rowCount() + 1)
                for j, elem in enumerate(row):
                    item = QTableWidgetItem(str(elem))
                    self.table_score.setItem(i, j, item)
                    item.setFlags(QtCore.Qt.ItemIsEnabled)
            self.table_score.setHorizontalHeaderItem(
                0, QTableWidgetItem("Имя игрока"))
            self.table_score.setHorizontalHeaderItem(1,
                                                     QTableWidgetItem("Счет"))
            self.table_score.setColumnWidth(0, 127)
            self.table_score.setColumnWidth(1, 20)
        con.close()

    def append_bd_score_and_name(self):
        con = sqlite3.connect("DB/score.db")
        # Создание курсора
        cur = con.cursor()
        cur.execute("INSERT INTO main VALUES(?, ?)",
                    (self.input_name.text(), self.get_score()))
        con.commit()
        self.bd_score()
        con.close()

    def get_score(self):
        return self._score

    def set_score(self, score):
        self._score = score

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Right:
            self.hero.go_right()
        elif event.key() == Qt.Key_Left:
            self.hero.go_left()
        elif event.key() == Qt.Key_Up:
            self.hero.go_up()
        elif event.key() == Qt.Key_Down:
            self.hero.go_down()
        elif event.key() == Qt.Key_Space:
            self.hero.go_dig()

    def keyReleaseEvent(self, QKeyEvent):
        if self._flag_1 and not QKeyEvent.isAutoRepeat():
            self.hero.stop()

    def edit_map_menu(self):
        game.setVisible(False)
        edit_map.create_map()
        edit_map.show()

    def exit(self):
        sys.exit(app.exec())

    def center(self):
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    def reload_game(self):
        score = self.get_score()
        lifes = self.hero.get_lifes()
        self.start_game()
        self.set_score(score)
        self.hero.set_lifes(lifes)

    def start_game(self):
        self.player.play()
        self.set_score(0)
        self.map = Map(resource_path("MAP/" + str(self.file_number) + ".map"))
        for enemy in self.enemies:
            enemy.create_labirint()
        self.button_menu.setVisible(True)
        self.button_menu.setFocusPolicy(False)
        self.lable_score.setVisible(True)
        self.lable_score.move(0, 0)
        self.button_menu.move(self.map.get_col() * 40 - 120, 0)
        self.lable_score.setText("00000")
        sp_0_1 = []
        for j in range(self.map.get_row()):
            for i in range(self.map.get_col()):
                if type(self.map.get_elem_xy(i, j)) == Empty and (
                        self.map.get_row() - 1 == j
                        or type(self.map.get_elem_xy(
                            i, j + 1)) in [Stairs, Brick]):
                    sp_0_1.append((i, j))

        zerro = Zerro()
        one = One()
        for i in range(int(round(len(sp_0_1) * 0.2))):
            t = randrange(0, len(sp_0_1))
            x, y = sp_0_1[t]
            self.map.set_elem_xy(x, y, choice([zerro, one]))
            del sp_0_1[t]
        self.hide_menu()

    def show_menu(self):
        self.append_bd_score_and_name()
        game.player.pause()
        self.hero.timer.stop()
        for enemy in self.enemies:
            enemy.timer.stop()
        self.timer_game.stop()
        self.main_pic.show()
        self.table_score.show()
        self.input_name.show()
        self.lable_name.show()
        self.button_menu.setVisible(False)
        self.lable_score.setVisible(False)
        self.button_edit_map.setVisible(True)
        self.button_exit.setVisible(True)
        self.button_start_game.setVisible(True)
        self.resize(500, 500)
        self.center()
        self._flag_1 = False

    def hide_menu(self):
        game.player.play()
        self.hero.timer.start(200)
        for enemy in self.enemies:
            enemy.timer.start(200)
        game.timer_game.start(200)
        self.main_pic.hide()
        self.table_score.hide()
        self.input_name.hide()
        self.lable_name.hide()
        self.button_menu.setVisible(True)
        self.lable_score.setVisible(True)
        self.button_edit_map.setVisible(False)
        self.button_exit.setVisible(False)
        self.button_start_game.setVisible(False)
        self.resize(self.map.get_col() * 40, self.map.get_row() * 40 + 40)
        self.center()
        self._flag_1 = True

    def paintEvent(self, event):
        qp = QPainter()
        qp.begin(self)
        if self._flag_1:
            for i in range(self.map.get_col()):
                for j in range(self.map.get_row()):
                    qp.drawPixmap(
                        i * 40, j * 40 + 40,
                        self.map.get_elem_xy(
                            i, j).get_pic())  # рисуем картинками из elem_map
            qp.drawPixmap(self.hero.get_x() * 40,
                          self.hero.get_y() * 40 + 40, self.hero.get_pic())
            for enemy in self.enemies:
                qp.drawPixmap(enemy.get_x() * 40,
                              enemy.get_y() * 40 + 40, enemy.get_pic())
            for i in range(game.hero.get_lifes()):
                qp.drawPixmap(280 + i * 40, 0,
                              self.hero._pic[self.hero._STOP][0])

            txt = "00000" + str(self._score)
            self.lable_score.setText(txt[len(txt) - 5:len(txt)])
        qp.end()

    def updateValues(self):
        self.update()

    def you_lose(self):
        self.player.pause()
        self.hero.timer.stop()
        for enemy in self.enemies:
            enemy.timer.stop()
        self.timer_game.stop()
        if self.hero.get_lifes() > 1:
            game.player.pause()
            buttonReply = QMessageBox.question(
                self, 'ВЫ ПРОИГРАЛИ', "Продолжить игру?",
                QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
            if buttonReply == QMessageBox.Yes:
                self.hero.set_lifes(self.hero.get_lifes() - 1)
                self.reload_game()
                self.timer_game.start(200)
            else:
                self.set_score(0)
                self.show_menu()
        else:
            game.player.pause()
            buttonReply = QMessageBox.information(self, 'ИГРА ОКОНЧЕНА',
                                                  "Вы проиграли.")
            self.append_bd_score_and_name()
            self.show_menu()
Beispiel #34
0
class SearchDialog(QDialog, Ui_Dialog):

    SEARCH_TEXT = _('&Search')
    STOP_TEXT = _('&Stop')

    def __init__(self, gui, parent=None, query=''):
        QDialog.__init__(self, parent)
        self.setupUi(self)

        self.config = JSONConfig('store/search')
        self.search_title.initialize('store_search_search_title')
        self.search_author.initialize('store_search_search_author')
        self.search_edit.initialize('store_search_search')

        # Loads variables that store various settings.
        # This needs to be called soon in __init__ because
        # the variables it sets up are used later.
        self.load_settings()

        self.gui = gui

        # Setup our worker threads.
        self.search_pool = SearchThreadPool(self.search_thread_count)
        self.cache_pool = CacheUpdateThreadPool(self.cache_thread_count)
        self.results_view.model().cover_pool.set_thread_count(self.cover_thread_count)
        self.results_view.model().details_pool.set_thread_count(self.details_thread_count)
        self.results_view.setCursor(Qt.PointingHandCursor)

        # Check for results and hung threads.
        self.checker = QTimer()
        self.progress_checker = QTimer()
        self.hang_check = 0

        # Update store caches silently.
        for p in self.gui.istores.values():
            self.cache_pool.add_task(p, self.timeout)

        self.store_checks = {}
        self.setup_store_checks()

        # Set the search query
        if isinstance(query, (bytes, unicode_type)):
            self.search_edit.setText(query)
        elif isinstance(query, dict):
            if 'author' in query:
                self.search_author.setText(query['author'])
            if 'title' in query:
                self.search_title.setText(query['title'])

        # Create and add the progress indicator
        self.pi = ProgressIndicator(self, 24)
        self.button_layout.takeAt(0)
        self.button_layout.setAlignment(Qt.AlignCenter)
        self.button_layout.insertWidget(0, self.pi, 0, Qt.AlignCenter)

        self.adv_search_button.setIcon(QIcon(I('gear.png')))
        self.adv_search_button.setToolTip(_('Advanced search'))
        self.configure.setIcon(QIcon(I('config.png')))

        self.adv_search_button.clicked.connect(self.build_adv_search)
        self.search.clicked.connect(self.toggle_search)
        self.checker.timeout.connect(self.get_results)
        self.progress_checker.timeout.connect(self.check_progress)
        self.results_view.activated.connect(self.result_item_activated)
        self.results_view.download_requested.connect(self.download_book)
        self.results_view.open_requested.connect(self.open_store)
        self.results_view.model().total_changed.connect(self.update_book_total)
        self.select_all_stores.clicked.connect(self.stores_select_all)
        self.select_invert_stores.clicked.connect(self.stores_select_invert)
        self.select_none_stores.clicked.connect(self.stores_select_none)
        self.configure.clicked.connect(self.do_config)
        self.finished.connect(self.dialog_closed)
        self.searching = False

        self.progress_checker.start(100)

        self.restore_state()

    def setup_store_checks(self):
        first_run = self.config.get('first_run', True)

        # Add check boxes for each store so the user
        # can disable searching specific stores on a
        # per search basis.
        existing = {}
        for n in self.store_checks:
            existing[n] = self.store_checks[n].isChecked()

        self.store_checks = {}

        stores_check_widget = QWidget()
        store_list_layout = QGridLayout()
        stores_check_widget.setLayout(store_list_layout)

        icon = QIcon(I('donate.png'))
        for i, x in enumerate(sorted(self.gui.istores.keys(), key=lambda x: x.lower())):
            cbox = QCheckBox(x)
            cbox.setChecked(existing.get(x, first_run))
            store_list_layout.addWidget(cbox, i, 0, 1, 1)
            if self.gui.istores[x].base_plugin.affiliate:
                iw = QLabel(self)
                iw.setToolTip('<p>' + _('Buying from this store supports the calibre developer: %s</p>') % self.gui.istores[x].base_plugin.author + '</p>')
                iw.setPixmap(icon.pixmap(16, 16))
                store_list_layout.addWidget(iw, i, 1, 1, 1)
            self.store_checks[x] = cbox
        store_list_layout.setRowStretch(store_list_layout.rowCount(), 10)
        self.store_list.setWidget(stores_check_widget)

        self.config['first_run'] = False

    def build_adv_search(self):
        adv = AdvSearchBuilderDialog(self)
        if adv.exec_() == QDialog.Accepted:
            self.search_edit.setText(adv.search_string())

    def resize_columns(self):
        total = 600
        # Cover
        self.results_view.setColumnWidth(0, 85)
        total = total - 85
        # Title / Author
        self.results_view.setColumnWidth(1,int(total*.40))
        # Price
        self.results_view.setColumnWidth(2,int(total*.12))
        # DRM
        self.results_view.setColumnWidth(3, int(total*.15))
        # Store / Formats
        self.results_view.setColumnWidth(4, int(total*.25))
        # Download
        self.results_view.setColumnWidth(5, 20)
        # Affiliate
        self.results_view.setColumnWidth(6, 20)

    def toggle_search(self):
        if self.searching:
            self.search_pool.abort()
            m = self.results_view.model()
            m.details_pool.abort()
            m.cover_pool.abort()
            self.search.setText(self.SEARCH_TEXT)
            self.checker.stop()
            self.searching = False
        else:
            self.do_search()
        # Prevent hitting the enter key twice in quick succession causing
        # the search to start and stop
        self.search.setEnabled(False)
        QTimer.singleShot(1000, lambda :self.search.setEnabled(True))

    def do_search(self):
        # Stop all running threads.
        self.checker.stop()
        self.search_pool.abort()
        # Clear the visible results.
        self.results_view.model().clear_results()

        # Don't start a search if there is nothing to search for.
        query = []
        if self.search_title.text():
            query.append(u'title2:"~%s"' % unicode_type(self.search_title.text()).replace('"', ' '))
        if self.search_author.text():
            query.append(u'author2:"%s"' % unicode_type(self.search_author.text()).replace('"', ' '))
        if self.search_edit.text():
            query.append(unicode_type(self.search_edit.text()))
        query = " ".join(query)
        if not query.strip():
            error_dialog(self, _('No query'),
                        _('You must enter a title, author or keyword to'
                          ' search for.'), show=True)
            return
        self.searching = True
        self.search.setText(self.STOP_TEXT)
        # Give the query to the results model so it can do
        # futher filtering.
        self.results_view.model().set_query(query)

        # Plugins are in random order that does not change.
        # Randomize the ord of the plugin names every time
        # there is a search. This way plugins closer
        # to a don't have an unfair advantage over
        # plugins further from a.
        store_names = list(self.store_checks)
        if not store_names:
            return
        # Remove all of our internal filtering logic from the query.
        query = self.clean_query(query)
        shuffle(store_names)
        # Add plugins that the user has checked to the search pool's work queue.
        self.gui.istores.join(4.0)  # Wait for updated plugins to load
        for n in store_names:
            if self.store_checks[n].isChecked():
                self.search_pool.add_task(query, n, self.gui.istores[n], self.max_results, self.timeout)
        self.hang_check = 0
        self.checker.start(100)
        self.pi.startAnimation()

    def clean_query(self, query):
        query = query.lower()
        # Remove control modifiers.
        query = query.replace('\\', '')
        query = query.replace('!', '')
        query = query.replace('=', '')
        query = query.replace('~', '')
        query = query.replace('>', '')
        query = query.replace('<', '')
        # Remove the prefix.
        for loc in ('all', 'author', 'author2', 'authors', 'title', 'title2'):
            query = re.sub(r'%s:"(?P<a>[^\s"]+)"' % loc, r'\g<a>', query)
            query = query.replace('%s:' % loc, '')
        # Remove the prefix and search text.
        for loc in ('cover', 'download', 'downloads', 'drm', 'format', 'formats', 'price', 'store'):
            query = re.sub(r'%s:"[^"]"' % loc, '', query)
            query = re.sub(r'%s:[^\s]*' % loc, '', query)
        # Remove logic.
        query = re.sub(r'(^|\s|")(and|not|or|a|the|is|of)(\s|$|")', r' ', query)
        # Remove "
        query = query.replace('"', '')
        # Remove excess whitespace.
        query = re.sub(r'\s+', ' ', query)
        query = query.strip()
        return query.encode('utf-8')

    def save_state(self):
        self.config['geometry'] = bytearray(self.saveGeometry())
        self.config['store_splitter_state'] = bytearray(self.store_splitter.saveState())
        self.config['results_view_column_width'] = [self.results_view.columnWidth(i) for i in range(self.results_view.model().columnCount())]
        self.config['sort_col'] = self.results_view.model().sort_col
        self.config['sort_order'] = self.results_view.model().sort_order
        self.config['open_external'] = self.open_external.isChecked()

        store_check = {}
        for k, v in self.store_checks.items():
            store_check[k] = v.isChecked()
        self.config['store_checked'] = store_check

    def restore_state(self):
        geometry = self.config.get('geometry', None)
        if geometry:
            self.restoreGeometry(geometry)

        splitter_state = self.config.get('store_splitter_state', None)
        if splitter_state:
            self.store_splitter.restoreState(splitter_state)

        results_cwidth = self.config.get('results_view_column_width', None)
        if results_cwidth:
            for i, x in enumerate(results_cwidth):
                if i >= self.results_view.model().columnCount():
                    break
                self.results_view.setColumnWidth(i, x)
        else:
            self.resize_columns()

        self.open_external.setChecked(self.should_open_external)

        store_check = self.config.get('store_checked', None)
        if store_check:
            for n in store_check:
                if n in self.store_checks:
                    self.store_checks[n].setChecked(store_check[n])

        self.results_view.model().sort_col = self.config.get('sort_col', 2)
        self.results_view.model().sort_order = self.config.get('sort_order', Qt.AscendingOrder)
        self.results_view.header().setSortIndicator(self.results_view.model().sort_col, self.results_view.model().sort_order)

    def load_settings(self):
        # Seconds
        self.timeout = self.config.get('timeout', 75)
        # Milliseconds
        self.hang_time = self.config.get('hang_time', 75) * 1000

        self.max_results = self.config.get('max_results', 15)
        self.should_open_external = self.config.get('open_external', True)

        # Number of threads to run for each type of operation
        self.search_thread_count = self.config.get('search_thread_count', 4)
        self.cache_thread_count = self.config.get('cache_thread_count', 2)
        self.cover_thread_count = self.config.get('cover_thread_count', 2)
        self.details_thread_count = self.config.get('details_thread_count', 4)

    def do_config(self):
        # Save values that need to be synced between the dialog and the
        # search widget.
        self.config['open_external'] = self.open_external.isChecked()

        # Create the config dialog. It's going to put two config widgets
        # into a QTabWidget for displaying all of the settings.
        d = QDialog(self)
        button_box = QDialogButtonBox(QDialogButtonBox.Close)
        v = QVBoxLayout(d)
        button_box.accepted.connect(d.accept)
        button_box.rejected.connect(d.reject)
        d.setWindowTitle(_('Customize Get books search'))

        tab_widget = QTabWidget(d)
        v.addWidget(tab_widget)
        v.addWidget(button_box)

        chooser_config_widget = StoreChooserWidget()
        search_config_widget = StoreConfigWidget(self.config)

        tab_widget.addTab(chooser_config_widget, _('Choose s&tores'))
        tab_widget.addTab(search_config_widget, _('Configure s&earch'))

        # Restore dialog state.
        geometry = self.config.get('config_dialog_geometry', None)
        if geometry:
            d.restoreGeometry(geometry)
        else:
            d.resize(800, 600)
        tab_index = self.config.get('config_dialog_tab_index', 0)
        tab_index = min(tab_index, tab_widget.count() - 1)
        tab_widget.setCurrentIndex(tab_index)

        d.exec_()

        # Save dialog state.
        self.config['config_dialog_geometry'] = bytearray(d.saveGeometry())
        self.config['config_dialog_tab_index'] = tab_widget.currentIndex()

        search_config_widget.save_settings()
        self.config_changed()
        self.gui.load_store_plugins()
        self.setup_store_checks()

    def config_changed(self):
        self.load_settings()

        self.open_external.setChecked(self.should_open_external)
        self.search_pool.set_thread_count(self.search_thread_count)
        self.cache_pool.set_thread_count(self.cache_thread_count)
        self.results_view.model().cover_pool.set_thread_count(self.cover_thread_count)
        self.results_view.model().details_pool.set_thread_count(self.details_thread_count)

    def get_results(self):
        # We only want the search plugins to run
        # a maximum set amount of time before giving up.
        self.hang_check += 1
        if self.hang_check >= self.hang_time:
            self.search_pool.abort()
            self.checker.stop()
        else:
            # Stop the checker if not threads are running.
            if not self.search_pool.threads_running() and not self.search_pool.has_tasks():
                self.checker.stop()

        while self.search_pool.has_results():
            res, store_plugin = self.search_pool.get_result()
            if res:
                self.results_view.model().add_result(res, store_plugin)

        if not self.search_pool.threads_running() and not self.results_view.model().has_results():
            info_dialog(self, _('No matches'), _('Couldn\'t find any books matching your query.'), show=True, show_copy_button=False)

    def update_book_total(self, total):
        self.total.setText('%s' % total)

    def result_item_activated(self, index):
        result = self.results_view.model().get_result(index)

        if result.downloads:
            self.download_book(result)
        else:
            self.open_store(result)

    def download_book(self, result):
        d = ChooseFormatDialog(self, _('Choose format to download to your library.'), result.downloads.keys())
        if d.exec_() == d.Accepted:
            ext = d.format()
            fname = result.title[:60] + '.' + ext.lower()
            fname = ascii_filename(fname)
            show_download_info(result.title, parent=self)
            self.gui.download_ebook(result.downloads[ext], filename=fname, create_browser=result.create_browser)

    def open_store(self, result):
        self.gui.istores[result.store_name].open(self, result.detail_item, self.open_external.isChecked())

    def check_progress(self):
        m = self.results_view.model()
        if not self.search_pool.threads_running() and not m.cover_pool.threads_running() and not m.details_pool.threads_running():
            self.pi.stopAnimation()
            self.search.setText(self.SEARCH_TEXT)
            self.searching = False
        else:
            self.searching = True
            if unicode_type(self.search.text()) != self.STOP_TEXT:
                self.search.setText(self.STOP_TEXT)
            if not self.pi.isAnimated():
                self.pi.startAnimation()

    def stores_select_all(self):
        for check in self.store_checks.values():
            check.setChecked(True)

    def stores_select_invert(self):
        for check in self.store_checks.values():
            check.setChecked(not check.isChecked())

    def stores_select_none(self):
        for check in self.store_checks.values():
            check.setChecked(False)

    def dialog_closed(self, result):
        self.results_view.model().closing()
        self.search_pool.abort()
        self.cache_pool.abort()
        self.save_state()

    def exec_(self):
        if unicode_type(self.search_edit.text()).strip() or unicode_type(self.search_title.text()).strip() or unicode_type(self.search_author.text()).strip():
            self.do_search()
        return QDialog.exec_(self)
Beispiel #35
0
class BasePlotter(QWidget):
    """
    Базовый класс для отображения данных
    """

    __metaclass__ = ABCMeta

    _mSecs_for_grid = (1000, 5000, 15000, 30000, 60000, 300000, 900000, 1800000, 3600000, 7200000, 10800000,
                       14400000, 18000000, 21600000, 43200000, 86400000, 172800000, 259200000, 345600000,
                       432000000, 518400000, 604800000, 691200000, 777600000, 864000000, 950400000, 1036800000,
                       1123200000, 1209600000, 1296000000, 1382400000, 1468800000, 1555200000, 1641600000,
                       1728000000, 1814400000, 1900800000, 1987200000, 2073600000, 2160000000, 2246400000,
                       2332800000, 2419200000, 2505600000, 2592000000, 5184000000, 7776000000, 10368000000,
                       12960000000, 15552000000, 18144000000, 20736000000, 23328000000, 25920000000, 28512000000)

    def __init__(self, QWidget_parent=None):
        QWidget.__init__(self, QWidget_parent)

        # параметры
        self._Margin = 50
        self._isWatcher = False
        self._isChangeIndex = False
        self._is1Day = False
        self._isMore1Day = False
        self._date = QDate()
        self._curves = {}
        self._legend = ""
        self._plotterScale = PlotterScale(self)

        # параметры, которые сохраняются
        self._colorExt = QColor(Qt.white)
        self._colorInside = QColor(Qt.white)
        self._colorCurve = QColor(Qt.red)
        self._fontGrid = QFont('Arial', 8, QFont.Normal)
        self._fontTitle = QFont('Arial', 20, QFont.Normal)
        self._fontLegend = QFont('Arial', 8, QFont.Normal)
        self._fontTitleY = QFont('Arial', 16, QFont.Normal)
        self._pixmapSaveWidth = 800
        self._pixmapSaveHeight = 600

        self._toolbar = QToolBar(self)

        self._contexMenu = QMenu(self)
        saveAction = self._contexMenu.addAction(self.tr('Сохранить...'))
        saveAction.triggered.connect(self.onSavePlotter)

        self._timer = QTimer(self)
        self._timer.setTimerType(Qt.VeryCoarseTimer)
        self._timer.timeout.connect(self.tickTimer)

        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        self.timerTick = pyqtSignal(name='timerTick')
        self.noWatching = pyqtSignal(bool, name='noWatching')
        self.variablesPlotter = pyqtSignal(QDate, int, name='variablesPlotter')

    @abstractmethod
    def drawOnPlotter(self, painter):
        """
        рисуем на форме
        """

    @abstractmethod
    def addData(self, receiver):
        """
        добавляем данные на форму
        :param receiver: объект, содержащий информацию о данных и сами данные
        """

    @abstractmethod
    def setLegend(self, receiver):
        """
        устанавливаем легенду формы
        :param receiver: объект, содержащий информацию о данных и сами данные
        """

    @abstractmethod
    def drawGridY(self, painter):
        """
        Рисуем сетку по Y
        """

    def readSettings(self):
        station = App.settings.value(App.stationName())

        App.settings.beginGroup('/Settings')
        App.settings.beginGroup('/' + station)
        App.settings.beginGroup('/Plotters')
        App.settings.beginGroup('/BasePlotter')

        self._colorExt = QColor(App.settings.value('/ColorExt', self._colorExt))
        self._colorInside = QColor(App.settings.value('/ColorInside', self._colorInside))
        self._colorCurve = QColor(App.settings.value('/ColorCurve', self._colorCurve))
        self._fontGrid = QFont(App.settings.value('/FontGrid', self._fontGrid))
        self._fontTitle = QFont(App.settings.value('/FontTitle', self._fontTitle))
        self._fontLegend = QFont(App.settings.value('/FontLegend', self._fontLegend))
        self._fontTitleY = QFont(App.settings.value('/FontTitleY', self._fontTitleY))
        self._pixmapSaveWidth = int(App.settings.value('/PixmapSaveWidth', self._pixmapSaveWidth))
        self._pixmapSaveHeight = int(App.settings.value('/PixmapSaveHeight', self._pixmapSaveHeight))

        App.settings.endGroup()
        App.settings.endGroup()
        App.settings.endGroup()
        App.settings.endGroup()

    def updateSettings(self):
        self.readSettings()
        self.repaint()

    def deleteCurves(self):
        print('---delete {0} curves'.format(self))
        self._curves.clear()

    def getRectPlotter(self):
        return QRect(self._Margin + 50, self._Margin, self.width() - 2 * self._Margin - 50,
                     self.height() - 2 * self._Margin)

    def drawBackground(self, painter):
        painter.save()

        painter.setBrush(self._colorExt)
        painter.setPen(Qt.NoPen)
        painter.drawRect(0, 0, painter.window().width(), painter.window().height())
        painter.setBrush(self._colorInside)
        painter.setPen(Qt.SolidLine)
        painter.drawRect(self.getRectPlotter())

        painter.restore()

    def drawLegend(self, painter):
        rect = self.getRectPlotter()

        painter.save()

        fontMetricsF = QFontMetricsF(self._fontLegend)
        painter.setFont(self._fontLegend)
        rect_legend = QRect(rect.left(), rect.top() - fontMetricsF.height(), fontMetricsF.width(self._legend) + 10,
                            fontMetricsF.height())
        painter.drawText(rect_legend, self._legend)

        painter.rectore()

    def drawTitle(self, painter):
        rect = self.getRectPlotter()

        painter.save()

        fontMetricsF = QFontMetricsF(self._fontTitle)
        painter.setFont(self._fontTitle)
        title = self._date.toString('dd.MM.yyyy')
        rect_title = QRect(rect.left() + (rect.width() / 2) - (fontMetricsF.width(title) / 2),
                           rect.top() - fontMetricsF.height(), fontMetricsF.width(self._legend) + 10,
                           fontMetricsF.height())
        painter.drawText(rect_title, self._legend)

        painter.rectore()

    def drawTitleY(self, painter):
        rect = self.getRectPlotter()

        painter.save()

        fontMetricsF = QFontMetricsF(self._fontTitleY)
        painter.setFont(self._fontTitleY)
        rect_titley = QRect(-(rect.top() + (rect.height() / 2) + (fontMetricsF.width(self._titleY) / 2)),
                            (rect.left() / 2) - fontMetricsF.height(),
                            fontMetricsF.width(self._titleY),
                            fontMetricsF.height())
        painter.rotate(-90)
        painter.drawText(rect_titley, self._legend)

        painter.rectore()

    def drawGridX(self, painter):
        rect = self.getRectPlotter()
        if not rect.isValid():
            return
        painter.save()

        painter.setFont(self._fontGrid)
        painter.setPen(QPen(Qt.black))
        painter.setBrush(Qt.NoBrush)

        # Axis ticks
        dxmsec = rect.width() / self._plotterScale.Scale.X.span()
        fontMetricsF = QFontMetricsF(self._fontGrid)
        index = findIndex(fontMetricsF.width('   00:00   ') / dxmsec, BasePlotter._mSecs_for_grid)
        msec0 = self._plotterScale.Scale.X.Min
        offset = findOffset(msec0, BasePlotter._mSecs_for_grid[index])
        dx = dxmsec * BasePlotter._mSecs_for_grid[index]
        step = BasePlotter._mSecs_for_grid[index]

        for x in numpy.arange(rect.left() - dxmsec * offset, rect.right(), dx):
            if x < rect.left():
                continue

            painter.save()

            heightTick = 3
            datetimelabel = QDateTime.fromMSecsSinceEpoch(msec0, Qt.UTC)

            if step >= 60000:  # больше минуты
                if step >= 86400000:  # больше суток
                    label = datetimelabel.toString('dd.MM.yy')
                    if datetimelabel.time().minute() == 0:
                        heightTick = 8
                        painter.setPen(Qt.SolidLine)
                else:
                    t0 = QDateTime.fromMSecsSinceEpoch(self._plotterScale.Scale.X.Min, Qt.UTC)
                    t1 = QDateTime.fromMSecsSinceEpoch(self._plotterScale.Scale.X.Max, Qt.UTC)
                    if (datetimelabel.time().hour() == 0 and datetimelabel.time().minute() == 0) and (
                                t1.date() > t0.date()):  # Метка даты. Появляется,если листаем график между днями
                        font = QFont(painter.font().family(), painter.font().pointSize())
                        font.setBold(True)
                        painter.setFont(font)
                        label = datetimelabel.toString('dd.MM.yy')
                    else:
                        label = datetimelabel.toString('hh:mm')
                        if datetimelabel.time().minute() == 0:
                            heightTick = 8
                            painter.setPen(Qt.SolidLine)
                        if datetimelabel.time().minute() == 30:
                            heightTick = 6
                            painter.setPen(Qt.DashLine)
                        if datetimelabel.time().minute() == 15 or datetimelabel.time().minute() == 45:
                            heightTick = 4
                            painter.setPen(Qt.DashLine)
            else:
                label = datetimelabel.time().toString('hh:mm:ss')

            painter.drawLine(x, rect.bottom(), x, rect.top())
            rect_label = QRectF(x - fontMetricsF.width(label) / 2, rect.bottom() + heightTick,
                                fontMetricsF.width(label), 2 * fontMetricsF.height())
            painter.drawLine(x, rect.bottom() - heightTick, x, rect.bottom() + heightTick)
            painter.drawText(rect_label, label)

            msec0 += step

            painter.restore()

        painter.restore()

    def clearPlotter(self, b_newData):
        self.deleteCurves()
        self._legend = ""
        if b_newData:
            self._date = QDate()

    def contextMenuEvent(self, event):
        self._contexMenu.exec(event.globalPos())

    def onSavePlotter(self):
        saveFile, exp = QFileDialog.getSaveFileName(self, '', '', self.tr('Рисунок (*.png *.jpg)'))
        if not saveFile:
            return
        pixmap = QPixmap(self._pixmapSaveWidth, self._pixmapSaveHeight)
        painter = QPainter(pixmap)
        self.drawOnPlotter(painter)
        pixmap.save(saveFile)

    def onToggledTimer(self, b_check):
        if self._isWatcher:
            if b_check:
                self.timerTick.emit()
                self._timer.start(
                    App.settings.value('/Settings/{0}/Main/TimeInterval'.format(App.settings.value(App.stationName())),
                                       5000) * 1000)
            else:
                self._timer.stop()
        else:
            self.noWatching.emit(b_check)

    def setCurves(self, data, indexFreq):
        for key in data:
            canals = data[key]
            if not key in self._curves.keys():
                if indexFreq == -1:
                    self._curves[key] = canals
                else:
                    pass
Beispiel #36
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()
Beispiel #37
0
class CoverDelegate(QStyledItemDelegate):  # {{{

    needs_redraw = pyqtSignal()

    def __init__(self, parent):
        QStyledItemDelegate.__init__(self, parent)

        self.angle = 0
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.frame_changed)
        self.color = parent.palette().color(QPalette.WindowText)
        self.spinner_width = 64

    def frame_changed(self, *args):
        self.angle = (self.angle + 30) % 360
        self.needs_redraw.emit()

    def start_animation(self):
        self.angle = 0
        self.timer.start(200)

    def stop_animation(self):
        self.timer.stop()

    def draw_spinner(self, painter, rect):
        width = rect.width()

        outer_radius = (width - 1) * 0.5
        inner_radius = (width - 1) * 0.5 * 0.38

        capsule_height = outer_radius - inner_radius
        capsule_width = int(capsule_height * (0.23 if width > 32 else 0.35))
        capsule_radius = capsule_width // 2

        painter.save()
        painter.setRenderHint(painter.Antialiasing)

        for i in xrange(12):
            color = QColor(self.color)
            color.setAlphaF(1.0 - (i / 12.0))
            painter.setPen(Qt.NoPen)
            painter.setBrush(color)
            painter.save()
            painter.translate(rect.center())
            painter.rotate(self.angle - i * 30.0)
            painter.drawRoundedRect(
                -capsule_width * 0.5,
                -(inner_radius + capsule_height),
                capsule_width,
                capsule_height,
                capsule_radius,
                capsule_radius,
            )
            painter.restore()
        painter.restore()

    def paint(self, painter, option, index):
        QStyledItemDelegate.paint(self, painter, option, index)
        style = QApplication.style()
        waiting = self.timer.isActive() and bool(index.data(Qt.UserRole))
        if waiting:
            rect = QRect(0, 0, self.spinner_width, self.spinner_width)
            rect.moveCenter(option.rect.center())
            self.draw_spinner(painter, rect)
        else:
            # Ensure the cover is rendered over any selection rect
            style.drawItemPixmap(
                painter, option.rect, Qt.AlignTop | Qt.AlignHCenter, QPixmap(index.data(Qt.DecorationRole))
            )
Beispiel #38
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])

    @property
    def side_index_size(self):
        if self.count() < 2:
            return 0
        return self.sizes()[self.side_index]

    @side_index_size.setter
    def side_index_size(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()

    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()
Beispiel #39
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 show_gui:
            self.show()

        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)

        # ######################## 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()
        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
        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()
Beispiel #40
0
class Preview(QWidget):

    sync_requested = pyqtSignal(object, object)
    split_requested = pyqtSignal(object, object, object)
    split_start_requested = pyqtSignal()
    link_clicked = pyqtSignal(object, object)
    refresh_starting = pyqtSignal()
    refreshed = pyqtSignal()
    live_css_data = pyqtSignal(object)
    render_process_restarted = pyqtSignal()
    open_file_with = pyqtSignal(object, object, object)
    edit_file = pyqtSignal(object)

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.l = l = QVBoxLayout()
        self.setLayout(l)
        l.setContentsMargins(0, 0, 0, 0)
        self.view = WebView(self)
        self.view._page.bridge.request_sync.connect(self.request_sync)
        self.view._page.bridge.request_split.connect(self.request_split)
        self.view._page.bridge.live_css_data.connect(self.live_css_data)
        self.view._page.loadFinished.connect(self.load_finished)
        self.view.render_process_restarted.connect(
            self.render_process_restarted)
        self.pending_go_to_anchor = None
        self.inspector = self.view.inspector
        l.addWidget(self.view)
        self.bar = QToolBar(self)
        l.addWidget(self.bar)

        ac = actions['auto-reload-preview']
        ac.setCheckable(True)
        ac.setChecked(True)
        ac.toggled.connect(self.auto_reload_toggled)
        self.auto_reload_toggled(ac.isChecked())
        self.bar.addAction(ac)

        ac = actions['sync-preview-to-editor']
        ac.setCheckable(True)
        ac.setChecked(True)
        ac.toggled.connect(self.sync_toggled)
        self.sync_toggled(ac.isChecked())
        self.bar.addAction(ac)

        self.bar.addSeparator()

        ac = actions['split-in-preview']
        ac.setCheckable(True)
        ac.setChecked(False)
        ac.toggled.connect(self.split_toggled)
        self.split_toggled(ac.isChecked())
        self.bar.addAction(ac)

        ac = actions['reload-preview']
        ac.triggered.connect(self.refresh)
        self.bar.addAction(ac)

        actions['preview-dock'].toggled.connect(self.visibility_changed)

        self.current_name = None
        self.last_sync_request = None
        self.refresh_timer = QTimer(self)
        self.refresh_timer.timeout.connect(self.refresh)
        parse_worker.start()
        self.current_sync_request = None

        self.search = HistoryLineEdit2(self)
        self.search.initialize('tweak_book_preview_search')
        self.search.setPlaceholderText(_('Search in preview'))
        self.search.returnPressed.connect(self.find_next)
        self.bar.addSeparator()
        self.bar.addWidget(self.search)
        for d in ('next', 'prev'):
            ac = actions['find-%s-preview' % d]
            ac.triggered.connect(getattr(self, 'find_' + d))
            self.bar.addAction(ac)

    def find(self, direction):
        text = unicode_type(self.search.text())
        self.view._page.findText(text,
                                 (QWebEnginePage.FindBackward if direction
                                  == 'prev' else QWebEnginePage.FindFlags(0)))

    def find_next(self):
        self.find('next')

    def find_prev(self):
        self.find('prev')

    def go_to_anchor(self, anchor):
        self.view._page.go_to_anchor(anchor)

    def request_sync(self, tagname, href, lnum):
        if self.current_name:
            c = current_container()
            if tagname == 'a' and href:
                if href and href.startswith('#'):
                    name = self.current_name
                else:
                    name = c.href_to_name(href,
                                          self.current_name) if href else None
                if name == self.current_name:
                    return self.go_to_anchor(urlparse(href).fragment)
                if name and c.exists(name) and c.mime_map[name] in OEB_DOCS:
                    return self.link_clicked.emit(
                        name,
                        urlparse(href).fragment or TOP)
            self.sync_requested.emit(self.current_name, lnum)

    def request_split(self, loc, totals):
        actions['split-in-preview'].setChecked(False)
        if not loc or not totals:
            return error_dialog(self,
                                _('Invalid location'),
                                _('Cannot split on the body tag'),
                                show=True)
        if self.current_name:
            self.split_requested.emit(self.current_name, loc, totals)

    def sync_to_editor(self, name, sourceline_address):
        self.current_sync_request = (name, sourceline_address)
        QTimer.singleShot(100, self._sync_to_editor)

    def _sync_to_editor(self):
        if not actions['sync-preview-to-editor'].isChecked():
            return
        try:
            if self.refresh_timer.isActive(
            ) or self.current_sync_request[0] != self.current_name:
                return QTimer.singleShot(100, self._sync_to_editor)
        except TypeError:
            return  # Happens if current_sync_request is None
        sourceline_address = self.current_sync_request[1]
        self.current_sync_request = None
        self.view._page.go_to_sourceline_address(sourceline_address)

    def report_worker_launch_error(self):
        if parse_worker.launch_error is not None:
            tb, parse_worker.launch_error = parse_worker.launch_error, None
            error_dialog(
                self,
                _('Failed to launch worker'),
                _('Failed to launch the worker process used for rendering the preview'
                  ),
                det_msg=tb,
                show=True)

    def name_to_qurl(self, name=None):
        name = name or self.current_name
        qurl = QUrl()
        qurl.setScheme(FAKE_PROTOCOL), qurl.setAuthority(
            FAKE_HOST), qurl.setPath('/' + name)
        return qurl

    def show(self, name):
        if name != self.current_name:
            self.refresh_timer.stop()
            self.current_name = name
            self.report_worker_launch_error()
            parse_worker.add_request(name)
            self.view.setUrl(self.name_to_qurl())
            return True

    def refresh(self):
        if self.current_name:
            self.refresh_timer.stop()
            # This will check if the current html has changed in its editor,
            # and re-parse it if so
            self.report_worker_launch_error()
            parse_worker.add_request(self.current_name)
            # Tell webkit to reload all html and associated resources
            current_url = self.name_to_qurl()
            self.refresh_starting.emit()
            if current_url != self.view.url():
                # The container was changed
                self.view.setUrl(current_url)
            else:
                self.view.refresh()
            self.refreshed.emit()

    def clear(self):
        self.view.clear()
        self.current_name = None

    @property
    def is_visible(self):
        return actions['preview-dock'].isChecked()

    @property
    def live_css_is_visible(self):
        try:
            return actions['live-css-dock'].isChecked()
        except KeyError:
            return False

    def start_refresh_timer(self):
        if self.live_css_is_visible or (
                self.is_visible
                and actions['auto-reload-preview'].isChecked()):
            self.refresh_timer.start(tprefs['preview_refresh_time'] * 1000)

    def stop_refresh_timer(self):
        self.refresh_timer.stop()

    def auto_reload_toggled(self, checked):
        if self.live_css_is_visible and not actions[
                'auto-reload-preview'].isChecked():
            actions['auto-reload-preview'].setChecked(True)
            error_dialog(
                self,
                _('Cannot disable'),
                _('Auto reloading of the preview panel cannot be disabled while the'
                  ' Live CSS panel is open.'),
                show=True)
        actions['auto-reload-preview'].setToolTip(
            _('Auto reload preview when text changes in editor'
              ) if not checked else _('Disable auto reload of preview'))

    def sync_toggled(self, checked):
        actions['sync-preview-to-editor'].setToolTip(
            _('Disable syncing of preview position to editor position'
              ) if checked else _(
                  'Enable syncing of preview position to editor position'))

    def visibility_changed(self, is_visible):
        if is_visible:
            self.refresh()

    def split_toggled(self, checked):
        actions['split-in-preview'].setToolTip(
            textwrap.fill(
                _('Abort file split') if checked else
                _('Split this file at a specified location.\n\nAfter clicking this button, click'
                  ' inside the preview panel above at the location you want the file to be split.'
                  )))
        if checked:
            self.split_start_requested.emit()
        else:
            self.view._page.split_mode(False)

    def do_start_split(self):
        self.view._page.split_mode(True)

    def stop_split(self):
        actions['split-in-preview'].setChecked(False)

    def load_finished(self, ok):
        if self.pending_go_to_anchor:
            self.view._page.go_to_anchor(self.pending_go_to_anchor)
            self.pending_go_to_anchor = None
        if actions['split-in-preview'].isChecked():
            if ok:
                self.do_start_split()
            else:
                self.stop_split()

    def request_live_css_data(self, editor_name, sourceline, tags):
        if self.view._page.bridge.ready:
            self.view._page.bridge.live_css(editor_name, sourceline, tags)

    def apply_settings(self):
        s = self.view.settings()
        s.setFontSize(s.DefaultFontSize, tprefs['preview_base_font_size'])
        s.setFontSize(s.DefaultFixedFontSize, tprefs['preview_mono_font_size'])
        s.setFontSize(s.MinimumLogicalFontSize,
                      tprefs['preview_minimum_font_size'])
        s.setFontSize(s.MinimumFontSize, tprefs['preview_minimum_font_size'])
        sf, ssf, mf = tprefs['engine_preview_serif_family'], tprefs[
            'engine_preview_sans_family'], tprefs['engine_preview_mono_family']
        if sf:
            s.setFontFamily(s.SerifFont, sf)
        if ssf:
            s.setFontFamily(s.SansSerifFont, ssf)
        if mf:
            s.setFontFamily(s.FixedFont, mf)
        stdfnt = tprefs['preview_standard_font_family'] or 'serif'
        stdfnt = getattr(s, {
            'serif': 'SerifFont',
            'sans': 'SansSerifFont',
            'mono': 'FixedFont'
        }[stdfnt])
        s.setFontFamily(s.StandardFont, s.fontFamily(stdfnt))
Beispiel #41
0
class Widget(QWidget):
    signalProcessEvents=pyqtSignal()
    def __init__(self):
        QWidget.__init__(self, None)
        # Set pixmaps resource before Main Window initialized
        self._resource = os.path.join(ctx.consts.theme_dir, ctx.flags.theme, ctx.consts.pixmaps_resource_file)
        if os.path.exists(self._resource):
            resource = QResource()
            resource.registerResource(self._resource)
        else:
            raise yali.Error, _("Pixmaps resources file doesn't exists")

        self.ui = Ui_YaliMain()
        self.ui.setupUi(self)

        self.font = 10
        self.animation_type = None

        self.screens = None
        self.screens_content = None

        self.pds_helper = HelpWidget(self.ui.scrollAreaWidgetContents)

        # shortcut to open help
        self.help_shortcut = QShortcut(QKeySequence(Qt.Key_F1), self)

        # shortcut to open debug window
        #self.debugShortCut = QtWidgets.QShortcut(QtWidgets.QKeySequence(Qt.Key_F2),self)

        # something funny
        self.tetris_shortcut = QShortcut(QKeySequence(Qt.Key_F6), self)
        self.cursor_shortcut = QShortcut(QKeySequence(Qt.Key_F7), self)
        self.theme_shortcut  = QShortcut(QKeySequence(Qt.Key_F8), self)

        # shortcut to open a console
        self.console_shortcut = QShortcut(QKeySequence(Qt.Key_F11), self)


        # set style
        self._style = os.path.join(ctx.consts.theme_dir, ctx.flags.theme, ctx.consts.style_file)
        if os.path.exists(self._style):
            self.updateStyle()
        else:
            raise yali.Error, _("Style file doesn't exists")

        # set screens content
        release_file = os.path.join(ctx.consts.branding_dir, ctx.flags.branding, ctx.consts.release_file)
        if os.path.exists(release_file):
            self.screens_content = yali.util.parse_branding_screens(release_file)
        else:
            raise yali.Error, _("Release file doesn't exists")


        # move one step at a time
        self.step_increment = 1

        # ToolButton Popup Menu
        self.menu = QMenu()
        self.shutdown = self.menu.addAction(QIcon(QPixmap(":/images/system-shutdown.png")), _("Turn Off Computer"))
        self.reboot = self.menu.addAction(QIcon(QPixmap(":/images/system-reboot.png")), _("Restart Computer"))
        self.restart = self.menu.addAction(QIcon(QPixmap(":/images/system-yali-reboot.png")), _("Restart YALI"))
        #self.menu.setDefaultAction(self.shutdown)
        self.ui.system_menu.setMenu(self.menu)
        self.ui.system_menu.setDefaultAction(self.shutdown)

        # Main Slots
        self.help_shortcut.activated.connect(self.pds_helper.toggleHelp)
        #self.debugShortCut.activated.connect(self.toggleDebug)
        self.console_shortcut.activated.connect(self.toggleConsole)
        self.cursor_shortcut.activated.connect(self.toggleCursor)
        self.theme_shortcut.activated.connect(self.toggleTheme)
        self.tetris_shortcut.activated.connect(self.toggleTetris)
        self.ui.buttonNext.clicked.connect(self.slotNext)
        self.ui.buttonBack.clicked.connect(self.slotBack)
        self.ui.toggleHelp.clicked.connect(self.pds_helper.toggleHelp)
        if not ctx.flags.install_type == ctx.STEP_FIRST_BOOT:
            self.ui.releaseNotes.clicked.connect(self.showReleaseNotes)
        else:
            self.ui.releaseNotes.hide()
        self.menu.triggered[QAction].connect(self.slotMenu)

        self.cmb = _("right")
        self.dont_ask_again = False
        self.terminal = None
        self.tetris = None

        self.ui.helpContentFrame.hide()

        self.effect = QGraphicsOpacityEffect(self)
        self.ui.mainStack.setGraphicsEffect(self.effect)
        self.effect.setOpacity(1.0)

        self.anime = QTimer(self)
        self.anime.timeout.connect(self.animate)

    def mousePressEvent(self, event):
        if event.button() == Qt.RightButton and not self.dont_ask_again:
            if self.cmb == _("left"):
                ocmb = _("right")
            else:
                ocmb = _("left")
            reply = QuestionDialog(_("Mouse Settings"),
                                   _("You just clicked the <b>%s</b> mouse button.") % self.cmb,
                                   _("Do you want to switch to the <b>%s</b> handed configuration?") % ocmb,
                                   dontAsk = True)
            if reply == "yes":
                yali.sysutils.setMouse(self.cmb)
                self.cmb = ocmb
            elif reply == "dontask":
                self.dont_ask_again = True

    def updateStyle(self):
        self.setStyleSheet(file(self._style).read())
        self.font = 10

    def setFontPlus(self):
        self.increaseFontSize(1)

    def setFontMinus(self):
        self.increaseFontSize(-1)

    def increaseFontSize(self, num):
        # We have to edit style sheet to set new fonts
        # Because if you use a style sheet in your application
        # ::setFont gets useless :( http://doc.trolltech.com/4.5/qapplication.html#setFont
        old = "QWidget{font:%dpt;}" % self.font
        self.font = self.font + num
        new = "QWidget{font:%dpt;}" % self.font
        self.setStyleSheet(self.styleSheet().replace(old, new))

    def slotMenu(self, action):
        if action == self.shutdown:
            reply = QuestionDialog(_("Warning"),
                                   _("Are you sure you want to shut down your computer now?"))
            if reply == "yes":
                yali.util.shutdown()
        elif action == self.reboot:
            reply = QuestionDialog(_("Warning"),
                                   _("Are you sure you want to restart your computer now?"))
            if reply == "yes":
                yali.util.reboot()
        else:
            reply = QuestionDialog(_("Warning"),
                                   _("Are you sure you want to restart the YALI installer now?"))
            if reply == "yes":
                os.execv("/usr/bin/yali-bin", sys.argv)

    def toggleTheme(self):
        "This easter egg will be implemented later"
        """
        if self._style == os.path.join(ctx.consts.theme_dir, "%s/style.qss" % ctx.flags.theme):
            if os.path.join(ctx.consts.theme_dir, "%s/style.glass.qss" % ctx.flags.theme):
                self._style = os.path.join(ctx.consts.theme_dir, "%s/style.glass.qss" % ctx.flags.theme)
        else:
            self._style = os.path.join(ctx.consts.theme_dir, "%s/style.qss" % ctx.flags.theme)
        self.updateStyle()
        """

    def toggleConsole(self):
        if not self.terminal:
            terminal = QTermWidget()
            terminal.setScrollBarPosition(QTermWidget.ScrollBarRight)
            terminal.setColorScheme(1)
            terminal.sendText("export TERM='xterm'\nclear\n")
            self.terminal = Dialog(_("Terminal"), terminal, True, QKeySequence(Qt.Key_F11))
            self.terminal.resize(700, 500)
        self.terminal.exec_()

    def toggleTetris(self):
        self.tetris = Dialog(_("Tetris"), None, True, QKeySequence(Qt.Key_F6))
        _tetris = Tetris(self.tetris)
        self.tetris.addWidget(_tetris)
        self.tetris.resize(240, 500)
        _tetris.start()
        self.tetris.exec_()

    def toggleCursor(self):
        if self.cursor().shape() == QCursor(Qt.ArrowCursor).shape():
            raw = QPixmap(":/gui/pics/pardusman-icon.png")
            raw.setMask(raw.mask())
            self.setCursor(QCursor(raw, 2, 2))
        else:
            self.unsetCursor()

    # show/hide help text
    def slotToggleHelp(self):
        self.ui.helpContentFrame.setFixedHeight(self.ui.helpContent.height())
        if self.ui.helpContentFrame.isVisible():
            self.ui.helpContentFrame.hide()
        else:
            self.ui.helpContentFrame.show()
        widget = self.ui.mainStack.currentWidget()
        widget.update()

    # show/hide debug window
    def toggleDebug(self):
        if ctx.debugger.isVisible():
            ctx.debugger.hideWindow()
        else:
            ctx.debugger.showWindow()

    # returns the id of current stack
    def getCurrent(self, index):
        new_index   = self.ui.mainStack.currentIndex() + index
        total_index = self.ui.mainStack.count()
        if new_index < 0: new_index = 0
        if new_index > total_index: new_index = total_index
        return new_index

    # move to id numbered step
    def setCurrent(self, index=None):
        if index:
            self.stackMove(index)

    # execute next step
    def slotNext(self, dry_run=False):
        widget = self.ui.mainStack.currentWidget()
        ret = True
        if not dry_run:
            ret = widget.execute()
        if ret:
            self.pds_helper.hideHelp()
            self.ui.toggleHelp.setChecked(False)
            self.stackMove(self.getCurrent(self.step_increment))
            self.step_increment = 1

    # execute previous step
    def slotBack(self):
        widget = self.ui.mainStack.currentWidget()
        if widget.backCheck():
            self.stackMove(self.getCurrent(self.step_increment * -1))
        self.pds_helper.hideHelp()
        self.ui.toggleHelp.setChecked(False)
        self.step_increment = 1

    # move to id numbered stack
    def stackMove(self, index):
        if not index == self.ui.mainStack.currentIndex() or index == 0:
            self.effect.setOpacity(0.0)
            self.animation_type = "fade-in"
            self.anime.start(50)
            self.ui.mainStack.setCurrentIndex(index)
            widget = self.ui.mainStack.currentWidget()
            # Hack to fix goodbye screen help content
            # BUG:#15860, #15444
            if widget.name == "goodbye":
                widget_id = "%s%s" % (widget.name, ctx.flags.install_type)
            else:
                widget_id = widget.name

            widget_icon = self.screens_content[widget_id][0]

            if self.screens_content[widget_id][1].has_key(ctx.consts.lang):
                widget_title = self.screens_content[widget_id][1][ctx.consts.lang]
            else:
                widget_title = self.screens_content[widget_id][1]["en"]

            if self.screens_content[widget_id][2].has_key(ctx.consts.lang):
                widget_help = self.screens_content[widget_id][2][ctx.consts.lang]
            else:
                widget_help = self.screens_content[widget_id][2]["en"]

            self.ui.screenName.setText(widget_title)
            self.pds_helper.ui.helpContent.setText(widget_help)
            self.pds_helper.setHelp(widget_help)
            self.ui.screenIcon.setPixmap(QPixmap(":/gui/pics/%s.png" % (widget_icon)))

            ctx.mainScreen.processEvents()
            widget.update()
            ctx.mainScreen.processEvents()
            widget.shown()

    def animate(self):
        if self.animation_type == "fade-in":
            if self.effect.opacity() < 1.0:
                self.effect.setOpacity(self.effect.opacity() + 0.2)
            else:
                self.anime.stop()
        if self.animation_type == "fade-out":
            if self.effect.opacity() > 0.0:
                self.effect.setOpacity(self.effect.opacity() - 0.2)
            else:
                self.anime.stop()

    def createWidgets(self, screens=[]):
        if not self.screens:
            self.screens = screens
        self.ui.mainStack.removeWidget(self.ui.page)

        for screen in screens:
            #if ctx.flags.debug:
                # debug all screens.
            #    weave_all_object_methods(ctx.aspect, screen)

            # enable navigation buttons before shown
            weave_object_method(enableNavButtonsAspect, screen, "shown")
            # disable navigation buttons before the execute.
            weave_object_method(disableNavButtonsAspect, screen, "execute")
            try:
                self.ui.mainStack.addWidget(screen())
            except Exception, msg:
                rc = ctx.interface.messageWindow(_("Error"),
                                                 _("An error occurred when attempting "
                                                    "to load screens:%s") % msg,
                                                 type="custom", customIcon="error",
                                                 customButtons=[_("Exit")])
                if not rc:
                    sys.exit(0)

        #weave_all_object_methods(ctx.aspect, self)
        self.stackMove(ctx.flags.startup)
Beispiel #42
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.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.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.0))
        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.print_menu.actions()[0].triggered.connect(self.print_preview)
        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.shutdown():
            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
        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 get_remember_current_page_opt(self):
        from calibre.gui2.viewer.documentview import config

        c = config().parse()
        return c.remember_current_page

    def print_book(self):
        p = Printing(self.iterator, self)
        p.start_print()

    def print_preview(self):
        p = Printing(self.iterator, self)
        p.start_preview()

    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 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
        if self.window_mode_changed:
            # This resize is part of a window mode change, special case it
            self.handle_window_mode_toggle()
        else:
            self.view.document.page_position.restore()
            if self.isFullScreen():
                self.relayout_fullscreen_labels()
        self.view.document.after_resize()
        # For some reason scroll_fraction returns incorrect results in paged
        # mode for some time after a resize is finished. No way of knowing
        # exactly how long, so we update it in a second, in the hopes that it
        # will be enough *most* of the time.
        QTimer.singleShot(1000, self.update_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()
        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.get_remember_current_page_opt():
                    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.get_remember_current_page_opt():
            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)
        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.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.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)
Beispiel #43
0
class Widget(QWidget, ScreenWidget):
    name = "timeSetup"

    def __init__(self):
        QWidget.__init__(self)
        self.ui = Ui_DateTimeWidget()
        self.ui.setupUi(self)
        self.timer = QTimer(self)
        self.from_time_updater = True
        self.is_date_changed = False

        self.current_zone = ""

        self.tz_dict = {}
        self.continents = []
        self.countries = []

        for country, data in yali.localedata.locales.items():
            if country == ctx.consts.lang:
                if data.has_key("timezone"):
                    ctx.installData.timezone = data["timezone"]

        # Append continents and countries the time zone dictionary
        self.createTZDictionary()

        # Sort continent list
        self.sortContinents()

        # Append sorted continents to combobox
        self.loadContinents()

        # Load current continents country list
        self.getCountries(self.current_zone["continent"])

        # Highlight the current zone
        self.index = self.ui.continentList.findText(
            self.current_zone["continent"])
        self.ui.continentList.setCurrentIndex(self.index)

        self.index = self.ui.countryList.findText(self.current_zone["country"])
        self.ui.countryList.setCurrentIndex(self.index)

        # Initialize widget signal and slots
        self.__initSignals__()

        self.ui.calendarWidget.setDate(QDate.currentDate())

        self.pthread = None
        self.pds_messagebox = PMessageBox(self)
        self.pds_messagebox.enableOverlay()

        self.timer.start(1000)

    def __initSignals__(self):
        self.ui.timeEdit.timeChanged[QTime].connect(self.timerStop)
        self.ui.calendarWidget.dateChanged.connect(self.dateChanged)
        self.timer.timeout.connect(self.updateClock)
        self.ui.continentList.activated[str].connect(self.getCountries)

    def createTZDictionary(self):
        tz = TimeZoneList()
        zones = [x.timeZone for x in tz.getEntries()]
        zones.sort()

        for zone in zones:
            split = zone.split("/")

            # Human readable continent names
            continent_pretty_name = split[0].replace("_", " ")
            continent_pretty_name = continent_pretty_name

            # Some country names can be like Argentina/Catamarca so this fixes the splitting problem
            # caused by zone.split("/")
            #
            # Remove continent info and take the rest as the country name
            split.pop(0)
            country_pretty_name = " / ".join(split)

            # Human readable country names
            country_pretty_name = country_pretty_name.replace("_", " ")

            # Get current zone
            if zone == ctx.installData.timezone:
                self.current_zone = {
                    "continent": continent_pretty_name,
                    "country": country_pretty_name
                }

            # Append to dictionary
            if self.tz_dict.has_key(continent_pretty_name):
                self.tz_dict[continent_pretty_name].append(
                    [country_pretty_name, zone])
            else:
                self.tz_dict[continent_pretty_name] = [[
                    country_pretty_name, zone
                ]]

    def sortContinents(self):
        for continent in self.tz_dict.keys():
            self.continents.append(continent)
        self.continents.sort()

    def loadContinents(self):
        for continent in self.continents:
            self.ui.continentList.addItem(continent)

    def getCountries(self, continent):
        # Countries of the selected continent
        countries = self.tz_dict[str(continent)]

        self.ui.countryList.clear()

        for country, zone in countries:
            self.ui.countryList.addItem(country, zone)
            self.countries.append(country)

    def dateChanged(self):
        self.is_date_changed = True

    def timerStop(self):
        if self.from_time_updater:
            return
        # Human action detected; stop the timer.
        self.timer.stop()

    def updateClock(self):

        # What time is it ?
        cur = QTime.currentTime()

        self.from_time_updater = True
        self.ui.timeEdit.setTime(cur)
        self.from_time_updater = False

    def shown(self):
        self.timer.start(1000)

        if ctx.flags.install_type == ctx.STEP_BASE:
            self.pthread = PThread(self, self.startInit, self.dummy)

    def dummy(self):
        pass

    def setTime(self):
        ctx.interface.informationWindow.update(_("Adjusting time settings"))
        date = self.ui.calendarWidget.date()
        time = self.ui.timeEdit.time()
        args = "%02d%02d%02d%02d%04d.%02d" % (date.month(), date.day(),
                                              time.hour(), time.minute(),
                                              date.year(), time.second())

        # Set current date and time
        ctx.logger.debug("Date/Time setting to %s" % args)
        yali.util.run_batch("date", [args])

        # Sync date time with hardware
        ctx.logger.debug("YALI's time is syncing with the system.")
        yali.util.run_batch("hwclock", ["--systohc"])
        ctx.interface.informationWindow.hide()

    def execute(self):
        if not self.timer.isActive() or self.is_date_changed:
            QTimer.singleShot(500, self.setTime)
            self.timer.stop()

        index = self.ui.countryList.currentIndex()
        ctx.installData.timezone = self.ui.countryList.itemData(index)
        ctx.logger.debug("Time zone selected as %s " %
                         ctx.installData.timezone)

        if ctx.flags.install_type == ctx.STEP_BASE:
            #FIXME:Refactor hacky code
            ctx.installData.rootPassword = ctx.consts.default_password
            ctx.installData.hostName = yali.util.product_release()
            if ctx.storageInitialized:
                disks = filter(lambda d: not d.format.hidden,
                               ctx.storage.disks)
                if len(disks) == 1:
                    ctx.storage.clearPartDisks = [disks[0].name]
                    ctx.mainScreen.step_increment = 2
                else:
                    ctx.mainScreen.step_increment = 1
                return True
            else:
                self.pds_messagebox.setMessage(
                    _("Storage Devices initialising..."))
                self.pds_messagebox.animate(start=MIDCENTER, stop=MIDCENTER)
                ctx.mainScreen.step_increment = 0
                self.pthread.start()
                QTimer.singleShot(2, self.startStorageInitialize)
                return False

        return True

    def startInit(self):
        self.pds_messagebox.animate(start=MIDCENTER, stop=MIDCENTER)

    def startStorageInitialize(self):
        ctx.storageInitialized = yali.storage.initialize(
            ctx.storage, ctx.interface)
        self.initFinished()

    def initFinished(self):
        self.pds_messagebox.animate(start=CURRENT, stop=CURRENT, direction=OUT)
        disks = filter(lambda d: not d.format.hidden, ctx.storage.disks)
        if ctx.storageInitialized:
            if len(disks) == 1:
                ctx.storage.clearPartDisks = [disks[0].name]
                ctx.mainScreen.step_increment = 2
            else:
                ctx.mainScreen.step_increment = 1
            ctx.mainScreen.slotNext(dry_run=True)
        else:
            ctx.mainScreen.enableBack()
Beispiel #44
0
class Widget(QWidget):
    signalProcessEvents = pyqtSignal()

    def __init__(self):
        QWidget.__init__(self, None)
        # Set pixmaps resource before Main Window initialized
        self._resource = os.path.join(ctx.consts.theme_dir, ctx.flags.theme,
                                      ctx.consts.pixmaps_resource_file)
        if os.path.exists(self._resource):
            resource = QResource()
            resource.registerResource(self._resource)
        else:
            raise yali.Error, _("Pixmaps resources file doesn't exists")

        self.ui = Ui_YaliMain()
        self.ui.setupUi(self)

        self.font = 10
        self.animation_type = None

        self.screens = None
        self.screens_content = None

        self.pds_helper = HelpWidget(self.ui.scrollAreaWidgetContents)

        # shortcut to open help
        self.help_shortcut = QShortcut(QKeySequence(Qt.Key_F1), self)

        # shortcut to open debug window
        #self.debugShortCut = QtWidgets.QShortcut(QtWidgets.QKeySequence(Qt.Key_F2),self)

        # something funny
        self.tetris_shortcut = QShortcut(QKeySequence(Qt.Key_F6), self)
        self.cursor_shortcut = QShortcut(QKeySequence(Qt.Key_F7), self)
        self.theme_shortcut = QShortcut(QKeySequence(Qt.Key_F8), self)

        # shortcut to open a console
        self.console_shortcut = QShortcut(QKeySequence(Qt.Key_F11), self)

        # set style
        self._style = os.path.join(ctx.consts.theme_dir, ctx.flags.theme,
                                   ctx.consts.style_file)
        if os.path.exists(self._style):
            self.updateStyle()
        else:
            raise yali.Error, _("Style file doesn't exists")

        # set screens content
        release_file = os.path.join(ctx.consts.branding_dir,
                                    ctx.flags.branding,
                                    ctx.consts.release_file)
        if os.path.exists(release_file):
            self.screens_content = yali.util.parse_branding_screens(
                release_file)
        else:
            raise yali.Error, _("Release file doesn't exists")

        # move one step at a time
        self.step_increment = 1

        # ToolButton Popup Menu
        self.menu = QMenu()
        self.shutdown = self.menu.addAction(
            QIcon(QPixmap(":/images/system-shutdown.png")),
            _("Turn Off Computer"))
        self.reboot = self.menu.addAction(
            QIcon(QPixmap(":/images/system-reboot.png")),
            _("Restart Computer"))
        self.restart = self.menu.addAction(
            QIcon(QPixmap(":/images/system-yali-reboot.png")),
            _("Restart YALI"))
        #self.menu.setDefaultAction(self.shutdown)
        self.ui.system_menu.setMenu(self.menu)
        self.ui.system_menu.setDefaultAction(self.shutdown)

        # Main Slots
        self.help_shortcut.activated.connect(self.pds_helper.toggleHelp)
        #self.debugShortCut.activated.connect(self.toggleDebug)
        self.console_shortcut.activated.connect(self.toggleConsole)
        self.cursor_shortcut.activated.connect(self.toggleCursor)
        self.theme_shortcut.activated.connect(self.toggleTheme)
        self.tetris_shortcut.activated.connect(self.toggleTetris)
        self.ui.buttonNext.clicked.connect(self.slotNext)
        self.ui.buttonBack.clicked.connect(self.slotBack)
        self.ui.toggleHelp.clicked.connect(self.pds_helper.toggleHelp)
        if not ctx.flags.install_type == ctx.STEP_FIRST_BOOT:
            self.ui.releaseNotes.clicked.connect(self.showReleaseNotes)
        else:
            self.ui.releaseNotes.hide()
        self.menu.triggered[QAction].connect(self.slotMenu)

        self.cmb = _("right")
        self.dont_ask_again = False
        self.terminal = None
        self.tetris = None

        self.ui.helpContentFrame.hide()

        self.effect = QGraphicsOpacityEffect(self)
        self.ui.mainStack.setGraphicsEffect(self.effect)
        self.effect.setOpacity(1.0)

        self.anime = QTimer(self)
        self.anime.timeout.connect(self.animate)

    def mousePressEvent(self, event):
        if event.button() == Qt.RightButton and not self.dont_ask_again:
            if self.cmb == _("left"):
                ocmb = _("right")
            else:
                ocmb = _("left")
            reply = QuestionDialog(
                _("Mouse Settings"),
                _("You just clicked the <b>%s</b> mouse button.") % self.cmb,
                _("Do you want to switch to the <b>%s</b> handed configuration?"
                  ) % ocmb,
                dontAsk=True)
            if reply == "yes":
                yali.sysutils.setMouse(self.cmb)
                self.cmb = ocmb
            elif reply == "dontask":
                self.dont_ask_again = True

    def updateStyle(self):
        self.setStyleSheet(file(self._style).read())
        self.font = 10

    def setFontPlus(self):
        self.increaseFontSize(1)

    def setFontMinus(self):
        self.increaseFontSize(-1)

    def increaseFontSize(self, num):
        # We have to edit style sheet to set new fonts
        # Because if you use a style sheet in your application
        # ::setFont gets useless :( http://doc.trolltech.com/4.5/qapplication.html#setFont
        old = "QWidget{font:%dpt;}" % self.font
        self.font = self.font + num
        new = "QWidget{font:%dpt;}" % self.font
        self.setStyleSheet(self.styleSheet().replace(old, new))

    def slotMenu(self, action):
        if action == self.shutdown:
            reply = QuestionDialog(
                _("Warning"),
                _("Are you sure you want to shut down your computer now?"))
            if reply == "yes":
                yali.util.shutdown()
        elif action == self.reboot:
            reply = QuestionDialog(
                _("Warning"),
                _("Are you sure you want to restart your computer now?"))
            if reply == "yes":
                yali.util.reboot()
        else:
            reply = QuestionDialog(
                _("Warning"),
                _("Are you sure you want to restart the YALI installer now?"))
            if reply == "yes":
                os.execv("/usr/bin/yali-bin", sys.argv)

    def toggleTheme(self):
        "This easter egg will be implemented later"
        """
        if self._style == os.path.join(ctx.consts.theme_dir, "%s/style.qss" % ctx.flags.theme):
            if os.path.join(ctx.consts.theme_dir, "%s/style.glass.qss" % ctx.flags.theme):
                self._style = os.path.join(ctx.consts.theme_dir, "%s/style.glass.qss" % ctx.flags.theme)
        else:
            self._style = os.path.join(ctx.consts.theme_dir, "%s/style.qss" % ctx.flags.theme)
        self.updateStyle()
        """

    def toggleConsole(self):
        if not self.terminal:
            terminal = QTermWidget()
            terminal.setScrollBarPosition(QTermWidget.ScrollBarRight)
            terminal.setColorScheme(1)
            terminal.sendText("export TERM='xterm'\nclear\n")
            self.terminal = Dialog(_("Terminal"), terminal, True,
                                   QKeySequence(Qt.Key_F11))
            self.terminal.resize(700, 500)
        self.terminal.exec_()

    def toggleTetris(self):
        self.tetris = Dialog(_("Tetris"), None, True, QKeySequence(Qt.Key_F6))
        _tetris = Tetris(self.tetris)
        self.tetris.addWidget(_tetris)
        self.tetris.resize(240, 500)
        _tetris.start()
        self.tetris.exec_()

    def toggleCursor(self):
        if self.cursor().shape() == QCursor(Qt.ArrowCursor).shape():
            raw = QPixmap(":/gui/pics/pardusman-icon.png")
            raw.setMask(raw.mask())
            self.setCursor(QCursor(raw, 2, 2))
        else:
            self.unsetCursor()

    # show/hide help text
    def slotToggleHelp(self):
        self.ui.helpContentFrame.setFixedHeight(self.ui.helpContent.height())
        if self.ui.helpContentFrame.isVisible():
            self.ui.helpContentFrame.hide()
        else:
            self.ui.helpContentFrame.show()
        widget = self.ui.mainStack.currentWidget()
        widget.update()

    # show/hide debug window
    def toggleDebug(self):
        if ctx.debugger.isVisible():
            ctx.debugger.hideWindow()
        else:
            ctx.debugger.showWindow()

    # returns the id of current stack
    def getCurrent(self, index):
        new_index = self.ui.mainStack.currentIndex() + index
        total_index = self.ui.mainStack.count()
        if new_index < 0: new_index = 0
        if new_index > total_index: new_index = total_index
        return new_index

    # move to id numbered step
    def setCurrent(self, index=None):
        if index:
            self.stackMove(index)

    # execute next step
    def slotNext(self, dry_run=False):
        widget = self.ui.mainStack.currentWidget()
        ret = True
        if not dry_run:
            ret = widget.execute()
        if ret:
            self.pds_helper.hideHelp()
            self.ui.toggleHelp.setChecked(False)
            self.stackMove(self.getCurrent(self.step_increment))
            self.step_increment = 1

    # execute previous step
    def slotBack(self):
        widget = self.ui.mainStack.currentWidget()
        if widget.backCheck():
            self.stackMove(self.getCurrent(self.step_increment * -1))
        self.pds_helper.hideHelp()
        self.ui.toggleHelp.setChecked(False)
        self.step_increment = 1

    # move to id numbered stack
    def stackMove(self, index):
        if not index == self.ui.mainStack.currentIndex() or index == 0:
            self.effect.setOpacity(0.0)
            self.animation_type = "fade-in"
            self.anime.start(50)
            self.ui.mainStack.setCurrentIndex(index)
            widget = self.ui.mainStack.currentWidget()
            # Hack to fix goodbye screen help content
            # BUG:#15860, #15444
            if widget.name == "goodbye":
                widget_id = "%s%s" % (widget.name, ctx.flags.install_type)
            else:
                widget_id = widget.name

            widget_icon = self.screens_content[widget_id][0]

            if self.screens_content[widget_id][1].has_key(ctx.consts.lang):
                widget_title = self.screens_content[widget_id][1][
                    ctx.consts.lang]
            else:
                widget_title = self.screens_content[widget_id][1]["en"]

            if self.screens_content[widget_id][2].has_key(ctx.consts.lang):
                widget_help = self.screens_content[widget_id][2][
                    ctx.consts.lang]
            else:
                widget_help = self.screens_content[widget_id][2]["en"]

            self.ui.screenName.setText(widget_title)
            self.pds_helper.ui.helpContent.setText(widget_help)
            self.pds_helper.setHelp(widget_help)
            self.ui.screenIcon.setPixmap(
                QPixmap(":/gui/pics/%s.png" % (widget_icon)))

            ctx.mainScreen.processEvents()
            widget.update()
            ctx.mainScreen.processEvents()
            widget.shown()

    def animate(self):
        if self.animation_type == "fade-in":
            if self.effect.opacity() < 1.0:
                self.effect.setOpacity(self.effect.opacity() + 0.2)
            else:
                self.anime.stop()
        if self.animation_type == "fade-out":
            if self.effect.opacity() > 0.0:
                self.effect.setOpacity(self.effect.opacity() - 0.2)
            else:
                self.anime.stop()

    def createWidgets(self, screens=[]):
        if not self.screens:
            self.screens = screens
        self.ui.mainStack.removeWidget(self.ui.page)

        for screen in screens:
            #if ctx.flags.debug:
            # debug all screens.
            #    weave_all_object_methods(ctx.aspect, screen)

            # enable navigation buttons before shown
            weave_object_method(enableNavButtonsAspect, screen, "shown")
            # disable navigation buttons before the execute.
            weave_object_method(disableNavButtonsAspect, screen, "execute")
            try:
                self.ui.mainStack.addWidget(screen())
            except Exception, msg:
                rc = ctx.interface.messageWindow(
                    _("Error"),
                    _("An error occurred when attempting "
                      "to load screens:%s") % msg,
                    type="custom",
                    customIcon="error",
                    customButtons=[_("Exit")])
                if not rc:
                    sys.exit(0)

        #weave_all_object_methods(ctx.aspect, self)
        self.stackMove(ctx.flags.startup)
Beispiel #45
0
class Comments(QTextBrowser):  # {{{
    def __init__(self, parent=None):
        QTextBrowser.__init__(self, parent)
        self.setAcceptDrops(False)
        self.setMaximumWidth(300)
        self.setMinimumWidth(300)
        self.wait_timer = QTimer(self)
        self.wait_timer.timeout.connect(self.update_wait)
        self.wait_timer.setInterval(800)
        self.dots_count = 0

        palette = self.palette()
        palette.setBrush(QPalette.Base, Qt.transparent)
        self.setPalette(palette)
        self.setAttribute(Qt.WA_OpaquePaintEvent, False)
        self.anchorClicked.connect(self.link_clicked)

    def link_clicked(self, url):
        from calibre.gui2 import open_url
        if url.scheme() in {'http', 'https'}:
            open_url(url)

    def show_wait(self):
        self.dots_count = 0
        self.wait_timer.start()
        self.update_wait()

    def update_wait(self):
        self.dots_count += 1
        self.dots_count %= 10
        self.dots_count = self.dots_count or 1
        self.setHtml(
            '<h2>' + _('Please wait') +
            '<br><span id="dots">{}</span></h2>'.format('.' * self.dots_count))

    def show_data(self, html):
        self.wait_timer.stop()

        def color_to_string(col):
            ans = '#000000'
            if col.isValid():
                col = col.toRgb()
                if col.isValid():
                    ans = unicode_type(col.name())
            return ans

        fi = QFontInfo(QApplication.font(self.parent()))
        f = fi.pixelSize() + 1 + int(
            tweaks['change_book_details_font_size_by'])
        fam = unicode_type(fi.family()).strip().replace('"', '')
        if not fam:
            fam = 'sans-serif'

        c = color_to_string(QApplication.palette().color(
            QPalette.Normal, QPalette.WindowText))
        templ = '''\
        <html>
            <head>
            <style type="text/css">
                body, td {background-color: transparent; font-family: "%s"; font-size: %dpx; color: %s }
                a { text-decoration: none; color: blue }
                div.description { margin-top: 0; padding-top: 0; text-indent: 0 }
                table { margin-bottom: 0; padding-bottom: 0; }
            </style>
            </head>
            <body>
            <div class="description">
            %%s
            </div>
            </body>
        <html>
        ''' % (fam, f, c)
        self.setHtml(templ % html)

    def sizeHint(self):
        # This is needed, because on windows the dialog cannot be resized to
        # so that this widgets height become < sizeHint().height(). Qt sets the
        # sizeHint to (800, 600), which makes the dialog unusable on smaller
        # screens.
        return QSize(800, 300)
Beispiel #46
0
class GridView(QListView):

    update_item = pyqtSignal(object)
    files_dropped = pyqtSignal(object)
    books_dropped = pyqtSignal(object)

    def __init__(self, parent):
        QListView.__init__(self, parent)
        self._ncols = None
        self.gesture_manager = GestureManager(self)
        setup_dnd_interface(self)
        self.setUniformItemSizes(True)
        self.setWrapping(True)
        self.setFlow(self.LeftToRight)
        # We cannot set layout mode to batched, because that breaks
        # restore_vpos()
        # self.setLayoutMode(self.Batched)
        self.setResizeMode(self.Adjust)
        self.setSelectionMode(self.ExtendedSelection)
        self.setVerticalScrollMode(self.ScrollPerPixel)
        self.delegate = CoverDelegate(self)
        self.delegate.animation.valueChanged.connect(self.animation_value_changed)
        self.delegate.animation.finished.connect(self.animation_done)
        self.setItemDelegate(self.delegate)
        self.setSpacing(self.delegate.spacing)
        self.set_color()
        self.ignore_render_requests = Event()
        dpr = self.device_pixel_ratio
        self.thumbnail_cache = ThumbnailCache(max_size=gprefs['cover_grid_disk_cache_size'],
            thumbnail_size=(int(dpr * self.delegate.cover_size.width()), int(dpr * self.delegate.cover_size.height())))
        self.render_thread = None
        self.update_item.connect(self.re_render, type=Qt.QueuedConnection)
        self.doubleClicked.connect(self.double_clicked)
        self.setCursor(Qt.PointingHandCursor)
        self.gui = parent
        self.context_menu = None
        self.update_timer = QTimer(self)
        self.update_timer.setInterval(200)
        self.update_timer.timeout.connect(self.update_viewport)
        self.update_timer.setSingleShot(True)
        self.resize_timer = t = QTimer(self)
        t.setInterval(200), t.setSingleShot(True)
        t.timeout.connect(self.update_memory_cover_cache_size)

    def viewportEvent(self, ev):
        try:
            ret = self.gesture_manager.handle_event(ev)
        except AttributeError:
            ret = None
        if ret is not None:
            return ret
        return QListView.viewportEvent(self, ev)

    @property
    def device_pixel_ratio(self):
        try:
            return self.devicePixelRatioF()
        except AttributeError:
            return self.devicePixelRatio()

    @property
    def first_visible_row(self):
        geom = self.viewport().geometry()
        for y in range(geom.top(), (self.spacing()*2) + geom.top(), 5):
            for x in range(geom.left(), (self.spacing()*2) + geom.left(), 5):
                ans = self.indexAt(QPoint(x, y)).row()
                if ans > -1:
                    return ans

    @property
    def last_visible_row(self):
        geom = self.viewport().geometry()
        for y in range(geom.bottom(), geom.bottom() - 2 * self.spacing(), -5):
            for x in range(geom.left(), (self.spacing()*2) + geom.left(), 5):
                ans = self.indexAt(QPoint(x, y)).row()
                if ans > -1:
                    item_width = self.delegate.item_size.width() + 2*self.spacing()
                    return ans + (geom.width() // item_width)

    def update_viewport(self):
        self.ignore_render_requests.clear()
        self.update_timer.stop()
        m = self.model()
        for r in range(self.first_visible_row or 0, self.last_visible_row or (m.count() - 1)):
            self.update(m.index(r, 0))

    def start_view_animation(self, index):
        d = self.delegate
        if d.animating is None and not config['disable_animations']:
            d.animating = index
            d.animation.start()

    def double_clicked(self, index):
        self.start_view_animation(index)
        if tweaks['doubleclick_on_library_view'] == 'open_viewer':
            self.gui.iactions['View'].view_triggered(index)
        elif tweaks['doubleclick_on_library_view'] in {'edit_metadata', 'edit_cell'}:
            self.gui.iactions['Edit Metadata'].edit_metadata(False, False)

    def animation_value_changed(self, value):
        if self.delegate.animating is not None:
            self.update(self.delegate.animating)

    def animation_done(self):
        if self.delegate.animating is not None:
            idx = self.delegate.animating
            self.delegate.animating = None
            self.update(idx)

    def set_color(self):
        r, g, b = gprefs['cover_grid_color']
        tex = gprefs['cover_grid_texture']
        pal = self.palette()
        pal.setColor(pal.Base, QColor(r, g, b))
        self.setPalette(pal)
        ss = ''
        if tex:
            from calibre.gui2.preferences.texture_chooser import texture_path
            path = texture_path(tex)
            if path:
                path = os.path.abspath(path).replace(os.sep, '/')
                ss += 'background-image: url({});'.format(path)
                ss += 'background-attachment: fixed;'
                pm = QPixmap(path)
                if not pm.isNull():
                    val = pm.scaled(1, 1).toImage().pixel(0, 0)
                    r, g, b = qRed(val), qGreen(val), qBlue(val)
        dark = max(r, g, b) < 115
        ss += 'color: {};'.format('white' if dark else 'black')
        self.delegate.highlight_color = QColor(Qt.white if dark else Qt.black)
        self.setStyleSheet('QListView {{ {} }}'.format(ss))

    def refresh_settings(self):
        size_changed = (
            gprefs['cover_grid_width'] != self.delegate.original_width or gprefs['cover_grid_height'] != self.delegate.original_height
        )
        if (size_changed or gprefs[
            'cover_grid_show_title'] != self.delegate.original_show_title or gprefs[
                'show_emblems'] != self.delegate.original_show_emblems or gprefs[
                    'emblem_size'] != self.delegate.orginal_emblem_size or gprefs[
                        'emblem_position'] != self.delegate.orginal_emblem_position):
            self.delegate.set_dimensions()
            self.setSpacing(self.delegate.spacing)
            if size_changed:
                self.delegate.cover_cache.clear()
        if gprefs['cover_grid_spacing'] != self.delegate.original_spacing:
            self.delegate.calculate_spacing()
            self.setSpacing(self.delegate.spacing)
        self.set_color()
        self.set_thumbnail_cache_image_size()
        cs = gprefs['cover_grid_disk_cache_size']
        if (cs*(1024**2)) != self.thumbnail_cache.max_size:
            self.thumbnail_cache.set_size(cs)
        self.update_memory_cover_cache_size()

    def set_thumbnail_cache_image_size(self):
        dpr = self.device_pixel_ratio
        self.thumbnail_cache.set_thumbnail_size(
            int(dpr * self.delegate.cover_size.width()), int(dpr*self.delegate.cover_size.height()))

    def resizeEvent(self, ev):
        self._ncols = None
        self.resize_timer.start()
        return QListView.resizeEvent(self, ev)

    def update_memory_cover_cache_size(self):
        try:
            sz = self.delegate.item_size
        except AttributeError:
            return
        rows, cols = self.width() // sz.width(), self.height() // sz.height()
        num = (rows + 1) * (cols + 1)
        limit = max(100, num * max(2, gprefs['cover_grid_cache_size_multiple']))
        if limit != self.delegate.cover_cache.limit:
            self.delegate.cover_cache.set_limit(limit)

    def shown(self):
        self.update_memory_cover_cache_size()
        if self.render_thread is None:
            self.thumbnail_cache.set_database(self.gui.current_db)
            self.render_thread = Thread(target=self.render_covers)
            self.render_thread.daemon = True
            self.render_thread.start()

    def render_covers(self):
        q = self.delegate.render_queue
        while True:
            book_id = q.get()
            try:
                if book_id is None:
                    return
                if self.ignore_render_requests.is_set():
                    continue
                try:
                    self.render_cover(book_id)
                except:
                    import traceback
                    traceback.print_exc()
            finally:
                q.task_done()

    def render_cover(self, book_id):
        if self.ignore_render_requests.is_set():
            return
        dpr = self.device_pixel_ratio
        page_width = int(dpr * self.delegate.cover_size.width())
        page_height = int(dpr * self.delegate.cover_size.height())
        tcdata, timestamp = self.thumbnail_cache[book_id]
        use_cache = False
        if timestamp is None:
            # Not in cache
            has_cover, cdata, timestamp = self.model().db.new_api.cover_or_cache(book_id, 0)
        else:
            has_cover, cdata, timestamp = self.model().db.new_api.cover_or_cache(book_id, timestamp)
            if has_cover and cdata is None:
                # The cached cover is fresh
                cdata = tcdata
                use_cache = True

        if has_cover:
            p = QImage()
            p.loadFromData(cdata, CACHE_FORMAT if cdata is tcdata else 'JPEG')
            p.setDevicePixelRatio(dpr)
            if p.isNull() and cdata is tcdata:
                # Invalid image in cache
                self.thumbnail_cache.invalidate((book_id,))
                self.update_item.emit(book_id)
                return
            cdata = None if p.isNull() else p
            if not use_cache:  # cache is stale
                if cdata is not None:
                    width, height = p.width(), p.height()
                    scaled, nwidth, nheight = fit_image(
                        width, height, page_width, page_height)
                    if scaled:
                        if self.ignore_render_requests.is_set():
                            return
                        p = p.scaled(nwidth, nheight, Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
                        p.setDevicePixelRatio(dpr)
                    cdata = p
                # update cache
                if cdata is None:
                    self.thumbnail_cache.invalidate((book_id,))
                else:
                    try:
                        self.thumbnail_cache.insert(book_id, timestamp, image_to_data(cdata))
                    except EncodeError as err:
                        self.thumbnail_cache.invalidate((book_id,))
                        prints(err)
                    except Exception:
                        import traceback
                        traceback.print_exc()
        elif tcdata is not None:
            # Cover was removed, but it exists in cache, remove from cache
            self.thumbnail_cache.invalidate((book_id,))
        self.delegate.cover_cache.set(book_id, cdata)
        self.update_item.emit(book_id)

    def re_render(self, book_id):
        self.delegate.cover_cache.clear_staging()
        m = self.model()
        try:
            index = m.db.row(book_id)
        except (IndexError, ValueError, KeyError):
            return
        self.update(m.index(index, 0))

    def shutdown(self):
        self.ignore_render_requests.set()
        self.delegate.render_queue.put(None)
        self.thumbnail_cache.shutdown()

    def set_database(self, newdb, stage=0):
        if stage == 0:
            self.ignore_render_requests.set()
            try:
                for x in (self.delegate.cover_cache, self.thumbnail_cache):
                    self.model().db.new_api.remove_cover_cache(x)
            except AttributeError:
                pass  # db is None
            for x in (self.delegate.cover_cache, self.thumbnail_cache):
                newdb.new_api.add_cover_cache(x)
            try:
                # Use a timeout so that if, for some reason, the render thread
                # gets stuck, we dont deadlock, future covers wont get
                # rendered, but this is better than a deadlock
                join_with_timeout(self.delegate.render_queue)
            except RuntimeError:
                print('Cover rendering thread is stuck!')
            finally:
                self.ignore_render_requests.clear()
        else:
            self.delegate.cover_cache.clear()

    def select_rows(self, rows):
        sel = QItemSelection()
        sm = self.selectionModel()
        m = self.model()
        # Create a range based selector for each set of contiguous rows
        # as supplying selectors for each individual row causes very poor
        # performance if a large number of rows has to be selected.
        for k, g in itertools.groupby(enumerate(rows), lambda i_x:i_x[0]-i_x[1]):
            group = list(map(operator.itemgetter(1), g))
            sel.merge(QItemSelection(m.index(min(group), 0), m.index(max(group), 0)), sm.Select)
        sm.select(sel, sm.ClearAndSelect)

    def selectAll(self):
        # We re-implement this to ensure that only indexes from column 0 are
        # selected. The base class implementation selects all columns. This
        # causes problems with selection syncing, see
        # https://bugs.launchpad.net/bugs/1236348
        m = self.model()
        sm = self.selectionModel()
        sel = QItemSelection(m.index(0, 0), m.index(m.rowCount(QModelIndex())-1, 0))
        sm.select(sel, sm.ClearAndSelect)

    def set_current_row(self, row):
        sm = self.selectionModel()
        sm.setCurrentIndex(self.model().index(row, 0), sm.NoUpdate)

    def set_context_menu(self, menu):
        self.context_menu = menu

    def contextMenuEvent(self, event):
        if self.context_menu is None:
            return
        from calibre.gui2.main_window import clone_menu
        m = clone_menu(self.context_menu) if islinux else self.context_menu
        m.popup(event.globalPos())
        event.accept()

    def get_selected_ids(self):
        m = self.model()
        return [m.id(i) for i in self.selectionModel().selectedIndexes()]

    def restore_vpos(self, vpos):
        self.verticalScrollBar().setValue(vpos)

    def restore_hpos(self, hpos):
        pass

    def handle_mouse_press_event(self, ev):
        if QApplication.keyboardModifiers() & Qt.ShiftModifier:
            # Shift-Click in QListView is broken. It selects extra items in
            # various circumstances, for example, click on some item in the
            # middle of a row then click on an item in the next row, all items
            # in the first row will be selected instead of only items after the
            # middle item.
            index = self.indexAt(ev.pos())
            if not index.isValid():
                return
            ci = self.currentIndex()
            sm = self.selectionModel()
            sm.setCurrentIndex(index, sm.NoUpdate)
            if not ci.isValid():
                return
            if not sm.hasSelection():
                sm.select(index, sm.ClearAndSelect)
                return
            cr = ci.row()
            tgt = index.row()
            top = self.model().index(min(cr, tgt), 0)
            bottom = self.model().index(max(cr, tgt), 0)
            sm.select(QItemSelection(top, bottom), sm.Select)
        else:
            return QListView.mousePressEvent(self, ev)

    def indices_for_merge(self, resolved=True):
        return self.selectionModel().selectedIndexes()

    def number_of_columns(self):
        # Number of columns currently visible in the grid
        if self._ncols is None:
            dpr = self.device_pixel_ratio
            width = int(dpr * self.delegate.cover_size.width())
            height = int(dpr * self.delegate.cover_size.height())
            step = max(10, self.spacing())
            for y in range(step, 2 * height, step):
                for x in range(step, 2 * width, step):
                    i = self.indexAt(QPoint(x, y))
                    if i.isValid():
                        for x in range(self.viewport().width() - step, self.viewport().width() - width, -step):
                            j = self.indexAt(QPoint(x, y))
                            if j.isValid():
                                self._ncols = j.row() - i.row() + 1
                                return self._ncols
        return self._ncols

    def keyPressEvent(self, ev):
        if handle_enter_press(self, ev, self.start_view_animation, False):
            return
        k = ev.key()
        if ev.modifiers() & Qt.ShiftModifier and k in (Qt.Key_Left, Qt.Key_Right, Qt.Key_Up, Qt.Key_Down):
            ci = self.currentIndex()
            if not ci.isValid():
                return
            c = ci.row()
            ncols = self.number_of_columns() or 1
            delta = {Qt.Key_Left: -1, Qt.Key_Right: 1, Qt.Key_Up: -ncols, Qt.Key_Down: ncols}[k]
            n = max(0, min(c + delta, self.model().rowCount(None) - 1))
            if n == c:
                return
            sm = self.selectionModel()
            rows = {i.row() for i in sm.selectedIndexes()}
            if rows:
                mi, ma = min(rows), max(rows)
                end = mi if c == ma else ma if c == mi else c
            else:
                end = c
            top = self.model().index(min(n, end), 0)
            bottom = self.model().index(max(n, end), 0)
            sm.select(QItemSelection(top, bottom), sm.ClearAndSelect)
            sm.setCurrentIndex(self.model().index(n, 0), sm.NoUpdate)
        else:
            return QListView.keyPressEvent(self, ev)

    @property
    def current_book(self):
        ci = self.currentIndex()
        if ci.isValid():
            try:
                return self.model().db.data.index_to_id(ci.row())
            except (IndexError, ValueError, KeyError, TypeError, AttributeError):
                pass

    def current_book_state(self):
        return self.current_book

    def restore_current_book_state(self, state):
        book_id = state
        self.setFocus(Qt.OtherFocusReason)
        try:
            row = self.model().db.data.id_to_index(book_id)
        except (IndexError, ValueError, KeyError, TypeError, AttributeError):
            return
        self.set_current_row(row)
        self.select_rows((row,))
        self.scrollTo(self.model().index(row, 0), self.PositionAtCenter)

    def marked_changed(self, old_marked, current_marked):
        changed = old_marked | current_marked
        m = self.model()
        for book_id in changed:
            try:
                self.update(m.index(m.db.data.id_to_index(book_id), 0))
            except ValueError:
                pass

    def moveCursor(self, action, modifiers):
        index = QListView.moveCursor(self, action, modifiers)
        if action in (QListView.MoveLeft, QListView.MoveRight) and index.isValid():
            ci = self.currentIndex()
            if ci.isValid() and index.row() == ci.row():
                nr = index.row() + (1 if action == QListView.MoveRight else -1)
                if 0 <= nr < self.model().rowCount(QModelIndex()):
                    index = self.model().index(nr, 0)
        return index

    def selectionCommand(self, index, event):
        if event and event.type() == event.KeyPress and event.key() in (Qt.Key_Home, Qt.Key_End) and event.modifiers() & Qt.CTRL:
            return QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows
        return super(GridView, self).selectionCommand(index, event)

    def wheelEvent(self, ev):
        if ev.phase() not in (Qt.ScrollUpdate, 0):
            return
        number_of_pixels = ev.pixelDelta()
        number_of_degrees = ev.angleDelta() / 8.0
        b = self.verticalScrollBar()
        if number_of_pixels.isNull() or islinux:
            # pixelDelta() is broken on linux with wheel mice
            dy = number_of_degrees.y() / 15.0
            # Scroll by approximately half a row
            dy = int(math.ceil((dy) * b.singleStep() / 2.0))
        else:
            dy = number_of_pixels.y()
        if abs(dy) > 0:
            b.setValue(b.value() - dy)

    def paintEvent(self, ev):
        dpr = self.device_pixel_ratio
        page_width = int(dpr * self.delegate.cover_size.width())
        page_height = int(dpr * self.delegate.cover_size.height())
        size_changed = self.thumbnail_cache.set_thumbnail_size(page_width, page_height)
        if size_changed:
            self.delegate.cover_cache.clear()

        return super(GridView, self).paintEvent(ev)
Beispiel #47
0
class LiveCSS(QWidget):

    goto_declaration = pyqtSignal(object)

    def __init__(self, preview, parent=None):
        QWidget.__init__(self, parent)
        self.preview = preview
        self.preview_is_refreshing = False
        self.refresh_needed = False
        preview.refresh_starting.connect(self.preview_refresh_starting)
        preview.refreshed.connect(self.preview_refreshed)
        self.apply_theme()
        self.setAutoFillBackground(True)
        self.update_timer = QTimer(self)
        self.update_timer.timeout.connect(self.update_data)
        self.update_timer.setSingleShot(True)
        self.update_timer.setInterval(500)
        self.now_showing = (None, None, None)

        self.stack = s = QStackedLayout(self)
        self.setLayout(s)

        self.clear_label = la = QLabel(
            '<h3>' + _('No style information found') + '</h3><p>' +
            _('Move the cursor inside a HTML tag to see what styles'
              ' apply to that tag.'))
        la.setWordWrap(True)
        la.setAlignment(Qt.AlignTop | Qt.AlignLeft)
        s.addWidget(la)

        self.box = box = Box(self)
        box.hyperlink_activated.connect(self.goto_declaration,
                                        type=Qt.QueuedConnection)
        self.scroll = sc = QScrollArea(self)
        sc.setWidget(box)
        sc.setWidgetResizable(True)
        s.addWidget(sc)

    def preview_refresh_starting(self):
        self.preview_is_refreshing = True

    def preview_refreshed(self):
        self.preview_is_refreshing = False
        # We must let the event loop run otherwise the webview will return
        # stale data in read_data()
        self.refresh_needed = True
        self.start_update_timer()

    def apply_theme(self):
        f = self.font()
        f.setFamily(tprefs['editor_font_family'] or default_font_family())
        f.setPointSize(tprefs['editor_font_size'])
        self.setFont(f)
        theme = get_theme(tprefs['editor_theme'])
        pal = self.palette()
        pal.setColor(pal.Window, theme_color(theme, 'Normal', 'bg'))
        pal.setColor(pal.WindowText, theme_color(theme, 'Normal', 'fg'))
        pal.setColor(pal.AlternateBase,
                     theme_color(theme, 'HighlightRegion', 'bg'))
        pal.setColor(pal.Link, theme_color(theme, 'Link', 'fg'))
        pal.setColor(pal.LinkVisited, theme_color(theme, 'Keyword', 'fg'))
        self.setPalette(pal)
        if hasattr(self, 'box'):
            self.box.relayout()
        self.update()

    def clear(self):
        self.stack.setCurrentIndex(0)

    def show_data(self, editor_name, sourceline, tags):
        if self.preview_is_refreshing:
            return
        if sourceline is None:
            self.clear()
        else:
            data = self.read_data(sourceline, tags)
            if data is None or len(data['computed_css']) < 1:
                if editor_name == self.current_name and (
                        editor_name, sourceline, tags) == self.now_showing:
                    # Try again in a little while in case there was a transient
                    # error in the web view
                    self.start_update_timer()
                    return
                if self.now_showing == (
                        None, None,
                        None) or self.now_showing[0] != self.current_name:
                    self.clear()
                    return
                # Try to refresh the data for the currently shown tag instead
                # of clearing
                editor_name, sourceline, tags = self.now_showing
                data = self.read_data(sourceline, tags)
                if data is None or len(data['computed_css']) < 1:
                    self.clear()
                    return
            self.now_showing = (editor_name, sourceline, tags)
            data['html_name'] = editor_name
            self.box.show_data(data)
            self.refresh_needed = False
            self.stack.setCurrentIndex(1)

    def read_data(self, sourceline, tags):
        mf = self.preview.view.page().mainFrame()
        tags = [x.lower() for x in tags]
        result = str(
            mf.evaluateJavaScript(
                'window.calibre_preview_integration.live_css(%s, %s)' %
                (json.dumps(sourceline), json.dumps(tags))) or '')
        try:
            result = json.loads(result)
        except ValueError:
            result = None
        if result is not None:
            maximum_specificities = {}
            for node in result['nodes']:
                is_ancestor = node['is_ancestor']
                for rule in node['css']:
                    self.process_rule(rule, is_ancestor, maximum_specificities)
            for node in result['nodes']:
                for rule in node['css']:
                    for prop in rule['properties']:
                        if prop.specificity < maximum_specificities[prop.name]:
                            prop.is_overriden = True

        return result

    def process_rule(self, rule, is_ancestor, maximum_specificities):
        selector = rule['selector']
        sheet_index = rule['sheet_index']
        rule_address = rule['rule_address'] or ()
        if selector is not None:
            try:
                specificity = [0] + list(parse(selector)[0].specificity())
            except (AttributeError, TypeError, SelectorError):
                specificity = [0, 0, 0, 0]
        else:  # style attribute
            specificity = [1, 0, 0, 0]
        specificity.extend((sheet_index, tuple(rule_address)))
        ancestor_specificity = 0 if is_ancestor else 1
        properties = []
        for prop in rule['properties']:
            important = 1 if prop[-1] == 'important' else 0
            p = Property(prop,
                         [ancestor_specificity] + [important] + specificity)
            properties.append(p)
            if p.specificity > maximum_specificities.get(
                    p.name, (0, 0, 0, 0, 0, 0)):
                maximum_specificities[p.name] = p.specificity
        rule['properties'] = properties

        href = rule['href']
        if hasattr(href, 'startswith') and href.startswith(
                '%s://%s' % (FAKE_PROTOCOL, FAKE_HOST)):
            qurl = QUrl(href)
            name = qurl.path()[1:]
            if name:
                rule['href'] = name

    @property
    def current_name(self):
        return self.preview.current_name

    @property
    def is_visible(self):
        return self.isVisible()

    def showEvent(self, ev):
        self.update_timer.start()
        actions['auto-reload-preview'].setEnabled(True)
        return QWidget.showEvent(self, ev)

    def sync_to_editor(self):
        self.update_data()

    def update_data(self):
        if not self.is_visible or self.preview_is_refreshing:
            return
        editor_name = self.current_name
        ed = editors.get(editor_name, None)
        if self.update_timer.isActive() or (ed is None
                                            and editor_name is not None):
            return QTimer.singleShot(100, self.update_data)
        if ed is not None:
            sourceline, tags = ed.current_tag(for_position_sync=False)
            if self.refresh_needed or self.now_showing != (editor_name,
                                                           sourceline, tags):
                self.show_data(editor_name, sourceline, tags)

    def start_update_timer(self):
        if self.is_visible:
            self.update_timer.start()

    def stop_update_timer(self):
        self.update_timer.stop()

    def navigate_to_declaration(self, data, editor):
        if data['type'] == 'inline':
            sourceline, tags = data['sourceline_address']
            editor.goto_sourceline(sourceline, tags, attribute='style')
        elif data['type'] == 'sheet':
            editor.goto_css_rule(data['rule_address'])
        elif data['type'] == 'elem':
            editor.goto_css_rule(data['rule_address'],
                                 sourceline_address=data['sourceline_address'])
Beispiel #48
0
class Preview(QWidget):

    sync_requested = pyqtSignal(object, object)
    split_requested = pyqtSignal(object, object, object)
    split_start_requested = pyqtSignal()
    link_clicked = pyqtSignal(object, object)
    refresh_starting = pyqtSignal()
    refreshed = pyqtSignal()

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.l = l = QVBoxLayout()
        self.setLayout(l)
        l.setContentsMargins(0, 0, 0, 0)
        self.view = WebView(self)
        self.view.page().sync_requested.connect(self.request_sync)
        self.view.page().split_requested.connect(self.request_split)
        self.view.page().loadFinished.connect(self.load_finished)
        self.inspector = self.view.inspector
        self.inspector.setPage(self.view.page())
        l.addWidget(self.view)
        self.bar = QToolBar(self)
        l.addWidget(self.bar)

        ac = actions['auto-reload-preview']
        ac.setCheckable(True)
        ac.setChecked(True)
        ac.toggled.connect(self.auto_reload_toggled)
        self.auto_reload_toggled(ac.isChecked())
        self.bar.addAction(ac)

        ac = actions['sync-preview-to-editor']
        ac.setCheckable(True)
        ac.setChecked(True)
        ac.toggled.connect(self.sync_toggled)
        self.sync_toggled(ac.isChecked())
        self.bar.addAction(ac)

        self.bar.addSeparator()

        ac = actions['split-in-preview']
        ac.setCheckable(True)
        ac.setChecked(False)
        ac.toggled.connect(self.split_toggled)
        self.split_toggled(ac.isChecked())
        self.bar.addAction(ac)

        ac = actions['reload-preview']
        ac.triggered.connect(self.refresh)
        self.bar.addAction(ac)

        actions['preview-dock'].toggled.connect(self.visibility_changed)

        self.current_name = None
        self.last_sync_request = None
        self.refresh_timer = QTimer(self)
        self.refresh_timer.timeout.connect(self.refresh)
        parse_worker.start()
        self.current_sync_request = None

        self.search = HistoryLineEdit2(self)
        self.search.initialize('tweak_book_preview_search')
        self.search.setPlaceholderText(_('Search in preview'))
        self.search.returnPressed.connect(partial(self.find, 'next'))
        self.bar.addSeparator()
        self.bar.addWidget(self.search)
        for d in ('next', 'prev'):
            ac = actions['find-%s-preview' % d]
            ac.triggered.connect(partial(self.find, d))
            self.bar.addAction(ac)

    def find(self, direction):
        text = unicode(self.search.text())
        self.view.findText(text, QWebPage.FindWrapsAroundDocument | (
            QWebPage.FindBackward if direction == 'prev' else QWebPage.FindFlags(0)))

    def request_sync(self, tagname, href, lnum):
        if self.current_name:
            c = current_container()
            if tagname == 'a' and href:
                if href and href.startswith('#'):
                    name = self.current_name
                else:
                    name = c.href_to_name(href, self.current_name) if href else None
                if name == self.current_name:
                    return self.view.page().go_to_anchor(urlparse(href).fragment, lnum)
                if name and c.exists(name) and c.mime_map[name] in OEB_DOCS:
                    return self.link_clicked.emit(name, urlparse(href).fragment or TOP)
            self.sync_requested.emit(self.current_name, lnum)

    def request_split(self, loc, totals):
        if self.current_name:
            self.split_requested.emit(self.current_name, loc, totals)

    def sync_to_editor(self, name, sourceline_address):
        self.current_sync_request = (name, sourceline_address)
        QTimer.singleShot(100, self._sync_to_editor)

    def _sync_to_editor(self):
        if not actions['sync-preview-to-editor'].isChecked():
            return
        try:
            if self.refresh_timer.isActive() or self.current_sync_request[0] != self.current_name:
                return QTimer.singleShot(100, self._sync_to_editor)
        except TypeError:
            return  # Happens if current_sync_request is None
        sourceline_address = self.current_sync_request[1]
        self.current_sync_request = None
        self.view.page().go_to_sourceline_address(sourceline_address)

    def report_worker_launch_error(self):
        if parse_worker.launch_error is not None:
            tb, parse_worker.launch_error = parse_worker.launch_error, None
            error_dialog(self, _('Failed to launch worker'), _(
                'Failed to launch the worker process used for rendering the preview'), det_msg=tb, show=True)

    def show(self, name):
        if name != self.current_name:
            self.refresh_timer.stop()
            self.current_name = name
            self.report_worker_launch_error()
            parse_worker.add_request(name)
            self.view.setUrl(QUrl.fromLocalFile(current_container().name_to_abspath(name)))
            return True

    def refresh(self):
        if self.current_name:
            self.refresh_timer.stop()
            # This will check if the current html has changed in its editor,
            # and re-parse it if so
            self.report_worker_launch_error()
            parse_worker.add_request(self.current_name)
            # Tell webkit to reload all html and associated resources
            current_url = QUrl.fromLocalFile(current_container().name_to_abspath(self.current_name))
            self.refresh_starting.emit()
            if current_url != self.view.url():
                # The container was changed
                self.view.setUrl(current_url)
            else:
                self.view.refresh()
            self.refreshed.emit()

    def clear(self):
        self.view.clear()
        self.current_name = None

    @property
    def current_root(self):
        return self.view.page().current_root

    @property
    def is_visible(self):
        return actions['preview-dock'].isChecked()

    @property
    def live_css_is_visible(self):
        try:
            return actions['live-css-dock'].isChecked()
        except KeyError:
            return False

    def start_refresh_timer(self):
        if self.live_css_is_visible or (self.is_visible and actions['auto-reload-preview'].isChecked()):
            self.refresh_timer.start(tprefs['preview_refresh_time'] * 1000)

    def stop_refresh_timer(self):
        self.refresh_timer.stop()

    def auto_reload_toggled(self, checked):
        if self.live_css_is_visible and not actions['auto-reload-preview'].isChecked():
            actions['auto-reload-preview'].setChecked(True)
            error_dialog(self, _('Cannot disable'), _(
                'Auto reloading of the preview panel cannot be disabled while the'
                ' Live CSS panel is open.'), show=True)
        actions['auto-reload-preview'].setToolTip(_(
            'Auto reload preview when text changes in editor') if not checked else _(
                'Disable auto reload of preview'))

    def sync_toggled(self, checked):
        actions['sync-preview-to-editor'].setToolTip(_(
            'Disable syncing of preview position to editor position') if checked else _(
                'Enable syncing of preview position to editor position'))

    def visibility_changed(self, is_visible):
        if is_visible:
            self.refresh()

    def split_toggled(self, checked):
        actions['split-in-preview'].setToolTip(textwrap.fill(_(
            'Abort file split') if checked else _(
                'Split this file at a specified location.\n\nAfter clicking this button, click'
                ' inside the preview panel above at the location you want the file to be split.')))
        if checked:
            self.split_start_requested.emit()
        else:
            self.view.page().split_mode(False)

    def do_start_split(self):
        self.view.page().split_mode(True)

    def stop_split(self):
        actions['split-in-preview'].setChecked(False)

    def load_finished(self, ok):
        if actions['split-in-preview'].isChecked():
            if ok:
                self.do_start_split()
            else:
                self.stop_split()

    def apply_settings(self):
        s = self.view.page().settings()
        s.setFontSize(s.DefaultFontSize, tprefs['preview_base_font_size'])
        s.setFontSize(s.DefaultFixedFontSize, tprefs['preview_mono_font_size'])
        s.setFontSize(s.MinimumLogicalFontSize, tprefs['preview_minimum_font_size'])
        s.setFontSize(s.MinimumFontSize, tprefs['preview_minimum_font_size'])
        sf, ssf, mf = tprefs['preview_serif_family'], tprefs['preview_sans_family'], tprefs['preview_mono_family']
        s.setFontFamily(s.StandardFont, {'serif':sf, 'sans':ssf, 'mono':mf, None:sf}[tprefs['preview_standard_font_family']])
        s.setFontFamily(s.SerifFont, sf)
        s.setFontFamily(s.SansSerifFont, ssf)
        s.setFontFamily(s.FixedFont, mf)
Beispiel #49
0
class DndWidget(QWidget):
    ANIMATION_SPEED = (1.0 / 8)
    ANIMATION_INITIAL_DELAY = 200  # ms

    filesDropped = pyqtSignal('QVariantList')

    def __init__(self, parent=None):
        super().__init__(parent)

        # self.setBackgroundRole(QPalette.Base)
        # self.setAutoFillBackground(True)

        self._mousePos = QPoint()
        self.setAcceptDrops(True)
        self._dragInProgress = False

        self._updateTimer = QTimer(self)
        self._updateTimer.setInterval(100)  # 10 Hz
        self._updateTimer.timeout.connect(self.update)

        self._animationLoop = 0
        self._lastFrameTime = 0
        self._animationStartTime = 0
        self._easingCurve = QEasingCurve(QEasingCurve.OutInCubic)

    @pyqtSlot()
    def startAnimation(self):
        self._updateTimer.start()
        self._animationLoop = 0
        self._animationStartTime = QDateTime.currentMSecsSinceEpoch()

    @pyqtSlot()
    def stopAnimation(self):
        self._updateTimer.stop()

    def dragEnterEvent(self, e: QDragEnterEvent):
        logger.debug(
            f"format: {e.mimeData().formats()}, hasImage: {e.mimeData().hasImage()}"
        )

        ignoreAction = False
        if not e.mimeData().hasUrls():
            ignoreAction = True
        for url in e.mimeData().urls():
            if not url.isLocalFile():
                ignoreAction = True
        if ignoreAction:
            e.setDropAction(Qt.IgnoreAction)
            e.ignore()
        else:
            e.setDropAction(Qt.CopyAction)
            e.accept()
            self._dragInProgress = True
            self.startAnimation()

    def dragMoveEvent(self, e: QDragMoveEvent):
        e.setDropAction(Qt.CopyAction)
        e.accept()
        if not self.hasFocus():
            self.update()
        self._mousePos = e.pos()
        self.startAnimation()

    def dragLeaveEvent(self, e: QDragLeaveEvent):
        if not self._dragInProgress:
            super().dragLeaveEvent(e)
        e.accept()
        logger.debug("")
        self.stopAnimation()
        self._dragInProgress = False
        self.update()

    def dropEvent(self, e: QDropEvent):
        if e.dropAction() != Qt.CopyAction:
            e.ignore()
        e.acceptProposedAction()
        self.stopAnimation()
        files = [url.toLocalFile() for url in e.mimeData().urls()]
        self._dragInProgress = False
        self.update()
        self.filesDropped.emit(files)

    def _drawPulses(self, painter: QPainter):
        curTime = QDateTime.currentMSecsSinceEpoch()
        if curTime - self._animationStartTime <= DndWidget.ANIMATION_INITIAL_DELAY:
            return
        p = self._mousePos
        s = self.size()
        MAX_CIRCLE_RADIUS = max(p.x(),
                                s.width() - p.x(), p.y(),
                                s.height() - p.y())

        outerRad = float(curTime -
                         self._lastFrameTime) * (self._animationLoop + 1)
        outerRad *= DndWidget.ANIMATION_SPEED
        outerRadRatio = float(outerRad) / MAX_CIRCLE_RADIUS
        outerRad *= self._easingCurve.valueForProgress(outerRadRatio)

        INNER_RADIUS_DIFF = 20

        innerRad = outerRad - INNER_RADIUS_DIFF

        logger.debug(f"outerRad={outerRad:.2f}, {(outerRadRatio * 100):.2f}%, "
                     f"curTime={curTime}, lastFrameTime={self._lastFrameTime}")

        painter.setPen(QPen(QBrush(Qt.red), 3))
        painter.drawEllipse(p, outerRad, outerRad)

        if innerRad > 0:
            painter.setPen(QPen(QBrush(Qt.blue), 3))
            painter.drawEllipse(QPoint(p.x(), p.y()), innerRad, innerRad)

        if outerRad >= MAX_CIRCLE_RADIUS:
            self._animationLoop = 0

    def _drawText(self, painter: QPainter, text: str):
        widgetRect: QRect = self.geometry()
        font: QFont = QFontDatabase.systemFont(QFontDatabase.GeneralFont)
        font.setPointSize(18)
        painter.setFont(font)

        textRect: QRect = painter.boundingRect(widgetRect, Qt.AlignCenter,
                                               text)
        # DEBUG: ...
        # painter.setPen(QPen(QBrush(Qt.black), 1))
        # painter.drawRect(textRect)

        palette: QPalette = QApplication.instance().palette()
        painter.setPen(QPen(palette.color(QPalette.Active, QPalette.Dark), 1))
        textRect.moveTo(widgetRect.width() / 2 - textRect.width() / 2,
                        widgetRect.height() / 2 - textRect.height() / 2)
        painter.drawText(textRect, Qt.AlignCenter, text)

    def paintEvent(self, event: QPaintEvent):
        super().paintEvent(event)

        painter = QPainter(self)
        # painter.fillRect(QRect(QPoint(0, 0), self.size()), Qt.yellow)  # DEBUG: Remove
        painter.setRenderHint(QPainter.Antialiasing)

        # TODO: Show number of files.
        if self._dragInProgress:
            self._drawText(painter, self.tr("Drop Movies"))
        else:
            self._drawText(painter, self.tr("Drag Movies Here"))
            return

        self._drawPulses(painter)

        self._lastFrameTime = QDateTime.currentMSecsSinceEpoch()
        self._animationLoop += 1
Beispiel #50
0
class GridView(QListView):

    update_item = pyqtSignal(object)
    files_dropped = pyqtSignal(object)
    books_dropped = pyqtSignal(object)

    def __init__(self, parent):
        QListView.__init__(self, parent)
        self._ncols = None
        self.gesture_manager = GestureManager(self)
        setup_dnd_interface(self)
        self.setUniformItemSizes(True)
        self.setWrapping(True)
        self.setFlow(self.LeftToRight)
        # We cannot set layout mode to batched, because that breaks
        # restore_vpos()
        # self.setLayoutMode(self.Batched)
        self.setResizeMode(self.Adjust)
        self.setSelectionMode(self.ExtendedSelection)
        self.setVerticalScrollMode(self.ScrollPerPixel)
        self.delegate = CoverDelegate(self)
        self.delegate.animation.valueChanged.connect(self.animation_value_changed)
        self.delegate.animation.finished.connect(self.animation_done)
        self.setItemDelegate(self.delegate)
        self.setSpacing(self.delegate.spacing)
        self.set_color()
        self.ignore_render_requests = Event()
        dpr = self.device_pixel_ratio
        self.thumbnail_cache = ThumbnailCache(max_size=gprefs['cover_grid_disk_cache_size'],
            thumbnail_size=(int(dpr * self.delegate.cover_size.width()), int(dpr * self.delegate.cover_size.height())))
        self.render_thread = None
        self.update_item.connect(self.re_render, type=Qt.QueuedConnection)
        self.doubleClicked.connect(self.double_clicked)
        self.setCursor(Qt.PointingHandCursor)
        self.gui = parent
        self.context_menu = None
        self.update_timer = QTimer(self)
        self.update_timer.setInterval(200)
        self.update_timer.timeout.connect(self.update_viewport)
        self.update_timer.setSingleShot(True)
        self.resize_timer = t = QTimer(self)
        t.setInterval(200), t.setSingleShot(True)
        t.timeout.connect(self.update_memory_cover_cache_size)

    def viewportEvent(self, ev):
        try:
            ret = self.gesture_manager.handle_event(ev)
        except AttributeError:
            ret = None
        if ret is not None:
            return ret
        return QListView.viewportEvent(self, ev)

    @property
    def device_pixel_ratio(self):
        try:
            return self.devicePixelRatioF()
        except AttributeError:
            return self.devicePixelRatio()

    @property
    def first_visible_row(self):
        geom = self.viewport().geometry()
        for y in range(geom.top(), (self.spacing()*2) + geom.top(), 5):
            for x in range(geom.left(), (self.spacing()*2) + geom.left(), 5):
                ans = self.indexAt(QPoint(x, y)).row()
                if ans > -1:
                    return ans

    @property
    def last_visible_row(self):
        geom = self.viewport().geometry()
        for y in range(geom.bottom(), geom.bottom() - 2 * self.spacing(), -5):
            for x in range(geom.left(), (self.spacing()*2) + geom.left(), 5):
                ans = self.indexAt(QPoint(x, y)).row()
                if ans > -1:
                    item_width = self.delegate.item_size.width() + 2*self.spacing()
                    return ans + (geom.width() // item_width)

    def update_viewport(self):
        self.ignore_render_requests.clear()
        self.update_timer.stop()
        m = self.model()
        for r in range(self.first_visible_row or 0, self.last_visible_row or (m.count() - 1)):
            self.update(m.index(r, 0))

    def start_view_animation(self, index):
        d = self.delegate
        if d.animating is None and not config['disable_animations']:
            d.animating = index
            d.animation.start()

    def double_clicked(self, index):
        self.start_view_animation(index)
        if tweaks['doubleclick_on_library_view'] == 'open_viewer':
            self.gui.iactions['View'].view_triggered(index)
        elif tweaks['doubleclick_on_library_view'] in {'edit_metadata', 'edit_cell'}:
            self.gui.iactions['Edit Metadata'].edit_metadata(False, False)

    def animation_value_changed(self, value):
        if self.delegate.animating is not None:
            self.update(self.delegate.animating)

    def animation_done(self):
        if self.delegate.animating is not None:
            idx = self.delegate.animating
            self.delegate.animating = None
            self.update(idx)

    def set_color(self):
        r, g, b = gprefs['cover_grid_color']
        pal = QPalette()
        col = QColor(r, g, b)
        pal.setColor(pal.Base, col)
        tex = gprefs['cover_grid_texture']
        if tex:
            from calibre.gui2.preferences.texture_chooser import texture_path
            path = texture_path(tex)
            if path:
                pm = QPixmap(path)
                if not pm.isNull():
                    val = pm.scaled(1, 1).toImage().pixel(0, 0)
                    r, g, b = qRed(val), qGreen(val), qBlue(val)
                    pal.setBrush(pal.Base, QBrush(pm))
        dark = (r + g + b)/3.0 < 128
        pal.setColor(pal.Text, QColor(Qt.white if dark else Qt.black))
        self.setPalette(pal)
        self.delegate.highlight_color = pal.color(pal.Text)

    def refresh_settings(self):
        size_changed = (
            gprefs['cover_grid_width'] != self.delegate.original_width or gprefs['cover_grid_height'] != self.delegate.original_height
        )
        if (size_changed or gprefs[
            'cover_grid_show_title'] != self.delegate.original_show_title or gprefs[
                'show_emblems'] != self.delegate.original_show_emblems or gprefs[
                    'emblem_size'] != self.delegate.orginal_emblem_size or gprefs[
                        'emblem_position'] != self.delegate.orginal_emblem_position):
            self.delegate.set_dimensions()
            self.setSpacing(self.delegate.spacing)
            if size_changed:
                self.delegate.cover_cache.clear()
        if gprefs['cover_grid_spacing'] != self.delegate.original_spacing:
            self.delegate.calculate_spacing()
            self.setSpacing(self.delegate.spacing)
        self.set_color()
        self.set_thumbnail_cache_image_size()
        cs = gprefs['cover_grid_disk_cache_size']
        if (cs*(1024**2)) != self.thumbnail_cache.max_size:
            self.thumbnail_cache.set_size(cs)
        self.update_memory_cover_cache_size()

    def set_thumbnail_cache_image_size(self):
        dpr = self.device_pixel_ratio
        self.thumbnail_cache.set_thumbnail_size(
            int(dpr * self.delegate.cover_size.width()), int(dpr*self.delegate.cover_size.height()))

    def resizeEvent(self, ev):
        self._ncols = None
        self.resize_timer.start()
        return QListView.resizeEvent(self, ev)

    def update_memory_cover_cache_size(self):
        try:
            sz = self.delegate.item_size
        except AttributeError:
            return
        rows, cols = self.width() // sz.width(), self.height() // sz.height()
        num = (rows + 1) * (cols + 1)
        limit = max(100, num * max(2, gprefs['cover_grid_cache_size_multiple']))
        if limit != self.delegate.cover_cache.limit:
            self.delegate.cover_cache.set_limit(limit)

    def shown(self):
        self.update_memory_cover_cache_size()
        if self.render_thread is None:
            self.thumbnail_cache.set_database(self.gui.current_db)
            self.render_thread = Thread(target=self.render_covers)
            self.render_thread.daemon = True
            self.render_thread.start()

    def render_covers(self):
        q = self.delegate.render_queue
        while True:
            book_id = q.get()
            try:
                if book_id is None:
                    return
                if self.ignore_render_requests.is_set():
                    continue
                try:
                    self.render_cover(book_id)
                except:
                    import traceback
                    traceback.print_exc()
            finally:
                q.task_done()

    def render_cover(self, book_id):
        if self.ignore_render_requests.is_set():
            return
        dpr = self.device_pixel_ratio
        page_width = int(dpr * self.delegate.cover_size.width())
        page_height = int(dpr * self.delegate.cover_size.height())
        tcdata, timestamp = self.thumbnail_cache[book_id]
        use_cache = False
        if timestamp is None:
            # Not in cache
            has_cover, cdata, timestamp = self.model().db.new_api.cover_or_cache(book_id, 0)
        else:
            has_cover, cdata, timestamp = self.model().db.new_api.cover_or_cache(book_id, timestamp)
            if has_cover and cdata is None:
                # The cached cover is fresh
                cdata = tcdata
                use_cache = True

        if has_cover:
            p = QImage()
            p.loadFromData(cdata, CACHE_FORMAT if cdata is tcdata else 'JPEG')
            p.setDevicePixelRatio(dpr)
            if p.isNull() and cdata is tcdata:
                # Invalid image in cache
                self.thumbnail_cache.invalidate((book_id,))
                self.update_item.emit(book_id)
                return
            cdata = None if p.isNull() else p
            if not use_cache:  # cache is stale
                if cdata is not None:
                    width, height = p.width(), p.height()
                    scaled, nwidth, nheight = fit_image(
                        width, height, page_width, page_height)
                    if scaled:
                        if self.ignore_render_requests.is_set():
                            return
                        p = p.scaled(nwidth, nheight, Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
                        p.setDevicePixelRatio(dpr)
                    cdata = p
                # update cache
                if cdata is None:
                    self.thumbnail_cache.invalidate((book_id,))
                else:
                    try:
                        self.thumbnail_cache.insert(book_id, timestamp, image_to_data(cdata))
                    except EncodeError as err:
                        self.thumbnail_cache.invalidate((book_id,))
                        prints(err)
                    except Exception:
                        import traceback
                        traceback.print_exc()
        elif tcdata is not None:
            # Cover was removed, but it exists in cache, remove from cache
            self.thumbnail_cache.invalidate((book_id,))
        self.delegate.cover_cache.set(book_id, cdata)
        self.update_item.emit(book_id)

    def re_render(self, book_id):
        self.delegate.cover_cache.clear_staging()
        m = self.model()
        try:
            index = m.db.row(book_id)
        except (IndexError, ValueError, KeyError):
            return
        self.update(m.index(index, 0))

    def shutdown(self):
        self.ignore_render_requests.set()
        self.delegate.render_queue.put(None)
        self.thumbnail_cache.shutdown()

    def set_database(self, newdb, stage=0):
        if stage == 0:
            self.ignore_render_requests.set()
            try:
                for x in (self.delegate.cover_cache, self.thumbnail_cache):
                    self.model().db.new_api.remove_cover_cache(x)
            except AttributeError:
                pass  # db is None
            for x in (self.delegate.cover_cache, self.thumbnail_cache):
                newdb.new_api.add_cover_cache(x)
            try:
                # Use a timeout so that if, for some reason, the render thread
                # gets stuck, we dont deadlock, future covers wont get
                # rendered, but this is better than a deadlock
                join_with_timeout(self.delegate.render_queue)
            except RuntimeError:
                print('Cover rendering thread is stuck!')
            finally:
                self.ignore_render_requests.clear()
        else:
            self.delegate.cover_cache.clear()

    def select_rows(self, rows):
        sel = QItemSelection()
        sm = self.selectionModel()
        m = self.model()
        # Create a range based selector for each set of contiguous rows
        # as supplying selectors for each individual row causes very poor
        # performance if a large number of rows has to be selected.
        for k, g in itertools.groupby(enumerate(rows), lambda i_x:i_x[0]-i_x[1]):
            group = list(map(operator.itemgetter(1), g))
            sel.merge(QItemSelection(m.index(min(group), 0), m.index(max(group), 0)), sm.Select)
        sm.select(sel, sm.ClearAndSelect)

    def selectAll(self):
        # We re-implement this to ensure that only indexes from column 0 are
        # selected. The base class implementation selects all columns. This
        # causes problems with selection syncing, see
        # https://bugs.launchpad.net/bugs/1236348
        m = self.model()
        sm = self.selectionModel()
        sel = QItemSelection(m.index(0, 0), m.index(m.rowCount(QModelIndex())-1, 0))
        sm.select(sel, sm.ClearAndSelect)

    def set_current_row(self, row):
        sm = self.selectionModel()
        sm.setCurrentIndex(self.model().index(row, 0), sm.NoUpdate)

    def set_context_menu(self, menu):
        self.context_menu = menu

    def contextMenuEvent(self, event):
        if self.context_menu is None:
            return
        from calibre.gui2.main_window import clone_menu
        m = clone_menu(self.context_menu) if islinux else self.context_menu
        m.popup(event.globalPos())
        event.accept()

    def get_selected_ids(self):
        m = self.model()
        return [m.id(i) for i in self.selectionModel().selectedIndexes()]

    def restore_vpos(self, vpos):
        self.verticalScrollBar().setValue(vpos)

    def restore_hpos(self, hpos):
        pass

    def handle_mouse_press_event(self, ev):
        if QApplication.keyboardModifiers() & Qt.ShiftModifier:
            # Shift-Click in QListView is broken. It selects extra items in
            # various circumstances, for example, click on some item in the
            # middle of a row then click on an item in the next row, all items
            # in the first row will be selected instead of only items after the
            # middle item.
            index = self.indexAt(ev.pos())
            if not index.isValid():
                return
            ci = self.currentIndex()
            sm = self.selectionModel()
            sm.setCurrentIndex(index, sm.NoUpdate)
            if not ci.isValid():
                return
            if not sm.hasSelection():
                sm.select(index, sm.ClearAndSelect)
                return
            cr = ci.row()
            tgt = index.row()
            top = self.model().index(min(cr, tgt), 0)
            bottom = self.model().index(max(cr, tgt), 0)
            sm.select(QItemSelection(top, bottom), sm.Select)
        else:
            return QListView.mousePressEvent(self, ev)

    def indices_for_merge(self, resolved=True):
        return self.selectionModel().selectedIndexes()

    def number_of_columns(self):
        # Number of columns currently visible in the grid
        if self._ncols is None:
            step = max(10, self.spacing())
            for y in range(step, 500, step):
                for x in range(step, 500, step):
                    i = self.indexAt(QPoint(x, y))
                    if i.isValid():
                        for x in range(self.viewport().width() - step, self.viewport().width() - 300, -step):
                            j = self.indexAt(QPoint(x, y))
                            if j.isValid():
                                self._ncols = j.row() - i.row() + 1
                                return self._ncols
        return self._ncols

    def keyPressEvent(self, ev):
        if handle_enter_press(self, ev, self.start_view_animation, False):
            return
        k = ev.key()
        if ev.modifiers() & Qt.ShiftModifier and k in (Qt.Key_Left, Qt.Key_Right, Qt.Key_Up, Qt.Key_Down):
            ci = self.currentIndex()
            if not ci.isValid():
                return
            c = ci.row()
            delta = {Qt.Key_Left: -1, Qt.Key_Right: 1, Qt.Key_Up: -self.number_of_columns(), Qt.Key_Down: self.number_of_columns()}[k]
            n = max(0, min(c + delta, self.model().rowCount(None) - 1))
            if n == c:
                return
            sm = self.selectionModel()
            rows = {i.row() for i in sm.selectedIndexes()}
            if rows:
                mi, ma = min(rows), max(rows)
                end = mi if c == ma else ma if c == mi else c
            else:
                end = c
            top = self.model().index(min(n, end), 0)
            bottom = self.model().index(max(n, end), 0)
            sm.select(QItemSelection(top, bottom), sm.ClearAndSelect)
            sm.setCurrentIndex(self.model().index(n, 0), sm.NoUpdate)
        else:
            return QListView.keyPressEvent(self, ev)

    @property
    def current_book(self):
        ci = self.currentIndex()
        if ci.isValid():
            try:
                return self.model().db.data.index_to_id(ci.row())
            except (IndexError, ValueError, KeyError, TypeError, AttributeError):
                pass

    def current_book_state(self):
        return self.current_book

    def restore_current_book_state(self, state):
        book_id = state
        self.setFocus(Qt.OtherFocusReason)
        try:
            row = self.model().db.data.id_to_index(book_id)
        except (IndexError, ValueError, KeyError, TypeError, AttributeError):
            return
        self.set_current_row(row)
        self.select_rows((row,))
        self.scrollTo(self.model().index(row, 0), self.PositionAtCenter)

    def marked_changed(self, old_marked, current_marked):
        changed = old_marked | current_marked
        m = self.model()
        for book_id in changed:
            try:
                self.update(m.index(m.db.data.id_to_index(book_id), 0))
            except ValueError:
                pass

    def moveCursor(self, action, modifiers):
        index = QListView.moveCursor(self, action, modifiers)
        if action in (QListView.MoveLeft, QListView.MoveRight) and index.isValid():
            ci = self.currentIndex()
            if ci.isValid() and index.row() == ci.row():
                nr = index.row() + (1 if action == QListView.MoveRight else -1)
                if 0 <= nr < self.model().rowCount(QModelIndex()):
                    index = self.model().index(nr, 0)
        return index

    def selectionCommand(self, index, event):
        if event and event.type() == event.KeyPress and event.key() in (Qt.Key_Home, Qt.Key_End) and event.modifiers() & Qt.CTRL:
            return QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows
        return super(GridView, self).selectionCommand(index, event)

    def wheelEvent(self, ev):
        if ev.phase() not in (Qt.ScrollUpdate, 0):
            return
        number_of_pixels = ev.pixelDelta()
        number_of_degrees = ev.angleDelta() / 8.0
        b = self.verticalScrollBar()
        if number_of_pixels.isNull() or islinux:
            # pixelDelta() is broken on linux with wheel mice
            dy = number_of_degrees.y() / 15.0
            # Scroll by approximately half a row
            dy = int(math.ceil((dy) * b.singleStep() / 2.0))
        else:
            dy = number_of_pixels.y()
        if abs(dy) > 0:
            b.setValue(b.value() - dy)

    def paintEvent(self, ev):
        dpr = self.device_pixel_ratio
        page_width = int(dpr * self.delegate.cover_size.width())
        page_height = int(dpr * self.delegate.cover_size.height())
        size_changed = self.thumbnail_cache.set_thumbnail_size(page_width, page_height)
        if size_changed:
            self.delegate.cover_cache.clear()

        return super(GridView, self).paintEvent(ev)
Beispiel #51
0
class LiveCSS(QWidget):

    goto_declaration = pyqtSignal(object)

    def __init__(self, preview, parent=None):
        QWidget.__init__(self, parent)
        self.preview = preview
        self.preview_is_refreshing = False
        self.refresh_needed = False
        preview.refresh_starting.connect(self.preview_refresh_starting)
        preview.refreshed.connect(self.preview_refreshed)
        self.apply_theme()
        self.setAutoFillBackground(True)
        self.update_timer = QTimer(self)
        self.update_timer.timeout.connect(self.update_data)
        self.update_timer.setSingleShot(True)
        self.update_timer.setInterval(500)
        self.now_showing = (None, None, None)

        self.stack = s = QStackedLayout(self)
        self.setLayout(s)

        self.clear_label = la = QLabel(
            "<h3>"
            + _("No style information found")
            + "</h3><p>"
            + _("Move the cursor inside a HTML tag to see what styles" " apply to that tag.")
        )
        la.setWordWrap(True)
        la.setAlignment(Qt.AlignTop | Qt.AlignLeft)
        s.addWidget(la)

        self.box = box = Box(self)
        box.hyperlink_activated.connect(self.goto_declaration, type=Qt.QueuedConnection)
        self.scroll = sc = QScrollArea(self)
        sc.setWidget(box)
        sc.setWidgetResizable(True)
        s.addWidget(sc)

    def preview_refresh_starting(self):
        self.preview_is_refreshing = True

    def preview_refreshed(self):
        self.preview_is_refreshing = False
        # We must let the event loop run otherwise the webview will return
        # stale data in read_data()
        self.refresh_needed = True
        self.start_update_timer()

    def apply_theme(self):
        f = self.font()
        f.setFamily(tprefs["editor_font_family"] or default_font_family())
        f.setPointSize(tprefs["editor_font_size"])
        self.setFont(f)
        theme = get_theme(tprefs["editor_theme"])
        pal = self.palette()
        pal.setColor(pal.Window, theme_color(theme, "Normal", "bg"))
        pal.setColor(pal.WindowText, theme_color(theme, "Normal", "fg"))
        pal.setColor(pal.AlternateBase, theme_color(theme, "HighlightRegion", "bg"))
        pal.setColor(pal.Link, theme_color(theme, "Link", "fg"))
        pal.setColor(pal.LinkVisited, theme_color(theme, "Keyword", "fg"))
        self.setPalette(pal)
        if hasattr(self, "box"):
            self.box.relayout()
        self.update()

    def clear(self):
        self.stack.setCurrentIndex(0)

    def show_data(self, editor_name, sourceline, tags):
        if self.preview_is_refreshing:
            return
        if sourceline is None:
            self.clear()
        else:
            data = self.read_data(sourceline, tags)
            if data is None or len(data["computed_css"]) < 1:
                if editor_name == self.current_name and (editor_name, sourceline, tags) == self.now_showing:
                    # Try again in a little while in case there was a transient
                    # error in the web view
                    self.start_update_timer()
                    return
                if self.now_showing == (None, None, None) or self.now_showing[0] != self.current_name:
                    self.clear()
                    return
                # Try to refresh the data for the currently shown tag instead
                # of clearing
                editor_name, sourceline, tags = self.now_showing
                data = self.read_data(sourceline, tags)
                if data is None or len(data["computed_css"]) < 1:
                    self.clear()
                    return
            self.now_showing = (editor_name, sourceline, tags)
            data["html_name"] = editor_name
            self.box.show_data(data)
            self.refresh_needed = False
            self.stack.setCurrentIndex(1)

    def read_data(self, sourceline, tags):
        mf = self.preview.view.page().mainFrame()
        tags = [x.lower() for x in tags]
        result = unicode(
            mf.evaluateJavaScript(
                "window.calibre_preview_integration.live_css(%s, %s)" % (json.dumps(sourceline), json.dumps(tags))
            )
            or ""
        )
        try:
            result = json.loads(result)
        except ValueError:
            result = None
        if result is not None:
            maximum_specificities = {}
            for node in result["nodes"]:
                is_ancestor = node["is_ancestor"]
                for rule in node["css"]:
                    self.process_rule(rule, is_ancestor, maximum_specificities)
            for node in result["nodes"]:
                for rule in node["css"]:
                    for prop in rule["properties"]:
                        if prop.specificity < maximum_specificities[prop.name]:
                            prop.is_overriden = True

        return result

    def process_rule(self, rule, is_ancestor, maximum_specificities):
        selector = rule["selector"]
        sheet_index = rule["sheet_index"]
        rule_address = rule["rule_address"] or ()
        if selector is not None:
            try:
                specificity = [0] + list(parse(selector)[0].specificity())
            except (AttributeError, TypeError):
                specificity = [0, 0, 0, 0]
        else:  # style attribute
            specificity = [1, 0, 0, 0]
        specificity.extend((sheet_index, tuple(rule_address)))
        ancestor_specificity = 0 if is_ancestor else 1
        properties = []
        for prop in rule["properties"]:
            important = 1 if prop[-1] == "important" else 0
            p = Property(prop, [ancestor_specificity] + [important] + specificity)
            properties.append(p)
            if p.specificity > maximum_specificities.get(p.name, (0, 0, 0, 0, 0, 0)):
                maximum_specificities[p.name] = p.specificity
        rule["properties"] = properties

        href = rule["href"]
        if hasattr(href, "startswith") and href.startswith("file://"):
            href = href[len("file://") :]
            if iswindows and href.startswith("/"):
                href = href[1:]
            if href:
                rule["href"] = current_container().abspath_to_name(href, root=self.preview.current_root)

    @property
    def current_name(self):
        return self.preview.current_name

    @property
    def is_visible(self):
        return self.isVisible()

    def showEvent(self, ev):
        self.update_timer.start()
        actions["auto-reload-preview"].setEnabled(True)
        return QWidget.showEvent(self, ev)

    def sync_to_editor(self):
        self.update_data()

    def update_data(self):
        if not self.is_visible or self.preview_is_refreshing:
            return
        editor_name = self.current_name
        ed = editors.get(editor_name, None)
        if self.update_timer.isActive() or (ed is None and editor_name is not None):
            return QTimer.singleShot(100, self.update_data)
        if ed is not None:
            sourceline, tags = ed.current_tag(for_position_sync=False)
            if self.refresh_needed or self.now_showing != (editor_name, sourceline, tags):
                self.show_data(editor_name, sourceline, tags)

    def start_update_timer(self):
        if self.is_visible:
            self.update_timer.start()

    def stop_update_timer(self):
        self.update_timer.stop()

    def navigate_to_declaration(self, data, editor):
        if data["type"] == "inline":
            sourceline, tags = data["sourceline_address"]
            editor.goto_sourceline(sourceline, tags, attribute="style")
        elif data["type"] == "sheet":
            editor.goto_css_rule(data["rule_address"])
        elif data["type"] == "elem":
            editor.goto_css_rule(data["rule_address"], sourceline_address=data["sourceline_address"])