def subscribe_to_field_test(self, component, bla):

        field = str(bla)

        if (component, field) in self._m3field_values:
            rospy.logwarn("Already subscribed to field. Exiting.")
            return


        dt = Floats
        topic = str("/meka_ros_pub/" + component + "/" + field)
        self._dashboard_mekaros_subs[(component, field)] = rospy.Subscriber(topic, dt, self.field_callback, (component, field))
        self._m3field_values[(component, field)] = []

        name_label = QLabel(component + "->" + field + ":")
        for val in range(bla):
            label = QLabel(str(val)[:5])
            label.setStyleSheet("border: 2px solid grey");
            self._m3field_values[(component, field)].append(label)

        idx = self.inspection_layout.rowCount()

        plot_pixmap = QPixmap(self._path + "/plot.png")
        plot_icon = QIcon(plot_pixmap);
        plot_btn = QPushButton()
        plot_btn.setIcon(plot_icon)
        plot_btn.setIconSize(plot_pixmap.rect().size())
        plot_btn.setFixedWidth(30)
        plot_btn.clicked.connect(partial(self.plot_values, component, field))

        close_pixmap = QPixmap(self._path + "/close.png")
        close_icon = QIcon(close_pixmap);
        close_btn = QPushButton()
        close_btn.setIcon(close_icon)
        close_btn.setIconSize(close_pixmap.rect().size())
        close_btn.setFixedWidth(30)
        close_btn.clicked.connect(partial(self.remove_row, self.inspection_layout, idx, False, component, field))

        self.inspection_layout.addWidget(name_label, idx, 0)
        val_layout = QHBoxLayout()
        for label in self._m3field_values[(component, field)]:
            print label
            val_layout.addWidget(label);
        self.inspection_layout.addLayout(val_layout, idx, 1)
        self.inspection_layout.addWidget(plot_btn, idx, 2)
        self.inspection_layout.addWidget(close_btn, idx, 3)
 def subscribe_to_field(self, component, field):
     
     if (component, field) in self._m3field_values:
         rospy.logwarn("Already subscribed to field. Exiting.")
         return
             
     try:
         resp = self.req_vals_client(component, field, "", self.hz_rate.value())
     except rospy.ServiceException:
         rospy.logerr("Could not call request_values")
         return
     
     print resp.values
     
     dt = Floats
     topic = str("/meka_ros_pub/"+component+"/"+field)
     self._dashboard_mekaros_subs[(component, field)] = rospy.Subscriber(topic, dt, self.field_callback, (component, field))
     self._m3field_values[(component, field)] = []
     
     name_label = QLabel(component+"->"+field+":")
     for val in resp.values:
         label = QLabel(str(val)[:5])
         label.setStyleSheet("border: 2px solid grey");
         self._m3field_values[(component, field)].append(label)
     
     idx = self.inspection_layout.rowCount()
     
     plot_pixmap = QPixmap(self._path + "/plot.png")
     plot_icon = QIcon(plot_pixmap);
     #plot_btn = QPushButton()
     #plot_btn.setIcon(plot_icon)
     #plot_btn.setIconSize(plot_pixmap.rect().size())
     #plot_btn.setFixedWidth(30)
     #plot_btn.clicked.connect(partial(self.plot_values, component, field))
     
     close_pixmap = QPixmap(self._path + "/close.png")
     close_icon = QIcon(close_pixmap);
     close_btn = QPushButton()
     close_btn.setIcon(close_icon)
     close_btn.setIconSize(close_pixmap.rect().size())
     close_btn.setFixedWidth(30)
     close_btn.clicked.connect(partial(self.remove_row, self.inspection_layout, idx, False, component, field))
     
     self.inspection_layout.addWidget(name_label, idx, 0)
     val_layout = QHBoxLayout()
     for label in self._m3field_values[(component, field)]:
         val_layout.addWidget(label);
     self.inspection_layout.addLayout(val_layout, idx, 1)
     #self.inspection_layout.addWidget(plot_btn, idx, 2)
     self.inspection_layout.addWidget(close_btn, idx, 3)
Exemple #3
0
def missing_icon():
    global _missing_icon
    if _missing_icon is None:
        p = QPixmap(ICON_SIZE, ICON_SIZE)
        p.fill(Qt.transparent)
        painter = QPainter(p)
        pal = QApplication.instance().palette()
        painter.setPen(QPen(pal.color(pal.Text), 0, Qt.DashLine))
        margin = 3
        r = p.rect().adjusted(margin, margin, -margin, -margin)
        painter.drawRect(r)
        painter.end()
        _missing_icon = QIcon(p)
    return _missing_icon
    def testbaloon(self, name):

        name_label = QLabel(name)
        lineedit = QLineEdit("random")
        name_label.setBuddy(lineedit);

        close_btn = QPushButton()

        pixmap = QPixmap(self._path + "/close.png")
        icon = QIcon(pixmap);
        close_btn.setIcon(icon)
        close_btn.setIconSize(pixmap.rect().size())

        idx = self.inspection_layout.rowCount()

        close_btn.clicked.connect(partial(self.remove_row, self.inspection_layout, idx, False))
        self.inspection_layout.addWidget(name_label, idx, 0);
        self.inspection_layout.addWidget(lineedit, idx, 1);
        self.inspection_layout.addWidget(close_btn, idx, 2);
Exemple #5
0
    def _exportImageToPNG(self, filename=None):
        if not filename:
            if not self._export_png_dialog:
                dialog = self._export_png_dialog = QFileDialog(
                    self, "Export image to PNG", ".", "*.png")
                dialog.setDefaultSuffix("png")
                dialog.setFileMode(QFileDialog.AnyFile)
                dialog.setAcceptMode(QFileDialog.AcceptSave)
                dialog.setModal(True)
                dialog.filesSelected['QStringList'].connect(
                    self._exportImageToPNG)
                # attempt to add limit 4K option - not available on Ubuntu Unity
                layout = dialog.layout()
                if layout is not None:
                    checkbox = QCheckBox("Limit to 4K image")
                    checkbox.setChecked(False)
                    checkbox.setToolTip("Limits the image output to 4K")
                    checkbox.toggled.connect(self._exportImageResolution)
                    layout.addWidget(checkbox)
                    dialog.setLayout(layout)
            return self._export_png_dialog.exec_() == QDialog.Accepted
        busy = BusyIndicator()
        if isinstance(filename, QStringList):
            filename = filename[0]
        filename = str(filename)
        # get image dimensions
        nx, ny = self.image.imageDims()
        # export either max resolution possible or default to 4K. If image is small then no scaling occurs.
        if not self._exportMaxRes:
            # get free memory. Note: Linux only!
            import os
            total_memory, used_memory, free_memory = map(
                int,
                os.popen('free -t -m').readlines()[-1].split()[1:])
            # use 90% of free memory available
            free_memory = free_memory * 0.9
            # use an approximation to find the max image size that can be generated
            if nx >= ny and nx > free_memory:
                scale_factor = round(free_memory / nx, 1)
            elif ny > nx and ny > free_memory:
                scale_factor = round(free_memory / ny, 1)
            else:
                scale_factor = 1
        else:
            # default to 4K
            if nx > 4000:
                scale_factor = 4000 / nx
            elif ny > nx and ny > 4000:
                scale_factor = 4000 / ny
            else:
                scale_factor = 1

        # make QPixmap
        nx = nx * scale_factor
        ny = ny * scale_factor
        (l0, l1), (m0, m1) = self.image.getExtents()
        pixmap = QPixmap(nx, ny)
        painter = QPainter(pixmap)
        # use QwtPlot implementation of draw canvas, since we want to avoid caching
        xmap = QwtScaleMap()
        xmap.setPaintInterval(0, nx)
        xmap.setScaleInterval(l1, l0)
        ymap = QwtScaleMap()
        ymap.setPaintInterval(ny, 0)
        ymap.setScaleInterval(m0, m1)
        # call painter with clear cache option for consistent file size output.
        self.image.draw(painter, xmap, ymap, pixmap.rect(), use_cache=False)
        painter.end()
        # save to file
        try:
            pixmap.save(filename, "PNG")
            # clean up export items
            pixmap.detach()
            del xmap
            del ymap
            del pixmap
            del painter
        except Exception as exc:
            self._imgman.signalShowErrorMessage[str, int].emit(
                "Error writing %s: %s" % (filename, str(exc)), 3000)
            busy.reset_cursor()
        else:
            busy.reset_cursor()
            self._imgman.signalShowMessage[str, int].emit(
                "Exported image to file %s" % filename, 3000)
class MetadataComparisonDialog(SizePersistedDialog, Ui_Dialog, Logger):
    BORDER_COLOR = "#FDFF99"
    BORDER_WIDTH = 5
    COVER_ICON_SIZE = 200
    MISMATCH_COLOR = QColor(0xFD, 0xFF, 0x99)

    marvin_device_status_changed = pyqtSignal(dict)

    def accept(self):
        self._log_location()
        super(MetadataComparisonDialog, self).accept()

    def close(self):
        self._log_location()
        super(MetadataComparisonDialog, self).close()

    def dispatch_button_click(self, button):
        '''
        BUTTON_ROLES = ['AcceptRole', 'RejectRole', 'DestructiveRole', 'ActionRole',
                        'HelpRole', 'YesRole', 'NoRole', 'ApplyRole', 'ResetRole']
        '''
        self._log_location()
        if self.bb.buttonRole(button) == QDialogButtonBox.AcceptRole:
            self._log("AcceptRole")
            self.accept()
        elif self.bb.buttonRole(button) == QDialogButtonBox.RejectRole:
            self.close()

    def esc(self, *args):
        self.close()

    def initialize(self, parent, book_id, cid, installed_book, enable_metadata_updates, marvin_db_path):
        '''
        __init__ is called on SizePersistedDialog()
        shared attributes of interest:
            .authors
            .author_sort
            .cover_hash
            .pubdate
            .publisher
            .rating
            .series
            .series_index
            .title
            .title_sort
            .comments
            .tags
            .uuid
        '''
        self.setupUi(self)
        self.book_id = book_id
        self.cid = cid
        self.connected_device = parent.opts.gui.device_manager.device
        self.installed_book = installed_book
        self.marvin_db_path = marvin_db_path
        self.opts = parent.opts
        self.parent = parent
        self.stored_command = None
        self.verbose = parent.verbose
        self.BORDER_LR = 4
        self.BORDER_TB = 8
        self.GREY_FG = '<font style="color:#A0A0A0">{0}</font>'
        self.YELLOW_BG = '<font style="background:#FDFF99">{0}</font>'

        self._log_location(installed_book.title)

        # Subscribe to Marvin driver change events
        self.connected_device.marvin_device_signals.reader_app_status_changed.connect(
            self.marvin_status_changed)

        #self._log("mismatches:\n%s" % repr(installed_book.metadata_mismatches))
        self.mismatches = installed_book.metadata_mismatches

        self._populate_title()
        self._populate_title_sort()
        self._populate_series()
        self._populate_authors()
        self._populate_author_sort()
        self._populate_uuid()
        self._populate_covers()
        self._populate_subjects()
        self._populate_publisher()
        self._populate_pubdate()
        self._populate_rating()
        self._populate_description()

        # ~~~~~~~~ Export to Marvin button ~~~~~~~~
        self.export_to_marvin_button.setIcon(QIcon(os.path.join(self.parent.opts.resources_path,
                                                   'icons',
                                                   'from_calibre.png')))
        self.export_to_marvin_button.clicked.connect(partial(self.store_command, 'export_metadata'))
        self.export_to_marvin_button.setEnabled(enable_metadata_updates)

        # ~~~~~~~~ Import from Marvin button ~~~~~~~~
        self.import_from_marvin_button.setIcon(QIcon(os.path.join(self.parent.opts.resources_path,
                                                     'icons',
                                                     'from_marvin.png')))
        self.import_from_marvin_button.clicked.connect(partial(self.store_command, 'import_metadata'))
        self.import_from_marvin_button.setEnabled(enable_metadata_updates)

        # If no calibre book, or no mismatches, adjust the display accordingly
        if not self.cid:
            #self._log("self.cid: %s" % repr(self.cid))
            #self._log("self.mismatches: %s" % repr(self.mismatches))
            self.calibre_gb.setVisible(False)
            self.import_from_marvin_button.setVisible(False)
            self.setWindowTitle(u'Marvin metadata')
        elif not self.mismatches:
            # Show both panels, but hide the transfer buttons
            self.export_to_marvin_button.setVisible(False)
            self.import_from_marvin_button.setVisible(False)
        else:
            self.setWindowTitle(u'Metadata Summary')

        if False:
            # Set the Marvin QGroupBox to Marvin red
            marvin_red = QColor()
            marvin_red.setRgb(189, 17, 20, alpha=255)
            palette = QPalette()
            palette.setColor(QPalette.Background, marvin_red)
            self.marvin_gb.setPalette(palette)

        # ~~~~~~~~ Add a Close or Cancel button ~~~~~~~~
        self.close_button = QPushButton(QIcon(I('window-close.png')), 'Close')
        if self.mismatches:
            self.close_button.setText('Cancel')
        self.bb.addButton(self.close_button, QDialogButtonBox.RejectRole)

        self.bb.clicked.connect(self.dispatch_button_click)

        # Restore position
        self.resize_dialog()

    def marvin_status_changed(self, cmd_dict):
        '''

        '''
        self.marvin_device_status_changed.emit(cmd_dict)
        command = cmd_dict['cmd']

        self._log_location(command)

        if command in ['disconnected', 'yanked']:
            self._log("closing dialog: %s" % command)
            self.close()

    def store_command(self, command):
        '''
        '''
        self._log_location(command)
        self.stored_command = command
        self.accept()

    def _populate_authors(self):
        if 'authors' in self.mismatches:
            cs_authors = ', '.join(self.mismatches['authors']['calibre'])
            self.calibre_authors.setText(self.YELLOW_BG.format(cs_authors))
            ms_authors = ', '.join(self.mismatches['authors']['Marvin'])
            self.marvin_authors.setText(self.YELLOW_BG.format(ms_authors))
        else:
            authors = ', '.join(self.installed_book.authors)
            self.calibre_authors.setText(authors)
            self.marvin_authors.setText(authors)

    def _populate_author_sort(self):
        if 'author_sort' in self.mismatches:
            cs_author_sort = self.mismatches['author_sort']['calibre']
            self.calibre_author_sort.setText(self.YELLOW_BG.format(cs_author_sort))
            ms_author_sort = self.mismatches['author_sort']['Marvin']
            self.marvin_author_sort.setText(self.YELLOW_BG.format(ms_author_sort))
        else:
            author_sort = self.installed_book.author_sort
            self.calibre_author_sort.setText(self.GREY_FG.format(author_sort))
            self.marvin_author_sort.setText(self.GREY_FG.format(author_sort))

    def _populate_covers(self):
        '''
        Display calibre cover for both unless mismatch
        '''
        def _fetch_marvin_cover(border_width=0):
            '''
            Retrieve LargeCoverJpg from cache
            '''
            #self._log_location('border_width: {0}'.format(border_width))
            con = sqlite3.connect(self.marvin_db_path)
            with con:
                con.row_factory = sqlite3.Row

                # Fetch Hash from mainDb
                cover_cur = con.cursor()
                cover_cur.execute('''SELECT
                                      Hash
                                     FROM Books
                                     WHERE ID = '{0}'
                                  '''.format(self.book_id))
                row = cover_cur.fetchone()

            book_hash = row[b'Hash']
            large_covers_subpath = self.connected_device._cover_subpath(size="large")
            cover_path = '/'.join([large_covers_subpath, '%s.jpg' % book_hash])
            stats = self.parent.ios.exists(cover_path)
            if stats:
                self._log("fetching large cover from cache")
                #self._log("cover size: {:,} bytes".format(int(stats['st_size'])))
                cover_bytes = self.parent.ios.read(cover_path, mode='rb')
                m_image = QImage()
                m_image.loadFromData(cover_bytes)

                if border_width:
                    # Construct a QPixmap with oversized yellow background
                    m_image = m_image.scaledToHeight(
                        self.COVER_ICON_SIZE - border_width * 2,
                        Qt.SmoothTransformation)

                    self.m_pixmap = QPixmap(
                        QSize(m_image.width() + border_width * 2,
                              m_image.height() + border_width * 2))

                    m_painter = QPainter(self.m_pixmap)
                    m_painter.setRenderHints(m_painter.Antialiasing)

                    m_painter.fillRect(self.m_pixmap.rect(), self.MISMATCH_COLOR)
                    m_painter.drawImage(border_width,
                                        border_width,
                                        m_image)
                else:
                    m_image = m_image.scaledToHeight(
                        self.COVER_ICON_SIZE,
                        Qt.SmoothTransformation)

                    self.m_pixmap = QPixmap(
                        QSize(m_image.width(),
                              m_image.height()))

                    m_painter = QPainter(self.m_pixmap)
                    m_painter.setRenderHints(m_painter.Antialiasing)

                    m_painter.drawImage(0, 0, m_image)

                self.marvin_cover.setPixmap(self.m_pixmap)
            else:
                # No cover available, use generic
                self._log("No cached cover, using generic")
                pixmap = QPixmap()
                pixmap.load(I('book.png'))
                pixmap = pixmap.scaled(self.COVER_ICON_SIZE,
                                       self.COVER_ICON_SIZE,
                                       aspectRatioMode=Qt.KeepAspectRatio,
                                       transformMode=Qt.SmoothTransformation)
                self.marvin_cover.setPixmap(pixmap)

        self.calibre_cover.setMaximumSize(QSize(self.COVER_ICON_SIZE, self.COVER_ICON_SIZE))
        self.calibre_cover.setText('')
        self.calibre_cover.setScaledContents(False)

        self.marvin_cover.setMaximumSize(QSize(self.COVER_ICON_SIZE, self.COVER_ICON_SIZE))
        self.marvin_cover.setText('')
        self.marvin_cover.setScaledContents(False)

        if self.cid:
            db = self.opts.gui.current_db
            if 'cover_hash' not in self.mismatches:
                mi = db.get_metadata(self.cid, index_is_id=True, get_cover=True, cover_as_data=True)

                c_image = QImage()
                if mi.has_cover:
                    c_image.loadFromData(mi.cover_data[1])
                    c_image = c_image.scaledToHeight(self.COVER_ICON_SIZE,
                                                     Qt.SmoothTransformation)
                    self.c_pixmap = QPixmap(QSize(c_image.width(),
                                                  c_image.height()))
                    c_painter = QPainter(self.c_pixmap)
                    c_painter.setRenderHints(c_painter.Antialiasing)
                    c_painter.drawImage(0, 0, c_image)
                else:
                    c_image.load(I('book.png'))
                    c_image = c_image.scaledToWidth(135,
                                                    Qt.SmoothTransformation)
                    # Construct a QPixmap with dialog background
                    self.c_pixmap = QPixmap(
                        QSize(c_image.width(),
                              c_image.height()))
                    c_painter = QPainter(self.c_pixmap)
                    c_painter.setRenderHints(c_painter.Antialiasing)
                    bgcolor = self.palette().color(QPalette.Background)
                    c_painter.fillRect(self.c_pixmap.rect(), bgcolor)
                    c_painter.drawImage(0, 0, c_image)

                # Set calibre cover
                self.calibre_cover.setPixmap(self.c_pixmap)

                if self.opts.prefs.get('development_mode', False):
                    # Show individual covers
                    _fetch_marvin_cover()
                else:
                    # Show calibre cover on both sides
                    self.marvin_cover.setPixmap(self.c_pixmap)

            else:
                # Covers don't match - render with border
                # Construct a QImage with the cover sized to fit inside border
                c_image = QImage()
                cdata = db.cover(self.cid, index_is_id=True)
                if cdata is None:
                    c_image.load(I('book.png'))
                    self.calibre_cover.setScaledContents(True)
                else:
                    c_image.loadFromData(cdata)

                c_image = c_image.scaledToHeight(
                    self.COVER_ICON_SIZE - self.BORDER_WIDTH * 2,
                    Qt.SmoothTransformation)

                # Construct a QPixmap with yellow background
                self.c_pixmap = QPixmap(
                    QSize(c_image.width() + self.BORDER_WIDTH * 2,
                          c_image.height() + self.BORDER_WIDTH * 2))
                c_painter = QPainter(self.c_pixmap)
                c_painter.setRenderHints(c_painter.Antialiasing)
                c_painter.fillRect(self.c_pixmap.rect(),self.MISMATCH_COLOR)
                c_painter.drawImage(self.BORDER_WIDTH, self.BORDER_WIDTH, c_image)
                self.calibre_cover.setPixmap(self.c_pixmap)

                # Render Marvin cover with small border if different covers,
                # large cover if no cover hash (loaded via OPDS)
                border_width = self.BORDER_WIDTH
                if self.mismatches['cover_hash']['Marvin'] is None:
                    border_width = self.BORDER_WIDTH * 3
                _fetch_marvin_cover(border_width=border_width)
        else:
            _fetch_marvin_cover()

    def _populate_description(self):
        # Set the bg color of the description text fields to the dialog bg color
        bgcolor = self.palette().color(QPalette.Background)
        palette = QPalette()
        palette.setColor(QPalette.Base, bgcolor)
        self.calibre_description.setPalette(palette)
        self.marvin_description.setPalette(palette)

        if 'comments' in self.mismatches:
            self.calibre_description_label.setText(self.YELLOW_BG.format("<b>Description</b>"))
            if self.mismatches['comments']['calibre']:
                self.calibre_description.setText(self.mismatches['comments']['calibre'])

            self.marvin_description_label.setText(self.YELLOW_BG.format("<b>Description</b>"))
            if self.mismatches['comments']['Marvin']:
                self.marvin_description.setText(self.mismatches['comments']['Marvin'])
        else:
            if self.installed_book.comments:
                self.calibre_description.setText(self.installed_book.comments)
                self.marvin_description.setText(self.installed_book.comments)

    def _populate_pubdate(self):
        if 'pubdate' in self.mismatches:
            if self.mismatches['pubdate']['calibre']:
                cs_pubdate = "<b>Published:</b> {0}".format(strftime("%d %B %Y", t=self.mismatches['pubdate']['calibre']))
            else:
                cs_pubdate = "<b>Published:</b> Date unknown"
            self.calibre_pubdate.setText(self.YELLOW_BG.format(cs_pubdate))

            if self.mismatches['pubdate']['Marvin']:
                ms_pubdate = "<b>Published:</b> {0}".format(strftime("%d %B %Y", t=self.mismatches['pubdate']['Marvin']))
            else:
                ms_pubdate = "<b>Published:</b> Date unknown"
            self.marvin_pubdate.setText(self.YELLOW_BG.format(ms_pubdate))
        elif self.installed_book.pubdate:
            pubdate = "<b>Published:</b> {0}".format(strftime("%d %B %Y", t=self.installed_book.pubdate))
            self.calibre_pubdate.setText(pubdate)
            self.marvin_pubdate.setText(pubdate)
        else:
            pubdate = "<b>Published:</b> Date unknown"
            self.calibre_pubdate.setText(pubdate)
            self.marvin_pubdate.setText(pubdate)

    def _populate_publisher(self):
        if 'publisher' in self.mismatches:
            csp = self.mismatches['publisher']['calibre']
            if not csp:
                cs_publisher = "<b>Publisher:</b> Unknown"
            else:
                cs_publisher = "<b>Publisher:</b> {0}".format(csp)
            self.calibre_publisher.setText(self.YELLOW_BG.format(cs_publisher))

            msp = self.mismatches['publisher']['Marvin']
            if not msp:
                ms_publisher = "<b>Publisher:</b> Unknown"
            else:
                ms_publisher = "<b>Publisher:</b> {0}".format(msp)
            self.marvin_publisher.setText(self.YELLOW_BG.format(ms_publisher))
        else:
            if not self.installed_book.publisher:
                publisher = "<b>Publisher:</b> Unknown"
            else:
                publisher = "<b>Publisher:</b> {0}".format(self.installed_book.publisher)
            self.calibre_publisher.setText(publisher)
            self.marvin_publisher.setText(publisher)

    def _populate_rating(self):

        def _construct_stars(rating):
            '''
            Marvin ratings colors:
            Yellow: 242,220,109 F2DC6D
            Gray: 240,240,240 E0E0E0
            '''
            EMPTY = '<span style="color:#CCC">{0}</span>'
            FULL = '<span style="color:#000">{0}</span>'
            ans = ''
            empty = 5 - rating
            for x in range(rating):
                ans += FULL.format(FULL_STAR)
            for x in range(empty):
                ans += EMPTY.format(EMPTY_STAR)
            return ans

        if self.installed_book.rating is not None:
            if 'rating' in self.mismatches:
                calibre_stars = _construct_stars(self.mismatches['rating']['calibre'])
                self.calibre_rating.setText(self.YELLOW_BG.format(calibre_stars))
                marvin_stars = _construct_stars(self.mismatches['rating']['Marvin'])
                self.marvin_rating.setText(self.YELLOW_BG.format(marvin_stars))
            else:
                self.calibre_rating.setText(_construct_stars(self.installed_book.rating))
                self.marvin_rating.setText(_construct_stars(self.installed_book.rating))
        else:
            self.calibre_rating.setVisible(False)
            self.marvin_rating.setVisible(False)

    def _populate_series(self):
        if 'series' in self.mismatches:
            cs_index = str(self.mismatches['series_index']['calibre'])
            if cs_index.endswith('.0'):
                cs_index = cs_index[:-2]
            cs = "%s (%s)" % (self.mismatches['series']['calibre'], cs_index)
            self.calibre_series.setText(self.YELLOW_BG.format(cs))
            ms_index = str(self.mismatches['series_index']['Marvin'])
            if ms_index.endswith('.0'):
                ms_index = ms_index[:-2]
            ms = "%s (%s)" % (self.mismatches['series']['Marvin'], ms_index)
            self.marvin_series.setText(self.YELLOW_BG.format(ms))
        elif self.installed_book.series:
            cs_index = str(self.installed_book.series_index)
            if cs_index.endswith('.0'):
                cs_index = cs_index[:-2]
            cs = "%s (%s)" % (self.installed_book.series, cs_index)
            self.calibre_series.setText(cs)
            self.marvin_series.setText(cs)
        else:
            self.calibre_series.setVisible(False)
            self.marvin_series.setVisible(False)

    def _populate_subjects(self):
        '''
        '''
        # Setting size policy allows us to match Subjects fields height
        sp = QSizePolicy()
        sp.setHorizontalStretch(True)
        sp.setVerticalStretch(False)
        sp.setHeightForWidth(False)
        self.calibre_subjects.setSizePolicy(sp)
        self.marvin_subjects.setSizePolicy(sp)

        if 'tags' in self.mismatches:
            cs = "<b>Subjects:</b> {0}".format(', '.join(self.mismatches['tags']['calibre']))
            self.calibre_subjects.setText(self.YELLOW_BG.format(cs))
            ms = "<b>Subjects:</b> {0}".format(', '.join(self.mismatches['tags']['Marvin']))
            self.marvin_subjects.setText(self.YELLOW_BG.format(ms))

            calibre_height = self.calibre_subjects.sizeHint().height()
            marvin_height = self.marvin_subjects.sizeHint().height()
            if calibre_height > marvin_height:
                self.marvin_subjects.setMinimumHeight(calibre_height)
                self.marvin_subjects.setMaximumHeight(calibre_height)
            elif marvin_height > calibre_height:
                self.calibre_subjects.setMinimumHeight(marvin_height)
                self.calibre_subjects.setMaximumHeight(marvin_height)
        else:
            #self._log(repr(self.installed_book.tags))
            cs = "<b>Subjects:</b> {0}".format(', '.join(self.installed_book.tags))
            #self._log("cs: %s" % repr(cs))
            self.calibre_subjects.setText(cs)
            self.marvin_subjects.setText(cs)

    def _populate_title(self):
        if 'title' in self.mismatches:
            ct = self.mismatches['title']['calibre']
            self.calibre_title.setText(self.YELLOW_BG.format(ct))
            mt = self.mismatches['title']['Marvin']
            self.marvin_title.setText(self.YELLOW_BG.format(mt))
        else:
            title = self.installed_book.title
            self.calibre_title.setText(title)
            self.marvin_title.setText(title)

    def _populate_title_sort(self):
        if 'title_sort' in self.mismatches:
            cts = self.mismatches['title_sort']['calibre']
            self.calibre_title_sort.setText(self.YELLOW_BG.format(cts))
            mts = self.mismatches['title_sort']['Marvin']
            self.marvin_title_sort.setText(self.YELLOW_BG.format(mts))
        else:
            title_sort = self.installed_book.title_sort
            self.calibre_title_sort.setText(self.GREY_FG.format(title_sort))
            self.marvin_title_sort.setText(self.GREY_FG.format(title_sort))

    def _populate_uuid(self):
        if 'uuid' in self.mismatches:
            if self.mismatches['uuid']['calibre']:
                self.calibre_uuid.setText(self.YELLOW_BG.format('uuid'))
            if self.mismatches['uuid']['Marvin']:
                self.marvin_uuid.setText(self.YELLOW_BG.format('uuid'))
            else:
                self.marvin_uuid.setText(self.YELLOW_BG.format('no uuid'))
        else:
            self.calibre_uuid.setVisible(False)
            self.marvin_uuid.setVisible(False)
Exemple #7
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)
    def drawIconWithShadow(self,
                           icon,
                           rect,
                           painter,
                           iconMode,
                           radius=3,
                           color=QColor(0, 0, 0, 130),
                           offset=QPoint(1, -2)):
        '''
        @brief: Draw a cached pixmap with shadow
        @param: icon QIcon
        @param: rect QRect
        @param: painter QPainter
        @param: iconMode QIcon.Mode
        @param: radius int
        @param: color QColor
        @param: offset QPoint
        '''
        cache = QPixmap()
        pixmapName = 'icon %s %s %s' % (icon.cacheKey(), iconMode,
                                        rect.height())

        cache = QPixmapCache.find(pixmapName)
        if not cache:
            px = icon.pixmap(rect.size(), iconMode)
            px.setDevicePixelRatio(gVar.app.devicePixelRatio())
            cache = QPixmap(px.size() + QSize(radius * 2, radius * 2))
            cache.setDevicePixelRatio(px.devicePixelRatioF())
            cache.fill(Qt.transparent)

            cachePainter = QPainter(cache)

            # Draw shadow
            tmp = QImage(px.size() + QSize(radius * 2, radius * 2 + 1),
                         QImage.Format_ARGB32_Premultiplied)
            tmp.setDevicePixelRatio(px.devicePixelRatioF())
            tmp.fill(Qt.transparent)

            tmpPainter = QPainter(tmp)

            tmpPainter.setCompositionMode(QPainter.CompositionMode_Source)
            tmpPainter.drawPixmap(QPoint(radius, radius), px)
            tmpPainter.end()

            # blur the alpha channel
            blurred = QImage(tmp.size(), QImage.Format_ARGB32_Premultiplied)
            blurred.fill(Qt.transparent)
            blurPainter = QPainter(blurred)
            # TODO:
            #qt_blurImage(blurPainter, tmp, radius, False, True)
            blurPainter.end()

            tmp = blurred

            # blacken the image...
            tmpPainter.begin(tmp)
            tmpPainter.setCompositionMode(QPainter.CompositionMode_SourceIn)
            tmpPainter.fillRect(tmp.rect(), color)
            tmpPainter.end()

            tmpPainter.begin(tmp)
            tmpPainter.setCompositionMode(QPainter.CompositionMode_SourceIn)
            tmpPainter.fillRect(tmp.rect(), color)
            tmpPainter.end()

            # draw the blurred drop shadow...
            cachePainter.drawImage(
                QRect(0, 0,
                      cache.rect().width() / cache.devicePixelRatioF(),
                      cache.rect().height() / cache.devicePixelRatioF()), tmp)
            # Draw the actual pixmap...
            cachePainter.drawPixmap(QPoint(radius, radius) + offset, px)
            if self.usePixmapCache():
                QPixmapCache.insert(pixmapName, cache)

            sip.delete(cachePainter)
            sip.delete(tmpPainter)
            sip.delete(blurPainter)

        targetRect = QRect(cache.rect())
        targetRect.setWidth(cache.rect().width() / cache.devicePixelRatioF())
        targetRect.setHeight(cache.rect().height() / cache.devicePixelRatioF())
        targetRect.moveCenter(rect.center())
        painter.drawPixmap(targetRect.topLeft() - offset, cache)
Exemple #9
0
class MetadataComparisonDialog(SizePersistedDialog, Ui_Dialog, Logger):
    BORDER_COLOR = "#FDFF99"
    BORDER_WIDTH = 5
    COVER_ICON_SIZE = 200
    MISMATCH_COLOR = QColor(0xFD, 0xFF, 0x99)

    marvin_device_status_changed = pyqtSignal(dict)

    def accept(self):
        self._log_location()
        super(MetadataComparisonDialog, self).accept()

    def close(self):
        self._log_location()
        super(MetadataComparisonDialog, self).close()

    def dispatch_button_click(self, button):
        '''
        BUTTON_ROLES = ['AcceptRole', 'RejectRole', 'DestructiveRole', 'ActionRole',
                        'HelpRole', 'YesRole', 'NoRole', 'ApplyRole', 'ResetRole']
        '''
        self._log_location()
        if self.bb.buttonRole(button) == QDialogButtonBox.AcceptRole:
            self._log("AcceptRole")
            self.accept()
        elif self.bb.buttonRole(button) == QDialogButtonBox.RejectRole:
            self.close()

    def esc(self, *args):
        self.close()

    def initialize(self, parent, book_id, cid, installed_book,
                   enable_metadata_updates, marvin_db_path):
        '''
        __init__ is called on SizePersistedDialog()
        shared attributes of interest:
            .authors
            .author_sort
            .cover_hash
            .pubdate
            .publisher
            .rating
            .series
            .series_index
            .title
            .title_sort
            .comments
            .tags
            .uuid
        '''
        self.setupUi(self)
        self.book_id = book_id
        self.cid = cid
        self.connected_device = parent.opts.gui.device_manager.device
        self.installed_book = installed_book
        self.marvin_db_path = marvin_db_path
        self.opts = parent.opts
        self.parent = parent
        self.stored_command = None
        self.verbose = parent.verbose
        self.BORDER_LR = 4
        self.BORDER_TB = 8
        self.GREY_FG = '<font style="color:#A0A0A0">{0}</font>'
        self.YELLOW_BG = '<font style="background:#FDFF99">{0}</font>'

        self._log_location(installed_book.title)

        # Subscribe to Marvin driver change events
        self.connected_device.marvin_device_signals.reader_app_status_changed.connect(
            self.marvin_status_changed)

        #self._log("mismatches:\n%s" % repr(installed_book.metadata_mismatches))
        self.mismatches = installed_book.metadata_mismatches

        self._populate_title()
        self._populate_title_sort()
        self._populate_series()
        self._populate_authors()
        self._populate_author_sort()
        self._populate_uuid()
        self._populate_covers()
        self._populate_subjects()
        self._populate_publisher()
        self._populate_pubdate()
        self._populate_rating()
        self._populate_description()

        # ~~~~~~~~ Export to Marvin button ~~~~~~~~
        self.export_to_marvin_button.setIcon(
            QIcon(
                os.path.join(self.parent.opts.resources_path, 'icons',
                             'from_calibre.png')))
        self.export_to_marvin_button.clicked.connect(
            partial(self.store_command, 'export_metadata'))
        self.export_to_marvin_button.setEnabled(enable_metadata_updates)

        # ~~~~~~~~ Import from Marvin button ~~~~~~~~
        self.import_from_marvin_button.setIcon(
            QIcon(
                os.path.join(self.parent.opts.resources_path, 'icons',
                             'from_marvin.png')))
        self.import_from_marvin_button.clicked.connect(
            partial(self.store_command, 'import_metadata'))
        self.import_from_marvin_button.setEnabled(enable_metadata_updates)

        # If no calibre book, or no mismatches, adjust the display accordingly
        if not self.cid:
            #self._log("self.cid: %s" % repr(self.cid))
            #self._log("self.mismatches: %s" % repr(self.mismatches))
            self.calibre_gb.setVisible(False)
            self.import_from_marvin_button.setVisible(False)
            self.setWindowTitle(u'Marvin metadata')
        elif not self.mismatches:
            # Show both panels, but hide the transfer buttons
            self.export_to_marvin_button.setVisible(False)
            self.import_from_marvin_button.setVisible(False)
        else:
            self.setWindowTitle(u'Metadata Summary')

        if False:
            # Set the Marvin QGroupBox to Marvin red
            marvin_red = QColor()
            marvin_red.setRgb(189, 17, 20, alpha=255)
            palette = QPalette()
            palette.setColor(QPalette.Background, marvin_red)
            self.marvin_gb.setPalette(palette)

        # ~~~~~~~~ Add a Close or Cancel button ~~~~~~~~
        self.close_button = QPushButton(QIcon(I('window-close.png')), 'Close')
        if self.mismatches:
            self.close_button.setText('Cancel')
        self.bb.addButton(self.close_button, QDialogButtonBox.RejectRole)

        self.bb.clicked.connect(self.dispatch_button_click)

        # Restore position
        self.resize_dialog()

    def marvin_status_changed(self, cmd_dict):
        '''

        '''
        self.marvin_device_status_changed.emit(cmd_dict)
        command = cmd_dict['cmd']

        self._log_location(command)

        if command in ['disconnected', 'yanked']:
            self._log("closing dialog: %s" % command)
            self.close()

    def store_command(self, command):
        '''
        '''
        self._log_location(command)
        self.stored_command = command
        self.accept()

    def _populate_authors(self):
        if 'authors' in self.mismatches:
            cs_authors = ', '.join(self.mismatches['authors']['calibre'])
            self.calibre_authors.setText(self.YELLOW_BG.format(cs_authors))
            ms_authors = ', '.join(self.mismatches['authors']['Marvin'])
            self.marvin_authors.setText(self.YELLOW_BG.format(ms_authors))
        else:
            authors = ', '.join(self.installed_book.authors)
            self.calibre_authors.setText(authors)
            self.marvin_authors.setText(authors)

    def _populate_author_sort(self):
        if 'author_sort' in self.mismatches:
            cs_author_sort = self.mismatches['author_sort']['calibre']
            self.calibre_author_sort.setText(
                self.YELLOW_BG.format(cs_author_sort))
            ms_author_sort = self.mismatches['author_sort']['Marvin']
            self.marvin_author_sort.setText(
                self.YELLOW_BG.format(ms_author_sort))
        else:
            author_sort = self.installed_book.author_sort
            self.calibre_author_sort.setText(self.GREY_FG.format(author_sort))
            self.marvin_author_sort.setText(self.GREY_FG.format(author_sort))

    def _populate_covers(self):
        '''
        Display calibre cover for both unless mismatch
        '''
        def _fetch_marvin_cover(border_width=0):
            '''
            Retrieve LargeCoverJpg from cache
            '''
            #self._log_location('border_width: {0}'.format(border_width))
            con = sqlite3.connect(self.marvin_db_path)
            with con:
                con.row_factory = sqlite3.Row

                # Fetch Hash from mainDb
                cover_cur = con.cursor()
                cover_cur.execute('''SELECT
                                      Hash
                                     FROM Books
                                     WHERE ID = '{0}'
                                  '''.format(self.book_id))
                row = cover_cur.fetchone()

            book_hash = row[b'Hash']
            large_covers_subpath = self.connected_device._cover_subpath(
                size="large")
            cover_path = '/'.join([large_covers_subpath, '%s.jpg' % book_hash])
            stats = self.parent.ios.exists(cover_path)
            if stats:
                self._log("fetching large cover from cache")
                #self._log("cover size: {:,} bytes".format(int(stats['st_size'])))
                cover_bytes = self.parent.ios.read(cover_path, mode='rb')
                m_image = QImage()
                m_image.loadFromData(cover_bytes)

                if border_width:
                    # Construct a QPixmap with oversized yellow background
                    m_image = m_image.scaledToHeight(
                        self.COVER_ICON_SIZE - border_width * 2,
                        Qt.SmoothTransformation)

                    self.m_pixmap = QPixmap(
                        QSize(m_image.width() + border_width * 2,
                              m_image.height() + border_width * 2))

                    m_painter = QPainter(self.m_pixmap)
                    m_painter.setRenderHints(m_painter.Antialiasing)

                    m_painter.fillRect(self.m_pixmap.rect(),
                                       self.MISMATCH_COLOR)
                    m_painter.drawImage(border_width, border_width, m_image)
                else:
                    m_image = m_image.scaledToHeight(self.COVER_ICON_SIZE,
                                                     Qt.SmoothTransformation)

                    self.m_pixmap = QPixmap(
                        QSize(m_image.width(), m_image.height()))

                    m_painter = QPainter(self.m_pixmap)
                    m_painter.setRenderHints(m_painter.Antialiasing)

                    m_painter.drawImage(0, 0, m_image)

                self.marvin_cover.setPixmap(self.m_pixmap)
            else:
                # No cover available, use generic
                self._log("No cached cover, using generic")
                pixmap = QPixmap()
                pixmap.load(I('book.png'))
                pixmap = pixmap.scaled(self.COVER_ICON_SIZE,
                                       self.COVER_ICON_SIZE,
                                       aspectRatioMode=Qt.KeepAspectRatio,
                                       transformMode=Qt.SmoothTransformation)
                self.marvin_cover.setPixmap(pixmap)

        self.calibre_cover.setMaximumSize(
            QSize(self.COVER_ICON_SIZE, self.COVER_ICON_SIZE))
        self.calibre_cover.setText('')
        self.calibre_cover.setScaledContents(False)

        self.marvin_cover.setMaximumSize(
            QSize(self.COVER_ICON_SIZE, self.COVER_ICON_SIZE))
        self.marvin_cover.setText('')
        self.marvin_cover.setScaledContents(False)

        if self.cid:
            db = self.opts.gui.current_db
            if 'cover_hash' not in self.mismatches:
                mi = db.get_metadata(self.cid,
                                     index_is_id=True,
                                     get_cover=True,
                                     cover_as_data=True)

                c_image = QImage()
                if mi.has_cover:
                    c_image.loadFromData(mi.cover_data[1])
                    c_image = c_image.scaledToHeight(self.COVER_ICON_SIZE,
                                                     Qt.SmoothTransformation)
                    self.c_pixmap = QPixmap(
                        QSize(c_image.width(), c_image.height()))
                    c_painter = QPainter(self.c_pixmap)
                    c_painter.setRenderHints(c_painter.Antialiasing)
                    c_painter.drawImage(0, 0, c_image)
                else:
                    c_image.load(I('book.png'))
                    c_image = c_image.scaledToWidth(135,
                                                    Qt.SmoothTransformation)
                    # Construct a QPixmap with dialog background
                    self.c_pixmap = QPixmap(
                        QSize(c_image.width(), c_image.height()))
                    c_painter = QPainter(self.c_pixmap)
                    c_painter.setRenderHints(c_painter.Antialiasing)
                    bgcolor = self.palette().color(QPalette.Background)
                    c_painter.fillRect(self.c_pixmap.rect(), bgcolor)
                    c_painter.drawImage(0, 0, c_image)

                # Set calibre cover
                self.calibre_cover.setPixmap(self.c_pixmap)

                if self.opts.prefs.get('development_mode', False):
                    # Show individual covers
                    _fetch_marvin_cover()
                else:
                    # Show calibre cover on both sides
                    self.marvin_cover.setPixmap(self.c_pixmap)

            else:
                # Covers don't match - render with border
                # Construct a QImage with the cover sized to fit inside border
                c_image = QImage()
                cdata = db.cover(self.cid, index_is_id=True)
                if cdata is None:
                    c_image.load(I('book.png'))
                    self.calibre_cover.setScaledContents(True)
                else:
                    c_image.loadFromData(cdata)

                c_image = c_image.scaledToHeight(
                    self.COVER_ICON_SIZE - self.BORDER_WIDTH * 2,
                    Qt.SmoothTransformation)

                # Construct a QPixmap with yellow background
                self.c_pixmap = QPixmap(
                    QSize(c_image.width() + self.BORDER_WIDTH * 2,
                          c_image.height() + self.BORDER_WIDTH * 2))
                c_painter = QPainter(self.c_pixmap)
                c_painter.setRenderHints(c_painter.Antialiasing)
                c_painter.fillRect(self.c_pixmap.rect(), self.MISMATCH_COLOR)
                c_painter.drawImage(self.BORDER_WIDTH, self.BORDER_WIDTH,
                                    c_image)
                self.calibre_cover.setPixmap(self.c_pixmap)

                # Render Marvin cover with small border if different covers,
                # large cover if no cover hash (loaded via OPDS)
                border_width = self.BORDER_WIDTH
                if self.mismatches['cover_hash']['Marvin'] is None:
                    border_width = self.BORDER_WIDTH * 3
                _fetch_marvin_cover(border_width=border_width)
        else:
            _fetch_marvin_cover()

    def _populate_description(self):
        # Set the bg color of the description text fields to the dialog bg color
        bgcolor = self.palette().color(QPalette.Background)
        palette = QPalette()
        palette.setColor(QPalette.Base, bgcolor)
        self.calibre_description.setPalette(palette)
        self.marvin_description.setPalette(palette)

        if 'comments' in self.mismatches:
            self.calibre_description_label.setText(
                self.YELLOW_BG.format("<b>Description</b>"))
            if self.mismatches['comments']['calibre']:
                self.calibre_description.setText(
                    self.mismatches['comments']['calibre'])

            self.marvin_description_label.setText(
                self.YELLOW_BG.format("<b>Description</b>"))
            if self.mismatches['comments']['Marvin']:
                self.marvin_description.setText(
                    self.mismatches['comments']['Marvin'])
        else:
            if self.installed_book.comments:
                self.calibre_description.setText(self.installed_book.comments)
                self.marvin_description.setText(self.installed_book.comments)

    def _populate_pubdate(self):
        if 'pubdate' in self.mismatches:
            if self.mismatches['pubdate']['calibre']:
                cs_pubdate = "<b>Published:</b> {0}".format(
                    strftime("%d %B %Y",
                             t=self.mismatches['pubdate']['calibre']))
            else:
                cs_pubdate = "<b>Published:</b> Date unknown"
            self.calibre_pubdate.setText(self.YELLOW_BG.format(cs_pubdate))

            if self.mismatches['pubdate']['Marvin']:
                ms_pubdate = "<b>Published:</b> {0}".format(
                    strftime("%d %B %Y",
                             t=self.mismatches['pubdate']['Marvin']))
            else:
                ms_pubdate = "<b>Published:</b> Date unknown"
            self.marvin_pubdate.setText(self.YELLOW_BG.format(ms_pubdate))
        elif self.installed_book.pubdate:
            pubdate = "<b>Published:</b> {0}".format(
                strftime("%d %B %Y", t=self.installed_book.pubdate))
            self.calibre_pubdate.setText(pubdate)
            self.marvin_pubdate.setText(pubdate)
        else:
            pubdate = "<b>Published:</b> Date unknown"
            self.calibre_pubdate.setText(pubdate)
            self.marvin_pubdate.setText(pubdate)

    def _populate_publisher(self):
        if 'publisher' in self.mismatches:
            csp = self.mismatches['publisher']['calibre']
            if not csp:
                cs_publisher = "<b>Publisher:</b> Unknown"
            else:
                cs_publisher = "<b>Publisher:</b> {0}".format(csp)
            self.calibre_publisher.setText(self.YELLOW_BG.format(cs_publisher))

            msp = self.mismatches['publisher']['Marvin']
            if not msp:
                ms_publisher = "<b>Publisher:</b> Unknown"
            else:
                ms_publisher = "<b>Publisher:</b> {0}".format(msp)
            self.marvin_publisher.setText(self.YELLOW_BG.format(ms_publisher))
        else:
            if not self.installed_book.publisher:
                publisher = "<b>Publisher:</b> Unknown"
            else:
                publisher = "<b>Publisher:</b> {0}".format(
                    self.installed_book.publisher)
            self.calibre_publisher.setText(publisher)
            self.marvin_publisher.setText(publisher)

    def _populate_rating(self):
        def _construct_stars(rating):
            '''
            Marvin ratings colors:
            Yellow: 242,220,109 F2DC6D
            Gray: 240,240,240 E0E0E0
            '''
            EMPTY = '<span style="color:#CCC">{0}</span>'
            FULL = '<span style="color:#000">{0}</span>'
            ans = ''
            empty = 5 - rating
            for x in range(rating):
                ans += FULL.format(FULL_STAR)
            for x in range(empty):
                ans += EMPTY.format(EMPTY_STAR)
            return ans

        if self.installed_book.rating is not None:
            if 'rating' in self.mismatches:
                calibre_stars = _construct_stars(
                    self.mismatches['rating']['calibre'])
                self.calibre_rating.setText(
                    self.YELLOW_BG.format(calibre_stars))
                marvin_stars = _construct_stars(
                    self.mismatches['rating']['Marvin'])
                self.marvin_rating.setText(self.YELLOW_BG.format(marvin_stars))
            else:
                self.calibre_rating.setText(
                    _construct_stars(self.installed_book.rating))
                self.marvin_rating.setText(
                    _construct_stars(self.installed_book.rating))
        else:
            self.calibre_rating.setVisible(False)
            self.marvin_rating.setVisible(False)

    def _populate_series(self):
        if 'series' in self.mismatches:
            cs_index = str(self.mismatches['series_index']['calibre'])
            if cs_index.endswith('.0'):
                cs_index = cs_index[:-2]
            cs = "%s (%s)" % (self.mismatches['series']['calibre'], cs_index)
            self.calibre_series.setText(self.YELLOW_BG.format(cs))
            ms_index = str(self.mismatches['series_index']['Marvin'])
            if ms_index.endswith('.0'):
                ms_index = ms_index[:-2]
            ms = "%s (%s)" % (self.mismatches['series']['Marvin'], ms_index)
            self.marvin_series.setText(self.YELLOW_BG.format(ms))
        elif self.installed_book.series:
            cs_index = str(self.installed_book.series_index)
            if cs_index.endswith('.0'):
                cs_index = cs_index[:-2]
            cs = "%s (%s)" % (self.installed_book.series, cs_index)
            self.calibre_series.setText(cs)
            self.marvin_series.setText(cs)
        else:
            self.calibre_series.setVisible(False)
            self.marvin_series.setVisible(False)

    def _populate_subjects(self):
        '''
        '''
        # Setting size policy allows us to match Subjects fields height
        sp = QSizePolicy()
        sp.setHorizontalStretch(True)
        sp.setVerticalStretch(False)
        sp.setHeightForWidth(False)
        self.calibre_subjects.setSizePolicy(sp)
        self.marvin_subjects.setSizePolicy(sp)

        if 'tags' in self.mismatches:
            cs = "<b>Subjects:</b> {0}".format(', '.join(
                self.mismatches['tags']['calibre']))
            self.calibre_subjects.setText(self.YELLOW_BG.format(cs))
            ms = "<b>Subjects:</b> {0}".format(', '.join(
                self.mismatches['tags']['Marvin']))
            self.marvin_subjects.setText(self.YELLOW_BG.format(ms))

            calibre_height = self.calibre_subjects.sizeHint().height()
            marvin_height = self.marvin_subjects.sizeHint().height()
            if calibre_height > marvin_height:
                self.marvin_subjects.setMinimumHeight(calibre_height)
                self.marvin_subjects.setMaximumHeight(calibre_height)
            elif marvin_height > calibre_height:
                self.calibre_subjects.setMinimumHeight(marvin_height)
                self.calibre_subjects.setMaximumHeight(marvin_height)
        else:
            #self._log(repr(self.installed_book.tags))
            cs = "<b>Subjects:</b> {0}".format(', '.join(
                self.installed_book.tags))
            #self._log("cs: %s" % repr(cs))
            self.calibre_subjects.setText(cs)
            self.marvin_subjects.setText(cs)

    def _populate_title(self):
        if 'title' in self.mismatches:
            ct = self.mismatches['title']['calibre']
            self.calibre_title.setText(self.YELLOW_BG.format(ct))
            mt = self.mismatches['title']['Marvin']
            self.marvin_title.setText(self.YELLOW_BG.format(mt))
        else:
            title = self.installed_book.title
            self.calibre_title.setText(title)
            self.marvin_title.setText(title)

    def _populate_title_sort(self):
        if 'title_sort' in self.mismatches:
            cts = self.mismatches['title_sort']['calibre']
            self.calibre_title_sort.setText(self.YELLOW_BG.format(cts))
            mts = self.mismatches['title_sort']['Marvin']
            self.marvin_title_sort.setText(self.YELLOW_BG.format(mts))
        else:
            title_sort = self.installed_book.title_sort
            self.calibre_title_sort.setText(self.GREY_FG.format(title_sort))
            self.marvin_title_sort.setText(self.GREY_FG.format(title_sort))

    def _populate_uuid(self):
        if 'uuid' in self.mismatches:
            if self.mismatches['uuid']['calibre']:
                self.calibre_uuid.setText(self.YELLOW_BG.format('uuid'))
            if self.mismatches['uuid']['Marvin']:
                self.marvin_uuid.setText(self.YELLOW_BG.format('uuid'))
            else:
                self.marvin_uuid.setText(self.YELLOW_BG.format('no uuid'))
        else:
            self.calibre_uuid.setVisible(False)
            self.marvin_uuid.setVisible(False)