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")
Esempio n. 2
0
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()
Esempio n. 5
0
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
Esempio n. 7
0
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)
Esempio n. 9
0
 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 _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))
Esempio n. 13
0
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',
            '&middot;  &middot;  &bull;  &middot;  &#x2726;  &middot;  &bull;  &middot; &middot;'
        ))

    # 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
Esempio n. 14
0
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
Esempio n. 15
0
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))
Esempio n. 16
0
    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))
Esempio n. 17
0
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)
Esempio n. 18
0
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', '&middot;  &middot;  &bull;  &middot;  &#x2726;  &middot;  &bull;  &middot; &middot;'))

    # 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
Esempio n. 19
0
    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)
Esempio n. 20
0
    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()