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)
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);
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)
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)
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)