class ImportAnnotationsDialog(QDialog): def __init__(self, parent, friendly_name, rac): #self.dialog = QDialog(parent.gui) QDialog.__init__(self, parent.gui) self.parent = parent self.opts = parent.opts self.rac = rac parent_loc = self.parent.gui.pos() self.move(parent_loc.x(), parent_loc.y()) self.setWindowTitle(rac.import_dialog_title) self.setWindowIcon(self.opts.icon) l = QVBoxLayout() self.setLayout(l) self.pte = PlainTextEdit(self.parent) self.pte.setPlainText(rac.initial_dialog_text) self.pte.setMinimumWidth(400) l.addWidget(self.pte) self.dialogButtonBox = QDialogButtonBox(QDialogButtonBox.Cancel|QDialogButtonBox.Help) self.import_button = self.dialogButtonBox.addButton(self.dialogButtonBox.Ok) self.import_button.setText('Import') self.dialogButtonBox.clicked.connect(self.import_annotations_dialog_clicked) l.addWidget(self.dialogButtonBox) self.rejected.connect(self.close) self.exec_() self.text = str(self.pte.toPlainText()) def close(self): # Catch ESC and close button self.pte.setPlainText('') self.accept() def import_annotations_dialog_clicked(self, button): BUTTON_ROLES = ['AcceptRole', 'RejectRole', 'DestructiveRole', 'ActionRole', 'HelpRole', 'YesRole', 'NoRole', 'ApplyRole', 'ResetRole'] if self.dialogButtonBox.buttonRole(button) == QDialogButtonBox.AcceptRole: # Remove initial_dialog_text if user clicks OK without dropping file if self.text() == self.rac.initial_dialog_text: self.pte.clear() self.accept() elif self.dialogButtonBox.buttonRole(button) == QDialogButtonBox.HelpRole: hv = HelpView(self, self.opts.icon, self.opts.prefs, html=self.rac.import_help_text) hv.show() else: self.close() def text(self): return unicode(self.pte.toPlainText())
class FindAnnotationsDialog(SizePersistedDialog, Logger): GENERIC_STYLE = 'Any style' GENERIC_READER = 'Any reader' def __init__(self, opts): self.matched_ids = set() self.opts = opts self.prefs = opts.prefs super(FindAnnotationsDialog, self).__init__(self.opts.gui, 'find_annotations_dialog') self.setWindowTitle('Find Annotations') self.setWindowIcon(self.opts.icon) self.l = QVBoxLayout(self) self.setLayout(self.l) self.search_criteria_gb = QGroupBox(self) self.search_criteria_gb.setTitle("Search criteria") self.scgl = QGridLayout(self.search_criteria_gb) self.l.addWidget(self.search_criteria_gb) # addWidget(widget, row, col, rowspan, colspan) row = 0 # ~~~~~~~~ Create the Readers comboBox ~~~~~~~~ self.reader_label = QLabel('Reader') self.reader_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter) self.scgl.addWidget(self.reader_label, row, 0, 1, 1) self.find_annotations_reader_comboBox = QComboBox() self.find_annotations_reader_comboBox.setObjectName('find_annotations_reader_comboBox') self.find_annotations_reader_comboBox.setToolTip('Reader annotations to search for') self.find_annotations_reader_comboBox.addItem(self.GENERIC_READER) racs = ReaderApp.get_reader_app_classes() for ra in sorted(racs.keys()): self.find_annotations_reader_comboBox.addItem(ra) self.scgl.addWidget(self.find_annotations_reader_comboBox, row, 1, 1, 4) row += 1 # ~~~~~~~~ Create the Styles comboBox ~~~~~~~~ self.style_label = QLabel('Style') self.style_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter) self.scgl.addWidget(self.style_label, row, 0, 1, 1) self.find_annotations_color_comboBox = QComboBox() self.find_annotations_color_comboBox.setObjectName('find_annotations_color_comboBox') self.find_annotations_color_comboBox.setToolTip('Annotation style to search for') self.find_annotations_color_comboBox.addItem(self.GENERIC_STYLE) all_colors = COLOR_MAP.keys() all_colors.remove('Default') for color in sorted(all_colors): self.find_annotations_color_comboBox.addItem(color) self.scgl.addWidget(self.find_annotations_color_comboBox, row, 1, 1, 4) row += 1 # ~~~~~~~~ Create the Text LineEdit control ~~~~~~~~ self.text_label = QLabel('Text') self.text_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter) self.scgl.addWidget(self.text_label, row, 0, 1, 1) self.find_annotations_text_lineEdit = MyLineEdit() self.find_annotations_text_lineEdit.setObjectName('find_annotations_text_lineEdit') self.scgl.addWidget(self.find_annotations_text_lineEdit, row, 1, 1, 3) self.reset_text_tb = QToolButton() self.reset_text_tb.setObjectName('reset_text_tb') self.reset_text_tb.setToolTip('Clear search criteria') self.reset_text_tb.setIcon(QIcon(I('trash.png'))) self.reset_text_tb.clicked.connect(self.clear_text_field) self.scgl.addWidget(self.reset_text_tb, row, 4, 1, 1) row += 1 # ~~~~~~~~ Create the Note LineEdit control ~~~~~~~~ self.note_label = QLabel('Note') self.note_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter) self.scgl.addWidget(self.note_label, row, 0, 1, 1) self.find_annotations_note_lineEdit = MyLineEdit() self.find_annotations_note_lineEdit.setObjectName('find_annotations_note_lineEdit') self.scgl.addWidget(self.find_annotations_note_lineEdit, row, 1, 1, 3) self.reset_note_tb = QToolButton() self.reset_note_tb.setObjectName('reset_note_tb') self.reset_note_tb.setToolTip('Clear search criteria') self.reset_note_tb.setIcon(QIcon(I('trash.png'))) self.reset_note_tb.clicked.connect(self.clear_note_field) self.scgl.addWidget(self.reset_note_tb, row, 4, 1, 1) row += 1 # ~~~~~~~~ Create the Date range controls ~~~~~~~~ self.date_range_label = QLabel('Date range') self.date_range_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter) self.scgl.addWidget(self.date_range_label, row, 0, 1, 1) # Date 'From' self.find_annotations_date_from_dateEdit = MyDateEdit(self, datetime(1970,1,1)) self.find_annotations_date_from_dateEdit.setObjectName('find_annotations_date_from_dateEdit') #self.find_annotations_date_from_dateEdit.current_val = datetime(1970,1,1) self.find_annotations_date_from_dateEdit.clear_button.clicked.connect(self.find_annotations_date_from_dateEdit.reset_from_date) self.scgl.addWidget(self.find_annotations_date_from_dateEdit, row, 1, 1, 1) self.scgl.addWidget(self.find_annotations_date_from_dateEdit.clear_button, row, 2, 1, 1) # Date 'To' self.find_annotations_date_to_dateEdit = MyDateEdit(self, datetime.today()) self.find_annotations_date_to_dateEdit.setObjectName('find_annotations_date_to_dateEdit') #self.find_annotations_date_to_dateEdit.current_val = datetime.today() self.find_annotations_date_to_dateEdit.clear_button.clicked.connect(self.find_annotations_date_to_dateEdit.reset_to_date) self.scgl.addWidget(self.find_annotations_date_to_dateEdit, row, 3, 1, 1) self.scgl.addWidget(self.find_annotations_date_to_dateEdit.clear_button, row, 4, 1, 1) row += 1 # ~~~~~~~~ Create a horizontal line ~~~~~~~~ self.hl = QFrame(self) self.hl.setGeometry(QRect(0, 0, 1, 3)) self.hl.setFrameShape(QFrame.HLine) self.hl.setFrameShadow(QFrame.Raised) self.scgl.addWidget(self.hl, row, 0, 1, 5) row += 1 # ~~~~~~~~ Create the results label field ~~~~~~~~ self.result_label = QLabel('<p style="color:red">scanning…</p>') self.result_label.setAlignment(Qt.AlignCenter) self.result_label.setWordWrap(False) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Maximum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.result_label.sizePolicy().hasHeightForWidth()) self.result_label.setSizePolicy(sizePolicy) self.result_label.setMinimumSize(QtCore.QSize(250, 0)) self.scgl.addWidget(self.result_label, row, 0, 1, 5) row += 1 # ~~~~~~~~ Create the ButtonBox ~~~~~~~~ self.dialogButtonBox = QDialogButtonBox(self) self.dialogButtonBox.setOrientation(Qt.Horizontal) if False: self.update_button = QPushButton('Update results') self.update_button.setDefault(True) self.update_button.setVisible(False) self.dialogButtonBox.addButton(self.update_button, QDialogButtonBox.ActionRole) self.cancel_button = self.dialogButtonBox.addButton(self.dialogButtonBox.Cancel) self.find_button = self.dialogButtonBox.addButton(self.dialogButtonBox.Ok) self.find_button.setText('Find Matching Books') self.l.addWidget(self.dialogButtonBox) self.dialogButtonBox.clicked.connect(self.find_annotations_dialog_clicked) # ~~~~~~~~ Add a spacer ~~~~~~~~ self.spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.l.addItem(self.spacerItem) # ~~~~~~~~ Restore previously saved settings ~~~~~~~~ self.restore_settings() # ~~~~~~~~ Declare sizing ~~~~~~~~ sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth()) self.setSizePolicy(sizePolicy) self.resize_dialog() # ~~~~~~~~ Connect all signals ~~~~~~~~ self.find_annotations_reader_comboBox.currentIndexChanged.connect(partial(self.update_results, 'reader')) self.find_annotations_color_comboBox.currentIndexChanged.connect(partial(self.update_results, 'color')) self.find_annotations_text_lineEdit.editingFinished.connect(partial(self.update_results, 'text')) self.find_annotations_note_lineEdit.editingFinished.connect(partial(self.update_results, 'note')) # self.connect(self.find_annotations_text_lineEdit, pyqtSignal("return_pressed"), self.return_pressed) self.find_annotations_text_lineEdit.return_pressed.connect(self.return_pressed) # self.connect(self.find_annotations_note_lineEdit, pyqtSignal("return_pressed"), self.return_pressed) self.find_annotations_note_lineEdit.return_pressed.connect(self.return_pressed) # Date range signals connected in inventory_available() # ~~~~~~~~ Allow dialog to render before doing inventory ~~~~~~~~ #field = self.prefs.get('cfg_annotations_destination_field', None) field = get_cc_mapping('annotations', 'field', None) self.annotated_books_scanner = InventoryAnnotatedBooks(self.opts.gui, field, get_date_range=True) self.annotated_books_scanner.signal.connect(self.inventory_available) QTimer.singleShot(1, self.start_inventory_scan) def clear_note_field(self): if str(self.find_annotations_note_lineEdit.text()) > '': self.find_annotations_note_lineEdit.setText('') self.update_results('clear_note_field') def clear_text_field(self): if str(self.find_annotations_text_lineEdit.text()) > '': self.find_annotations_text_lineEdit.setText('') self.update_results('clear_text_field') def find_annotations_dialog_clicked(self, button): if self.dialogButtonBox.buttonRole(button) == QDialogButtonBox.AcceptRole: self.save_settings() self.accept() elif self.dialogButtonBox.buttonRole(button) == QDialogButtonBox.RejectRole: self.close() def inventory_available(self): ''' Update the Date range widgets with the rounded oldest, newest dates Don't connect date signals until date range available ''' self._log_location() # Reset the date range based on available annotations oldest = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.oldest_annotation)) oldest_day = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.oldest_annotation).replace(hour=0, minute=0, second=0)) newest = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.newest_annotation)) newest_day = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.newest_annotation).replace(hour=23, minute=59, second=59)) # Set 'From' date limits to inventory values self.find_annotations_date_from_dateEdit.setMinimumDateTime(oldest_day) self.find_annotations_date_from_dateEdit.current_val = oldest self.find_annotations_date_from_dateEdit.setMaximumDateTime(newest_day) # Set 'To' date limits to inventory values self.find_annotations_date_to_dateEdit.setMinimumDateTime(oldest_day) self.find_annotations_date_to_dateEdit.current_val = newest_day self.find_annotations_date_to_dateEdit.setMaximumDateTime(newest_day) # Connect the signals for date range changes self.find_annotations_date_from_dateEdit.dateTimeChanged.connect(partial(self.update_results, 'from_date')) self.find_annotations_date_to_dateEdit.dateTimeChanged.connect(partial(self.update_results, 'to_date')) self.update_results('inventory_available') def restore_settings(self): self.blockSignals(True) ra = self.prefs.get('find_annotations_reader_comboBox', self.GENERIC_READER) ra_index = self.find_annotations_reader_comboBox.findText(ra) self.find_annotations_reader_comboBox.setCurrentIndex(ra_index) color = self.prefs.get('find_annotations_color_comboBox', self.GENERIC_STYLE) color_index = self.find_annotations_color_comboBox.findText(color) self.find_annotations_color_comboBox.setCurrentIndex(color_index) text = self.prefs.get('find_annotations_text_lineEdit', '') self.find_annotations_text_lineEdit.setText(text) note = self.prefs.get('find_annotations_note_lineEdit', '') self.find_annotations_note_lineEdit.setText(note) if False: from_date = self.prefs.get('find_annotations_date_from_dateEdit', datetime(1970,1,1)) self.find_annotations_date_from_dateEdit.current_val = from_date to_date = self.prefs.get('find_annotations_date_to_dateEdit', datetime.today()) self.find_annotations_date_to_dateEdit.current_val = to_date self.blockSignals(False) def return_pressed(self): self.update_results("return_pressed") def save_settings(self): ra = str(self.find_annotations_reader_comboBox.currentText()) self.prefs.set('find_annotations_reader_comboBox', ra) color = str(self.find_annotations_color_comboBox.currentText()) self.prefs.set('find_annotations_color_comboBox', color) text = str(self.find_annotations_text_lineEdit.text()) self.prefs.set('find_annotations_text_lineEdit', text) note = str(self.find_annotations_note_lineEdit.text()) self.prefs.set('find_annotations_note_lineEdit', note) if False: from_date = self.find_annotations_date_from_dateEdit.current_val self.prefs.set('find_annotations_date_from_dateEdit', from_date) to_date = self.find_annotations_date_to_dateEdit.current_val self.prefs.set('find_annotations_date_to_dateEdit', to_date) def start_inventory_scan(self): self._log_location() self.annotated_books_scanner.start() def update_results(self, trigger): #self._log_location(trigger) reader_to_match = str(self.find_annotations_reader_comboBox.currentText()) color_to_match = str(self.find_annotations_color_comboBox.currentText()) text_to_match = str(self.find_annotations_text_lineEdit.text()) note_to_match = str(self.find_annotations_note_lineEdit.text()) from_date = self.find_annotations_date_from_dateEdit.dateTime().toTime_t() to_date = self.find_annotations_date_to_dateEdit.dateTime().toTime_t() annotation_map = self.annotated_books_scanner.annotation_map #field = self.prefs.get("cfg_annotations_destination_field", None) field = get_cc_mapping('annotations', 'field', None) db = self.opts.gui.current_db matched_titles = [] self.matched_ids = set() for cid in annotation_map: mi = db.get_metadata(cid, index_is_id=True) soup = None if field == 'Comments': if mi.comments: soup = BeautifulSoup(mi.comments) else: if mi.get_user_metadata(field, False)['#value#'] is not None: soup = BeautifulSoup(mi.get_user_metadata(field, False)['#value#']) if soup: uas = soup.findAll('div', 'annotation') for ua in uas: # Are we already logged? if cid in self.matched_ids: continue # Check reader if reader_to_match != self.GENERIC_READER: this_reader = ua['reader'] if this_reader != reader_to_match: continue # Check color if color_to_match != self.GENERIC_STYLE: this_color = ua.find('table')['color'] if this_color != color_to_match: continue # Check date range, allow for mangled timestamp try: timestamp = float(ua.find('td', 'timestamp')['uts']) if timestamp < from_date or timestamp > to_date: continue except: continue highlight_text = '' try: pels = ua.findAll('p', 'highlight') for pel in pels: highlight_text += pel.string + '\n' except: pass if text_to_match > '': if not re.search(text_to_match, highlight_text, flags=re.IGNORECASE): continue note_text = '' try: nels = ua.findAll('p', 'note') for nel in nels: note_text += nel.string + '\n' except: pass if note_to_match > '': if not re.search(note_to_match, note_text, flags=re.IGNORECASE): continue # If we made it this far, add the id to matched_ids self.matched_ids.add(cid) matched_titles.append(mi.title) # Update the results box matched_titles.sort() if len(annotation_map): if len(matched_titles): first_match = ("<i>%s</i>" % matched_titles[0]) if len(matched_titles) == 1: results = first_match else: results = first_match + (" and %d more." % (len(matched_titles) - 1)) self.result_label.setText('<p style="color:blue">{0}</p>'.format(results)) else: self.result_label.setText('<p style="color:red">no matches</p>') else: self.result_label.setText('<p style="color:red">no annotated books in library</p>') self.resize_dialog()
class CustomColumnWizard(SizePersistedDialog, Logger): STEP_ONE = _("Name your '{0}' column:") YELLOW_BG = '<font style="background:#FDFF99">{0}</font>' def __init__(self, parent, column_type, profile, verbose=True): super(CustomColumnWizard, self).__init__(parent, 'annotations plugin:custom column wizard') self.column_type = column_type self.db = parent.gui.current_db self.gui = parent.gui self.modified_column = None self.previous_name = None self.profile = profile self.verbose = verbose self._log_location() self.setWindowTitle(_('Custom column wizard')) layout = QVBoxLayout(self) self.setLayout(layout) title_layout = ImageTitleLayout(self, '../images/wizard.png', _('Custom column wizard'), has_help=False) layout.addLayout(title_layout) grid_layout = QGridLayout() layout.addLayout(grid_layout) self.calibre_destination_le = QLineEdit() grid_layout.addWidget(self.calibre_destination_le, 3, 1) self.step_1 = QLabel("1. Step 1", self) grid_layout.addWidget(self.step_1, 2, 1) self.bb = QDialogButtonBox(QDialogButtonBox.Cancel) layout.addWidget(self.bb) # Add the Accept button self.accept_button = self.bb.addButton('Button', QDialogButtonBox.AcceptRole) self.accept_button.setDefault(True) # Hook the QLineEdit box self.calibre_destination_le.textChanged.connect( self.validate_destination) self.populate_editor() self.highlight_step(1) # Hook the button events self.bb.clicked.connect(self.dispatch_button_click) def accept(self): self._log_location() super(CustomColumnWizard, self).accept() def close(self): self._log_location() super(CustomColumnWizard, self).close() def custom_column_add(self, requested_name, profile): ''' Add the requested custom column with profile ''' self._log_location(requested_name) self._log(profile) self.db.create_custom_column(profile['label'], requested_name, profile['datatype'], profile['is_multiple'], display=profile['display']) self.modified_column = { 'destination': requested_name, 'label': "#%s" % profile['label'], 'previous': self.previous_name, 'source': profile['source'] } def custom_column_rename(self, requested_name, profile): ''' The name already exists for label, update it ''' self._log_location(requested_name) self._log(profile) # Find the existing for cf in self.db.custom_field_keys(): #self._log(self.db.metadata_for_field(cf)) mi = self.db.metadata_for_field(cf) if mi['label'] == profile['label']: self.db.set_custom_column_metadata(mi['colnum'], name=requested_name, label=mi['label'], display=mi['display']) self.modified_column = { 'destination': requested_name, 'label': "#%s" % profile['label'], 'previous': self.previous_name, 'source': profile['source'] } break 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: requested_name = str(self.calibre_destination_le.text()) if requested_name in self.get_custom_column_names(): self._log("'%s' already in use" % requested_name) warning_dialog( self.gui, _("Already in use"), _("<p>'{0}' is an existing custom column.</p><p>Pick a different name.</p>" ).format(requested_name), show=True, show_copy_button=False) self.calibre_destination_le.selectAll() self.calibre_destination_le.setFocus() else: source = self.column_type #profile = self.FIELDS[source] self.profile['source'] = source if button.objectName() == 'add_button': self.custom_column_add(requested_name, self.profile) elif button.objectName() == 'rename_button': self.custom_column_rename(requested_name, self.profile) self.accept() elif self.bb.buttonRole(button) == QDialogButtonBox.RejectRole: self.close() def esc(self, *args): self.close() def highlight_step(self, step): ''' ''' self._log_location(step) if step == 1: #self.step_1.setText(self.YELLOW_BG.format(self.STEP_ONE.format(self.column_type))) self.step_1.setText(self.STEP_ONE.format(self.column_type)) def get_custom_column_names(self): ''' ''' self._log_location() existing_custom_names = [] for cf in self.db.custom_field_keys(): #self._log(self.db.metadata_for_field(cf)) existing_custom_names.append( self.db.metadata_for_field(cf)['name']) return existing_custom_names def reset_accept_button(self, action="add_button", enabled=False): ''' ''' self.accept_button.setObjectName(action) if action == "add_button": self.accept_button.setText(_('Add custom column')) self.accept_button.setIcon(QIcon(I('plus.png'))) elif action == "rename_button": self.accept_button.setText(_("Rename custom column")) self.accept_button.setIcon(QIcon(I('edit_input.png'))) self.accept_button.setEnabled(enabled) def populate_editor(self): ''' ''' self._log_location() selected = self.column_type existing = None #label = self.FIELDS[selected]['label'] label = self.profile['label'] for cf in self.db.custom_field_keys(): #self._log(self.db.metadata_for_field(cf)) cfd = self.db.metadata_for_field(cf) if cfd['label'] == label: existing = cfd['name'] break # Does label already exist? if existing: self.previous_name = existing self.calibre_destination_le.setText(existing) self.reset_accept_button(action="rename_button", enabled=True) else: # Populate the edit box with the default Column name self.calibre_destination_le.setText(selected) self.reset_accept_button(action="add_button", enabled=True) # Select the text self.calibre_destination_le.selectAll() self.calibre_destination_le.setFocus() def validate_destination(self, destination): ''' Confirm length of column name > 0 ''' enabled = len(str(destination)) self.accept_button.setEnabled(enabled)
class ImportAnnotationsDialog(QDialog): def __init__(self, parent, friendly_name, rac): #self.dialog = QDialog(parent.gui) QDialog.__init__(self, parent.gui) self.parent = parent self.opts = parent.opts self.rac = rac parent_loc = self.parent.gui.pos() self.move(parent_loc.x(), parent_loc.y()) self.setWindowTitle(rac.import_dialog_title) self.setWindowIcon(self.opts.icon) l = QVBoxLayout() self.setLayout(l) self.pte = PlainTextEdit(self.parent) self.pte.setPlainText(rac.initial_dialog_text) self.pte.setMinimumWidth(400) l.addWidget(self.pte) self.dialogButtonBox = QDialogButtonBox(QDialogButtonBox.Cancel | QDialogButtonBox.Help) self.import_button = self.dialogButtonBox.addButton( self.dialogButtonBox.Ok) self.import_button.setText(_('Import')) self.dialogButtonBox.clicked.connect( self.import_annotations_dialog_clicked) l.addWidget(self.dialogButtonBox) self.rejected.connect(self.close) self.exec_() self.text = str(self.pte.toPlainText()) def close(self): # Catch ESC and close button self.pte.setPlainText('') self.accept() def import_annotations_dialog_clicked(self, button): BUTTON_ROLES = [ 'AcceptRole', 'RejectRole', 'DestructiveRole', 'ActionRole', 'HelpRole', 'YesRole', 'NoRole', 'ApplyRole', 'ResetRole' ] if self.dialogButtonBox.buttonRole( button) == QDialogButtonBox.AcceptRole: # Remove initial_dialog_text if user clicks OK without dropping file if self.text() == self.rac.initial_dialog_text: self.pte.clear() self.accept() elif self.dialogButtonBox.buttonRole( button) == QDialogButtonBox.HelpRole: hv = HelpView(self, self.opts.icon, self.opts.prefs, html=self.rac.import_help_text) hv.show() else: self.close() def text(self): return unicode(self.pte.toPlainText())
class FindAnnotationsDialog(SizePersistedDialog, Logger): GENERIC_STYLE = 'Any style' GENERIC_READER = 'Any reader' def __init__(self, opts): self.matched_ids = set() self.opts = opts self.prefs = opts.prefs super(FindAnnotationsDialog, self).__init__(self.opts.gui, 'find_annotations_dialog') self.setWindowTitle(_('Find Annotations')) self.setWindowIcon(self.opts.icon) self.l = QVBoxLayout(self) self.setLayout(self.l) self.search_criteria_gb = QGroupBox(self) self.search_criteria_gb.setTitle(_("Search criteria")) self.scgl = QGridLayout(self.search_criteria_gb) self.l.addWidget(self.search_criteria_gb) # addWidget(widget, row, col, rowspan, colspan) row = 0 # ~~~~~~~~ Create the Readers comboBox ~~~~~~~~ self.reader_label = QLabel(_('Reader')) self.reader_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter) self.scgl.addWidget(self.reader_label, row, 0, 1, 1) self.find_annotations_reader_comboBox = QComboBox() self.find_annotations_reader_comboBox.setObjectName('find_annotations_reader_comboBox') self.find_annotations_reader_comboBox.setToolTip(_('Reader annotations to search for')) self.find_annotations_reader_comboBox.addItem(self.GENERIC_READER) racs = ReaderApp.get_reader_app_classes() for ra in sorted(racs.keys()): self.find_annotations_reader_comboBox.addItem(ra) self.scgl.addWidget(self.find_annotations_reader_comboBox, row, 1, 1, 4) row += 1 # ~~~~~~~~ Create the Styles comboBox ~~~~~~~~ self.style_label = QLabel(_('Style')) self.style_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter) self.scgl.addWidget(self.style_label, row, 0, 1, 1) self.find_annotations_color_comboBox = QComboBox() self.find_annotations_color_comboBox.setObjectName('find_annotations_color_comboBox') self.find_annotations_color_comboBox.setToolTip(_('Annotation style to search for')) self.find_annotations_color_comboBox.addItem(self.GENERIC_STYLE) all_colors = COLOR_MAP.keys() all_colors.remove('Default') for color in sorted(all_colors): self.find_annotations_color_comboBox.addItem(color) self.scgl.addWidget(self.find_annotations_color_comboBox, row, 1, 1, 4) row += 1 # ~~~~~~~~ Create the Text LineEdit control ~~~~~~~~ self.text_label = QLabel(_('Text')) self.text_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter) self.scgl.addWidget(self.text_label, row, 0, 1, 1) self.find_annotations_text_lineEdit = MyLineEdit() self.find_annotations_text_lineEdit.setObjectName('find_annotations_text_lineEdit') self.scgl.addWidget(self.find_annotations_text_lineEdit, row, 1, 1, 3) self.reset_text_tb = QToolButton() self.reset_text_tb.setObjectName('reset_text_tb') self.reset_text_tb.setToolTip(_('Clear search criteria')) self.reset_text_tb.setIcon(QIcon(I('trash.png'))) self.reset_text_tb.clicked.connect(self.clear_text_field) self.scgl.addWidget(self.reset_text_tb, row, 4, 1, 1) row += 1 # ~~~~~~~~ Create the Note LineEdit control ~~~~~~~~ self.note_label = QLabel(_('Note')) self.note_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter) self.scgl.addWidget(self.note_label, row, 0, 1, 1) self.find_annotations_note_lineEdit = MyLineEdit() self.find_annotations_note_lineEdit.setObjectName('find_annotations_note_lineEdit') self.scgl.addWidget(self.find_annotations_note_lineEdit, row, 1, 1, 3) self.reset_note_tb = QToolButton() self.reset_note_tb.setObjectName('reset_note_tb') self.reset_note_tb.setToolTip(_('Clear search criteria')) self.reset_note_tb.setIcon(QIcon(I('trash.png'))) self.reset_note_tb.clicked.connect(self.clear_note_field) self.scgl.addWidget(self.reset_note_tb, row, 4, 1, 1) row += 1 # ~~~~~~~~ Create the Date range controls ~~~~~~~~ self.date_range_label = QLabel(_('Date range')) self.date_range_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter) self.scgl.addWidget(self.date_range_label, row, 0, 1, 1) # Date 'From' self.find_annotations_date_from_dateEdit = MyDateEdit(self, datetime(1970,1,1)) self.find_annotations_date_from_dateEdit.setObjectName('find_annotations_date_from_dateEdit') #self.find_annotations_date_from_dateEdit.current_val = datetime(1970,1,1) self.find_annotations_date_from_dateEdit.clear_button.clicked.connect(self.find_annotations_date_from_dateEdit.reset_from_date) self.scgl.addWidget(self.find_annotations_date_from_dateEdit, row, 1, 1, 1) self.scgl.addWidget(self.find_annotations_date_from_dateEdit.clear_button, row, 2, 1, 1) # Date 'To' self.find_annotations_date_to_dateEdit = MyDateEdit(self, datetime.today()) self.find_annotations_date_to_dateEdit.setObjectName('find_annotations_date_to_dateEdit') #self.find_annotations_date_to_dateEdit.current_val = datetime.today() self.find_annotations_date_to_dateEdit.clear_button.clicked.connect(self.find_annotations_date_to_dateEdit.reset_to_date) self.scgl.addWidget(self.find_annotations_date_to_dateEdit, row, 3, 1, 1) self.scgl.addWidget(self.find_annotations_date_to_dateEdit.clear_button, row, 4, 1, 1) row += 1 # ~~~~~~~~ Create a horizontal line ~~~~~~~~ self.hl = QFrame(self) self.hl.setGeometry(QRect(0, 0, 1, 3)) self.hl.setFrameShape(QFrame.HLine) self.hl.setFrameShadow(QFrame.Raised) self.scgl.addWidget(self.hl, row, 0, 1, 5) row += 1 # ~~~~~~~~ Create the results label field ~~~~~~~~ self.result_label = QLabel('<p style="color:red">{0}</p>'.format(_('scanning…'))) self.result_label.setAlignment(Qt.AlignCenter) self.result_label.setWordWrap(False) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Maximum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.result_label.sizePolicy().hasHeightForWidth()) self.result_label.setSizePolicy(sizePolicy) self.result_label.setMinimumSize(QtCore.QSize(250, 0)) self.scgl.addWidget(self.result_label, row, 0, 1, 5) row += 1 # ~~~~~~~~ Create the ButtonBox ~~~~~~~~ self.dialogButtonBox = QDialogButtonBox(self) self.dialogButtonBox.setOrientation(Qt.Horizontal) if False: self.update_button = QPushButton(_('Update results')) self.update_button.setDefault(True) self.update_button.setVisible(False) self.dialogButtonBox.addButton(self.update_button, QDialogButtonBox.ActionRole) self.cancel_button = self.dialogButtonBox.addButton(self.dialogButtonBox.Cancel) self.find_button = self.dialogButtonBox.addButton(self.dialogButtonBox.Ok) self.find_button.setText(_('Find Matching Books')) self.l.addWidget(self.dialogButtonBox) self.dialogButtonBox.clicked.connect(self.find_annotations_dialog_clicked) # ~~~~~~~~ Add a spacer ~~~~~~~~ self.spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.l.addItem(self.spacerItem) # ~~~~~~~~ Restore previously saved settings ~~~~~~~~ self.restore_settings() # ~~~~~~~~ Declare sizing ~~~~~~~~ sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth()) self.setSizePolicy(sizePolicy) self.resize_dialog() # ~~~~~~~~ Connect all signals ~~~~~~~~ self.find_annotations_reader_comboBox.currentIndexChanged.connect(partial(self.update_results, 'reader')) self.find_annotations_color_comboBox.currentIndexChanged.connect(partial(self.update_results, 'color')) self.find_annotations_text_lineEdit.editingFinished.connect(partial(self.update_results, 'text')) self.find_annotations_note_lineEdit.editingFinished.connect(partial(self.update_results, 'note')) # self.connect(self.find_annotations_text_lineEdit, pyqtSignal("return_pressed"), self.return_pressed) self.find_annotations_text_lineEdit.return_pressed.connect(self.return_pressed) # self.connect(self.find_annotations_note_lineEdit, pyqtSignal("return_pressed"), self.return_pressed) self.find_annotations_note_lineEdit.return_pressed.connect(self.return_pressed) # Date range signals connected in inventory_available() # ~~~~~~~~ Allow dialog to render before doing inventory ~~~~~~~~ #field = self.prefs.get('cfg_annotations_destination_field', None) field = get_cc_mapping('annotations', 'field', None) self.annotated_books_scanner = InventoryAnnotatedBooks(self.opts.gui, field, get_date_range=True) self.annotated_books_scanner.signal.connect(self.inventory_available) QTimer.singleShot(1, self.start_inventory_scan) def clear_note_field(self): if str(self.find_annotations_note_lineEdit.text()) > '': self.find_annotations_note_lineEdit.setText('') self.update_results('clear_note_field') def clear_text_field(self): if str(self.find_annotations_text_lineEdit.text()) > '': self.find_annotations_text_lineEdit.setText('') self.update_results('clear_text_field') def find_annotations_dialog_clicked(self, button): if self.dialogButtonBox.buttonRole(button) == QDialogButtonBox.AcceptRole: self.save_settings() self.accept() elif self.dialogButtonBox.buttonRole(button) == QDialogButtonBox.RejectRole: self.close() def inventory_available(self): ''' Update the Date range widgets with the rounded oldest, newest dates Don't connect date signals until date range available ''' self._log_location() # Reset the date range based on available annotations oldest = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.oldest_annotation)) oldest_day = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.oldest_annotation).replace(hour=0, minute=0, second=0)) newest = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.newest_annotation)) newest_day = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.newest_annotation).replace(hour=23, minute=59, second=59)) # Set 'From' date limits to inventory values self.find_annotations_date_from_dateEdit.setMinimumDateTime(oldest_day) self.find_annotations_date_from_dateEdit.current_val = oldest self.find_annotations_date_from_dateEdit.setMaximumDateTime(newest_day) # Set 'To' date limits to inventory values self.find_annotations_date_to_dateEdit.setMinimumDateTime(oldest_day) self.find_annotations_date_to_dateEdit.current_val = newest_day self.find_annotations_date_to_dateEdit.setMaximumDateTime(newest_day) # Connect the signals for date range changes self.find_annotations_date_from_dateEdit.dateTimeChanged.connect(partial(self.update_results, 'from_date')) self.find_annotations_date_to_dateEdit.dateTimeChanged.connect(partial(self.update_results, 'to_date')) self.update_results('inventory_available') def restore_settings(self): self.blockSignals(True) ra = self.prefs.get('find_annotations_reader_comboBox', self.GENERIC_READER) ra_index = self.find_annotations_reader_comboBox.findText(ra) self.find_annotations_reader_comboBox.setCurrentIndex(ra_index) color = self.prefs.get('find_annotations_color_comboBox', self.GENERIC_STYLE) color_index = self.find_annotations_color_comboBox.findText(color) self.find_annotations_color_comboBox.setCurrentIndex(color_index) text = self.prefs.get('find_annotations_text_lineEdit', '') self.find_annotations_text_lineEdit.setText(text) note = self.prefs.get('find_annotations_note_lineEdit', '') self.find_annotations_note_lineEdit.setText(note) if False: from_date = self.prefs.get('find_annotations_date_from_dateEdit', datetime(1970,1,1)) self.find_annotations_date_from_dateEdit.current_val = from_date to_date = self.prefs.get('find_annotations_date_to_dateEdit', datetime.today()) self.find_annotations_date_to_dateEdit.current_val = to_date self.blockSignals(False) def return_pressed(self): self.update_results("return_pressed") def save_settings(self): ra = str(self.find_annotations_reader_comboBox.currentText()) self.prefs.set('find_annotations_reader_comboBox', ra) color = str(self.find_annotations_color_comboBox.currentText()) self.prefs.set('find_annotations_color_comboBox', color) text = str(self.find_annotations_text_lineEdit.text()) self.prefs.set('find_annotations_text_lineEdit', text) note = str(self.find_annotations_note_lineEdit.text()) self.prefs.set('find_annotations_note_lineEdit', note) if False: from_date = self.find_annotations_date_from_dateEdit.current_val self.prefs.set('find_annotations_date_from_dateEdit', from_date) to_date = self.find_annotations_date_to_dateEdit.current_val self.prefs.set('find_annotations_date_to_dateEdit', to_date) def start_inventory_scan(self): self._log_location() self.annotated_books_scanner.start() def update_results(self, trigger): #self._log_location(trigger) reader_to_match = str(self.find_annotations_reader_comboBox.currentText()) color_to_match = str(self.find_annotations_color_comboBox.currentText()) text_to_match = str(self.find_annotations_text_lineEdit.text()) note_to_match = str(self.find_annotations_note_lineEdit.text()) from_date = self.find_annotations_date_from_dateEdit.dateTime().toTime_t() to_date = self.find_annotations_date_to_dateEdit.dateTime().toTime_t() annotation_map = self.annotated_books_scanner.annotation_map #field = self.prefs.get("cfg_annotations_destination_field", None) field = get_cc_mapping('annotations', 'field', None) db = self.opts.gui.current_db matched_titles = [] self.matched_ids = set() for cid in annotation_map: mi = db.get_metadata(cid, index_is_id=True) soup = None if field == 'Comments': if mi.comments: soup = BeautifulSoup(mi.comments) else: if mi.get_user_metadata(field, False)['#value#'] is not None: soup = BeautifulSoup(mi.get_user_metadata(field, False)['#value#']) if soup: uas = soup.findAll('div', 'annotation') for ua in uas: # Are we already logged? if cid in self.matched_ids: continue # Check reader if reader_to_match != self.GENERIC_READER: this_reader = ua['reader'] if this_reader != reader_to_match: continue # Check color if color_to_match != self.GENERIC_STYLE: this_color = ua.find('table')['color'] if this_color != color_to_match: continue # Check date range, allow for mangled timestamp try: timestamp = float(ua.find('td', 'timestamp')['uts']) if timestamp < from_date or timestamp > to_date: continue except: continue highlight_text = '' try: pels = ua.findAll('p', 'highlight') highlight_text = '\n'.join([p.string for p in pels]) except: pass if text_to_match > '': if not re.search(text_to_match, highlight_text, flags=re.IGNORECASE): continue note_text = '' try: nels = ua.findAll('p', 'note') note_text = '\n'.join([n.string for n in nels]) except: pass if note_to_match > '': if not re.search(note_to_match, note_text, flags=re.IGNORECASE): continue # If we made it this far, add the id to matched_ids self.matched_ids.add(cid) matched_titles.append(mi.title) # Update the results box matched_titles.sort() if len(annotation_map): if len(matched_titles): first_match = ("<i>%s</i>" % matched_titles[0]) if len(matched_titles) == 1: results = first_match else: results = first_match + (_(" and {0} more.").format(len(matched_titles) - 1)) self.result_label.setText('<p style="color:blue">{0}</p>'.format(results)) else: self.result_label.setText('<p style="color:red">{0}</p>'.format(_('no matches'))) else: self.result_label.setText('<p style="color:red">{0}</p>'.format(_('no annotated books in library'))) self.resize_dialog()
class AnnotatedBooksDialog(SizePersistedDialog): ''' This dialog is shown when the user fetches or imports books self.fetch_single_annotations controls checkmark display, behavior of fetch button ''' if isosx: FONT = QFont('Monaco', 11) elif iswindows: FONT = QFont('Lucida Console', 9) elif islinux: FONT = QFont('Monospace', 9) FONT.setStyleHint(QFont.TypeWriter) def __init__(self, parent, book_list, get_annotations_as_HTML, source): self.opts = parent.opts self.parent = parent self.get_annotations_as_HTML = get_annotations_as_HTML self.show_confidence_colors = self.opts.prefs.get( 'annotated_books_dialog_show_confidence_as_bg_color', True) self.source = source # QDialog.__init__(self, parent=self.opts.gui) SizePersistedDialog.__init__( self, self.opts.gui, 'Annotations plugin:import annotations dialog') self.setWindowTitle(_('Import Annotations')) self.setWindowIcon(self.opts.icon) self.l = QVBoxLayout(self) self.setLayout(self.l) self.perfect_width = 0 from calibre_plugins.annotations.appearance import default_timestamp friendly_timestamp_format = plugin_prefs.get( 'appearance_timestamp_format', default_timestamp) # Are we collecting News clippings? collect_news_clippings = self.opts.prefs.get( 'cfg_news_clippings_checkbox', False) news_clippings_destination = self.opts.prefs.get( 'cfg_news_clippings_lineEdit', None) # Populate the table data self.tabledata = [] for book_data in book_list: enabled = QCheckBox() enabled.setChecked(True) # last_annotation sorts by timestamp last_annotation = SortableTableWidgetItem( strftime(friendly_timestamp_format, localtime(book_data['last_update'])), book_data['last_update']) # reader_app sorts case-insensitive reader_app = SortableTableWidgetItem( book_data['reader_app'], book_data['reader_app'].upper()) # title, author sort by title_sort, author_sort if not book_data['title_sort']: book_data['title_sort'] = book_data['title'] title = SortableTableWidgetItem(book_data['title'], book_data['title_sort'].upper()) if not book_data['author_sort']: book_data['author_sort'] = book_data['author'] author = SortableTableWidgetItem(book_data['author'], book_data['author_sort'].upper()) genres = book_data['genre'].split(', ') if 'News' in genres and collect_news_clippings: cid = get_clippings_cid(self, news_clippings_destination) confidence = 5 else: cid, confidence = parent.generate_confidence(book_data) # List order matches self.annotations_header this_book = [ book_data['uuid'], book_data['book_id'], book_data['genre'], enabled, reader_app, title, author, last_annotation, book_data['annotations'], confidence ] self.tabledata.append(this_book) self.tv = QTableView(self) self.l.addWidget(self.tv) self.annotations_header = [ 'uuid', 'book_id', 'genre', '', _('Reader App'), _('Title'), _('Author'), _('Last Annotation'), _('Annotations'), _('Confidence') ] self.ENABLED_COL = 3 self.READER_APP_COL = 4 self.TITLE_COL = 5 self.AUTHOR_COL = 6 self.LAST_ANNOTATION_COL = 7 self.CONFIDENCE_COL = 9 columns_to_center = [8] self.tm = MarkupTableModel(self, columns_to_center=columns_to_center) self.tv.setModel(self.tm) self.tv.setShowGrid(False) self.tv.setFont(self.FONT) self.tvSelectionModel = self.tv.selectionModel() self.tv.setAlternatingRowColors(not self.show_confidence_colors) self.tv.setShowGrid(False) self.tv.setWordWrap(False) self.tv.setSelectionBehavior(self.tv.SelectRows) # Connect signals self.tv.doubleClicked.connect(self.getTableRowDoubleClick) self.tv.horizontalHeader().sectionClicked.connect( self.capture_sort_column) # Hide the vertical self.header self.tv.verticalHeader().setVisible(False) # Hide uuid, book_id, genre, confidence self.tv.hideColumn(self.annotations_header.index('uuid')) self.tv.hideColumn(self.annotations_header.index('book_id')) self.tv.hideColumn(self.annotations_header.index('genre')) # self.tv.hideColumn(self.annotations_header.index(_('Confidence'))) self.tv.hideColumn(self.CONFIDENCE_COL) # Set horizontal self.header props self.tv.horizontalHeader().setStretchLastSection(True) narrow_columns = [ _('Last Annotation'), _('Reader App'), _('Annotations') ] extra_width = 10 breathing_space = 20 # Set column width to fit contents self.tv.resizeColumnsToContents() perfect_width = 10 + (len(narrow_columns) * extra_width) for i in range(3, 8): perfect_width += self.tv.columnWidth(i) + breathing_space self.tv.setMinimumSize(perfect_width, 100) self.perfect_width = perfect_width # Add some width to narrow columns for nc in narrow_columns: cw = self.tv.columnWidth(self.annotations_header.index(nc)) self.tv.setColumnWidth(self.annotations_header.index(nc), cw + extra_width) # Set row height fm = QFontMetrics(self.FONT) nrows = len(self.tabledata) for row in xrange(nrows): self.tv.setRowHeight(row, fm.height() + 4) self.tv.setSortingEnabled(True) sort_column = self.opts.prefs.get('annotated_books_dialog_sort_column', self.CONFIDENCE_COL) sort_order = self.opts.prefs.get('annotated_books_dialog_sort_order', Qt.DescendingOrder) self.tv.sortByColumn(sort_column, sort_order) # ~~~~~~~~ Create the ButtonBox ~~~~~~~~ self.dialogButtonBox = QDialogButtonBox(QDialogButtonBox.Cancel | QDialogButtonBox.Help) self.dialogButtonBox.setOrientation(Qt.Horizontal) self.import_button = self.dialogButtonBox.addButton( self.dialogButtonBox.Ok) self.import_button.setText(_('Import Annotations')) # Action buttons self.toggle_checkmarks_button = self.dialogButtonBox.addButton( _('Clear All'), QDialogButtonBox.ActionRole) self.toggle_checkmarks_button.setObjectName('toggle_checkmarks_button') scb_text = _('Show match status') if self.show_confidence_colors: scb_text = _("Hide match status") self.show_confidence_button = self.dialogButtonBox.addButton( scb_text, QDialogButtonBox.ActionRole) self.show_confidence_button.setObjectName('confidence_button') if self.show_confidence_colors: self.show_confidence_button.setIcon( get_icon('images/matches_hide.png')) else: self.show_confidence_button.setIcon( get_icon('images/matches_show.png')) self.preview_button = self.dialogButtonBox.addButton( _('Preview'), QDialogButtonBox.ActionRole) self.preview_button.setObjectName('preview_button') self.dialogButtonBox.clicked.connect( self.show_annotated_books_dialog_clicked) self.l.addWidget(self.dialogButtonBox) # Cause our dialog size to be restored from prefs or created on first usage self.resize_dialog() def capture_sort_column(self, sort_column): sort_order = self.tv.horizontalHeader().sortIndicatorOrder() self.opts.prefs.set('annotated_books_dialog_sort_column', sort_column) self.opts.prefs.set('annotated_books_dialog_sort_order', sort_order) def fetch_selected_annotations(self): ''' Invoked by 'Import annotations' button in show_annotated_books_dialog() Populate a list of books by Reader App: { 'iBooks': [{'title':, 'author':, 'uuid'}, ...], 'Marvin': [{'title':, 'author':, 'uuid'}, ...] } ''' self.selected_books = {} for i in range(len(self.tabledata)): self.tv.selectRow(i) enabled = bool(self.tm.arraydata[i][self.ENABLED_COL].checkState()) if not enabled: continue reader_app = str(self.tm.arraydata[i][self.READER_APP_COL].text()) if not reader_app in self.selected_books: self.selected_books[reader_app] = [] author = str(self.tm.arraydata[i][self.AUTHOR_COL].text()) book_id = self.tm.arraydata[i][self.annotations_header.index( 'book_id')] genre = self.tm.arraydata[i][self.annotations_header.index( 'genre')] title = str(self.tm.arraydata[i][self.TITLE_COL].text()) uuid = self.tm.arraydata[i][self.annotations_header.index('uuid')] book_mi = BookStruct() book_mi.author = author book_mi.book_id = book_id book_mi.genre = genre book_mi.reader_app = reader_app book_mi.title = title book_mi.uuid = uuid self.selected_books[reader_app].append(book_mi) def getTableRowDoubleClick(self, index): self.preview_annotations() def preview_annotations(self): """ The listed annotations are in annotations.db. AnnotationsDB:annotations_to_HTML() needs title, book_id, reader_app """ i = self.tvSelectionModel.currentIndex().row() reader_app = str(self.tm.arraydata[i][self.READER_APP_COL].text()) title = str(self.tm.arraydata[i][self.TITLE_COL].text()) book_mi = BookStruct() book_mi.book_id = self.tm.arraydata[i][self.annotations_header.index( 'book_id')] book_mi.reader_app = reader_app book_mi.title = title # Render annotations from db annotations_db = ReaderApp.generate_annotations_db_name( reader_app, self.source) annotations = self.get_annotations_as_HTML(annotations_db, book_mi) PreviewDialog(book_mi, annotations, parent=self.opts.gui).exec_() def show_annotated_books_dialog_clicked(self, button): ''' BUTTON_ROLES = ['AcceptRole', 'RejectRole', 'DestructiveRole', 'ActionRole', 'HelpRole', 'YesRole', 'NoRole', 'ApplyRole', 'ResetRole'] ''' if self.dialogButtonBox.buttonRole( button) == QDialogButtonBox.AcceptRole: self.fetch_selected_annotations() self.accept() elif self.dialogButtonBox.buttonRole( button) == QDialogButtonBox.ActionRole: if button.objectName() == 'confidence_button': self.toggle_confidence_colors() elif button.objectName() == 'preview_button': self.preview_annotations() elif button.objectName() == 'toggle_checkmarks_button': self.toggle_checkmarks() elif self.dialogButtonBox.buttonRole( button) == QDialogButtonBox.HelpRole: self.show_help() elif self.dialogButtonBox.buttonRole( button) == QDialogButtonBox.RejectRole: self.close() def show_help(self): ''' Display help file ''' hv = HelpView(self, self.opts.icon, self.opts.prefs, html=get_resources('help/import_annotations.html'), title=_("Import Annotations")) hv.show() def size_hint(self): return QtCore.QSize(self.perfect_width, self.height()) def start_confidence_scan(self): self.annotated_books_scanner.start() def toggle_checkmarks(self): button_text = str(self.toggle_checkmarks_button.text()) if button_text == _('Clear All'): for i in range(len(self.tabledata)): self.tm.arraydata[i][self.ENABLED_COL].setCheckState(False) self.toggle_checkmarks_button.setText(_('Set All')) else: for i in range(len(self.tabledata)): self.tm.arraydata[i][self.ENABLED_COL].setCheckState(True) self.toggle_checkmarks_button.setText(_('Clear All')) self.tm.refresh(self.show_confidence_colors) def toggle_confidence_colors(self): self.show_confidence_colors = not self.show_confidence_colors self.opts.prefs.set( 'annotated_books_dialog_show_confidence_as_bg_color', self.show_confidence_colors) if self.show_confidence_colors: self.show_confidence_button.setText(_("Hide match status")) self.show_confidence_button.setIcon( get_icon('images/matches_hide.png')) self.tv.sortByColumn(self.CONFIDENCE_COL, Qt.DescendingOrder) self.capture_sort_column(self.CONFIDENCE_COL) else: self.show_confidence_button.setText(_("Show match status")) self.show_confidence_button.setIcon( get_icon('images/matches_show.png')) self.tv.setAlternatingRowColors(not self.show_confidence_colors) self.tm.refresh(self.show_confidence_colors)
class AnnotatedBooksDialog(SizePersistedDialog): ''' This dialog is shown when the user fetches or imports books self.fetch_single_annotations controls checkmark display, behavior of fetch button ''' if isosx: FONT = QFont('Monaco', 11) elif iswindows: FONT = QFont('Lucida Console', 9) elif islinux: FONT = QFont('Monospace', 9) FONT.setStyleHint(QFont.TypeWriter) def __init__(self, parent, book_list, get_annotations_as_HTML, source): self.opts = parent.opts self.parent = parent self.get_annotations_as_HTML = get_annotations_as_HTML self.show_confidence_colors = self.opts.prefs.get('annotated_books_dialog_show_confidence_as_bg_color', True) self.source = source # QDialog.__init__(self, parent=self.opts.gui) SizePersistedDialog.__init__(self, self.opts.gui, 'Annotations plugin:import annotations dialog') self.setWindowTitle(u'Import Annotations') self.setWindowIcon(self.opts.icon) self.l = QVBoxLayout(self) self.setLayout(self.l) self.perfect_width = 0 from calibre_plugins.annotations.appearance import default_timestamp friendly_timestamp_format = plugin_prefs.get('appearance_timestamp_format', default_timestamp) # Are we collecting News clippings? collect_news_clippings = self.opts.prefs.get('cfg_news_clippings_checkbox', False) news_clippings_destination = self.opts.prefs.get('cfg_news_clippings_lineEdit', None) # Populate the table data self.tabledata = [] for book_data in book_list: enabled = QCheckBox() enabled.setChecked(True) # last_annotation sorts by timestamp last_annotation = SortableTableWidgetItem( strftime(friendly_timestamp_format, localtime(book_data['last_update'])), book_data['last_update']) # reader_app sorts case-insensitive reader_app = SortableTableWidgetItem( book_data['reader_app'], book_data['reader_app'].upper()) # title, author sort by title_sort, author_sort if not book_data['title_sort']: book_data['title_sort'] = book_data['title'] title = SortableTableWidgetItem( book_data['title'], book_data['title_sort'].upper()) if not book_data['author_sort']: book_data['author_sort'] = book_data['author'] author = SortableTableWidgetItem( book_data['author'], book_data['author_sort'].upper()) genres = book_data['genre'].split(', ') if 'News' in genres and collect_news_clippings: cid = get_clippings_cid(self, news_clippings_destination) confidence = 5 else: cid, confidence = parent.generate_confidence(book_data) # List order matches self.annotations_header this_book = [ book_data['uuid'], book_data['book_id'], book_data['genre'], enabled, reader_app, title, author, last_annotation, book_data['annotations'], confidence] self.tabledata.append(this_book) self.tv = QTableView(self) self.l.addWidget(self.tv) self.annotations_header = ['uuid', 'book_id', 'genre', '', 'Reader App', 'Title', 'Author', 'Last Annotation', 'Annotations', 'Confidence'] self.ENABLED_COL = 3 self.READER_APP_COL = 4 self.TITLE_COL = 5 self.AUTHOR_COL = 6 self.LAST_ANNOTATION_COL = 7 self.CONFIDENCE_COL = 9 columns_to_center = [8] self.tm = MarkupTableModel(self, columns_to_center=columns_to_center) self.tv.setModel(self.tm) self.tv.setShowGrid(False) self.tv.setFont(self.FONT) self.tvSelectionModel = self.tv.selectionModel() self.tv.setAlternatingRowColors(not self.show_confidence_colors) self.tv.setShowGrid(False) self.tv.setWordWrap(False) self.tv.setSelectionBehavior(self.tv.SelectRows) # Connect signals self.tv.doubleClicked.connect(self.getTableRowDoubleClick) self.tv.horizontalHeader().sectionClicked.connect(self.capture_sort_column) # Hide the vertical self.header self.tv.verticalHeader().setVisible(False) # Hide uuid, book_id, genre, confidence self.tv.hideColumn(self.annotations_header.index('uuid')) self.tv.hideColumn(self.annotations_header.index('book_id')) self.tv.hideColumn(self.annotations_header.index('genre')) self.tv.hideColumn(self.annotations_header.index('Confidence')) # Set horizontal self.header props self.tv.horizontalHeader().setStretchLastSection(True) narrow_columns = ['Last Annotation', 'Reader App', 'Annotations'] extra_width = 10 breathing_space = 20 # Set column width to fit contents self.tv.resizeColumnsToContents() perfect_width = 10 + (len(narrow_columns) * extra_width) for i in range(3, 8): perfect_width += self.tv.columnWidth(i) + breathing_space self.tv.setMinimumSize(perfect_width, 100) self.perfect_width = perfect_width # Add some width to narrow columns for nc in narrow_columns: cw = self.tv.columnWidth(self.annotations_header.index(nc)) self.tv.setColumnWidth(self.annotations_header.index(nc), cw + extra_width) # Set row height fm = QFontMetrics(self.FONT) nrows = len(self.tabledata) for row in xrange(nrows): self.tv.setRowHeight(row, fm.height() + 4) self.tv.setSortingEnabled(True) sort_column = self.opts.prefs.get('annotated_books_dialog_sort_column', self.annotations_header.index('Confidence')) sort_order = self.opts.prefs.get('annotated_books_dialog_sort_order', Qt.DescendingOrder) self.tv.sortByColumn(sort_column, sort_order) # ~~~~~~~~ Create the ButtonBox ~~~~~~~~ self.dialogButtonBox = QDialogButtonBox(QDialogButtonBox.Cancel | QDialogButtonBox.Help) self.dialogButtonBox.setOrientation(Qt.Horizontal) self.import_button = self.dialogButtonBox.addButton(self.dialogButtonBox.Ok) self.import_button.setText('Import Annotations') # Action buttons self.toggle_checkmarks_button = self.dialogButtonBox.addButton('Clear All', QDialogButtonBox.ActionRole) self.toggle_checkmarks_button.setObjectName('toggle_checkmarks_button') scb_text = 'Show match status' if self.show_confidence_colors: scb_text = "Hide match status" self.show_confidence_button = self.dialogButtonBox.addButton(scb_text, QDialogButtonBox.ActionRole) self.show_confidence_button.setObjectName('confidence_button') if self.show_confidence_colors: self.show_confidence_button.setIcon(get_icon('images/matches_hide.png')) else: self.show_confidence_button.setIcon(get_icon('images/matches_show.png')) self.preview_button = self.dialogButtonBox.addButton('Preview', QDialogButtonBox.ActionRole) self.preview_button.setObjectName('preview_button') self.dialogButtonBox.clicked.connect(self.show_annotated_books_dialog_clicked) self.l.addWidget(self.dialogButtonBox) # Cause our dialog size to be restored from prefs or created on first usage self.resize_dialog() def capture_sort_column(self, sort_column): sort_order = self.tv.horizontalHeader().sortIndicatorOrder() self.opts.prefs.set('annotated_books_dialog_sort_column', sort_column) self.opts.prefs.set('annotated_books_dialog_sort_order', sort_order) def fetch_selected_annotations(self): ''' Invoked by 'Import annotations' button in show_annotated_books_dialog() Populate a list of books by Reader App: { 'iBooks': [{'title':, 'author':, 'uuid'}, ...], 'Marvin': [{'title':, 'author':, 'uuid'}, ...] } ''' self.selected_books = {} for i in range(len(self.tabledata)): self.tv.selectRow(i) enabled = bool(self.tm.arraydata[i][self.ENABLED_COL].checkState()) if not enabled: continue reader_app = str(self.tm.arraydata[i][self.annotations_header.index('Reader App')].text()) if not reader_app in self.selected_books: self.selected_books[reader_app] = [] author = str(self.tm.arraydata[i][self.annotations_header.index('Author')].text()) book_id = self.tm.arraydata[i][self.annotations_header.index('book_id')] genre = self.tm.arraydata[i][self.annotations_header.index('genre')] title = str(self.tm.arraydata[i][self.annotations_header.index('Title')].text()) uuid = self.tm.arraydata[i][self.annotations_header.index('uuid')] book_mi = BookStruct() book_mi.author = author book_mi.book_id = book_id book_mi.genre = genre book_mi.reader_app = reader_app book_mi.title = title book_mi.uuid = uuid self.selected_books[reader_app].append(book_mi) def getTableRowDoubleClick(self, index): self.preview_annotations() def preview_annotations(self): """ The listed annotations are in annotations.db. AnnotationsDB:annotations_to_HTML() needs title, book_id, reader_app """ i = self.tvSelectionModel.currentIndex().row() reader_app = str(self.tm.arraydata[i][self.annotations_header.index('Reader App')].text()) title = str(self.tm.arraydata[i][self.annotations_header.index('Title')].text()) book_mi = BookStruct() book_mi.book_id = self.tm.arraydata[i][self.annotations_header.index('book_id')] book_mi.reader_app = reader_app book_mi.title = title # Render annotations from db annotations_db = ReaderApp.generate_annotations_db_name(reader_app, self.source) annotations = self.get_annotations_as_HTML(annotations_db, book_mi) PreviewDialog(book_mi, annotations, parent=self.opts.gui).exec_() def show_annotated_books_dialog_clicked(self, button): ''' BUTTON_ROLES = ['AcceptRole', 'RejectRole', 'DestructiveRole', 'ActionRole', 'HelpRole', 'YesRole', 'NoRole', 'ApplyRole', 'ResetRole'] ''' if self.dialogButtonBox.buttonRole(button) == QDialogButtonBox.AcceptRole: self.fetch_selected_annotations() self.accept() elif self.dialogButtonBox.buttonRole(button) == QDialogButtonBox.ActionRole: if button.objectName() == 'confidence_button': self.toggle_confidence_colors() elif button.objectName() == 'preview_button': self.preview_annotations() elif button.objectName() == 'toggle_checkmarks_button': self.toggle_checkmarks() elif self.dialogButtonBox.buttonRole(button) == QDialogButtonBox.HelpRole: self.show_help() elif self.dialogButtonBox.buttonRole(button) == QDialogButtonBox.RejectRole: self.close() def show_help(self): ''' Display help file ''' hv = HelpView(self, self.opts.icon, self.opts.prefs, html=get_resources('help/import_annotations.html'), title="Import Annotations") hv.show() def size_hint(self): return QtCore.QSize(self.perfect_width, self.height()) def start_confidence_scan(self): self.annotated_books_scanner.start() def toggle_checkmarks(self): button_text = str(self.toggle_checkmarks_button.text()) if button_text == 'Clear All': for i in range(len(self.tabledata)): self.tm.arraydata[i][self.ENABLED_COL].setCheckState(False) self.toggle_checkmarks_button.setText(' Set All ') else: for i in range(len(self.tabledata)): self.tm.arraydata[i][self.ENABLED_COL].setCheckState(True) self.toggle_checkmarks_button.setText('Clear All') self.tm.refresh(self.show_confidence_colors) def toggle_confidence_colors(self): self.show_confidence_colors = not self.show_confidence_colors self.opts.prefs.set('annotated_books_dialog_show_confidence_as_bg_color', self.show_confidence_colors) if self.show_confidence_colors: self.show_confidence_button.setText("Hide match status") self.show_confidence_button.setIcon(get_icon('images/matches_hide.png')) self.tv.sortByColumn(self.annotations_header.index('Confidence'), Qt.DescendingOrder) self.capture_sort_column(self.annotations_header.index('Confidence')) else: self.show_confidence_button.setText("Show match status") self.show_confidence_button.setIcon(get_icon('images/matches_show.png')) self.tv.setAlternatingRowColors(not self.show_confidence_colors) self.tm.refresh(self.show_confidence_colors)