Example #1
0
    def about(self):
        '''
        Display a short help message
        '''
        from os.path import join
        from calibre.ptempfile import TemporaryDirectory
        from calibre.gui2.dialogs.message_box import MessageBox
        from calibre_plugins.prince_pdf.help import help_txt, license_txt
        from calibre_plugins.prince_pdf import PrincePDFPlugin
        from calibre_plugins.prince_pdf import __license__

        author = PrincePDFPlugin.author
        version = "%i.%i.%i" % PrincePDFPlugin.version
        license = __license__
        with TemporaryDirectory('xxx') as tdir:
            for x in ('prince_icon.png', 'small_icon.png'):
                with open(join(tdir, x), 'w') as f:
                    f.write(get_resources('images/' + x))
            help_box = MessageBox(type_ = MessageBox.INFO, \
                                  title = _('About the Prince PDF Plugin'), \
                                  msg = help_txt % {'author':author, 'version':version, 'license':license, 'dir':tdir, 'code':'style="font-family:monospace ; font-weight:bold"'}, \
                                  det_msg = 'Copyright \u00a9 %s\n%s' % (__copyright__, license_txt), \
                                  q_icon = self.icon, \
                                  show_copy_button = False)
            #help_box.gridLayout.addWidget(help_box.icon_widget,0,0,Qt.AlignTop)
            help_box.gridLayout.setAlignment(help_box.icon_widget, Qt.AlignTop)
            help_box.exec_()
Example #2
0
    def about(self):
        '''
        Display a short help message
        '''
        from os.path import join
        from calibre.ptempfile import TemporaryDirectory
        from calibre.gui2.dialogs.message_box import MessageBox
        from calibre_plugins.prince_pdf.help import help_txt, license_txt
        from calibre_plugins.prince_pdf import PrincePDFPlugin
        from calibre_plugins.prince_pdf import __license__

        author = PrincePDFPlugin.author
        version = "%i.%i.%i" % PrincePDFPlugin.version
        license = __license__
        with TemporaryDirectory('xxx') as tdir:
          for x in ('prince_icon.png', 'small_icon.png'):
            with open(join(tdir, x),'w') as f:
              f.write(get_resources('images/' + x))
          help_box = MessageBox(type_ = MessageBox.INFO, \
                                title = _('About the Prince PDF Plugin'), \
                                msg = help_txt % {'author':author, 'version':version, 'license':license, 'dir':tdir, 'code':'style="font-family:monospace ; font-weight:bold"'}, \
                                det_msg = 'Copyright \u00a9 %s\n%s' % (__copyright__, license_txt), \
                                q_icon = self.icon, \
                                show_copy_button = False)
          #help_box.gridLayout.addWidget(help_box.icon_widget,0,0,Qt.AlignTop)
          help_box.gridLayout.setAlignment(help_box.icon_widget,Qt.AlignTop)
          help_box.exec_()
Example #3
0
    def add_pdf(self, book_id, pdf_file, exists):
        '''
        Add the PDF file to the book record, asking for replacement
        :param book_id: The book identifier
        :param pdf_file: The path to the PDF file
        :param exists: True if there is already a PDF in the book
        '''
        from calibre.constants import DEBUG
        from calibre.gui2.dialogs.message_box import MessageBox

        add_it = True
        if (exists):
            msg = MessageBox(
                MessageBox.QUESTION, _('Existing format'),
                _('The selected book already contains a PDF format. Are you sure you want to replace it?'
                  ),
                _("The temporary file can be found in:\n%s") % pdf_file)
            msg.toggle_det_msg()
            add_it = (msg.exec_())
        if (add_it):
            if DEBUG: print(_('Adding PDF...'))
            self.db.new_api.add_format(book_id, 'pdf', pdf_file)
            self.gui.library_view.model().refresh_ids([book_id])
            self.gui.library_view.refresh_book_details()
            self.gui.tags_view.recount()
Example #4
0
def question_dialog(parent, title, msg, det_msg='', show_copy_button=False,
        default_yes=True,
        # Skippable dialogs
        # Set skip_dialog_name to a unique name for this dialog
        # Set skip_dialog_msg to a message displayed to the user
        skip_dialog_name=None, skip_dialog_msg=_('Show this confirmation again'),
        skip_dialog_skipped_value=True, skip_dialog_skip_precheck=True,
        # Override icon (QIcon to be used as the icon for this dialog)
        override_icon=None):
    from calibre.gui2.dialogs.message_box import MessageBox

    auto_skip = set(gprefs.get('questions_to_auto_skip', []))
    if (skip_dialog_name is not None and skip_dialog_name in auto_skip):
        return bool(skip_dialog_skipped_value)

    d = MessageBox(MessageBox.QUESTION, title, msg, det_msg, parent=parent,
                    show_copy_button=show_copy_button, default_yes=default_yes,
                    q_icon=override_icon)

    if skip_dialog_name is not None and skip_dialog_msg:
        tc = d.toggle_checkbox
        tc.setVisible(True)
        tc.setText(skip_dialog_msg)
        tc.setChecked(bool(skip_dialog_skip_precheck))

    ret = d.exec_() == d.Accepted

    if skip_dialog_name is not None and not d.toggle_checkbox.isChecked():
        auto_skip.add(skip_dialog_name)
        gprefs.set('questions_to_auto_skip', list(auto_skip))

    return ret
Example #5
0
def question_dialog(parent, title, msg, det_msg='', show_copy_button=False,
        default_yes=True,
        # Skippable dialogs
        # Set skip_dialog_name to a unique name for this dialog
        # Set skip_dialog_msg to a message displayed to the user
        skip_dialog_name=None, skip_dialog_msg=_('Show this confirmation again'),
        skip_dialog_skipped_value=True, skip_dialog_skip_precheck=True,
        # Override icon (QIcon to be used as the icon for this dialog)
        override_icon=None):
    from calibre.gui2.dialogs.message_box import MessageBox

    auto_skip = set(gprefs.get('questions_to_auto_skip', []))
    if (skip_dialog_name is not None and skip_dialog_name in auto_skip):
        return bool(skip_dialog_skipped_value)

    d = MessageBox(MessageBox.QUESTION, title, msg, det_msg, parent=parent,
                    show_copy_button=show_copy_button, default_yes=default_yes,
                    q_icon=override_icon)

    if skip_dialog_name is not None and skip_dialog_msg:
        tc = d.toggle_checkbox
        tc.setVisible(True)
        tc.setText(skip_dialog_msg)
        tc.setChecked(bool(skip_dialog_skip_precheck))

    ret = d.exec_() == d.Accepted

    if skip_dialog_name is not None and not d.toggle_checkbox.isChecked():
        auto_skip.add(skip_dialog_name)
        gprefs.set('questions_to_auto_skip', list(auto_skip))

    return ret
Example #6
0
def info_dialog(parent, title, msg, det_msg="", show=False, show_copy_button=True):
    from calibre.gui2.dialogs.message_box import MessageBox

    d = MessageBox(MessageBox.INFO, title, msg, det_msg, parent=parent, show_copy_button=show_copy_button)

    if show:
        return d.exec_()
    return d
Example #7
0
 def restart_required(self, state):
     title = _('Restart required')
     msg = _('To apply changes, restart calibre.')
     d = MessageBox(MessageBox.WARNING,
                    title, msg,
                    show_copy_button=False)
     self._log_location("WARNING: %s" % (msg))
     d.exec_()
Example #8
0
    def do_one(self):
        try:
            i, book_ids, pd, only_fmts, errors = self.job_data
        except (TypeError, AttributeError):
            return
        if i >= len(book_ids) or pd.wasCanceled():
            pd.setValue(pd.maximum())
            pd.hide()
            self.pd_timer.stop()
            self.job_data = None
            self.gui.library_view.model().refresh_ids(book_ids)
            if i > 0:
                self.gui.status_bar.show_message(
                    ngettext('Embedded metadata in one book',
                             'Embedded metadata in {} books', i).format(i),
                    5000)
            if errors:
                det_msg = '\n\n'.join([
                    _('The {0} format of {1}:\n\n{2}\n').format(
                        (fmt or '').upper(), force_unicode(mi.title),
                        force_unicode(tb)) for mi, fmt, tb in errors
                ])
                from calibre.gui2.dialogs.message_box import MessageBox
                title, msg = _('Failed for some files'), _(
                    'Failed to embed metadata into some book files. Click "Show details" for details.'
                )
                d = MessageBox(MessageBox.WARNING,
                               _('WARNING:') + ' ' + title,
                               msg,
                               det_msg,
                               parent=self.gui,
                               show_copy_button=True)
                tc = d.toggle_checkbox
                tc.setVisible(True), tc.setText(
                    _('Show the &failed books in the main book list'))
                tc.setChecked(gprefs.get('show-embed-failed-books', False))
                d.resize_needed.emit()
                d.exec_()
                gprefs['show-embed-failed-books'] = tc.isChecked()
                if tc.isChecked():
                    failed_ids = {mi.book_id for mi, fmt, tb in errors}
                    db = self.gui.current_db
                    db.data.set_marked_ids(failed_ids)
                    self.gui.search.set_search_string('marked:true')
            return
        pd.setValue(i)
        db = self.gui.current_db.new_api
        book_id = book_ids[i]

        def report_error(mi, fmt, tb):
            mi.book_id = book_id
            errors.append((mi, fmt, tb))

        db.embed_metadata((book_id, ),
                          only_fmts=only_fmts,
                          report_error=report_error)
        self.job_data = (i + 1, book_ids, pd, only_fmts, errors)
Example #9
0
def warning_dialog(parent, title, msg, det_msg='', show=False,
        show_copy_button=True):
    from calibre.gui2.dialogs.message_box import MessageBox
    d = MessageBox(MessageBox.WARNING, _('WARNING:')+ ' ' +
            title, msg, det_msg, parent=parent,
            show_copy_button=show_copy_button)
    if show:
        return d.exec_()
    return d
Example #10
0
def error_dialog(parent, title, msg, det_msg='', show=False,
        show_copy_button=True):
    from calibre.gui2.dialogs.message_box import MessageBox
    d = MessageBox(MessageBox.ERROR, _('ERROR:')+ ' ' +
            title, msg, det_msg, parent=parent,
                    show_copy_button=show_copy_button)
    if show:
        return d.exec_()
    return d
Example #11
0
def info_dialog(parent, title, msg, det_msg='', show=False,
        show_copy_button=True):
    from calibre.gui2.dialogs.message_box import MessageBox
    d = MessageBox(MessageBox.INFO, title, msg, det_msg, parent=parent,
                    show_copy_button=show_copy_button)

    if show:
        return d.exec_()
    return d
Example #12
0
def warning_dialog(parent, title, msg, det_msg='', show=False,
        show_copy_button=True):
    from calibre.gui2.dialogs.message_box import MessageBox
    d = MessageBox(MessageBox.WARNING, _('WARNING:')+ ' ' +
            title, msg, det_msg, parent=parent,
            show_copy_button=show_copy_button)
    if show:
        return d.exec_()
    return d
Example #13
0
def error_dialog(parent, title, msg, det_msg='', show=False,
        show_copy_button=True):
    from calibre.gui2.dialogs.message_box import MessageBox
    d = MessageBox(MessageBox.ERROR, _('ERROR:')+ ' ' +
            title, msg, det_msg, parent=parent,
                    show_copy_button=show_copy_button)
    if show:
        return d.exec_()
    return d
Example #14
0
 def count_message(action, count, show_diff=False):
     msg = _('%(action)s %(num)s occurrences of %(query)s' % dict(num=count, query=errfind, action=action))
     if show_diff and count > 0:
         d = MessageBox(MessageBox.INFO, _('Searching done'), prepare_string_for_xml(msg), parent=gui_parent, show_copy_button=False)
         d.diffb = b = d.bb.addButton(_('See what &changed'), d.bb.ActionRole)
         b.setIcon(QIcon(I('diff.png'))), d.set_details(None), b.clicked.connect(d.accept)
         b.clicked.connect(partial(show_current_diff, allow_revert=True))
         d.exec_()
     else:
         info_dialog(gui_parent, _('Searching done'), prepare_string_for_xml(msg), show=True)
Example #15
0
 def count_message(action, count, show_diff=False):
     msg = _('%(action)s %(num)s occurrences of %(query)s' % dict(num=count, query=errfind, action=action))
     if show_diff and count > 0:
         d = MessageBox(MessageBox.INFO, _('Searching done'), prepare_string_for_xml(msg), parent=gui_parent, show_copy_button=False)
         d.diffb = b = d.bb.addButton(_('See what &changed'), d.bb.ActionRole)
         b.setIcon(QIcon(I('diff.png'))), d.set_details(None), b.clicked.connect(d.accept)
         b.clicked.connect(partial(show_current_diff, allow_revert=True))
         d.exec_()
     else:
         info_dialog(gui_parent, _('Searching done'), prepare_string_for_xml(msg), show=True)
Example #16
0
 def news_clippings_destination_changed(self):
     qs_new_destination_name = self.cfg_news_clippings_lineEdit.text()
     if not re.match(r'^\S+[A-Za-z0-9 ]+$', qs_new_destination_name):
         # Complain about News clippings title
         title = _('Invalid title for News clippings')
         msg = _("Supply a valid title for News clippings, for example 'My News Clippings'.")
         d = MessageBox(MessageBox.WARNING,
                        title, msg,
                        show_copy_button=False)
         self._log_location("WARNING: %s" % msg)
         d.exec_()
Example #17
0
 def __init__(self, filename, parent=None):
     MessageBox.__init__(
         self, MessageBox.INFO, _('Downloading book'), _(
             'The book {0} will be downloaded and added to your'
             ' calibre library automatically.').format(filename),
         show_copy_button=False, parent=parent
     )
     self.toggle_checkbox.setChecked(True)
     self.toggle_checkbox.setVisible(True)
     self.toggle_checkbox.setText(_('Show this message again'))
     self.toggle_checkbox.toggled.connect(self.show_again_changed)
     self.resize_needed.emit()
Example #18
0
 def __init__(self, filename, parent=None):
     MessageBox.__init__(
         self, MessageBox.INFO, _('Downloading book'), _(
             'The book {0} will be downloaded and added to your'
             ' calibre library automatically.').format(filename),
         show_copy_button=False, parent=parent
     )
     self.toggle_checkbox.setChecked(True)
     self.toggle_checkbox.setVisible(True)
     self.toggle_checkbox.setText(_('Show this message again'))
     self.toggle_checkbox.toggled.connect(self.show_again_changed)
     self.resize_needed.emit()
    def configure_appearance(self):
        '''
        '''
        self._log_location()
        appearance_settings = {
                                'appearance_css': default_elements,
                                'appearance_hr_checkbox': False,
                                'appearance_timestamp_format': default_timestamp
                              }

        # Save, hash the original settings
        original_settings = {}
        osh = hashlib.md5()
        for setting in appearance_settings:
            original_settings[setting] = plugin_prefs.get(setting, appearance_settings[setting])
            osh.update(repr(plugin_prefs.get(setting, appearance_settings[setting])))

        # Display the Annotations appearance dialog
        aa = AnnotationsAppearance(self, self.annotations_icon, plugin_prefs)
        cancelled = False
        if aa.exec_():
            # appearance_hr_checkbox and appearance_timestamp_format changed live to prefs during previews
            plugin_prefs.set('appearance_css', aa.elements_table.get_data())
            # Generate a new hash
            nsh = hashlib.md5()
            for setting in appearance_settings:
                nsh.update(repr(plugin_prefs.get(setting, appearance_settings[setting])))
        else:
            for setting in appearance_settings:
                plugin_prefs.set(setting, original_settings[setting])
            nsh = osh

        # If there were changes, and there are existing annotations,
        # and there is an active Annotations field, offer to re-render
        field = get_cc_mapping('annotations', 'field', None)
        if osh.digest() != nsh.digest() and existing_annotations(self.parent, field):
            title = 'Update annotations?'
            msg = '<p>Update existing annotations to new appearance settings?</p>'
            d = MessageBox(MessageBox.QUESTION,
                           title, msg,
                           show_copy_button=False)
            self._log_location("QUESTION: %s" % msg)
            if d.exec_():
                self._log_location("Updating existing annotations to modified appearance")

                # Wait for indexing to complete
                while not self.annotated_books_scanner.isFinished():
                    Application.processEvents()

                move_annotations(self, self.annotated_books_scanner.annotation_map,
                    field, field, window_title="Updating appearance")
    def configure_appearance(self):
        '''
        '''
        self._log_location()
        appearance_settings = {
                                'appearance_css': default_elements,
                                'appearance_hr_checkbox': False,
                                'appearance_timestamp_format': default_timestamp
                              }

        # Save, hash the original settings
        original_settings = {}
        osh = hashlib.md5()
        for setting in appearance_settings:
            original_settings[setting] = plugin_prefs.get(setting, appearance_settings[setting])
            osh.update(repr(plugin_prefs.get(setting, appearance_settings[setting])))

        # Display the Annotations appearance dialog
        aa = AnnotationsAppearance(self, self.annotations_icon, plugin_prefs)
        cancelled = False
        if aa.exec_():
            # appearance_hr_checkbox and appearance_timestamp_format changed live to prefs during previews
            plugin_prefs.set('appearance_css', aa.elements_table.get_data())
            # Generate a new hash
            nsh = hashlib.md5()
            for setting in appearance_settings:
                nsh.update(repr(plugin_prefs.get(setting, appearance_settings[setting])))
        else:
            for setting in appearance_settings:
                plugin_prefs.set(setting, original_settings[setting])
            nsh = osh

        # If there were changes, and there are existing annotations,
        # and there is an active Annotations field, offer to re-render
        field = get_cc_mapping('annotations', 'field', None)
        if osh.digest() != nsh.digest() and existing_annotations(self.parent, field):
            title = 'Update annotations?'
            msg = '<p>Update existing annotations to new appearance settings?</p>'
            d = MessageBox(MessageBox.QUESTION,
                           title, msg,
                           show_copy_button=False)
            self._log_location("QUESTION: %s" % msg)
            if d.exec_():
                self._log_location("Updating existing annotations to modified appearance")

                # Wait for indexing to complete
                while not self.annotated_books_scanner.isFinished():
                    Application.processEvents()

                move_annotations(self, self.annotated_books_scanner.annotation_map,
                    field, field, window_title="Updating appearance")
Example #21
0
    def _remove_all_assignments(self):
        '''
        '''
        self._log_location()
        self.stored_command = 'clear_all_collections'

        # Confirm
        title = "Are you sure?"
        msg = ("<p>Delete all collection assignments from calibre and Marvin?</p>")
        d = MessageBox(MessageBox.QUESTION, title, msg,
                       show_copy_button=False)
        if d.exec_():
            self.calibre_lw.clear()
            self.marvin_lw.clear()
Example #22
0
    def configure_appearance(self):
        '''
        '''
        from calibre_plugins.annotations.appearance import default_elements
        from calibre_plugins.annotations.appearance import default_timestamp
        appearance_settings = {
                                'appearance_css': default_elements,
                                'appearance_hr_checkbox': False,
                                'appearance_timestamp_format': default_timestamp
                              }

        # Save, hash the original settings
        original_settings = {}
        osh = hashlib.md5()
        for setting in appearance_settings:
            original_settings[setting] = plugin_prefs.get(setting, appearance_settings[setting])
            osh.update(repr(plugin_prefs.get(setting, appearance_settings[setting])))

        # Display the appearance dialog
        aa = AnnotationsAppearance(self, get_icon('images/annotations.png'), plugin_prefs)
        cancelled = False
        if aa.exec_():
            # appearance_hr_checkbox and appearance_timestamp_format changed live to prefs during previews
            plugin_prefs.set('appearance_css', aa.elements_table.get_data())
            # Generate a new hash
            nsh = hashlib.md5()
            for setting in appearance_settings:
                nsh.update(repr(plugin_prefs.get(setting, appearance_settings[setting])))
        else:
            for setting in appearance_settings:
                plugin_prefs.set(setting, original_settings[setting])
            nsh = osh

        # If there were changes, and there are existing annotations, offer to re-render
        field = get_cc_mapping('annotations', 'field', None)
        if osh.digest() != nsh.digest() and existing_annotations(self.opts.parent,field):
            title = _('Update annotations?')
            msg = _('<p>Update existing annotations to new appearance settings?</p>')
            d = MessageBox(MessageBox.QUESTION,
                           title, msg,
                           show_copy_button=False)
            self._log_location("QUESTION: %s" % msg)
            if d.exec_():
                self._log_location("Updating existing annotations to modified appearance")
                if self.annotated_books_scanner.isRunning():
                    self.annotated_books_scanner.wait()
                move_annotations(self, self.annotated_books_scanner.annotation_map,
                    field, field, window_title=_("Updating appearance"))
Example #23
0
    def save_pdf(self, pdf_file, pdf_base_file):
        '''
        Save the PDF file in the final location
        :param pdf_file: The path to the PDF file
        :param pdf_base_file: The desired file name and relative path
        '''
        from os import makedirs
        from os.path import basename, dirname, join, exists
        from shutil import move
        from calibre.constants import DEBUG
        from calibre.gui2 import choose_dir
        from calibre.gui2.dialogs.message_box import MessageBox
        from calibre.gui2 import error_dialog

        path = choose_dir(self.gui, 'save to disk dialog',
                          _('Choose destination directory'))
        if not path:
            return
        save_file = join(path, pdf_base_file)
        base_dir = dirname(save_file)
        try:
            makedirs(base_dir)
        except BaseException:
            if not exists(base_dir): raise
        try:
            move(pdf_file, save_file)
        except:
            error_dialog(self.gui,
                         _('Could not save PDF'),
                         _("Error writing the PDF file:\n%s" % save_file),
                         show=True)
            return
        if DEBUG: print(save_file)
        MessageBox(MessageBox.INFO, _('File saved'),
                   _("PDF file saved in:\n%s") % save_file).exec_()
Example #24
0
    def _remove_collection_assignment(self):
        '''
        Only one panel can have active selection
        '''
        def _remove_assignments(deletes, list_widget):
            row = list_widget.row(deletes[0])

            for item in deletes:
                list_widget.takeItem(list_widget.row(item))
            if row >= list_widget.count():
                row = list_widget.count() - 1
            if row >= 0:
                list_widget.scrollToItem(list_widget.item(row))

        self._log_location()

        if self.calibre_lw.selectedItems():
            deletes = self.calibre_lw.selectedItems()
            _remove_assignments(deletes, self.calibre_lw)

        elif self.marvin_lw.selectedItems():
            deletes = self.marvin_lw.selectedItems()
            _remove_assignments(deletes, self.marvin_lw)

        else:
            title = "No collection selected"
            msg = ("<p>Select a collection assignment to remove.</p>")
            MessageBox(MessageBox.INFO, title, msg,
                       show_copy_button=False).exec_()
Example #25
0
 def __init__(self, parent, title, msg, log='', det_msg=''):
     '''
     :param log: An HTML log
     :param title: The title for this popup
     :param msg: The msg to display
     :param det_msg: Detailed message
     '''
     MessageBox.__init__(self, MessageBox.INFO, title, msg,
             det_msg=det_msg, show_copy_button=False,
             parent=parent)
     self.log = log
     self.vlb = self.bb.addButton(_('View Report'), self.bb.ActionRole)
     self.vlb.setIcon(QIcon(I('dialog_information.png')))
     self.vlb.clicked.connect(self.show_log)
     self.det_msg_toggle.setVisible(bool(det_msg))
     self.vlb.setVisible(bool(log))
Example #26
0
 def _get_folder_location(self):
     '''
     Confirm specified folder location contains Marvin subfolder
     '''
     dfl = self.prefs.get('dropbox_folder', None)
     msg = None
     title = 'Invalid Dropbox folder'
     folder_location = None
     if not dfl:
         msg = '<p>No Dropbox folder location specified in Configuration dialog.</p>'
     else:
         # Confirm presence of Marvin subfolder
         if not os.path.exists(dfl):
             msg = "<p>Specified Dropbox folder <tt>{0}</tt> not found.".format(
                 dfl)
         else:
             path = os.path.join(dfl, 'Apps', 'com.marvinapp')
             if os.path.exists(path):
                 folder_location = path
             else:
                 msg = '<p>com.marvinapp not found in Apps folder.</p>'
     if msg:
         self._log_location("{0}: {1}".format(title, msg))
         MessageBox(MessageBox.WARNING,
                    title,
                    msg,
                    det_msg='',
                    show_copy_button=False).exec_()
     return folder_location
Example #27
0
    def import_opml(self):
        opml_files = choose_files(self, 'OPML chooser dialog',
                _('Select OPML file'), filters=[(_('OPML'), ['opml'])] )

        if not opml_files:
            return
        
        opml = OPML(self.oldest_article.oldest_article, self.max_articles.max_articles);
        for opml_file in opml_files:
            opml.load(opml_file)
            outlines = opml.parse()
            opml.import_recipes(outlines)

        # show a messagebox statingthat import finished
        msg_box = MessageBox(MessageBox.INFO, "Finished", "OPML to Recipe conversion complete", parent=self,
                    show_copy_button=False)
        msg_box.exec_()
Example #28
0
    def do_one(self):
        try:
            i, book_ids, pd, only_fmts, errors = self.job_data
        except (TypeError, AttributeError):
            return
        if i >= len(book_ids) or pd.wasCanceled():
            pd.setValue(pd.maximum())
            pd.hide()
            self.pd_timer.stop()
            self.job_data = None
            self.gui.library_view.model().refresh_ids(book_ids)
            if i > 0:
                self.gui.status_bar.show_message(ngettext(
                    'Embedded metadata in one book', 'Embedded metadata in {} books', i).format(i), 5000)
            if errors:
                det_msg = '\n\n'.join([_('The {0} format of {1}:\n\n{2}\n').format(
                    (fmt or '').upper(), force_unicode(mi.title), force_unicode(tb)) for mi, fmt, tb in errors])
                from calibre.gui2.dialogs.message_box import MessageBox
                title, msg = _('Failed for some files'), _(
                    'Failed to embed metadata into some book files. Click "Show details" for details.')
                d = MessageBox(MessageBox.WARNING, _('WARNING:')+ ' ' + title, msg, det_msg, parent=self.gui, show_copy_button=True)
                tc = d.toggle_checkbox
                tc.setVisible(True), tc.setText(_('Show the &failed books in the main book list'))
                tc.setChecked(gprefs.get('show-embed-failed-books', False))
                d.resize_needed.emit()
                d.exec_()
                gprefs['show-embed-failed-books'] = tc.isChecked()
                if tc.isChecked():
                    failed_ids = {mi.book_id for mi, fmt, tb in errors}
                    db = self.gui.current_db
                    db.data.set_marked_ids(failed_ids)
                    self.gui.search.set_search_string('marked:true')
            return
        pd.setValue(i)
        db = self.gui.current_db.new_api
        book_id = book_ids[i]

        def report_error(mi, fmt, tb):
            mi.book_id = book_id
            errors.append((mi, fmt, tb))
        db.embed_metadata((book_id,), only_fmts=only_fmts, report_error=report_error)
        self.job_data = (i + 1, book_ids, pd, only_fmts, errors)
Example #29
0
 def __init__(self, parent, title, msg, log=None, det_msg=''):
     '''
     :param log: An HTML or plain text log
     :param title: The title for this popup
     :param msg: The msg to display
     :param det_msg: Detailed message
     '''
     MessageBox.__init__(self,
                         MessageBox.INFO,
                         title,
                         msg,
                         det_msg=det_msg,
                         show_copy_button=False,
                         parent=parent)
     self.log = log
     self.vlb = self.bb.addButton(_('View log'), self.bb.ActionRole)
     self.vlb.setIcon(QIcon(I('debug.png')))
     self.vlb.clicked.connect(self.show_log)
     self.det_msg_toggle.setVisible(bool(det_msg))
     self.vlb.setVisible(True)
Example #30
0
    def forget_service(self):
        '''
        Remove the currently selected sync app
        '''
        self._log_location()

        key = str(self.sync_apps.currentText())

        title = "Forget syncing application".format(key)
        msg = ("<p>Forget '{}' syncing application?".format(key))
        dlg = MessageBox(MessageBox.QUESTION, title, msg,
                         parent=self.gui, show_copy_button=False)
        if dlg.exec_():
            # Delete key from prefs
            sync_apps = self.prefs.get('sync_apps', {})
            del sync_apps[key]
            self.prefs.set('sync_apps', sync_apps)

            # Remove from combobox
            index = self.sync_apps.currentIndex()
            self.sync_apps.removeItem(index)
Example #31
0
    def add_pdf(self, book_id, pdf_file, exists):
        '''
        Add the PDF file to the book record, asking for replacement
        :param book_id: The book identifier
        :param pdf_file: The path to the PDF file
        :param exists: True if there is already a PDF in the book
        '''
        from calibre.constants import DEBUG
        from calibre.gui2.dialogs.message_box import MessageBox

        add_it = True
        if (exists):
            msg = MessageBox(MessageBox.QUESTION, _('Existing format'),
                             _('The selected book already contains a PDF format. Are you sure you want to replace it?'),
                             _("The temporary file can be found in:\n%s") % pdf_file)
            msg.toggle_det_msg()
            add_it = (msg.exec_())
        if (add_it):
            if DEBUG: print(_('Adding PDF...'))
            self.db.new_api.add_format(book_id, 'pdf', pdf_file)
            self.gui.library_view.model().refresh_ids([book_id])
            self.gui.library_view.refresh_book_details()
            self.gui.tags_view.recount()
Example #32
0
 def _rename_collection(self):
     '''
     Only one panel can have active selection
     '''
     self._log_location()
     if self.calibre_lw.selectedItems():
         self.rename_calibre_tag()
     elif self.marvin_lw.selectedItems():
         self.rename_marvin_tag()
     else:
         title = "No collection selected"
         msg = ("<p>Select a collection to rename.</p>")
         MessageBox(MessageBox.INFO, title, msg,
                    show_copy_button=False).exec_()
Example #33
0
def get_selected_book_mi(opts, msg=None, det_msg=None):
    # Get currently selected books
    rows = opts.gui.library_view.selectionModel().selectedRows()

    if len(rows) == 0 or len(rows) > 1:
        MessageBox(MessageBox.WARNING,
                   _('Select a book to receive annotations'),
                   msg,
                   det_msg=det_msg,
                   show_copy_button=False,
                   parent=opts.gui).exec_()
        return None

    # Get the current metadata for this book from the db
    ids = list(map(opts.gui.library_view.model().id, rows))
    if ids:
        mi = opts.gui.current_db.get_metadata(ids[0], index_is_id=True)
        return mi
    else:
        return None
Example #34
0
def move_annotations(parent,
                     annotation_map,
                     old_destination_field,
                     new_destination_field,
                     window_title=_("Moving annotations")):
    '''
    Move annotations from old_destination_field to new_destination_field
    annotation_map precalculated in thread in config.py
    '''
    import calibre_plugins.annotations.config as cfg

    _log_location("%s -> %s" % (old_destination_field, new_destination_field))

    library_db = parent.opts.gui.current_db
    id = library_db.FIELD_MAP['id']

    # Show progress
    pb = ProgressBar(parent=parent, window_title=window_title, on_top=True)
    total_books = len(annotation_map)
    pb.set_maximum(total_books)
    pb.set_value(1)
    pb.set_label('{:^100}'.format('%s for %d books' %
                                  (window_title, total_books)))
    pb.show()

    id_map_old_destination_field = {}
    id_map_new_destination_field = {}
    transient_db = 'transient'

    # 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(
        cfg.plugin_prefs.get(
            'COMMENTS_DIVIDER',
            '&middot;  &middot;  &bull;  &middot;  &#x2726;  &middot;  &bull;  &middot; &middot;'
        ))

    for cid in annotation_map:
        mi = library_db.get_metadata(cid, index_is_id=True)

        # Comments -> custom
        if old_destination_field == 'Comments' and new_destination_field.startswith(
                '#'):
            if mi.comments:
                old_soup = BeautifulSoup(mi.comments)
                uas = old_soup.find('div', 'user_annotations')
                if uas:
                    # Remove user_annotations from Comments
                    uas.extract()

                    # Remove comments_divider from Comments
                    cd = old_soup.find('div', 'comments_divider')
                    if cd:
                        cd.extract()

                    # Capture content
                    annotation_list = parent.opts.db.capture_content(
                        uas, cid, transient_db)

                    # Regurgitate content with current CSS style
                    new_soup = parent.opts.db.rerender_to_html_from_list(
                        annotation_list)

                    id_map_old_destination_field[cid] = unicode(old_soup)
                    id_map_new_destination_field[cid] = unicode(new_soup)

                    pb.increment()

        # custom -> Comments
        elif old_destination_field.startswith(
                '#') and new_destination_field == 'Comments':
            if mi.get_user_metadata(old_destination_field,
                                    False)['#value#'] is not None:
                old_soup = BeautifulSoup(
                    mi.get_user_metadata(old_destination_field,
                                         False)['#value#'])
                uas = old_soup.find('div', 'user_annotations')
                if uas:
                    # Remove user_annotations from custom field
                    uas.extract()

                    # Capture content
                    annotation_list = parent.opts.db.capture_content(
                        uas, cid, transient_db)

                    # Regurgitate content with current CSS style
                    new_soup = parent.opts.db.rerender_to_html_from_list(
                        annotation_list)

                    # Add user_annotations to Comments
                    new_comments = ''
                    if mi.comments is None:
                        new_comments = unicode(new_soup)
                    else:
                        new_comments = mi.comments + \
                                      unicode(comments_divider) + \
                                      unicode(new_soup)

#                     # Update the record with stripped custom field, updated Comments
#                     library_db.set_metadata(cid, mi, set_title=False, set_authors=False,
#                                     commit=True, force_changes=True, notify=True)
                    id_map_old_destination_field[cid] = unicode(old_soup)
                    id_map_new_destination_field[cid] = new_comments
                    pb.increment()

        # custom -> custom
        elif old_destination_field.startswith(
                '#') and new_destination_field.startswith('#'):

            if mi.get_user_metadata(old_destination_field,
                                    False)['#value#'] is not None:
                old_soup = BeautifulSoup(
                    mi.get_user_metadata(old_destination_field,
                                         False)['#value#'])
                uas = old_soup.find('div', 'user_annotations')
                if uas:
                    # Remove user_annotations from originating custom field
                    uas.extract()

                    # Capture content
                    annotation_list = parent.opts.db.capture_content(
                        uas, cid, transient_db)

                    # Regurgitate content with current CSS style
                    new_soup = parent.opts.db.rerender_to_html_from_list(
                        annotation_list)

                    id_map_old_destination_field[cid] = unicode(old_soup)
                    id_map_new_destination_field[cid] = unicode(new_soup)
                    pb.increment()

        # same field -> same field - called from config:configure_appearance()
        elif (old_destination_field == new_destination_field):
            pb.set_label('{:^100}'.format(
                _('Updating annotations for {0} books').format(total_books)))

            if new_destination_field == 'Comments':
                if mi.comments:
                    old_soup = BeautifulSoup(mi.comments)
                    uas = old_soup.find('div', 'user_annotations')
                    if uas:
                        # Remove user_annotations from Comments
                        uas.extract()

                        # Remove comments_divider from Comments
                        cd = old_soup.find('div', 'comments_divider')
                        if cd:
                            cd.extract()

                        # Save stripped Comments
                        mi.comments = unicode(old_soup)

                        # Capture content
                        annotation_list = parent.opts.db.capture_content(
                            uas, cid, transient_db)

                        # Regurgitate content with current CSS style
                        new_soup = parent.opts.db.rerender_to_html_from_list(
                            annotation_list)

                        # Add user_annotations to Comments
                        new_comments = ''
                        if mi.comments is None:
                            new_comments = unicode(new_soup)
                        else:
                            new_comments = mi.comments + \
                                          unicode(comments_divider) + \
                                          unicode(new_soup)

                        # Update the record with stripped custom field, updated Comments


#                         library_db.set_metadata(cid, mi, set_title=False, set_authors=False,
#                                         commit=True, force_changes=True, notify=True)
                        id_map_old_destination_field[cid] = unicode(old_soup)
                        id_map_new_destination_field[cid] = unicode(new_soup)
                        pb.increment()

            else:
                # Update custom field
                old_soup = BeautifulSoup(
                    mi.get_user_metadata(old_destination_field,
                                         False)['#value#'])
                uas = old_soup.find('div', 'user_annotations')
                if uas:
                    # Remove user_annotations from originating custom field
                    uas.extract()

                    # Capture content
                    annotation_list = parent.opts.db.capture_content(
                        uas, cid, transient_db)

                    # Regurgitate content with current CSS style
                    new_soup = parent.opts.db.rerender_to_html_from_list(
                        annotation_list)

                    #                     # Add stripped old_soup plus new_soup to destination field
                    #                     um = mi.metadata_for_field(new_destination_field)
                    #                     um['#value#'] = unicode(old_soup) + unicode(new_soup)
                    #                     mi.set_user_metadata(new_destination_field, um)
                    #
                    #                     # Update the record
                    #                     library_db.set_metadata(cid, mi, set_title=False, set_authors=False,
                    #                                     commit=True, force_changes=True, notify=True)
                    id_map_old_destination_field[cid] = unicode(old_soup)
                    id_map_new_destination_field[cid] = unicode(new_soup)
                    pb.increment()

    if len(id_map_old_destination_field) > 0:
        debug_print(
            "move_annotations - Updating metadata - for column: %s number of changes=%d"
            % (old_destination_field, len(id_map_old_destination_field)))
        library_db.new_api.set_field(old_destination_field.lower(),
                                     id_map_old_destination_field)
    if len(id_map_new_destination_field) > 0:
        debug_print(
            "move_annotations - Updating metadata - for column: %s number of changes=%d"
            % (new_destination_field, len(id_map_new_destination_field)))
        library_db.new_api.set_field(new_destination_field.lower(),
                                     id_map_new_destination_field)

    # Hide the progress bar
    pb.hide()

    # Change field value to friendly name
    if old_destination_field.startswith('#'):
        for cf in parent.custom_fields:
            if parent.custom_fields[cf]['field'] == old_destination_field:
                old_destination_field = cf
                break
    if new_destination_field.startswith('#'):
        for cf in parent.custom_fields:
            if parent.custom_fields[cf]['field'] == new_destination_field:
                new_destination_field = cf
                break

    # Report what happened
    if len(annotation_map) == 1:
        book_word = _('book')
    else:
        book_word = _('books')
    if old_destination_field == new_destination_field:
        msg = _(
            "Annotations updated to new appearance settings for {0} {1}.</p>"
        ).format(len(annotation_map), book_word)
    else:
        msg = _("Annotations for {0} {1} moved from <b>{2}</b> to <b>{3}</b>."
                ).format(len(annotation_map), book_word, old_destination_field,
                         new_destination_field)
    msg = "<p>{0}</p>".format(msg)
    MessageBox(MessageBox.INFO,
               '',
               msg=msg,
               show_copy_button=False,
               parent=parent.gui).exec_()
    _log_location()
    _log("INFO: %s" % msg)
Example #35
0
def move_annotations(parent,
                     annotation_map,
                     old_destination_field,
                     new_destination_field,
                     window_title="Moving annotations"):
    '''
    Move annotations from old_destination_field to new_destination_field
    annotation_map precalculated in thread in config.py
    '''
    import calibre_plugins.marvin_manager.config as cfg

    _log_location(annotation_map)
    _log(" %s -> %s" % (old_destination_field, new_destination_field))

    db = parent.opts.gui.current_db
    id = db.FIELD_MAP['id']

    # Show progress
    pb = ProgressBar(parent=parent, window_title=window_title)
    total_books = len(annotation_map)
    pb.set_maximum(total_books)
    pb.set_value(1)
    pb.set_label('{:^100}'.format('Moving annotations for %d books' %
                                  total_books))
    pb.show()

    transient_db = 'transient'

    # 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(
        cfg.plugin_prefs.get(
            'COMMENTS_DIVIDER',
            '&middot;  &middot;  &bull;  &middot;  &#x2726;  &middot;  &bull;  &middot; &middot;'
        ))

    for cid in annotation_map:
        mi = db.get_metadata(cid, index_is_id=True)

        # Comments -> custom
        if old_destination_field == 'Comments' and new_destination_field.startswith(
                '#'):
            if mi.comments:
                old_soup = BeautifulSoup(mi.comments)
                uas = old_soup.find('div', 'user_annotations')
                if uas:
                    # Remove user_annotations from Comments
                    uas.extract()

                    # Remove comments_divider from Comments
                    cd = old_soup.find('div', 'comments_divider')
                    if cd:
                        cd.extract()

                    # Save stripped Comments
                    mi.comments = unicode(old_soup)

                    # Capture content
                    parent.opts.db.capture_content(uas, cid, transient_db)

                    # Regurgitate content with current CSS style
                    new_soup = parent.opts.db.rerender_to_html(
                        transient_db, cid)

                    # Add user_annotations to destination
                    um = mi.metadata_for_field(new_destination_field)
                    um['#value#'] = unicode(new_soup)
                    mi.set_user_metadata(new_destination_field, um)

                    # Update the record with stripped Comments, populated custom field
                    db.set_metadata(cid,
                                    mi,
                                    set_title=False,
                                    set_authors=False,
                                    commit=True,
                                    force_changes=True,
                                    notify=True)
                    pb.increment()

        # custom -> Comments
        elif old_destination_field.startswith(
                '#') and new_destination_field == 'Comments':
            if mi.get_user_metadata(old_destination_field,
                                    False)['#value#'] is not None:
                old_soup = BeautifulSoup(
                    mi.get_user_metadata(old_destination_field,
                                         False)['#value#'])
                uas = old_soup.find('div', 'user_annotations')
                if uas:
                    # Remove user_annotations from custom field
                    uas.extract()

                    # Capture content
                    parent.opts.db.capture_content(uas, cid, transient_db)

                    # Regurgitate content with current CSS style
                    new_soup = parent.opts.db.rerender_to_html(
                        transient_db, cid)

                    # Save stripped custom field data
                    um = mi.metadata_for_field(old_destination_field)
                    um['#value#'] = unicode(old_soup)
                    mi.set_user_metadata(old_destination_field, um)

                    # Add user_annotations to Comments
                    if mi.comments is None:
                        mi.comments = unicode(new_soup)
                    else:
                        mi.comments = mi.comments + \
                                      unicode(comments_divider) + \
                                      unicode(new_soup)

                    # Update the record with stripped custom field, updated Comments
                    db.set_metadata(cid,
                                    mi,
                                    set_title=False,
                                    set_authors=False,
                                    commit=True,
                                    force_changes=True,
                                    notify=True)
                    pb.increment()

        # custom -> custom
        elif old_destination_field.startswith(
                '#') and new_destination_field.startswith('#'):

            if mi.get_user_metadata(old_destination_field,
                                    False)['#value#'] is not None:
                old_soup = BeautifulSoup(
                    mi.get_user_metadata(old_destination_field,
                                         False)['#value#'])
                uas = old_soup.find('div', 'user_annotations')
                if uas:
                    # Remove user_annotations from originating custom field
                    uas.extract()

                    # Capture content
                    parent.opts.db.capture_content(uas, cid, transient_db)

                    # Regurgitate content with current CSS style
                    new_soup = parent.opts.db.rerender_to_html(
                        transient_db, cid)

                    # Save stripped custom field data
                    um = mi.metadata_for_field(old_destination_field)
                    um['#value#'] = unicode(old_soup)
                    mi.set_user_metadata(old_destination_field, um)

                    # Add new_soup to destination field
                    um = mi.metadata_for_field(new_destination_field)
                    um['#value#'] = unicode(new_soup)
                    mi.set_user_metadata(new_destination_field, um)

                    # Update the record
                    db.set_metadata(cid,
                                    mi,
                                    set_title=False,
                                    set_authors=False,
                                    commit=True,
                                    force_changes=True,
                                    notify=True)
                    pb.increment()

        # same field -> same field - called from config:configure_appearance()
        elif (old_destination_field == new_destination_field):
            pb.set_label('{:^100}'.format('Updating annotations for %d books' %
                                          total_books))

            if new_destination_field == 'Comments':
                if mi.comments:
                    old_soup = BeautifulSoup(mi.comments)
                    uas = old_soup.find('div', 'user_annotations')
                    if uas:
                        # Remove user_annotations from Comments
                        uas.extract()

                        # Remove comments_divider from Comments
                        cd = old_soup.find('div', 'comments_divider')
                        if cd:
                            cd.extract()

                        # Save stripped Comments
                        mi.comments = unicode(old_soup)

                        # Capture content
                        parent.opts.db.capture_content(uas, cid, transient_db)

                        # Regurgitate content with current CSS style
                        new_soup = parent.opts.db.rerender_to_html(
                            transient_db, cid)

                        # Add user_annotations to Comments
                        if mi.comments is None:
                            mi.comments = unicode(new_soup)
                        else:
                            mi.comments = mi.comments + \
                                          unicode(comments_divider) + \
                                          unicode(new_soup)

                        # Update the record with stripped custom field, updated Comments
                        db.set_metadata(cid,
                                        mi,
                                        set_title=False,
                                        set_authors=False,
                                        commit=True,
                                        force_changes=True,
                                        notify=True)
                        pb.increment()

            else:
                # Update custom field
                old_soup = BeautifulSoup(
                    mi.get_user_metadata(old_destination_field,
                                         False)['#value#'])
                uas = old_soup.find('div', 'user_annotations')
                if uas:
                    # Remove user_annotations from originating custom field
                    uas.extract()

                    # Capture content
                    parent.opts.db.capture_content(uas, cid, transient_db)

                    # Regurgitate content with current CSS style
                    new_soup = parent.opts.db.rerender_to_html(
                        transient_db, cid)

                    # Add stripped old_soup plus new_soup to destination field
                    um = mi.metadata_for_field(new_destination_field)
                    um['#value#'] = unicode(old_soup) + unicode(new_soup)
                    mi.set_user_metadata(new_destination_field, um)

                    # Update the record
                    db.set_metadata(cid,
                                    mi,
                                    set_title=False,
                                    set_authors=False,
                                    commit=True,
                                    force_changes=True,
                                    notify=True)
                    pb.increment()

    # Hide the progress bar
    pb.hide()

    # Get the eligible custom fields
    all_custom_fields = db.custom_field_keys()
    custom_fields = {}
    for cf in all_custom_fields:
        field_md = db.metadata_for_field(cf)
        if field_md['datatype'] in ['comments']:
            custom_fields[field_md['name']] = {
                'field': cf,
                'datatype': field_md['datatype']
            }

    # Change field value to friendly name
    if old_destination_field.startswith('#'):
        for cf in custom_fields:
            if custom_fields[cf]['field'] == old_destination_field:
                old_destination_field = cf
                break
    if new_destination_field.startswith('#'):
        for cf in custom_fields:
            if custom_fields[cf]['field'] == new_destination_field:
                new_destination_field = cf
                break

    # Report what happened
    if old_destination_field == new_destination_field:
        msg = "<p>Annotations updated to new appearance settings for %d {0}.</p>" % len(
            annotation_map)
    else:
        msg = (
            "<p>Annotations for %d {0} moved from <b>%s</b> to <b>%s</b>.</p>"
            % (len(annotation_map), old_destination_field,
               new_destination_field))
    if len(annotation_map) == 1:
        msg = msg.format('book')
    else:
        msg = msg.format('books')
    MessageBox(MessageBox.INFO,
               '',
               msg=msg,
               show_copy_button=False,
               parent=parent.gui).exec_()
    _log("INFO: %s" % msg)

    # Update the UI
    updateCalibreGUIView()
Example #36
0
    def parse_exported_highlights(self, raw, log_failure=True):
        """
        Extract highlights from pasted Annotation summary email
        Return True if no problems
        Return False if error
        """
        # Create the annotations, books table as needed
        self.annotations_db = "%s_imported_annotations" % self.app_name_
        self.create_annotations_table(self.annotations_db)
        self.books_db = "%s_imported_books" % self.app_name_
        self.create_books_table(self.books_db)

        self.annotated_book_list = []
        self.selected_books = None

        self._log("raw highlights: {0}".format(raw))

        # Generate the book metadata from the selected book
        row = self.opts.gui.library_view.currentIndex()
        book_id = self.opts.gui.library_view.model().id(row)
        db = self.opts.gui.current_db
        mi = db.get_metadata(book_id, index_is_id=True)

        # Grab the title from the front of raw
        try:
            title = re.match(r'(?m)File: (?P<title>.*)$', raw).group('title')
            self._log("title='{0}".format(title))

            # Populate a BookStruct
            book_mi = BookStruct()
            book_mi.active = True
            book_mi.author = 'Unknown'
            book_mi.book_id = mi.id
            book_mi.title = title
            book_mi.uuid = None
            book_mi.last_update = time.mktime(time.localtime())
            book_mi.reader_app = self.app_name
            book_mi.cid = mi.id

            gr_annotations = raw.split('\n')
            num_lines = len(gr_annotations)
            highlights = {}

            # Find the first annotation
            i = 0
            line = gr_annotations[i]
            self._log("Looking for Page: Line number={0} line='{1}'".format(
                i, line))
            while not line.startswith('--- Page'):
                self._log(" unable to parse GoodReader Annotation summary")
                i += 1
                line = gr_annotations[i]
                self._log(
                    "Looking for Page: Line number={0} line='{1}'".format(
                        i, line))

            while i < num_lines and not line.startswith(
                    '(report generated by GoodReader)'):
                # Extract the page number
                page_num = re.search('--- (Page \w+) ---', line)
                self._log("regex result: page_num={0}".format(page_num))
                if page_num:
                    page_num = page_num.group(1)
                    self._log("page_num={0}".format(page_num))

                    # Extract the highlight
                    i += 1
                    line = gr_annotations[i]
                    self._log(
                        "Looking for annotation start: Line number={0} line='{1}'"
                        .format(i, line))

                    prefix = None
                    while True:
                        prefix = re.search(
                            '^(?P<ann_type>{0})'.format(
                                '|'.join(self.ANNOTATION_TYPES +
                                         self.SKIP_TYPES)), line)
                        self._log("Searched for prefix={0}".format(prefix))
                        if prefix and prefix.group(
                                'ann_type') in self.SKIP_TYPES:
                            i += 1
                            line = gr_annotations[i]
                            self._log(
                                "Looking for annotation start: Line number={0} line='{1}'"
                                .format(i, line))
                            while not re.search(
                                    '^(?P<ann_type>{0})'.format('|'.join(
                                        self.ANNOTATION_TYPES)), line):
                                i += 1
                                line = gr_annotations[i]
                                self._log(
                                    "Looking for annotation start after a SKIP type: Line number={0} line='{1}'"
                                    .format(i, line))
                            continue
                        elif prefix:
                            self._log(
                                "Have annotation start: Line number={0} line='{1}' prefix={2}"
                                .format(i, line, prefix))
                            break
                        else:
                            i += 1
                            line = gr_annotations[i]
                            self._log(
                                "Looking for annotation start 2: Line number={0} line='{1}'"
                                .format(i, line))

                    annotation = self._extract_highlight(
                        line, prefix.group('ann_type'))
                    annotation.page_num = page_num
                    self._log(
                        "Started annotation: page_num={0} annotation='{1}'".
                        format(page_num, annotation))

                    # Get the annotation(s)
                    i += 1
                    line = gr_annotations[i]
                    self._log(
                        "Reading annotation text 1: Line number={0} line='{1}'"
                        .format(i, line))
                    ann = ''
                    while i < num_lines \
                        and not line.startswith('--- Page') \
                        and not line.startswith('(report generated by GoodReader)'):

                        if line:
                            prefix = re.search(
                                '^(?P<ann_type>{0})'.format(
                                    '|'.join(self.ANNOTATION_TYPES +
                                             self.SKIP_TYPES)), line)
                            if prefix and prefix.group(
                                    'ann_type') in self.SKIP_TYPES:
                                # Continue until next ann_type
                                i += 1
                                line = gr_annotations[i]
                                while not re.search(
                                        '^(?P<ann_type>{0})'.format('|'.join(
                                            self.ANNOTATION_TYPES)), line):
                                    i += 1
                                    if i == num_lines:
                                        break
                                    line = gr_annotations[i]
                                continue
                            elif prefix:
                                # Additional highlight on the same page
                                # write current annotation, start new annotation
                                self._store_annotation(highlights, annotation)
                                annotation = self._extract_highlight(
                                    line, prefix.group('ann_type'))
                                annotation.page_num = page_num
                                annotation.ann_type = prefix.group('ann_type')
                                ann = ''
                                i += 1
                                line = gr_annotations[i]
                                continue

                            if not ann:
                                ann = line
                            else:
                                ann += '\n' + line
                        i += 1
                        line = gr_annotations[i]
                        annotation.ann = ann

                    # Back up so that the next line is '--- Page' or '(report generated'
                    i -= 1
                    self._store_annotation(highlights, annotation)

                i += 1
                if i == num_lines:
                    break
                line = gr_annotations[i]
        except Exception as e:
            import traceback
            self._log("Exception parsing GoodReader Annotation summary: %s" %
                      e)
            traceback.print_exc()
            if log_failure:
                self._log(" unable to parse GoodReader Annotation summary")
                self._log("{:~^80}".format(" Imported Annotation summary "))
                self._log(raw)
                self._log(
                    "{:~^80}".format(" end imported Annotations summary "))
                import traceback
                traceback.print_exc()
                msg = ('Unable to parse Annotation summary from %s. ' %
                       self.app_name +
                       'Paste entire contents of emailed summary.')
                MessageBox(MessageBox.WARNING,
                           'Error importing annotations',
                           msg,
                           show_copy_button=False,
                           parent=self.opts.gui).exec_()
                self._log_location("WARNING: %s" % msg)
            return False

        # Finalize book_mi
        book_mi.annotations = len(highlights)
        # Add book to books_db
        self.add_to_books_db(self.books_db, book_mi)
        self.annotated_book_list.append(book_mi)

        sorted_keys = sorted(list(highlights.keys()))
        for dt in sorted_keys:
            highlight_text = None
            if 'text' in highlights[dt]:
                highlight_text = highlights[dt]['text']
            note_text = None
            if 'note' in highlights[dt]:
                note_text = highlights[dt]['note']

            # Populate an AnnotationStruct
            a_mi = AnnotationStruct()
            a_mi.annotation_id = dt
            a_mi.book_id = book_mi['book_id']
            a_mi.highlight_color = highlights[dt]['color']
            a_mi.highlight_text = highlight_text
            a_mi.location = highlights[dt]['page']
            a_mi.last_modification = dt
            a_mi.note_text = note_text

            # Location sort
            page_literal = re.match(r'^Page (?P<page>[0-9ivx]+).*$',
                                    a_mi.location).group('page')
            if re.match('[IXVL]', page_literal.upper()):
                whole = 0
                decimal = self._roman_to_int(page_literal)
            else:
                whole = int(page_literal)
                decimal = 0
            a_mi.location_sort = "%05d.%05d" % (whole, decimal)

            # Add annotation
            self.add_to_annotations_db(self.annotations_db, a_mi)
            self.update_book_last_annotation(self.books_db, dt,
                                             book_mi['book_id'])

        # Update the timestamp
        self.update_timestamp(self.annotations_db)
        self.update_timestamp(self.books_db)
        self.commit()

        return True
    def parse_exported_highlights(self, raw, log_failure=True):
        """
        Extract highlights from pasted Annotations summary, add them to selected book
        in calibre library

        Construct a BookStruct object with the book's metadata.
        Starred items are minimally required.
           BookStruct properties:
            *active: [True|False]
            *author: "John Smith"
             author_sort: (if known)
            *book_id: an int uniquely identifying the book.
                     Highlights are associated with books through book_id
             genre: "Fiction" (if known)
            *title: "The Story of John Smith"
             title_sort: "Story of John Smith, The" (if known)
             uuid: Calibre's uuid for this book, if known

        Construct an AnnotationStruct object with the
        highlight's metadata. Starred items are minimally required. Dashed items
        (highlight_text and note_text) may be one or both.
          AnnotationStruct properties:
            annotation_id: an int uniquely identifying the annotation
           *book_id: The book this annotation is associated with
            highlight_color: [Blue|Gray|Green|Pink|Purple|Underline|Yellow]
           -highlight_text: A list of paragraphs constituting the highlight
            last_modification: The timestamp of the annotation
            location: location of highlight in the book
           -note_text: A list of paragraphs constituting the note
           *timestamp: Unique timestamp of highlight's creation/modification time

        """
        # Create the annotations, books table as needed
        self.annotations_db = "%s_imported_annotations" % self.app_name_
        self.create_annotations_table(self.annotations_db)
        self.books_db = "%s_imported_books" % self.app_name_
        self.create_books_table(self.books_db)

        self.annotated_book_list = []
        self.selected_books = None

        # Generate the book metadata from the selected book
        row = self.opts.gui.library_view.currentIndex()
        book_id = self.opts.gui.library_view.model().id(row)
        db = self.opts.gui.current_db
        mi = db.get_metadata(book_id, index_is_id=True)

        try:
            lines = raw.split('\n')
            if len(lines) < 5:
                raise AnnotationsException("Invalid annotations summary")
            index = 0
            annotations = {}

            # Get the title, author, publisher from the first three lines
            title = lines[index]
            index += 1
            author = lines[index]
            index += 1
            publisher = lines[index]
            index += 1

            # Next line should be the first timestamp/location
            while index < len(lines):
                tsl = re.match(r'^(?P<timestamp>.*) \((?P<location>Page .*)\)', lines[index])
                if tsl:
                    ts = tsl.group('timestamp')
                    isoformat = parse_date(ts, as_utc=False)
                    isoformat = isoformat.replace(hour=12)
                    timestamp = mktime(isoformat.timetuple())
                    while timestamp in annotations:
                        timestamp += 60

                    location = tsl.group('location')
                    index += 1

                    # Continue with highlight
                    highlight_text = lines[index]
                    index += 1

                    # Next line is either Note: or a new tsl
                    note = re.match(r'^Notes: (?P<note_text>.*)', lines[index])
                    note_text = None
                    if note:
                        note_text = note.group('note_text')
                        index += 1

                    if re.match(r'^(?P<timestamp>.*) \((?P<location>Page .*)\)', lines[index]):
                        # New note - store the old one, continue
                        ann = AnnotationStruct()
                        ann.book_id = mi.id
                        ann.annotation_id = index
                        ann.highlight_color = 'Yellow'
                        ann.highlight_text = highlight_text
                        ann.location = location
                        ann.location_sort = "%05d" % int(re.match(r'^Page (?P<page>\d+).*$', location).group('page'))
                        ann.note_text = note_text
                        ann.last_modification = timestamp

                        # Add annotation to db
                        annotations[timestamp] = ann
                        continue
                else:
                    # Store the last one
                    ann = AnnotationStruct()
                    ann.book_id = mi.id
                    ann.annotation_id = index
                    ann.highlight_color = 'Yellow'
                    ann.highlight_text = highlight_text
                    ann.location = location
                    ann.location_sort = "%05d" % int(re.match(r'^Page (?P<page>\d+).*$', location).group('page'))
                    ann.note_text = note_text
                    ann.last_modification = timestamp
                    annotations[timestamp] = ann
                    break
        except:
            if log_failure:
                self._log(" unable to parse %s Annotations" % self.app_name)
                self._log("{:~^80}".format(" Imported Annotation summary "))
                self._log(raw)
                self._log("{:~^80}".format(" end imported Annotations summary "))
                import traceback
                traceback.print_exc()
                msg = ('Unable to parse Annotation summary from %s. ' % self.app_name +
                    'Paste entire contents of emailed summary.')
                MessageBox(MessageBox.WARNING,
                    'Error importing annotations',
                    msg,
                    show_copy_button=False,
                    parent=self.opts.gui).exec_()
                self._log_location("WARNING: %s" % msg)
            return False

        # Populate a BookStruct
        book_mi = BookStruct()
        book_mi.active = True
        book_mi.author = author
        book_mi.book_id = mi.id
        book_mi.title = title
        book_mi.uuid = None
        book_mi.last_update = time.mktime(time.localtime())
        book_mi.reader_app = self.app_name
        book_mi.cid = mi.id
        book_mi.annotations = len(annotations)

        # Add book to books_db
        self.add_to_books_db(self.books_db, book_mi)
        self.annotated_book_list.append(book_mi)

        # Add the annotations
        for timestamp in sorted(annotations.keys()):
            self.add_to_annotations_db(self.annotations_db, annotations[timestamp])
            self.update_book_last_annotation(self.books_db, timestamp, mi.id)
            self.opts.pb.increment()
            self.update_book_last_annotation(self.books_db, timestamp, mi.id)

        # Update the timestamp
        self.update_timestamp(self.annotations_db)
        self.update_timestamp(self.books_db)
        self.commit()

        # Return True if successful
        return True