def restore_state(ui, restore_position=False): from calibre_plugins.annotations.config import plugin_prefs if restore_position: _restore_ui_position(ui, ui.controls['owner']) # Restore stateful controls for control_list in ui.controls: if control_list == 'owner': continue index = CONTROL_TYPES.index(control_list) for control in ui.controls[control_list]: control_ref = getattr(ui, control, None) if control_ref is not None: if isinstance(CONTROL_SET[index], unicode): setter_ref = getattr(control_ref, CONTROL_SET[index], None) if setter_ref is not None: if callable(setter_ref): setter_ref(plugin_prefs.get(control, CONTROL_DEFAULT[index])) elif isinstance(CONTROL_SET[index], tuple) and len(CONTROL_SET[index]) == 2: # Special case for comboBox - first findText, then setCurrentIndex setter_ref = getattr(control_ref, CONTROL_SET[index][0], None) if setter_ref is not None: if callable(setter_ref): result = setter_ref(plugin_prefs.get(control, CONTROL_DEFAULT[index])) setter_ref = getattr(control_ref, CONTROL_SET[index][1], None) if setter_ref is not None: if callable(setter_ref): setter_ref(result) else: print(" invalid CONTROL_SET tuple for '%s'" % control) print(" maximum of two chained methods")
def sort_merged_annotations(merged_soup): ''' Input: a combined group of user annotations Output: sorted by location ''' include_hr = plugin_prefs.get('appearance_hr_checkbox', False) locations = merged_soup.findAll(location_sort=True) locs = [loc['location_sort'] for loc in locations] locs.sort() sorted_soup = BeautifulSoup(ANNOTATIONS_HEADER) dtc = 0 for i, loc in enumerate(locs): next_div = merged_soup.find(attrs={'location_sort': loc}) sorted_soup.div.insert(dtc, next_div) dtc += 1 if include_hr and i < len(locs) - 1: sorted_soup.div.insert(dtc, BeautifulSoup(plugin_prefs.get('HORIZONTAL_RULE', '<hr width="80%" />'))) dtc += 1 return sorted_soup
def sort_merged_annotations(merged_soup): ''' Input: a combined group of user annotations Output: sorted by location ''' include_hr = plugin_prefs.get('appearance_hr_checkbox', False) locations = merged_soup.findAll(location_sort=True) locs = [loc['location_sort'] for loc in locations] locs.sort() sorted_soup = BeautifulSoup(ANNOTATIONS_HEADER) dtc = 0 for i, loc in enumerate(locs): next_div = merged_soup.find(attrs={'location_sort': loc}) sorted_soup.div.insert(dtc, next_div) dtc += 1 if include_hr and i < len(locs) - 1: sorted_soup.div.insert(dtc, plugin_prefs.get('HORIZONTAL_RULE', '<hr width="80%" />')) dtc += 1 return sorted_soup
def _log(self, msg=None): ''' Print msg to console ''' from calibre_plugins.annotations.config import plugin_prefs if not plugin_prefs.get('cfg_plugin_debug_log_checkbox', False): return if msg: debug_print(" %s" % str(msg)) else: debug_print()
def _log(msg=None): ''' Print msg to console ''' from calibre_plugins.annotations.config import plugin_prefs if not plugin_prefs.get('cfg_plugin_debug_log_checkbox', False): return if msg: debug_print(" %s" % str(msg)) else: debug_print()
def _timestamp_to_datestr(self, timestamp): ''' Convert timestamp to 01 Jan 2011 12:34:56 ''' from calibre_plugins.annotations.appearance import default_timestamp d = datetime.fromtimestamp(float(timestamp)) friendly_timestamp_format = plugin_prefs.get('appearance_timestamp_format', default_timestamp) try: friendly_timestamp = d.strftime(friendly_timestamp_format) except: friendly_timestamp = d.strftime(default_timestamp) return friendly_timestamp
def restore_state(ui, restore_position=False): from calibre_plugins.annotations.config import plugin_prefs if restore_position: _restore_ui_position(ui, ui.controls['owner']) # Restore stateful controls for control_list in ui.controls: if control_list == 'owner': continue index = CONTROL_TYPES.index(control_list) for control in ui.controls[control_list]: control_ref = getattr(ui, control, None) if control_ref is not None: if isinstance(CONTROL_SET[index], unicode): setter_ref = getattr(control_ref, CONTROL_SET[index], None) if setter_ref is not None: if isinstance(setter_ref, collections.Callable): setter_ref( plugin_prefs.get(control, CONTROL_DEFAULT[index])) elif isinstance(CONTROL_SET[index], tuple) and len( CONTROL_SET[index]) == 2: # Special case for comboBox - first findText, then setCurrentIndex setter_ref = getattr(control_ref, CONTROL_SET[index][0], None) if setter_ref is not None: if isinstance(setter_ref, collections.Callable): result = setter_ref( plugin_prefs.get(control, CONTROL_DEFAULT[index])) setter_ref = getattr(control_ref, CONTROL_SET[index][1], None) if setter_ref is not None: if isinstance(setter_ref, collections.Callable): setter_ref(result) else: print(" invalid CONTROL_SET tuple for '%s'" % control) print(" maximum of two chained methods")
def set_cc_mapping(cc_name, field=None, combobox=None): ''' Store element to cc_name in prefs:cc_mappings ''' from calibre_plugins.annotations.config import plugin_prefs cc_mappings = plugin_prefs.get('cc_mappings', {}) current_library = current_library_name() if current_library in cc_mappings: cc_mappings[current_library][cc_name] = {'field': field, 'combobox': combobox} else: cc_mappings[current_library] = {cc_name: {'field': field, 'combobox': combobox}} plugin_prefs.set('cc_mappings', cc_mappings)
def _log_location(*args): LOCATION_TEMPLATE = "{cls}:{func}({arg1}) {arg2}" from calibre_plugins.annotations.config import plugin_prefs if not plugin_prefs.get('cfg_plugin_debug_log_checkbox', False): return arg1 = arg2 = '' if len(args) > 0: arg1 = str(args[0]) if len(args) > 1: arg2 = str(args[1]) debug_print(LOCATION_TEMPLATE.format(cls='common_utils', func=sys._getframe(1).f_code.co_name, arg1=arg1, arg2=arg2))
def get_cc_mapping(cc_name, element, default=None): ''' Return the element mapped to cc_name in prefs ''' from calibre_plugins.annotations.config import plugin_prefs if element not in ['field', 'combobox']: raise ValueError("invalid element '{0}' requested for custom column '{1}'".format( element, cc_name)) ans = default cc_mappings = plugin_prefs.get('cc_mappings', {}) current_library = current_library_name() if (current_library in cc_mappings and cc_name in cc_mappings[current_library] and element in cc_mappings[current_library][cc_name]): ans = cc_mappings[current_library][cc_name][element] return ans
def _log_location(self, *args): ''' Print location, args to console ''' from calibre_plugins.annotations.config import plugin_prefs if not plugin_prefs.get('cfg_plugin_debug_log_checkbox', False): return arg1 = arg2 = '' if len(args) > 0: arg1 = str(args[0]) if len(args) > 1: arg2 = str(args[1]) debug_print(self.LOCATION_TEMPLATE.format(cls=self.__class__.__name__, func=sys._getframe(1).f_code.co_name, arg1=arg1, arg2=arg2))
def merge_annotations_with_comments(parent, cid, comments_soup, new_soup): ''' comments_soup: comments potentially with user_annotations ''' # Prepare a new COMMENTS_DIVIDER comments_divider = '<div class="comments_divider"><p style="text-align:center;margin:1em 0 1em 0">{0}</p></div>'.format( plugin_prefs.get( 'COMMENTS_DIVIDER', '· · • · ✦ · • · ·' )) # Remove the old comments_divider cds = comments_soup.find('div', 'comments_divider') if cds: cds.extract() # Existing annotations? uas = comments_soup.find('div', 'user_annotations') if uas: # Save the existing annotations to old_soup old_soup = BeautifulSoup(unicode(uas)) # Remove any hrs from old_soup hrs = old_soup.findAll('hr') if hrs: for hr in hrs: hr.extract() # Remove the existing annotations from comments_soup uas.extract() # Merge old_soup with new_soup merged_soup = unicode(comments_soup) + \ unicode(comments_divider) + \ unicode(merge_annotations(parent, cid, old_soup, new_soup)) else: # No existing, just merge comments_soup with already sorted new_soup merged_soup = unicode(comments_soup) + \ unicode(comments_divider) + \ unicode(new_soup) return merged_soup
def get_cc_mapping(cc_name, element, default=None): ''' Return the element mapped to cc_name in prefs ''' from calibre_plugins.annotations.config import plugin_prefs if element not in ['field', 'combobox']: raise ValueError( "invalid element '{0}' requested for custom column '{1}'".format( element, cc_name)) ans = default cc_mappings = plugin_prefs.get('cc_mappings', {}) current_library = current_library_name() if (current_library in cc_mappings and cc_name in cc_mappings[current_library] and element in cc_mappings[current_library][cc_name]): ans = cc_mappings[current_library][cc_name][element] return ans
def _log_location(*args): LOCATION_TEMPLATE = "{cls}:{func}({arg1}) {arg2}" from calibre_plugins.annotations.config import plugin_prefs if not plugin_prefs.get('cfg_plugin_debug_log_checkbox', False): return arg1 = arg2 = '' if len(args) > 0: arg1 = str(args[0]) if len(args) > 1: arg2 = str(args[1]) debug_print( LOCATION_TEMPLATE.format(cls='common_utils', func=sys._getframe(1).f_code.co_name, arg1=arg1, arg2=arg2))
def _log_location(self, *args): ''' Print location, args to console ''' from calibre_plugins.annotations.config import plugin_prefs if not plugin_prefs.get('cfg_plugin_debug_log_checkbox', False): return arg1 = arg2 = '' if len(args) > 0: arg1 = str(args[0]) if len(args) > 1: arg2 = str(args[1]) debug_print( self.LOCATION_TEMPLATE.format(cls=self.__class__.__name__, func=sys._getframe(1).f_code.co_name, arg1=arg1, arg2=arg2))
def set_cc_mapping(cc_name, field=None, combobox=None): ''' Store element to cc_name in prefs:cc_mappings ''' from calibre_plugins.annotations.config import plugin_prefs cc_mappings = plugin_prefs.get('cc_mappings', {}) current_library = current_library_name() if current_library in cc_mappings: cc_mappings[current_library][cc_name] = { 'field': field, 'combobox': combobox } else: cc_mappings[current_library] = { cc_name: { 'field': field, 'combobox': combobox } } plugin_prefs.set('cc_mappings', cc_mappings)
def merge_annotations_with_comments(parent, cid, comments_soup, new_soup): ''' comments_soup: comments potentially with user_annotations ''' # Prepare a new COMMENTS_DIVIDER comments_divider = '<div class="comments_divider"><p style="text-align:center;margin:1em 0 1em 0">{0}</p></div>'.format( plugin_prefs.get('COMMENTS_DIVIDER', '· · • · ✦ · • · ·')) # Remove the old comments_divider cds = comments_soup.find('div', 'comments_divider') if cds: cds.extract() # Existing annotations? uas = comments_soup.find('div', 'user_annotations') if uas: # Save the existing annotations to old_soup old_soup = BeautifulSoup(unicode(uas)) # Remove any hrs from old_soup hrs = old_soup.findAll('hr') if hrs: for hr in hrs: hr.extract() # Remove the existing annotations from comments_soup uas.extract() # Merge old_soup with new_soup merged_soup = unicode(comments_soup) + \ unicode(comments_divider) + \ unicode(merge_annotations(parent, cid, old_soup, new_soup)) else: # No existing, just merge comments_soup with already sorted new_soup merged_soup = unicode(comments_soup) + \ unicode(comments_divider) + \ unicode(new_soup) return merged_soup
def to_HTML(self, header=''): ''' Generate HTML with user-specified CSS, element order ''' # Retrieve CSS prefs from calibre_plugins.annotations.appearance import default_elements stored_css = plugin_prefs.get('appearance_css', default_elements) elements = [] for element in stored_css: elements.append(element['name']) if element['name'] == 'Note': note_style = re.sub('\n', '', element['css']) elif element['name'] == 'Text': text_style = re.sub('\n', '', element['css']) elif element['name'] == 'Timestamp': ts_style = re.sub('\n', '', element['css']) # Additional CSS for timestamp color and bg to be formatted datetime_style = ("background-color:{0};color:{1};" + ts_style) # Order the elements according to stored preferences comments_body = '' for element in elements: if element == 'Text': comments_body += '{text}' elif element == 'Note': comments_body += '{note}' elif element == 'Timestamp': ts_css = '''<table cellpadding="0" width="100%" style="{ts_style}" color="{color}"> <tr> <td class="location" style="text-align:left">{location}</td> <td class="timestamp" uts="{unix_timestamp}" style="text-align:right">{friendly_timestamp}</td> </tr> </table>''' comments_body += re.sub(r'>\s+<', r'><', ts_css) # self._log_location("comments_body='%s'" % comments_body) if self.annotations: soup = BeautifulSoup(ANNOTATIONS_HEADER) dtc = 0 # Add the annotations for i, agroup in enumerate( sorted(self.annotations, key=self._annotation_sorter)): # self._log_location("agroup='%s'" % agroup) location = agroup.location if location is None: location = '' friendly_timestamp = self._timestamp_to_datestr( agroup.timestamp) text = '' if agroup.text: # self._log_location("agroup.text='%s'" % agroup.text) for agt in agroup.text: # self._log_location("agt='%s'" % agt) text += '<p class="highlight" style="{0}">{1}</p>'.format( text_style, agt) note = '' if agroup.note: # self._log_location("agroup.note='%s'" % agroup.note) for agn in agroup.note: # self._log_location("agn='%s'" % agn) note += '<p class="note" style="{0}">{1}</p>'.format( note_style, agn) try: dt_bgcolor = COLOR_MAP[agroup.highlightcolor]['bg'] dt_fgcolor = COLOR_MAP[agroup.highlightcolor]['fg'] except: if agroup.highlightcolor is None: msg = "No highlight color specified, using Default" else: msg = "Unknown color '%s' specified" % agroup.highlightcolor self._log_location(msg) dt_bgcolor = COLOR_MAP['Default']['bg'] dt_fgcolor = COLOR_MAP['Default']['fg'] if agroup.hash is not None: # Use existing hash when re-rendering annotation_hash = agroup.hash else: m = hashlib.md5() m.update(text.encode('utf-8')) m.update(note.encode('utf-8')) annotation_hash = m.hexdigest() try: ka_soup = BeautifulSoup() divTag = ka_soup.new_tag('div') # self._log_location("Used ka_soup.new_tag to create tag: %s" % divTag) except: divTag = Tag(BeautifulSoup(), 'div') # self._log_location("Used Tag(BeautifulSoup() to create tag: %s" % divTag) content_args = { 'color': agroup.highlightcolor, 'friendly_timestamp': friendly_timestamp, 'location': location, 'note': note, 'text': text, 'ts_style': datetime_style.format(dt_bgcolor, dt_fgcolor), 'unix_timestamp': agroup.timestamp, } # self._log_location("Generated comment soup: %s" % BeautifulSoup(comments_body.format(**content_args))) comments_body_soup = BeautifulSoup( comments_body.format(**content_args)) # self._log_location("Generated comment soup: comments_body_soup=%s" % comments_body_soup) # self._log_location("Generated comment soup: comments_body_soup.body=%s" % comments_body_soup.body) # self._log_location("Generated comment soup: comments_body_soup.body.children=%s" % comments_body_soup.body.children) # self._log_location("Generated comment soup: comments_body_soup.body.contents=%s" % comments_body_soup.body.contents) # self._log_location("Generated comment soup: len(comments_body_soup.body.contents)=%s" % len(comments_body_soup.body.contents)) # for i in range(0, len(comments_body_soup.body.contents)): # self._log_location("i=%s" % i) # self._log_location("comment_body_tag=%s" % comments_body_soup.body.contents[i]) while len(comments_body_soup.body.contents) > 0: # self._log_location("comment_body_tag=%s" % comments_body_soup.body.contents[0]) divTag.append(comments_body_soup.body.contents[0]) divTag['class'] = "annotation" divTag['genre'] = '' if agroup.genre: divTag['genre'] = escape(agroup.genre) divTag['hash'] = annotation_hash divTag['location_sort'] = agroup.location_sort divTag['reader'] = agroup.reader_app divTag['style'] = ANNOTATION_DIV_STYLE # self._log_location("An annotation - divTag=%s" % divTag) soup.div.insert(dtc, divTag) # self._log_location("Full soup after adding annotation - soup=%s" % soup) dtc += 1 if i < len(self.annotations) - 1 and \ plugin_prefs.get('appearance_hr_checkbox', False): soup.div.insert( dtc, BeautifulSoup( plugin_prefs.get('HORIZONTAL_RULE', '<hr width="80%" />'))) dtc += 1 else: soup = BeautifulSoup(ANNOTATIONS_HEADER) return unicode(soup)
def to_HTML(self, header=''): ''' Generate HTML with user-specified CSS, element order ''' # Retrieve CSS prefs from calibre_plugins.annotations.appearance import default_elements stored_css = plugin_prefs.get('appearance_css', default_elements) elements = [] for element in stored_css: elements.append(element['name']) if element['name'] == 'Note': note_style = re.sub('\n', '', element['css']) elif element['name'] == 'Text': text_style = re.sub('\n', '', element['css']) elif element['name'] == 'Timestamp': ts_style = re.sub('\n', '', element['css']) # Additional CSS for timestamp color and bg to be formatted datetime_style = ("background-color:{0};color:{1};" + ts_style) # Order the elements according to stored preferences comments_body = '' for element in elements: if element == 'Text': comments_body += '{text}' elif element == 'Note': comments_body += '{note}' elif element == 'Timestamp': ts_css = '''<table cellpadding="0" width="100%" style="{ts_style}" color="{color}"> <tr> <td class="location" style="text-align:left">{location}</td> <td class="timestamp" uts="{unix_timestamp}" style="text-align:right">{friendly_timestamp}</td> </tr> </table>''' comments_body += re.sub(r'>\s+<', r'><', ts_css) if self.annotations: soup = BeautifulSoup(ANNOTATIONS_HEADER) dtc = 0 # Add the annotations for i, agroup in enumerate(sorted(self.annotations, key=self._annotation_sorter)): location = agroup.location if location is None: location = '' friendly_timestamp = self._timestamp_to_datestr(agroup.timestamp) text = '' if agroup.text: for agt in agroup.text: text += '<p class="highlight" style="{0}">{1}</p>'.format(text_style, agt) note = '' if agroup.note: for agn in agroup.note: note += '<p class="note" style="{0}">{1}</p>'.format(note_style, agn) try: dt_bgcolor = COLOR_MAP[agroup.highlightcolor]['bg'] dt_fgcolor = COLOR_MAP[agroup.highlightcolor]['fg'] except: if agroup.highlightcolor is None: msg = "No highlight color specified, using Default" else: msg = "Unknown color '%s' specified" % agroup.highlightcolor self._log_location(msg) dt_bgcolor = COLOR_MAP['Default']['bg'] dt_fgcolor = COLOR_MAP['Default']['fg'] if agroup.hash is not None: # Use existing hash when re-rendering hash = agroup.hash else: m = hashlib.md5() m.update(text) m.update(note) hash = m.hexdigest() divTag = Tag(BeautifulSoup(), 'div') content_args = { 'color': agroup.highlightcolor, 'friendly_timestamp': friendly_timestamp, 'location': location, 'note': note, 'text': text, 'ts_style': datetime_style.format(dt_bgcolor, dt_fgcolor), 'unix_timestamp': agroup.timestamp, } divTag.insert(0, comments_body.format(**content_args)) divTag['class'] = "annotation" divTag['genre'] = '' if agroup.genre: divTag['genre'] = escape(agroup.genre) divTag['hash'] = hash divTag['location_sort'] = agroup.location_sort divTag['reader'] = agroup.reader_app divTag['style'] = ANNOTATION_DIV_STYLE soup.div.insert(dtc, divTag) dtc += 1 if i < len(self.annotations) - 1 and \ plugin_prefs.get('appearance_hr_checkbox', False): soup.div.insert(dtc, plugin_prefs.get('HORIZONTAL_RULE', '<hr width="80%" />')) dtc += 1 else: soup = BeautifulSoup(ANNOTATIONS_HEADER) return unicode(soup.renderContents())
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 __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()