示例#1
0
class TabBarTabMetrics(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self._metrics = {}  # QHash<int, int>

    def init(self):
        if self._metrics:
            return
        self._metrics[0] = 250  # normalMaxWidth
        self._metrics[1] = 100  # normalMinWidth
        self._metrics[2] = 100  # activeMinWidth
        self._metrics[3] = 100  # overflowedWidth
        self._metrics[4] = -1  # pinnedWidth Will be initialized from TabBar

    def _normalMaxWidth(self):
        return self._metrics[0]

    def setNormalMaxWidth(self, value):
        self._metrics[0] = value

    normalMaxWidth = pyqtProperty(int, _normalMaxWidth, setNormalMaxWidth)

    def _normalMinWidth(self):
        return self._metrics[1]

    def setNormalMinWidth(self, value):
        self._metrics[1] = value

    normalMinWidth = pyqtProperty(int, _normalMinWidth, setNormalMinWidth)

    def _activeMinWidth(self):
        return self._metrics[2]

    def setActiveMinWidth(self, value):
        self._metrics[2] = value

    activeMinWidth = pyqtProperty(int, _activeMinWidth, setActiveMinWidth)

    def _overflowedWidth(self):
        return self._metrics[3]

    def setOverflowedWidth(self, value):
        self._metrics[3] = value

    overflowedWidth = pyqtProperty(int, _overflowedWidth, setOverflowedWidth)

    def _pinnedWidth(self):
        return self._metrics[4]

    def setPinnedWidth(self, value):
        self._metrics[4] = value

    pinnedWidth = pyqtProperty(int, _pinnedWidth, setPinnedWidth)
示例#2
0
def quick_property(property_type, property_name):

    def getter(self):
        return getattr(self, '_{}'.format(property_name))

    def setter(self, val):
        return setattr(self, '_{}'.format(property_name), val)

    return pyqtProperty(property_type, fget=getter, fset=setter)
示例#3
0
文件: stats.py 项目: kba/calibre
class Page(QWebPage):  # {{{
    def __init__(self, log):
        self.log = log
        QWebPage.__init__(self)
        self.js = None
        self.evaljs = self.mainFrame().evaluateJavaScript
        self.bridge_value = None
        nam = self.networkAccessManager()
        nam.setNetworkAccessible(nam.NotAccessible)
        self.longjs_counter = 0

    def javaScriptConsoleMessage(self, msg, lineno, msgid):
        self.log(u'JS:', unicode(msg))

    def javaScriptAlert(self, frame, msg):
        self.log(unicode(msg))

    @pyqtSlot(result=bool)
    def shouldInterruptJavaScript(self):
        if self.longjs_counter < 5:
            self.log('Long running javascript, letting it proceed')
            self.longjs_counter += 1
            return False
        self.log.warn('Long running javascript, aborting it')
        return True

    def _pass_json_value_getter(self):
        val = json.dumps(self.bridge_value)
        return val

    def _pass_json_value_setter(self, value):
        # Qt WebKit in Qt 4.x adds extra null bytes to the end of the string
        # if the JSON contains non-BMP characters
        self.bridge_value = json.loads(unicode(value).rstrip('\0'))

    _pass_json_value = pyqtProperty(str,
                                    fget=_pass_json_value_getter,
                                    fset=_pass_json_value_setter)

    def load_js(self):
        self.longjs_counter = 0
        if self.js is None:
            from calibre.utils.resources import compiled_coffeescript
            self.js = compiled_coffeescript('ebooks.oeb.display.utils')
            self.js += compiled_coffeescript('ebooks.oeb.polish.font_stats')
        self.mainFrame().addToJavaScriptWindowObject("py_bridge", self)
        self.evaljs(self.js)
        self.evaljs('''
        py_bridge.__defineGetter__('value', function() {
            return JSON.parse(this._pass_json_value);
        });
        py_bridge.__defineSetter__('value', function(val) {
            this._pass_json_value = JSON.stringify(val);
        });
        ''')
示例#4
0
def proxy_property(child_name, child_type, child_property):
    meta_object = child_type.staticMetaObject
    meta_property = meta_object.property(meta_object.indexOfProperty(child_property))

    def getter(self):
        return meta_property.read(getattr(self, child_name))

    def setter(self, val):
        return meta_property.write(getattr(self, child_name), val)

    return pyqtProperty(meta_property.typeName(), fget=getter, fset=setter)
示例#5
0
    class MacToolButton(QPushButton):
        def __init__(self, parent=None):
            super().__init__(parent)
            self._autoRaise = False
            self._buttonFixedSize = QSize(18, 18)

        def setIconSize(self, size):
            super().setIconSize(size)
            self._buttonFixedSize = QSize(size.width() + 2, size.height() + 2)

        def setAutoRaise(self, enable):
            self._autoRaise = enable
            self.setFlat(enable)
            if enable:
                self.setFixedSize(self._buttonFixedSize)

        def _autoRaise(self):
            return self._autoRaise

        autoRaise = pyqtProperty(bool, _autoRaise, setAutoRaise)
示例#6
0
class PDFWriter(QObject):

    def _pass_json_value_getter(self):
        val = json.dumps(self.bridge_value)
        return val

    def _pass_json_value_setter(self, value):
        self.bridge_value = json.loads(unicode(value))

    _pass_json_value = pyqtProperty(str, fget=_pass_json_value_getter,
            fset=_pass_json_value_setter)

    @pyqtSlot(result=unicode)
    def title(self):
        return self.doc_title

    @pyqtSlot(result=unicode)
    def author(self):
        return self.doc_author

    @pyqtSlot(result=unicode)
    def section(self):
        return self.current_section

    @pyqtSlot(result=unicode)
    def tl_section(self):
        return self.current_tl_section

    def __init__(self, opts, log, cover_data=None, toc=None):
        from calibre.gui2 import must_use_qt
        must_use_qt()
        QObject.__init__(self)

        self.logger = self.log = log
        self.opts = opts
        self.cover_data = cover_data
        self.paged_js = None
        self.toc = toc

        self.loop = QEventLoop()
        self.view = QWebView()
        self.page = Page(opts, self.log)
        self.view.setPage(self.page)
        self.view.setRenderHints(QPainter.Antialiasing|
                    QPainter.TextAntialiasing|QPainter.SmoothPixmapTransform)
        self.view.loadFinished.connect(self.render_html,
                type=Qt.QueuedConnection)
        for x in (Qt.Horizontal, Qt.Vertical):
            self.view.page().mainFrame().setScrollBarPolicy(x,
                    Qt.ScrollBarAlwaysOff)
        self.report_progress = lambda x, y: x
        self.current_section = ''
        self.current_tl_section = ''

    def dump(self, items, out_stream, pdf_metadata):
        opts = self.opts
        page_size = get_page_size(self.opts)
        xdpi, ydpi = self.view.logicalDpiX(), self.view.logicalDpiY()
        # We cannot set the side margins in the webview as there is no right
        # margin for the last page (the margins are implemented with
        # -webkit-column-gap)
        ml, mr = opts.margin_left, opts.margin_right
        self.doc = PdfDevice(out_stream, page_size=page_size, left_margin=ml,
                             top_margin=0, right_margin=mr, bottom_margin=0,
                             xdpi=xdpi, ydpi=ydpi, errors=self.log.error,
                             debug=self.log.debug, compress=not
                             opts.uncompressed_pdf, opts=opts,
                             mark_links=opts.pdf_mark_links)
        self.footer = opts.pdf_footer_template
        if self.footer:
            self.footer = self.footer.strip()
        if not self.footer and opts.pdf_page_numbers:
            self.footer = '<p style="text-align:center; text-indent: 0">_PAGENUM_</p>'
        self.header = opts.pdf_header_template
        if self.header:
            self.header = self.header.strip()
        min_margin = 1.5 * opts._final_base_font_size
        if self.footer and opts.margin_bottom < min_margin:
            self.log.warn('Bottom margin is too small for footer, increasing it to %.1fpts' % min_margin)
            opts.margin_bottom = min_margin
        if self.header and opts.margin_top < min_margin:
            self.log.warn('Top margin is too small for header, increasing it to %.1fpts' % min_margin)
            opts.margin_top = min_margin

        self.page.setViewportSize(QSize(self.doc.width(), self.doc.height()))
        self.render_queue = items
        self.total_items = len(items)

        mt, mb = map(self.doc.to_px, (opts.margin_top, opts.margin_bottom))
        self.margin_top, self.margin_bottom = map(lambda x:int(floor(x)), (mt, mb))

        self.painter = QPainter(self.doc)
        self.doc.set_metadata(title=pdf_metadata.title,
                              author=pdf_metadata.author,
                              tags=pdf_metadata.tags, mi=pdf_metadata.mi)
        self.doc_title = pdf_metadata.title
        self.doc_author = pdf_metadata.author
        self.painter.save()
        try:
            if self.cover_data is not None:
                p = QPixmap()
                try:
                    p.loadFromData(self.cover_data)
                except TypeError:
                    self.log.warn('This ebook does not have a raster cover, cannot generate cover for PDF'
                                  '. Cover type: %s' % type(self.cover_data))
                if not p.isNull():
                    self.doc.init_page()
                    draw_image_page(QRect(*self.doc.full_page_rect),
                            self.painter, p,
                            preserve_aspect_ratio=self.opts.preserve_cover_aspect_ratio)
                    self.doc.end_page()
        finally:
            self.painter.restore()

        QTimer.singleShot(0, self.render_book)
        if self.loop.exec_() == 1:
            raise Exception('PDF Output failed, see log for details')

        if self.toc is not None and len(self.toc) > 0:
            self.doc.add_outline(self.toc)

        self.painter.end()

        if self.doc.errors_occurred:
            raise Exception('PDF Output failed, see log for details')

    def render_inline_toc(self):
        self.rendered_inline_toc = True
        from calibre.ebooks.pdf.render.toc import toc_as_html
        raw = toc_as_html(self.toc, self.doc, self.opts)
        pt = PersistentTemporaryFile('_pdf_itoc.htm')
        pt.write(raw)
        pt.close()
        self.render_queue.append(pt.name)
        self.render_next()

    def render_book(self):
        if self.doc.errors_occurred:
            return self.loop.exit(1)
        try:
            if not self.render_queue:
                if self.opts.pdf_add_toc and self.toc is not None and len(self.toc) > 0 and not hasattr(self, 'rendered_inline_toc'):
                    return self.render_inline_toc()
                self.loop.exit()
            else:
                self.render_next()
        except:
            self.logger.exception('Rendering failed')
            self.loop.exit(1)

    def render_next(self):
        item = unicode(self.render_queue.pop(0))

        self.logger.debug('Processing %s...' % item)
        self.current_item = item
        load_html(item, self.view)

    def render_html(self, ok):
        if ok:
            try:
                self.do_paged_render()
            except:
                self.log.exception('Rendering failed')
                self.loop.exit(1)
                return
        else:
            # The document is so corrupt that we can't render the page.
            self.logger.error('Document cannot be rendered.')
            self.loop.exit(1)
            return
        done = self.total_items - len(self.render_queue)
        self.report_progress(done/self.total_items,
                        _('Rendered %s'%os.path.basename(self.current_item)))
        self.render_book()

    @property
    def current_page_num(self):
        return self.doc.current_page_num

    def load_mathjax(self):
        evaljs = self.view.page().mainFrame().evaluateJavaScript
        mjpath = P(u'viewer/mathjax').replace(os.sep, '/')
        if iswindows:
            mjpath = u'/' + mjpath
        if bool(evaljs('''
                    window.mathjax.base = %s;
                    mathjax.check_for_math(); mathjax.math_present
                    '''%(json.dumps(mjpath, ensure_ascii=False)))):
            self.log.debug('Math present, loading MathJax')
            while not bool(evaljs('mathjax.math_loaded')):
                self.loop.processEvents(self.loop.ExcludeUserInputEvents)
            evaljs('document.getElementById("MathJax_Message").style.display="none";')

    def get_sections(self, anchor_map, only_top_level=False):
        sections = defaultdict(list)
        ci = os.path.abspath(os.path.normcase(self.current_item))
        if self.toc is not None:
            tocentries = self.toc.top_level_items() if only_top_level else self.toc.flat()
            for toc in tocentries:
                path = toc.abspath or None
                frag = toc.fragment or None
                if path is None:
                    continue
                path = os.path.abspath(os.path.normcase(path))
                if path == ci:
                    col = 0
                    if frag and frag in anchor_map:
                        col = anchor_map[frag]['column']
                    sections[col].append(toc.text or _('Untitled'))

        return sections

    def do_paged_render(self):
        if self.paged_js is None:
            import uuid
            from calibre.utils.resources import compiled_coffeescript as cc
            self.paged_js =  cc('ebooks.oeb.display.utils')
            self.paged_js += cc('ebooks.oeb.display.indexing')
            self.paged_js += cc('ebooks.oeb.display.paged')
            self.paged_js += cc('ebooks.oeb.display.mathjax')
            self.hf_uuid = str(uuid.uuid4()).replace('-', '')

        self.view.page().mainFrame().addToJavaScriptWindowObject("py_bridge", self)
        self.view.page().longjs_counter = 0
        evaljs = self.view.page().mainFrame().evaluateJavaScript
        evaljs(self.paged_js)
        self.load_mathjax()

        evaljs('''
        Object.defineProperty(py_bridge, 'value', {
               get : function() { return JSON.parse(this._pass_json_value); },
               set : function(val) { this._pass_json_value = JSON.stringify(val); }
        });

        document.body.style.backgroundColor = "white";
        paged_display.set_geometry(1, %d, %d, %d);
        paged_display.layout();
        paged_display.fit_images();
        py_bridge.value = book_indexing.all_links_and_anchors();
        window.scrollTo(0, 0); // This is needed as getting anchor positions could have caused the viewport to scroll
        '''%(self.margin_top, 0, self.margin_bottom))

        amap = self.bridge_value
        if not isinstance(amap, dict):
            amap = {'links':[], 'anchors':{}}  # Some javascript error occurred
        sections = self.get_sections(amap['anchors'])
        tl_sections = self.get_sections(amap['anchors'], True)
        col = 0

        if self.header:
            self.bridge_value = self.header
            evaljs('paged_display.header_template = py_bridge.value')
        if self.footer:
            self.bridge_value = self.footer
            evaljs('paged_display.footer_template = py_bridge.value')
        if self.header or self.footer:
            evaljs('paged_display.create_header_footer("%s");'%self.hf_uuid)

        start_page = self.current_page_num

        mf = self.view.page().mainFrame()

        def set_section(col, sections, attr):
            # If this page has no section, use the section from the previous page
            idx = col if col in sections else col - 1 if col - 1 in sections else None
            if idx is not None:
                setattr(self, attr, sections[idx][0])

        while True:
            set_section(col, sections, 'current_section')
            set_section(col, tl_sections, 'current_tl_section')
            self.doc.init_page()
            if self.header or self.footer:
                evaljs('paged_display.update_header_footer(%d)'%self.current_page_num)
            self.painter.save()
            mf.render(self.painter)
            self.painter.restore()
            try:
                nsl = int(evaljs('paged_display.next_screen_location()'))
            except (TypeError, ValueError):
                break
            self.doc.end_page(nsl <= 0)
            if nsl <= 0:
                break
            evaljs('window.scrollTo(%d, 0); paged_display.position_header_footer();'%nsl)
            if self.doc.errors_occurred:
                break
            col += 1

        if not self.doc.errors_occurred:
            self.doc.add_links(self.current_item, start_page, amap['links'],
                            amap['anchors'])
示例#7
0
class PDFWriter(QObject):  # {{{
    def __init__(self, opts, log, cover_data=None, toc=None):
        from calibre.gui2 import must_use_qt
        from calibre.utils.podofo import get_podofo
        must_use_qt()
        QObject.__init__(self)

        self.logger = self.log = log
        self.podofo = get_podofo()
        self.doc = self.podofo.PDFDoc()

        self.loop = QEventLoop()
        self.view = QWebView()
        self.page = Page(opts, self.log)
        self.view.setPage(self.page)
        self.view.setRenderHints(QPainter.Antialiasing
                                 | QPainter.TextAntialiasing
                                 | QPainter.SmoothPixmapTransform)
        self.view.loadFinished.connect(self._render_html,
                                       type=Qt.QueuedConnection)
        for x in (Qt.Horizontal, Qt.Vertical):
            self.view.page().mainFrame().setScrollBarPolicy(
                x, Qt.ScrollBarAlwaysOff)
        self.render_queue = []
        self.combine_queue = []
        self.tmp_path = PersistentTemporaryDirectory(u'_pdf_output_parts')

        self.opts = opts
        self.cover_data = cover_data
        self.paged_js = None
        self.toc = toc

    def dump(self, items, out_stream, pdf_metadata):
        self.metadata = pdf_metadata
        self._delete_tmpdir()
        self.outline = Outline(self.toc, items)

        self.render_queue = items
        self.combine_queue = []
        self.out_stream = out_stream
        self.insert_cover()

        self.render_succeeded = False
        self.current_page_num = self.doc.page_count()
        self.combine_queue.append(
            os.path.join(self.tmp_path, 'qprinter_out.pdf'))
        self.first_page = True
        self.setup_printer(self.combine_queue[-1])
        QTimer.singleShot(0, self._render_book)
        self.loop.exec_()
        if self.painter is not None:
            self.painter.end()
        if self.printer is not None:
            self.printer.abort()

        if not self.render_succeeded:
            raise Exception('Rendering HTML to PDF failed')

    def _render_book(self):
        try:
            if len(self.render_queue) == 0:
                self._write()
            else:
                self._render_next()
        except:
            self.logger.exception('Rendering failed')
            self.loop.exit(1)

    def _render_next(self):
        item = unicode(self.render_queue.pop(0))

        self.logger.debug('Processing %s...' % item)
        self.current_item = item
        load_html(item, self.view)

    def _render_html(self, ok):
        if ok:
            self.do_paged_render()
        else:
            # The document is so corrupt that we can't render the page.
            self.logger.error('Document cannot be rendered.')
            self.loop.exit(0)
            return
        self._render_book()

    def _pass_json_value_getter(self):
        val = json.dumps(self.bridge_value)
        return val

    def _pass_json_value_setter(self, value):
        self.bridge_value = json.loads(unicode(value))

    _pass_json_value = pyqtProperty(str,
                                    fget=_pass_json_value_getter,
                                    fset=_pass_json_value_setter)

    def setup_printer(self, outpath):
        self.printer = self.painter = None
        printer = get_pdf_printer(self.opts, output_file_name=outpath)
        painter = QPainter(printer)
        zoomx = printer.logicalDpiX() / self.view.logicalDpiX()
        zoomy = printer.logicalDpiY() / self.view.logicalDpiY()
        painter.scale(zoomx, zoomy)
        pr = printer.pageRect()
        self.printer, self.painter = printer, painter
        self.viewport_size = QSize(pr.width() / zoomx, pr.height() / zoomy)
        self.page.setViewportSize(self.viewport_size)

    def do_paged_render(self):
        if self.paged_js is None:
            from calibre.utils.resources import compiled_coffeescript
            self.paged_js = compiled_coffeescript('ebooks.oeb.display.utils')
            self.paged_js += compiled_coffeescript(
                'ebooks.oeb.display.indexing')
            self.paged_js += compiled_coffeescript('ebooks.oeb.display.paged')

        self.view.page().mainFrame().addToJavaScriptWindowObject(
            "py_bridge", self)
        evaljs = self.view.page().mainFrame().evaluateJavaScript
        evaljs(self.paged_js)
        evaljs('''
        Object.defineProperty(py_bridge, 'value', {
               get : function() { return JSON.parse(this._pass_json_value); },
               set : function(val) { this._pass_json_value = JSON.stringify(val); }
        });

        document.body.style.backgroundColor = "white";
        paged_display.set_geometry(1, 0, 0, 0);
        paged_display.layout();
        paged_display.fit_images();
        ''')
        mf = self.view.page().mainFrame()
        start_page = self.current_page_num
        if not self.first_page:
            start_page += 1
        while True:
            if not self.first_page:
                if self.printer.newPage():
                    self.current_page_num += 1
            self.first_page = False
            mf.render(self.painter)
            try:
                nsl = int(evaljs('paged_display.next_screen_location()'))
            except (TypeError, ValueError):
                break
            if nsl <= 0:
                break
            evaljs('window.scrollTo(%d, 0)' % nsl)

        self.bridge_value = tuple(self.outline.anchor_map[self.current_item])
        evaljs(
            'py_bridge.value = book_indexing.anchor_positions(py_bridge.value)'
        )
        amap = self.bridge_value
        if not isinstance(amap, dict):
            amap = {}  # Some javascript error occurred
        self.outline.set_pos(self.current_item, None, start_page, 0)
        for anchor, x in amap.iteritems():
            pagenum, ypos = x
            self.outline.set_pos(self.current_item, anchor,
                                 start_page + pagenum, ypos)

    def append_doc(self, outpath):
        doc = self.podofo.PDFDoc()
        with open(outpath, 'rb') as f:
            raw = f.read()
        doc.load(raw)
        self.doc.append(doc)

    def _delete_tmpdir(self):
        if os.path.exists(self.tmp_path):
            shutil.rmtree(self.tmp_path, True)
            self.tmp_path = PersistentTemporaryDirectory('_pdf_output_parts')

    def insert_cover(self):
        if not isinstance(self.cover_data, bytes):
            return
        item_path = os.path.join(self.tmp_path, 'cover.pdf')
        printer = get_pdf_printer(self.opts,
                                  output_file_name=item_path,
                                  for_comic=True)
        self.combine_queue.insert(0, item_path)
        p = QPixmap()
        p.loadFromData(self.cover_data)
        if not p.isNull():
            painter = QPainter(printer)
            draw_image_page(
                printer,
                painter,
                p,
                preserve_aspect_ratio=self.opts.preserve_cover_aspect_ratio)
            painter.end()
            self.append_doc(item_path)
        printer.abort()

    def _write(self):
        self.painter.end()
        self.printer.abort()
        self.painter = self.printer = None
        self.append_doc(self.combine_queue[-1])

        try:
            self.doc.creator = u'%s %s [http://calibre-ebook.com]' % (
                __appname__, __version__)
            self.doc.title = self.metadata.title
            self.doc.author = self.metadata.author
            if self.metadata.tags:
                self.doc.keywords = self.metadata.tags
            self.outline(self.doc)
            self.doc.save_to_fileobj(self.out_stream)
            self.render_succeeded = True
        finally:
            self._delete_tmpdir()
            self.loop.exit(0)
示例#8
0
class RecoveryJsObject(QObject):
    def __init__(self, manager):
        '''
        @param: manager RestoreManager
        '''
        super().__init__()
        self._manager = manager  # RestoreManager
        self._page = None  # WebPage

    def setPage(self, page):
        '''
        @param: page WebPage
        '''
        assert (page)
        self._page = page

    def restoreData(self):
        '''
        @return: QJsonArray
        '''
        out = []
        idx = 0
        for window in self._manager.restoreData().windows:
            jdx = 0
            tabs = []
            for tab in window.tabs:
                icon = tab.icon.isNull() and IconProvider.emptyWebIcon(
                ) or tab.icon
                item = {}
                item['tab'] = jdx
                item['icon'] = gVar.appTools.pixmapToDataUrl(icon.pixmap(16))
                item['title'] = tab.title
                item['url'] = tab.url.toString()
                item['pinned'] = tab.isPinned
                item['current'] = window.currentTab == jdx
                tabs.append(item)
                jdx += 1
            window = {}
            window['window'] = idx
            idx += 1
            window['tabs'] = tabs
            out.append(window)
        return out

    restoreData = pyqtProperty(list, restoreData, constant=True)

    # public Q_SLOTS:
    @pyqtSlot()
    def startNewSession(self):
        gVar.app.restoreManager().clearRestoreData()
        gVar.app.destroyRestoreManager()

        view = self._page.view()
        view.loadByUrl(QUrl('app:start'))

    @pyqtSlot(list, list)
    def restoreSession(self, excludeWin, excludeTab):
        '''
        @param excludeWin QStringList
        @param excludeTab QStringList
        '''
        assert (len(excludeWin) == len(excludeTab))

        # This assumes that excludeWin and excludeTab are sorted in descending order

        # RestoreData
        data = self._manager.restoreData()

        for idx in range(len(excludeWin)):
            win = excludeWin[idx]
            tab = excludeTab[idx]

            if not gVar.appTools.containsIndex(data.windows, win) or \
                    gVar.appTools.containsIndex(data.windows[win].tabs, tab):
                continue

            wd = data.windows[win]

            wd.tabs.remove(tab)
            if wd.currentTab >= tab:
                wd.currentTab -= 1

            if not wd.tabs:
                data.windows.remove(win)
                continue

            if wd.currentTab < 0:
                wd.currentTab = len(wd.tabs) - 1

        if gVar.app.restoreSession(None, data):
            self._closeTab()
        else:
            self.startNewSession()

    # private:
    def _closeTab(self):
        # TabbedWebView
        view = self._page.view()
        if not isinstance(view, TabbedWebView):
            return

        if view.browserWindow().tabCount() > 1:
            view.closeView()
        else:
            view.browserWindow().close()
示例#9
0
class CoverView(QWidget):  # {{{

    cover_changed = pyqtSignal(object, object)
    cover_removed = pyqtSignal(object)

    def __init__(self, vertical, parent=None):
        QWidget.__init__(self, parent)
        self._current_pixmap_size = QSize(120, 120)
        self.vertical = vertical

        self.animation = QPropertyAnimation(self, 'current_pixmap_size', self)
        self.animation.setEasingCurve(QEasingCurve(QEasingCurve.OutExpo))
        self.animation.setDuration(1000)
        self.animation.setStartValue(QSize(0, 0))
        self.animation.valueChanged.connect(self.value_changed)

        self.setSizePolicy(
                QSizePolicy.Expanding if vertical else QSizePolicy.Minimum,
                QSizePolicy.Expanding)

        self.default_pixmap = QPixmap(I('book.png'))
        self.pixmap = self.default_pixmap
        self.pwidth = self.pheight = None
        self.data = {}

        self.do_layout()

    def value_changed(self, val):
        self.update()

    def setCurrentPixmapSize(self, val):
        self._current_pixmap_size = val

    def do_layout(self):
        if self.rect().width() == 0 or self.rect().height() == 0:
            return
        pixmap = self.pixmap
        pwidth, pheight = pixmap.width(), pixmap.height()
        try:
            self.pwidth, self.pheight = fit_image(pwidth, pheight,
                            self.rect().width(), self.rect().height())[1:]
        except:
            self.pwidth, self.pheight = self.rect().width()-1, \
                    self.rect().height()-1
        self.current_pixmap_size = QSize(self.pwidth, self.pheight)
        self.animation.setEndValue(self.current_pixmap_size)

    def show_data(self, data):
        self.animation.stop()
        same_item = getattr(data, 'id', True) == self.data.get('id', False)
        self.data = {'id':data.get('id', None)}
        if data.cover_data[1]:
            self.pixmap = QPixmap.fromImage(data.cover_data[1])
            if self.pixmap.isNull() or self.pixmap.width() < 5 or \
                    self.pixmap.height() < 5:
                self.pixmap = self.default_pixmap
        else:
            self.pixmap = self.default_pixmap
        self.do_layout()
        self.update()
        if (not same_item and not config['disable_animations'] and
                self.isVisible()):
            self.animation.start()

    def paintEvent(self, event):
        canvas_size = self.rect()
        width = self.current_pixmap_size.width()
        extrax = canvas_size.width() - width
        if extrax < 0:
            extrax = 0
        x = int(extrax/2.)
        height = self.current_pixmap_size.height()
        extray = canvas_size.height() - height
        if extray < 0:
            extray = 0
        y = int(extray/2.)
        target = QRect(x, y, width, height)
        p = QPainter(self)
        p.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform)
        p.drawPixmap(target, self.pixmap.scaled(target.size(),
            Qt.KeepAspectRatio, Qt.SmoothTransformation))
        if gprefs['bd_overlay_cover_size']:
            sztgt = target.adjusted(0, 0, 0, -4)
            f = p.font()
            f.setBold(True)
            p.setFont(f)
            sz = u'\u00a0%d x %d\u00a0'%(self.pixmap.width(), self.pixmap.height())
            flags = Qt.AlignBottom|Qt.AlignRight|Qt.TextSingleLine
            szrect = p.boundingRect(sztgt, flags, sz)
            p.fillRect(szrect.adjusted(0, 0, 0, 4), QColor(0, 0, 0, 200))
            p.setPen(QPen(QColor(255,255,255)))
            p.drawText(sztgt, flags, sz)
        p.end()

    current_pixmap_size = pyqtProperty('QSize',
            fget=lambda self: self._current_pixmap_size,
            fset=setCurrentPixmapSize
            )

    def contextMenuEvent(self, ev):
        cm = QMenu(self)
        paste = cm.addAction(_('Paste Cover'))
        copy = cm.addAction(_('Copy Cover'))
        remove = cm.addAction(_('Remove Cover'))
        if not QApplication.instance().clipboard().mimeData().hasImage():
            paste.setEnabled(False)
        copy.triggered.connect(self.copy_to_clipboard)
        paste.triggered.connect(self.paste_from_clipboard)
        remove.triggered.connect(self.remove_cover)
        cm.exec_(ev.globalPos())

    def copy_to_clipboard(self):
        QApplication.instance().clipboard().setPixmap(self.pixmap)

    def paste_from_clipboard(self, pmap=None):
        if not isinstance(pmap, QPixmap):
            cb = QApplication.instance().clipboard()
            pmap = cb.pixmap()
            if pmap.isNull() and cb.supportsSelection():
                pmap = cb.pixmap(cb.Selection)
        if not pmap.isNull():
            self.pixmap = pmap
            self.do_layout()
            self.update()
            self.update_tooltip(getattr(self.parent(), 'current_path', ''))
            if not config['disable_animations']:
                self.animation.start()
            id_ = self.data.get('id', None)
            if id_ is not None:
                self.cover_changed.emit(id_,
                    pixmap_to_data(pmap))

    def remove_cover(self):
        id_ = self.data.get('id', None)
        self.pixmap = self.default_pixmap
        self.do_layout()
        self.update()
        if id_ is not None:
            self.cover_removed.emit(id_)

    def update_tooltip(self, current_path):
        try:
            sz = self.pixmap.size()
        except:
            sz = QSize(0, 0)
        self.setToolTip(
            '<p>'+_('Double-click to open Book Details window') +
            '<br><br>' + _('Path') + ': ' + current_path +
            '<br><br>' + _('Cover size: %(width)d x %(height)d')%dict(
                width=sz.width(), height=sz.height())
        )
示例#10
0
class FancyTabWidget(QWidget):
    # Values are persisted = only add to the end
    # enum Mode
    Mode_None = 0
    Mode_LargeSidebar = 1
    Mode_SmallSidebar = 2
    Mode_Tabs = 3
    Mode_IconOnlyTabs = 4
    Mode_PlainSidebar = 5

    def __init__(self, parent=None):
        super().__init__(parent)
        self._mode = self.Mode_None
        self._items = []  # QList<Item>

        self._tab_bar = None  # QWidget
        self._stack = QStackedLayout()  # QStackedLayout
        self._background_pixmap = QPixmap()
        self._side_widget = QWidget()  # QWidget
        self._side_layout = QVBoxLayout()  # QVBoxLayout
        self._top_layout = QVBoxLayout()  # QVBoxLayout

        self._use_background = False  # bool

        self._menu = None  # QMenu

        self._proxy_style = FancyTabProxyStyle()  # FancyTabProxyStyle

        self._side_layout.setSpacing(0)
        self._side_layout.setContentsMargins(0, 0, 0, 0)
        self._side_layout.addSpacerItem(
            QSpacerItem(0, 0, QSizePolicy.Fixed, QSizePolicy.Expanding))

        self._side_widget.setLayout(self._side_layout)
        self._side_widget.setSizePolicy(QSizePolicy.Fixed,
                                        QSizePolicy.Expanding)

        self._top_layout.setSpacing(0)
        self._top_layout.setContentsMargins(0, 0, 0, 0)
        self._top_layout.addLayout(self._stack)

        main_layout = QHBoxLayout()
        main_layout.setContentsMargins(0, 0, 0, 0)
        main_layout.setSpacing(1)
        main_layout.addWidget(self._side_widget)
        main_layout.addLayout(self._top_layout)
        self.setLayout(main_layout)

    class Item:
        # enum Type
        Type_Tab = 0
        Type_Spacer = 1

        def __init__(self, icon, label):
            '''
            @param: icon QIcon
            @param: label QString
            '''
            self.type = self.Type_Tab  # Type
            self.tab_label = label  # QString
            self.tab_icon = icon  # QIcon
            self.spacer_size = 0

    def AddTab(self, tab, icon, label):
        '''
        @param: tab QWidget
        @param: icon Qicon
        @param: label QString
        '''
        self._stack.addWidget(tab)
        self._items.append(self.Item(icon, label))

    def AddSpacer(self, size=40):
        self._items.append(self.Item(size))

    def SetBackgroundPixmap(self, pixmap):
        '''
        @param: pixmap QPixmap
        '''
        self._background_pixmap = pixmap
        self.update()

    def AddBottomWidget(self, widget):
        '''
        @param: widget QWidget
        '''
        self._top_layout.addWidget(widget)

    def current_index(self):
        '''
        @return: int
        '''
        return self._stack.currentIndex()

    def mode(self):
        '''
        @return: Mode
        '''
        return self._mode

    def bgPixmap(self):
        '''
        @return: QPixmap
        '''
        return self._background_pixmap

    bgPixmap = pyqtProperty(QPixmap, bgPixmap, SetBackgroundPixmap)

    # public Q_SLOTS:
    def SetCurrentIndex(self, index):
        bar = self._tab_bar
        if isinstance(bar, FancyTabBar):
            bar.setCurrentIndex(index)
        elif isinstance(bar, QTabBar):
            bar.setCurrentIndex(index)
        else:
            self._stack.setCurrentIndex(index)

    def SetMode(self, mode):
        # Remove previous tab bar
        del self._tab_bar
        self._tab_bar = None

        self._use_background = False

        # Create new tab bar
        if mode == self.Mode_None:
            pass
        elif mode == self.Mode_LargeSidebar:
            bar = FancyTabBar(self)
            self._side_layout.insertWidget(0, bar)
            self._tab_bar = bar
            for item in self._items:
                if item.type == self.Item.Type_Spacer:
                    bar.addSpacer(item.spacer_size)
                else:
                    bar.addTab(item.tab_icon, item.tab_label)

            bar.setCurrentIndex(self._stack.currentIndex())
            bar.currentChanged.connect(self._ShowWidget)
            self._use_background = True
        elif mode == self.Mode_Tabs:
            self._MakeTabBar(QTabBar.RoundedNorth, True, False, False)
        elif mode == self.Mode_IconOnlyTabs:
            self._MakeTabBar(QTabBar.RoundedNorth, False, True, False)
        elif mode == self.Mode_SmallSidebar:
            self._MakeTabBar(QTabBar.RoundedWest, True, True, True)
            self._use_background = True
        elif mode == self.Mode_PlainSidebar:
            self._MakeTabBar(QTabBar.RoundedWest, True, True, False)
        else:
            print('DEBUG: Unknown fancy tab mode %s' % mode)

        self._tab_bar.setSizePolicy(QSizePolicy.Preferred,
                                    QSizePolicy.Preferred)

        self._mode = mode
        self.ModeChanged.emit(mode)
        self.update()

    # Q_SIGNALS:
    CurrentChanged = pyqtSignal(int)  # index
    ModeChanged = pyqtSignal(int)  # mode FancyTabWidget::Mode

    # protected:
    # override
    def paintEvent(self, event):
        '''
        @param: event QPaintEvent
        '''
        if not self._use_background:
            return

        painter = QPainter(self)

        rect = self._side_widget.rect().adjusted(0, 0, 1, 0)
        rect = self.style().visualRect(self.layoutDirection(), self.geometry(),
                                       rect)
        styleHelper.verticalGradient(painter, rect, rect)

        if not self._background_pixmap.isNull():
            pixmap_rect = QRect(self._background_pixmap.rect())
            pixmap_rect.moveTo(rect.topLeft())

            while pixmap_rect.top() < rect.bottom():
                source_rect = QRect(pixmap_rect.intersected(rect))
                source_rect.moveTo(0, 0)
                painter.drawPixmap(pixmap_rect.topLeft(),
                                   self._background_pixmap, source_rect)
                pixmap_rect.moveTop(pixmap_rect.bottom() - 10)

        painter.setPen(styleHelper.borderColor())
        painter.drawLine(rect.topRight(), rect.bottomRight())

        # QColor
        light = styleHelper.sidebarHighlight()
        painter.setPen(light)
        painter.drawLine(rect.bottomLeft(), rect.bottomRight())

    # override
    def contextMenuEvent(self, event):
        '''
        @param: event QContextMenuEvent
        '''
        pass

    # private Q_SLOTS:
    def _ShowWidget(self, index):
        self._stack.setCurrentIndex(index)
        self.CurrentChanged.emit(index)

    # private:
    def _MakeTabBar(self, shap, text, icons, fancy):
        '''
        @param: shap QTabBar::Shap
        @param: text bool
        @param: icons bool
        @param: fancy bool
        '''
        bar = QTabBar(self)
        bar.setShape(shap)
        bar.setDocumentMode(True)
        bar.setUsesScrollButtons(True)

        if shap == QTabBar.RoundedWest:
            bar.setIconSize(QSize(22, 22))

        if fancy:
            bar.setStyle(self._proxy_style)

        if shap == QTabBar.RoundedNorth:
            self._top_layout.insertWidget(0, bar)
        else:
            self._side_layout.insertWidget(0, bar)

        # Item
        for item in self._items:
            if item.type != self.Item.Type_Tab:
                continue

            label = item.tab_label
            if shap == QTabBar.RoundedWest:
                label = QFontMetrics(self.font()).elidedText(
                    label, Qt.ElideMiddle, 100)

            tab_id = -1
            if icons and text:
                tab_id = bar.addTab(item.tab_icon, label)
            elif icons:
                tab_id = bar.addTab(item.tab_icon, '')
            elif text:
                tab_id = bar.addTab(label)

            bar.setTabToolTip(tab_id, item.tab_label)

        bar.setCurrentIndex(self._stack.currentIndex())
        bar.currentChanged.connect(self._ShowWidget)
        self._tab_bar = bar

    def _AddMenuItem(self, mapper, group, text, mode):
        '''
        @param: mapper QSignalMapper
        @param: group QActionGroup
        @param: text QString
        @param: mode Mode
        '''
        # QAction
        action = group.addAction(text)
        action.setCheckable(True)
        mapper.setMapping(action, mode)
        action.triggered.connect(mapper.map)

        if mode == self._mode:
            action.setChecked(True)
示例#11
0
class ToolButton(QToolButton):
    _MultiIconOption = 1
    _ShowMenuInsideOption = 2
    _ToolBarLookOption = 4
    _ShowMenuOnRightClick = 8
    def __init__(self, parent=None):
        super(ToolButton, self).__init__(parent)
        self._multiIcon = QImage()
        self._themeIcon = ''
        self._pressTimer = QTimer()
        self._menu = None  # QMenu
        self._options = 0

        self.setMinimumWidth(16)
        opt = QStyleOptionToolButton()
        self.initStyleOption(opt)

        self._pressTimer.setSingleShot(True)
        self._pressTimer.setInterval(QApplication.style().styleHint(
            QStyle.SH_ToolButton_PopupDelay, opt, self
        ))
        self._pressTimer.timeout.connect(self._showMenu)

    def size(self):
        return super().size()

    def setFixedSize(self, size):
        return super().setFixedSize(size)

    fixedsize = pyqtProperty(QSize, size, setFixedSize)

    def width(self):
        return super().width()

    def setFixedWidth(self, width):
        return super().setFixedWidth(width)

    fixedwidth = pyqtProperty(int, width, setFixedWidth)

    def height(self):
        return super().height()

    def setFixedHeight(self, height):
        return super().setFixedHeight(height)

    fixedheight = pyqtProperty(int, height, setFixedHeight)

    def multiIcon(self):
        '''
        @brief: MultiIcon - Image containing pixmaps for all button states
        @return: QImage
        '''
        return self._multiIcon

    def setMultiIcon(self, image):
        '''
        @param: image QImage
        '''
        self._options |= self._MultiIconOption
        self._multiIcon = image
        self.setFixedSize(self._multiIcon.width(), self._multiIcon.height())

        self.update()

    multiIcon = pyqtProperty(QImage, multiIcon, setMultiIcon)

    def icon(self):
        '''
        @brief: Icon - Standard QToolButton with icon
        @return: QIcon
        '''
        return super().icon()

    def setIcon(self, icon):
        '''
        @param: QIcon
        '''
        if self._options & self._MultiIconOption:
            self.setFixedSize(self.sizeHint())

        self._options &= ~self._MultiIconOption
        if not isinstance(icon, QIcon):
            icon = QIcon(icon)
        super().setIcon(icon)

    icon = pyqtProperty(QIcon, icon, setIcon)

    def themeIcon(self):
        '''
        @brief: ThemeIcon - Standard QToolButton with theme icon
        @return: QString
        '''
        return self._themeIcon

    def setThemeIcon(self, icon):
        '''
        @param: icon QString
        '''
        # QIcon ic
        ic = QIcon.fromTheme(icon)
        if not ic.isNull():
            self._themeIcon = icon
            self.setIcon(QIcon.fromTheme(self._themeIcon))

    themeIcon = pyqtProperty(str, themeIcon, setThemeIcon)

    def fallbackIcon(self):
        '''
        @brief: FallbackIcon - In case theme doesn't contain ThemeIcon
        @return: QIcon
        '''
        return self.icon

    def setFallbackIcon(self, fallbackIcon):
        '''
        @param: fallbackIcon QIcon
        '''
        if self.icon.isNull():
            self.setIcon(fallbackIcon)

    fallbackIcon = pyqtProperty(QIcon, fallbackIcon, setFallbackIcon)

    def menu(self):
        '''
        @note: Menu - Menu is handled in ToolButton and is not passed to QToolButton
            There won't be menu indicator shown in the button
            QToolButton::MenuButtonPopup is not supported
        @return: QMenu
        '''
        return self._menu

    def setMenu(self, menu):
        '''
        @param: menu QMenu
        '''
        assert(menu)

        if self._menu:
            self._menu.aboutToHide.disconnect(self._menuAboutToHide)

        self._menu = menu
        self._menu.aboutToHide.connect(self._menuAboutToHide)

    def showMenuInside(self):
        '''
        @brief: Align the right corner of menu to the right corner of button
        '''
        return self._options & self._ShowMenuInsideOption

    def setShowMenuInside(self, enable):
        if enable:
            self._options |= self._ShowMenuInsideOption
        else:
            self._options &= ~self._ShowMenuInsideOption

    def showMenuOnRightClick(self):
        '''
        @brief: Show button menu on right click
        '''
        return self._options & self._ShowMenuOnRightClick

    def setShowMenuOnRightClick(self, enable):
        if enable:
            self._options |= self._ShowMenuOnRightClick
        else:
            self._options &= ~self._ShowMenuOnRightClick

    def toolbarButtonLook(self):
        '''
        @brief: Set the button to look as it was in toolbar
            (it now only sets the correct icon size)
        @return: bool
        '''
        return self._options & self._ToolBarLookOption

    def setToolbarButtonLook(self, enable):
        if enable:
            self._options |= self._ToolBarLookOption

            opt = QStyleOption()
            opt.initFrom(self)
            size = self.style().pixelMetric(QStyle.PM_ToolBarIconSize, opt, self)
            self.setIconSize(QSize(size, size))
        else:
            self._options &= ~self._ToolBarLookOption

        self.setProperty('toolbar-look', enable)
        self.style().unpolish(self)
        self.style().polish(self)

    # Q_SIGNALS
    middleMouseClicked = pyqtSignal()
    controlClicked = pyqtSignal()
    doubleClicked = pyqtSignal()

    # It is needed to use these signals with ShowMenuInside
    aboutToShowMenu = pyqtSignal()
    aboutToHideMenu = pyqtSignal()

    # private Q_SLOTS
    def _menuAboutToHide(self):
        self.setDown(False)
        self.aboutToHideMenu.emit()

    def _showMenu(self):
        if not self._menu or self._menu.isVisible():
            return

        self.aboutToShowMenu.emit()

        pos = QPoint()

        if self._options & self._ShowMenuInsideOption:
            pos = self.mapToGlobal(self.rect().bottomRight())
            if QApplication.layoutDirection() == Qt.RightToLeft:
                pos.setX(pos.x() - self.rect().width())
            else:
                pos.setX(pos.x() - self._menu.sizeHint().width())
        else:
            pos = self.mapToGlobal(self.rect().bottomLeft())

        self._menu.popup(pos)

    # protected:
    # override
    def mousePressEvent(self, event):
        '''
        @param: event QMouseEvent
        '''
        buttons = event.buttons()
        if buttons == Qt.LeftButton and self.popupMode() == QToolButton.DelayedPopup:
            self._pressTimer.start()

        if buttons == Qt.LeftButton and self.menu() and self.popupMode() == QToolButton.InstantPopup:
            self.setDown(True)
            self._showMenu()
        elif buttons == Qt.RightButton and self.menu() and self._options & self.showMenuOnRightClick:
            self.setDown(True)
            self._showMenu()
        else:
            super().mousePressEvent(event)

    # override
    def mouseReleaseEvent(self, event):
        '''
        @param: event QMouseEvent
        '''
        self._pressTimer.stop()

        button = event.button()
        if button == Qt.MiddleButton and self.rect().contains(event.pos()):
            self.middleMouseClicked.emit()
            self.setDown(False)
        elif button == Qt.LeftButton and self.rect().contains(event.pos()) and \
                event.modifiers() == Qt.ControlModifier:
            self.controlClicked.emit()
            self.setDown(False)
        else:
            super().mouseReleaseEvent(event)

    # override
    def mouseDoubleClickEvent(self, event):
        '''
        @param: event QMouseEvent
        '''
        super().mouseDoubleClickEvent(event)

        self._pressTimer.stop()

        if event.buttons() == Qt.LeftButton:
            self.doubleClicked.emit()

    # override
    def contextMenuEvent(self, event):
        '''
        @param: event QContextMenuEvent
        '''
        # Block to prevent showing both context menu and button menu
        if self.menu() and self._options & self._ShowMenuOnRightClick:
            return

        super().contextMenuEvent(event)

    # override
    def paintEvent(self, event):
        '''
        @param: event QPaintEvent
        '''
        if not (self._options & self._MultiIconOption):
            super().paintEvent(event)
            return

        p = QPainter(self)

        w = self._multiIcon.width()
        h4 = self._multiIcon.height() / 4

        if not self.isEnabled():
            p.drawImage(0, 0, self._multiIcon, 0, h4 * 3, w, h4)
        elif self.isDown():
            p.drawImage(0, 0, self._multiIcon, 0, h4 * 2, w, h4)
        elif self.underMouse():
            p.drawImage(0, 0, self._multiIcon, 0, h4 * 1, w, h4)
        else:
            p.drawImage(0, 0, self._multiIcon, 0, h4 * 0, w, h4)
示例#12
0
class SlideFlip(QWidget):

    # API {{{

    # In addition the isVisible() and setVisible() methods must be present

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

        self.setGeometry(0, 0, 1, 1)
        self._current_width = 0
        self.before_image = self.after_image = None
        self.animation = QPropertyAnimation(self, b'current_width', self)
        self.setVisible(False)
        self.animation.valueChanged.connect(self.update)
        self.animation.finished.connect(self.finished)
        self.flip_forwards = True
        self.setAttribute(Qt.WA_OpaquePaintEvent)

    @property
    def running(self):
        'True iff animation is currently running'
        return self.animation.state() == self.animation.Running

    def initialize(self, image, forwards=True):
        '''
        Initialize the flipper, causes the flipper to show itself displaying
        the full `image`.

        :param image: The image to display as background
        :param forwards: If True flipper will flip forwards, otherwise
                         backwards

        '''
        self.flip_forwards = forwards
        self.before_image = QPixmap.fromImage(image)
        self.after_image = None
        self.setGeometry(0, 0, image.width(), image.height())
        self.setVisible(True)

    def __call__(self, image, duration=0.5):
        '''
        Start the animation. You must have called :meth:`initialize` first.

        :param duration: Animation duration in seconds.

        '''
        if self.running:
            return
        self.after_image = QPixmap.fromImage(image)

        if self.flip_forwards:
            self.animation.setStartValue(image.width())
            self.animation.setEndValue(0)
            t = self.before_image
            self.before_image = self.after_image
            self.after_image = t
            self.animation.setEasingCurve(QEasingCurve(QEasingCurve.InExpo))
        else:
            self.animation.setStartValue(0)
            self.animation.setEndValue(image.width())
            self.animation.setEasingCurve(QEasingCurve(QEasingCurve.OutExpo))

        self.animation.setDuration(duration * 1000)
        self.animation.start()

    # }}}

    def finished(self):
        self.setVisible(False)
        self.before_image = self.after_image = None

    def paintEvent(self, ev):
        if self.before_image is None:
            return
        canvas_size = self.rect()
        p = QPainter(self)
        p.setRenderHints(QPainter.Antialiasing
                         | QPainter.SmoothPixmapTransform)

        p.drawPixmap(canvas_size, self.before_image, self.before_image.rect())
        if self.after_image is not None:
            width = self._current_width
            iw = self.after_image.width()
            sh = min(self.after_image.height(), canvas_size.height())

            if self.flip_forwards:
                source = QRect(max(0, iw - width), 0, width, sh)
            else:
                source = QRect(0, 0, width, sh)

            target = QRect(source)
            target.moveLeft(0)
            p.drawPixmap(target, self.after_image, source)

        p.end()

    def set_current_width(self, val):
        self._current_width = val

    current_width = pyqtProperty('int',
                                 fget=lambda self: self._current_width,
                                 fset=set_current_width)
示例#13
0
class IconProvider(QWidget):
    _instance = None

    def __init__(self):
        super().__init__()
        self._emptyWebImage = QImage()
        self._bookmarkIcon = QIcon()
        self._iconBuffer = []  # QVector<[QUrl, QImage]>
        # TODO: cache limit size
        self._urlImageCache = {}  # QCache<QByteArray, QImage>
        self._iconCacheMutex = Lock()  # QMutex

        self._autoSaver = AutoSaver(self)  # AutoSaver
        self._autoSaver.save.connect(self.saveIconsToDatabase)

    def saveIcon(self, view):
        '''
        @param: view WebView
        '''
        # Don't save icons in private mode
        if gVar.app.isPrivate():
            return

        icon = view.icon(True)
        if icon.isNull():
            return

        ignoredSchemes = ['app', 'ftp', 'file', 'view-source', 'data', 'about']

        if view.url().scheme() in ignoredSchemes:
            return

        for idx in range(len(self._iconBuffer)):
            if self._iconBuffer[idx][0] == view.url():
                self._iconBuffer.pop(idx)
                break

        item = (view.url(), icon.pixmap(16).toImage())

        self._autoSaver.changeOccurred()
        self._iconBuffer.append(item)

    def _bookmarkIcon(self):
        '''
        @return: QIcon
        '''
        return QIcon.fromTheme('bookmarks', self._bookmarkIcon)

    def setBookmarkIcon(self, icon):
        '''
        @param: icon QIcon
        '''
        self._bookmarkIcon = icon

    bookmarkIcon = pyqtProperty(QIcon, _bookmarkIcon, setBookmarkIcon)

    # QStyle equivalent
    @classmethod  # noqa C901
    def standardIcon(cls, icon):
        '''
        @param: icon QStyle::StandardPixmap
        @return: QIcon
        '''
        defIcon = QApplication.style().standardIcon(icon)
        if icon == QStyle.SP_MessageBoxCritical:
            return QIcon.fromTheme('dialog-error', defIcon)
        elif icon == QStyle.SP_MessageBoxInformation:
            return QIcon.fromTheme('dialog-information', defIcon)
        elif icon == QStyle.SP_MessageBoxQuestion:
            return QIcon.fromTheme('dialog-question', defIcon)
        elif icon == QStyle.SP_MessageBoxWarning:
            return QIcon.fromTheme('dialog-warning', defIcon)
        elif icon == QStyle.SP_DialogCloseButton:
            return QIcon.fromTheme('dialog-close', defIcon)
        elif icon == QStyle.SP_BrowserStop:
            return QIcon.fromTheme('progress-stop', defIcon)
        elif icon == QStyle.SP_BrowserReload:
            return QIcon.fromTheme('view-refresh', defIcon)
        elif icon == QStyle.SP_FileDialogToParent:
            return QIcon.fromTheme('go-up', defIcon)
        elif icon == QStyle.SP_ArrowUp:
            return QIcon.fromTheme('go-up', defIcon)
        elif icon == QStyle.SP_ArrowDown:
            return QIcon.fromTheme('go-down', defIcon)
        elif icon == QStyle.SP_ArrowForward:
            if QApplication.layoutDirection() == Qt.RightToLeft:
                return QIcon.fromTheme('go-previous', defIcon)
            else:
                return QIcon.fromTheme('go-next', defIcon)
        elif icon == QStyle.SP_ArrowBack:
            if QApplication.layoutDirection() == Qt.RightToLeft:
                return QIcon.fromTheme('go-next', defIcon)
            else:
                return QIcon.fromTheme('go-previous', defIcon)
        else:
            return defIcon

    @classmethod
    def newTabIcon(cls):
        '''
        @return: QIcon
        '''
        return QIcon.fromTheme('tab-new', QIcon(':/icons/menu/tab-new.svg'))

    @classmethod
    def newWindowIcon(cls):
        '''
        @return: QIcon
        '''
        return QIcon.fromTheme('window-new',
                               QIcon(':/icons/menu/window-new.svg'))

    @classmethod
    def privateBrowsingIcon(cls):
        '''
        @return: QIcon
        '''
        return QIcon.fromTheme('view-private-symbolic',
                               QIcon(':/icons/menu/privatebrowsing.png'))

    @classmethod
    def settingsIcon(cls):
        '''
        @return: QIcon
        '''
        return QIcon.fromTheme('configure', QIcon(':/icons/menu/settings.svg'))

    # Icon for empty page
    @classmethod
    def emptyWebIcon(cls):
        '''
        @return: QIcon
        '''
        return QIcon(QPixmap.fromImage(cls.emptyWebImage()))

    @classmethod
    def emptyWebImage(cls):
        '''
        @return: QIcon
        '''
        if cls.instance()._emptyWebImage.isNull():
            cls.instance()._emptyWebImage = QIcon(
                ':/icons/other/webpage.svg').pixmap(16).toImage()

        return cls.instance()._emptyWebImage

    # Icon for url (only available for urls in history)
    @classmethod
    def iconForUrl(cls, url, allowNull=False):
        '''
        @param: url QUrl
        @return: QIcon
        '''
        return cls.instance()._iconFromImage(cls.imageForUrl(url, allowNull))

    @classmethod
    def imageForUrl(cls, url, allowNull=False):
        '''
        @param: url QUrl
        @return: QImage
        '''
        if not url.path():
            return allowNull and QImage() or cls.emptyWebImage()

        with cls.instance()._iconCacheMutex:
            encUrl = encodeUrl(url)

            # find in urlImageCache
            img = cls.instance()._urlImageCache.get(encUrl, None)
            if img:
                if not img.isNull():
                    return img
                if not allowNull:
                    return cls.emptyWebImage()
                return img

            # find from icon buffer
            for url0, img in cls.instance()._iconBuffer:
                if encodeUrl(url0) == encUrl:
                    return img

            # TODO: is it necessary to use escapeSqlGlobString
            escapedUrl = gVar.appTools.escapeSqlGlobString(
                encUrl.data().decode())

            urlPattern = '%s*' % escapedUrl
            icon = IconsDbModel.select().filter(
                IconsDbModel.url.contains(urlPattern)).first()
            img = QImage()
            if icon:
                img.loadFromData(icon.icon)
            cls.instance()._urlImageCache[encUrl] = img

            if not img.isNull():
                return img
            if not allowNull:
                return cls.emptyWebImage()
            return img

    # Icon for domain (only available for urls in history)
    @classmethod
    def iconForDomain(cls, url, allowNull=False):
        '''
        @param: url QUrl
        @return: QIcon
        '''
        return cls.instance()._iconFromImage(cls.imageForDomain(
            url, allowNull))

    @classmethod
    def imageForDomain(cls, url, allowNull=False):
        '''
        @param: url QUrl
        @return: QIcon
        '''
        if not url.host():
            if allowNull:
                return QImage()
            return cls.emptyWebImage()

        with cls.instance()._iconCacheMutex:
            for url0, img in cls.instance()._iconBuffer:
                if url0.host() == url.host():
                    return img

            # TODO: is it necessary to use escapeSqlGlobString
            escapedHost = gVar.appTools.escapeSqlGlobString(url.host())
            hostPattern = '*%s*' % escapedHost
            icon = IconsDbModel.select().filter(
                IconsDbModel.url.contains(hostPattern)).first()
            img = QImage()
            if icon:
                img.loadFromData(icon.icon)

            if not img.isNull():
                return img
            if not allowNull:
                return cls.emptyWebImage()
            return img

    # public Q_SLOTS:
    def saveIconsToDatabase(self):
        gVar.executor.submit(self._saveIconsToDatabase)

    def _saveIconsToDatabase(self):
        with self._iconCacheMutex:
            for url, img in self._iconBuffer:
                ba = QByteArray()
                buff = QBuffer(ba)
                buff.open(QIODevice.WriteOnly)
                img.save(buff, 'PNG')

                # QByteArray
                encodedUrl = encodeUrl(url)
                self._urlImageCache.pop(encodedUrl, None)

                IconsDbModel.insert(icon=buff.data(),
                        url=encodedUrl.data().decode()) \
                    .on_conflict('replace') \
                    .execute()
            self._iconBuffer.clear()

    def clearOldIconsInDatabase(self):
        # Delete icons for entries older than 6 months
        gVar.executor.submit(self._clearOldIconsInDatabase)

    def _clearOldIconsInDatabase(self):
        date = QDateTime.currentDateTime().addMonths(-6)

        urls = HistoryDbModel.select().where(
            HistoryDbModel.date < date.toMSecsSinceEpoch())
        IconsDbModel.delete().where(IconsDbModel.url.in_(urls)).execute()

    # private:
    def _iconFromImage(self, image):
        '''
        @param: image QImage
        @return: QIcon
        '''
        return QIcon(QPixmap.fromImage(image))

    @classmethod
    def instance(cls):
        if not cls._instance:
            cls._instance = cls()
        return cls._instance
示例#14
0
class ClickableLabel(QLabel):
    def __init__(self, parent=None):
        super().__init__(parent)
        self._themeIcon = ''
        self._fallbackIcon = QIcon()

    def size(self):
        return super().size()

    def setFixedSize(self, sz):
        super().setFixedSize(sz)

    fixedsize = pyqtProperty(QSize, size, setFixedSize)

    def width(self):
        return super().width()

    def setFixedWidth(self, width):
        super().setFixedWidth(width)

    fixedwidth = pyqtProperty(int, width, setFixedWidth)

    def height(self):
        return super().height()

    def setFixedHeight(self, height):
        super().setFixedHeight(height)

    fixedheight = pyqtProperty(int, height, setFixedHeight)

    def themeIcon(self):
        '''
        @return: QString
        '''
        return self._themeIcon

    def setThemeIcon(self, name):
        '''
        @param: name QString
        '''
        self._themeIcon = name
        self._updateIcon()

    themeIcon = pyqtProperty(str, themeIcon, setThemeIcon)

    def fallbackIcon(self):
        '''
        @return: QIcon
        '''
        return self._fallbackIcon

    def setFallbackIcon(self, fallbackIcon):
        '''
        @param: fallbackIcon QIcon
        '''
        self._fallbackIcon = fallbackIcon
        self._updateIcon()

    fallbackIcon = pyqtProperty(QIcon, fallbackIcon, setFallbackIcon)

    # public Q_SIGNALS
    clicked = pyqtSignal(QPoint)
    middleClicked = pyqtSignal(QPoint)

    # private:
    def _updateIcon(self):
        if self._themeIcon:
            icon = QIcon.fromTheme(self._themeIcon)
            if not icon.isNull():
                self.setPixmap(icon.pixmap(self.size()))
                return

        if self._fallbackIcon:
            self.setPixmap(self._fallbackIcon.pixmap(self.size()))

    # override
    def resizeEvent(self, event):
        '''
        @param: event QResizeEvent
        '''
        super().resizeEvent(event)
        self._updateIcon()

    def mouseReleaseEvent(self, event):
        '''
        @param: event QMouseEvent
        '''
        evtBtn = event.button()
        if evtBtn == Qt.LeftButton and self.rect().contains(event.pos()):
            if event.modifiers() == Qt.ControlModifier:
                self.middleClicked.emit(event.globalPos())
            else:
                self.clicked.emit(event.globalPos())
        elif evtBtn == Qt.MiddleButton and self.rect().contains(event.pos()):
            self.middleClicked.emit(event.globalPos())
        else:
            super().mouseReleaseEvent(event)
class OpenSearchEngine(QObject):
    # Q_SIGNALS:
    imageChanged = pyqtSignal()
    suggestions = pyqtSignal([])  # suggestions

    # typedef QPair<QString, QString> Parameter
    # typedef QList<Parameter> Parameters
    def __init__(self, parent=None):
        super().__init__(parent)
        self._name = ''
        self._description = ''

        self._imageUrl = ''
        self._image = QImage()

        self._searchUrlTemplate = ''
        self._suggestionsUrlTemplate = ''
        self._searchParameters = []  # QList<Parameter>
        self._suggestionsParameters = []  # QList<Parameter>
        self._searchMethod = ''
        self._suggestionsMethod = ''

        self._preparedSuggestionsParameters = QByteArray()
        self._preparedSuggestionsUrl = ''

        self._requestMethods = {
        }  # QMap<QString, QNetworkAccessManager::Operation>

        self._networkAccessManager = None  # QNetworkAccessManager
        self._suggestionsReply = None  # QNetworkReply

        self._delegate = None  # OpenSearchEngineDelegate

    # public:
    def name(self):
        '''
        @return: QString
        '''
        pass

    def setName(self, name):
        '''
        @param: name QString
        '''
        pass

    def description(self):
        '''
        @return: QString
        '''
        pass

    def setDescription(self, description):
        '''
        @param: description QString
        '''
        pass

    def searchUrlTemplate(self):
        '''
        @return: QString
        '''
        pass

    def setSearchUrlTemplate(self, searchUrl):
        '''
        @param: searchUrl QString
        '''
        pass

    def searchUrl(self, searchTerm):
        '''
        @param: searchTerm QString
        @return: QUrl
        '''
        pass

    def getPostData(self, searchTerm):
        '''
        @param: searchTerm QString
        @return: QByteArray
        '''
        pass

    def providesSuggestions(self):
        '''
        @return: bool
        '''
        pass

    def suggestionsUrlTemplate(self):
        '''
        @return: QString
        '''
        pass

    def setSuggestionsUrlTemplate(self, suggestionsUrl):
        '''
        @param: suggestionsUrl QString
        '''
        pass

    def suggestionsUrl(self, searchTerm):
        '''
        @param: searchTerm QString
        '''
        pass

    def searchParameters(self):
        '''
        @return: QList<Parameter> (which Parameter is QList<QString, QString>)
        '''
        pass

    def setSearchParameters(self, searchParameters):
        '''
        @param: searchParameters QList<Parameter> (which Parameter is QList<QString, QString>)
        '''
        pass

    def suggestionsParameters(self):
        '''
        @return: QList<Parameter>
        '''
        pass

    def setSuggestionsParameters(self, suggestionsParameters):
        '''
        @param: suggestionsParameters QList<Parameter>
        '''
        pass

    def searchMethod(self):
        '''
        @return: QString
        '''
        pass

    def setSearchMethod(self, method):
        '''
        @param: method QString
        '''
        pass

    def suggestionsMethod(self):
        '''
        @return: QString
        '''
        pass

    def setSuggestionsMethod(self, method):
        '''
        @param: method QString
        '''
        pass

    def imageUrl(self):
        '''
        @return: QString
        '''
        pass

    def setImageUrl(self, url):
        '''
        @param: url QString
        '''
        pass

    def image(self):
        '''
        @return: QImage
        '''
        pass

    def setImage(self, image):
        '''
        @param: image QImage
        '''
        pass

    def isValid(self):
        '''
        @return: bool
        '''
        pass

    def setSuggestionsUrl(self, string):
        '''
        @param: string QString
        '''
        pass

    def setSuggestionsParametersByBytes(self, parameters):
        '''
        @param: parameters QByteArray
        '''
        pass

    def getSuggestionsUrl(self):
        '''
        @return: QString
        '''
        pass

    def getSuggestionsParameters(self):
        '''
        @return: QByteArray
        '''
        pass

    def networkAccessManager(self):
        '''
        @return: QNetworkAccessManager
        '''
        pass

    def setNetworkAccessManager(self, networkAccessManager):
        '''
        @param: networkAccessManager QNetworkAccessManager
        '''
        pass

    def delegate(self):
        '''
        @return: OpenSearchEngineDelegate
        '''
        pass

    def setDelegate(self, delegate):
        '''
        @param: delegate OpenSearchEngineDelegate
        '''
        pass

    def __eq__(self, other):
        pass

    def __lt__(self, other):
        pass

    # public Q_SLOTS:
    def requestSuggestions(self, searchTerm):
        '''
        @param: searchTerm QString
        '''
        pass

    def requestSearchResults(self, searchTerm):
        '''
        @param: searchTerm QString
        '''
        pass

    name = pyqtProperty(str, name, setName)
    description = pyqtProperty(str, description, setDescription)
    searchUrlTemplate = pyqtProperty(str, searchUrlTemplate,
                                     setSearchUrlTemplate)
    searchParameters = pyqtProperty(list, searchParameters,
                                    setSearchParameters)
    searchMethod = pyqtProperty(str, searchMethod, setSearchMethod)
    suggestionsUrlTemplate = pyqtProperty(str, suggestionsUrlTemplate,
                                          setSuggestionsUrlTemplate)
    suggestionsParameters = pyqtProperty(list, suggestionsParameters,
                                         setSuggestionsParameters)
    suggestionsMethod = pyqtProperty(str, suggestionsMethod,
                                     setSuggestionsMethod)
    providesSuggestions = pyqtProperty(bool, providesSuggestions)
    imageUrl = pyqtProperty(str, imageUrl, setImageUrl)
    valid = pyqtProperty(bool, isValid)
    networkAccessManager = pyqtProperty(QNetworkAccessManager,
                                        networkAccessManager,
                                        setNetworkAccessManager)

    # protected:
    @staticmethod
    def _parseTemplate(cls, searchTerm, searchTemplate):
        pass

    def _loadImage(self):
        pass

    # private Q_SLOTS:
    def _imageObtained(self):
        pass

    def _suggestionsObtained(self):
        pass
class ExternalJsObject(QObject):
    _s_extraObjects = {}  # QHash<QString, QObject>
    def __init__(self, page):
        '''
        @param: page WebPage
        '''
        super().__init__(page)
        self._page = page  # WebPage
        self._autoFill = None  # AutoFillJsObject

        self._autoFill = AutoFillJsObject(self)

    def page(self):
        '''
        @return: WebPage
        '''
        return self._page

    @classmethod
    def setupWebChannel(cls, webChannel, page):
        '''
        @param: webChannel QWebChannel
        @param: page WebPage
        '''
        webChannel.registerObject('app_object', ExternalJsObject(page))

        for key, val in cls._s_extraObjects.items():
            webChannel.registerObject('app_' + key, val)

    @classmethod
    def registerExtraObject(cls, id_, object_):
        '''
        @param: id_ QString
        @param: object_ QObject
        '''
        cls._s_extraObjects[id_] = object_

    @classmethod
    def unregisterExtraObject(cls, object_):
        '''
        @param: object_ QObject
        '''
        removeKey = None
        for key, val in cls._s_extraObjects.items():
            if val == object_:
                removeKey = key
                break

        if removeKey is not None:
            cls._s_extraObjects.pop(removeKey)

    # private:
    def _speedDial(self):
        '''
        @return: QObject
        '''
        if self._page.url().toString() != 'app:speeddial':
            return None

        return gVar.app.plugins().speedDial()

    speedDial = pyqtProperty(QObject, _speedDial, constant=True)

    def _autoFill(self):
        '''
        @return: QObject
        '''
        return self._autoFill

    autoFill = pyqtProperty(QObject, _autoFill, constant=True)

    def _recovery(self):
        '''
        @return: QObject
        '''
        if not gVar.app.restoreManager() or self._page.url().toString() != 'app:restore':
            return None

        return gVar.app.restoreManager().recoveryObject(self._page)

    recovery = pyqtProperty(QObject, _recovery, constant=True)
示例#17
0
class CoverView(QWidget):  # {{{

    cover_changed = pyqtSignal(object, object)
    cover_removed = pyqtSignal(object)
    open_cover_with = pyqtSignal(object, object)
    search_internet = pyqtSignal(object)

    def __init__(self, vertical, parent=None):
        QWidget.__init__(self, parent)
        self._current_pixmap_size = QSize(120, 120)
        self.vertical = vertical

        self.animation = QPropertyAnimation(self, b'current_pixmap_size', self)
        self.animation.setEasingCurve(QEasingCurve(QEasingCurve.OutExpo))
        self.animation.setDuration(1000)
        self.animation.setStartValue(QSize(0, 0))
        self.animation.valueChanged.connect(self.value_changed)

        self.setSizePolicy(
                QSizePolicy.Expanding if vertical else QSizePolicy.Minimum,
                QSizePolicy.Expanding)

        self.default_pixmap = QPixmap(I('default_cover.png'))
        self.pixmap = self.default_pixmap
        self.pwidth = self.pheight = None
        self.data = {}

        self.do_layout()

    def value_changed(self, val):
        self.update()

    def setCurrentPixmapSize(self, val):
        self._current_pixmap_size = val

    def do_layout(self):
        if self.rect().width() == 0 or self.rect().height() == 0:
            return
        pixmap = self.pixmap
        pwidth, pheight = pixmap.width(), pixmap.height()
        try:
            self.pwidth, self.pheight = fit_image(pwidth, pheight,
                            self.rect().width(), self.rect().height())[1:]
        except:
            self.pwidth, self.pheight = self.rect().width()-1, \
                    self.rect().height()-1
        self.current_pixmap_size = QSize(self.pwidth, self.pheight)
        self.animation.setEndValue(self.current_pixmap_size)

    def show_data(self, data):
        self.animation.stop()
        same_item = getattr(data, 'id', True) == self.data.get('id', False)
        self.data = {'id':data.get('id', None)}
        if data.cover_data[1]:
            self.pixmap = QPixmap.fromImage(data.cover_data[1])
            if self.pixmap.isNull() or self.pixmap.width() < 5 or \
                    self.pixmap.height() < 5:
                self.pixmap = self.default_pixmap
        else:
            self.pixmap = self.default_pixmap
        self.do_layout()
        self.update()
        if (not same_item and not config['disable_animations'] and
                self.isVisible()):
            self.animation.start()

    def paintEvent(self, event):
        canvas_size = self.rect()
        width = self.current_pixmap_size.width()
        extrax = canvas_size.width() - width
        if extrax < 0:
            extrax = 0
        x = int(extrax/2.)
        height = self.current_pixmap_size.height()
        extray = canvas_size.height() - height
        if extray < 0:
            extray = 0
        y = int(extray/2.)
        target = QRect(x, y, width, height)
        p = QPainter(self)
        p.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform)
        try:
            dpr = self.devicePixelRatioF()
        except AttributeError:
            dpr = self.devicePixelRatio()
        spmap = self.pixmap.scaled(target.size() * dpr, Qt.KeepAspectRatio, Qt.SmoothTransformation)
        spmap.setDevicePixelRatio(dpr)
        p.drawPixmap(target, spmap)
        if gprefs['bd_overlay_cover_size']:
            sztgt = target.adjusted(0, 0, 0, -4)
            f = p.font()
            f.setBold(True)
            p.setFont(f)
            sz = u'\u00a0%d x %d\u00a0'%(self.pixmap.width(), self.pixmap.height())
            flags = Qt.AlignBottom|Qt.AlignRight|Qt.TextSingleLine
            szrect = p.boundingRect(sztgt, flags, sz)
            p.fillRect(szrect.adjusted(0, 0, 0, 4), QColor(0, 0, 0, 200))
            p.setPen(QPen(QColor(255,255,255)))
            p.drawText(sztgt, flags, sz)
        p.end()

    current_pixmap_size = pyqtProperty('QSize',
            fget=lambda self: self._current_pixmap_size,
            fset=setCurrentPixmapSize
            )

    def contextMenuEvent(self, ev):
        from calibre.gui2.open_with import populate_menu, edit_programs
        cm = QMenu(self)
        paste = cm.addAction(_('Paste cover'))
        copy = cm.addAction(_('Copy cover'))
        remove = cm.addAction(_('Remove cover'))
        gc = cm.addAction(_('Generate cover from metadata'))
        cm.addSeparator()
        if not QApplication.instance().clipboard().mimeData().hasImage():
            paste.setEnabled(False)
        copy.triggered.connect(self.copy_to_clipboard)
        paste.triggered.connect(self.paste_from_clipboard)
        remove.triggered.connect(self.remove_cover)
        gc.triggered.connect(self.generate_cover)

        m = QMenu(_('Open cover with...'))
        populate_menu(m, self.open_with, 'cover_image')
        if len(m.actions()) == 0:
            cm.addAction(_('Open cover with...'), self.choose_open_with)
        else:
            m.addSeparator()
            m.addAction(_('Add another application to open cover...'), self.choose_open_with)
            m.addAction(_('Edit Open with applications...'), partial(edit_programs, 'cover_image', self))
            cm.ocw = m
            cm.addMenu(m)
        cm.si = m = create_search_internet_menu(self.search_internet.emit)
        cm.addMenu(m)
        cm.exec_(ev.globalPos())

    def open_with(self, entry):
        id_ = self.data.get('id', None)
        if id_ is not None:
            self.open_cover_with.emit(id_, entry)

    def choose_open_with(self):
        from calibre.gui2.open_with import choose_program
        entry = choose_program('cover_image', self)
        if entry is not None:
            self.open_with(entry)

    def copy_to_clipboard(self):
        QApplication.instance().clipboard().setPixmap(self.pixmap)

    def paste_from_clipboard(self, pmap=None):
        if not isinstance(pmap, QPixmap):
            cb = QApplication.instance().clipboard()
            pmap = cb.pixmap()
            if pmap.isNull() and cb.supportsSelection():
                pmap = cb.pixmap(cb.Selection)
        if not pmap.isNull():
            self.update_cover(pmap)

    def update_cover(self, pmap=None, cdata=None):
        if pmap is None:
            pmap = QPixmap()
            pmap.loadFromData(cdata)
        if pmap.isNull():
            return
        if pmap.hasAlphaChannel():
            pmap = QPixmap.fromImage(blend_image(image_from_x(pmap)))
        self.pixmap = pmap
        self.do_layout()
        self.update()
        self.update_tooltip(getattr(self.parent(), 'current_path', ''))
        if not config['disable_animations']:
            self.animation.start()
        id_ = self.data.get('id', None)
        if id_ is not None:
            self.cover_changed.emit(id_, cdata or pixmap_to_data(pmap))

    def generate_cover(self, *args):
        book_id = self.data.get('id')
        if book_id is not None:
            from calibre.ebooks.covers import generate_cover
            from calibre.gui2.ui import get_gui
            mi = get_gui().current_db.new_api.get_metadata(book_id)
            cdata = generate_cover(mi)
            self.update_cover(cdata=cdata)

    def remove_cover(self):
        id_ = self.data.get('id', None)
        self.pixmap = self.default_pixmap
        self.do_layout()
        self.update()
        if id_ is not None:
            self.cover_removed.emit(id_)

    def update_tooltip(self, current_path):
        try:
            sz = self.pixmap.size()
        except:
            sz = QSize(0, 0)
        self.setToolTip(
            '<p>'+_('Double click to open the Book details window') +
            '<br><br>' + _('Path') + ': ' + current_path +
            '<br><br>' + _('Cover size: %(width)d x %(height)d pixels')%dict(
                width=sz.width(), height=sz.height())
        )
示例#18
0
class FancyTab(QWidget):
    def __init__(self, tabbar):
        '''
        @param: tabbar QWidget
        '''
        super().__init__(tabbar)
        self.icon = QIcon()
        self.text = ''
        self._animator = QPropertyAnimation()
        self._tabbar = tabbar  # QWidget
        self._fader = 0.0

        self._animator.setPropertyName(b'fader')
        self._animator.setTargetObject(self)
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum)

    def fader(self):
        '''
        @return: float
        '''
        return self._fader

    def setFader(self, value):
        '''
        @param: value float
        '''
        self._fader = value
        self._tabbar.update()

    fader = pyqtProperty(float, fader, setFader)

    # override
    def sizeHint(self):
        '''
        @return: QSize
        '''
        boldFont = QFont(self.font())
        boldFont.setPointSizeF(styleHelper.sidebarFontSize())
        boldFont.setBold(True)
        fm = QFontMetrics(boldFont)
        spacing = 8
        width = 60 + spacing + 2
        iconHeight = 32
        ret = QSize(width, iconHeight + spacing + fm.height())
        return ret

    def fadeIn(self):
        self._animator.stop()
        self._animator.setDuration(80)
        self._animator.setEndValue(40)
        self._animator.start()

    def fadeOut(self):
        self._animator.stop()
        self._animator.setDuration(160)
        self._animator.setEndValue(0)
        self._animator.start()

    # protected:
    # override
    def enterEvent(self, event):
        self.fadeIn()

    # override
    def leaveEvent(self, event):
        self.fadeOut()