예제 #1
0
class RemoveTags(QWidget):

    def __init__(self, parent, values):
        QWidget.__init__(self, parent)
        layout = QHBoxLayout()
        layout.setSpacing(5)
        layout.setContentsMargins(0, 0, 0, 0)

        self.tags_box = EditWithComplete(parent)
        self.tags_box.update_items_cache(values)
        layout.addWidget(self.tags_box, stretch=3)
        self.remove_tags_button = QToolButton(parent)
        self.remove_tags_button.setToolTip(_('Open Item Editor'))
        self.remove_tags_button.setIcon(QIcon(I('chapters.png')))
        layout.addWidget(self.remove_tags_button)
        self.checkbox = QCheckBox(_('Remove all tags'), parent)
        layout.addWidget(self.checkbox)
        layout.addStretch(1)
        self.setLayout(layout)
        self.checkbox.stateChanged[int].connect(self.box_touched)

    def box_touched(self, state):
        if state:
            self.tags_box.setText('')
            self.tags_box.setEnabled(False)
        else:
            self.tags_box.setEnabled(True)
예제 #2
0
class MovedDialog(QDialog):  # {{{
    def __init__(self, stats, location, parent=None):
        QDialog.__init__(self, parent)
        self.setWindowTitle(_('No library found'))
        self._l = l = QGridLayout(self)
        self.setLayout(l)
        self.stats, self.location = stats, location

        loc = self.oldloc = location.replace('/', os.sep)
        self.header = QLabel(
            _('No existing calibre library was found at %s. '
              'If the library was moved, select its new location below. '
              'Otherwise calibre will forget this library.') % loc)
        self.header.setWordWrap(True)
        ncols = 2
        l.addWidget(self.header, 0, 0, 1, ncols)
        self.cl = QLabel('<b>' + _('New location of this library:'))
        l.addWidget(self.cl, l.rowCount(), 0, 1, ncols)
        self.loc = QLineEdit(loc, self)
        l.addWidget(self.loc, l.rowCount(), 0, 1, 1)
        self.cd = QToolButton(self)
        self.cd.setIcon(QIcon(I('document_open.png')))
        self.cd.clicked.connect(self.choose_dir)
        l.addWidget(self.cd, l.rowCount() - 1, 1, 1, 1)
        self.bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Abort)
        b = self.bb.addButton(_('Library moved'),
                              QDialogButtonBox.ButtonRole.AcceptRole)
        b.setIcon(QIcon(I('ok.png')))
        b = self.bb.addButton(_('Forget library'),
                              QDialogButtonBox.ButtonRole.RejectRole)
        b.setIcon(QIcon(I('edit-clear.png')))
        b.clicked.connect(self.forget_library)
        self.bb.accepted.connect(self.accept)
        self.bb.rejected.connect(self.reject)
        l.addWidget(self.bb, 3, 0, 1, ncols)
        self.resize(self.sizeHint() + QSize(120, 0))

    def choose_dir(self):
        d = choose_dir(self,
                       'library moved choose new loc',
                       _('New library location'),
                       default_dir=self.oldloc)
        if d is not None:
            self.loc.setText(d)

    def forget_library(self):
        self.stats.remove(self.location)

    def accept(self):
        newloc = unicode_type(self.loc.text())
        if not db_class().exists_at(newloc):
            error_dialog(self,
                         _('No library found'),
                         _('No existing calibre library found at %s') % newloc,
                         show=True)
            return
        self.stats.rename(self.location, newloc)
        self.newloc = newloc
        QDialog.accept(self)
예제 #3
0
 def create_color_button(key, text):
     b = ColorButton(data, key, text, self)
     b.changed.connect(self.changed), l.addWidget(b)
     bc = QToolButton(self)
     bc.setIcon(QIcon(I('clear_left.png')))
     bc.setToolTip(_('Remove color'))
     bc.clicked.connect(b.clear)
     h = QHBoxLayout()
     h.addWidget(b), h.addWidget(bc)
     return h
예제 #4
0
class MovedDialog(QDialog):  # {{{
    def __init__(self, stats, location, parent=None):
        QDialog.__init__(self, parent)
        self.setWindowTitle(_("No library found"))
        self._l = l = QGridLayout(self)
        self.setLayout(l)
        self.stats, self.location = stats, location

        loc = self.oldloc = location.replace("/", os.sep)
        self.header = QLabel(
            _(
                "No existing calibre library was found at %s. "
                "If the library was moved, select its new location below. "
                "Otherwise calibre will forget this library."
            )
            % loc
        )
        self.header.setWordWrap(True)
        ncols = 2
        l.addWidget(self.header, 0, 0, 1, ncols)
        self.cl = QLabel("<br><b>" + _("New location of this library:"))
        l.addWidget(self.cl, 1, 0, 1, ncols)
        self.loc = QLineEdit(loc, self)
        l.addWidget(self.loc, 2, 0, 1, 1)
        self.cd = QToolButton(self)
        self.cd.setIcon(QIcon(I("document_open.png")))
        self.cd.clicked.connect(self.choose_dir)
        l.addWidget(self.cd, 2, 1, 1, 1)
        self.bb = QDialogButtonBox(QDialogButtonBox.Abort)
        b = self.bb.addButton(_("Library moved"), self.bb.AcceptRole)
        b.setIcon(QIcon(I("ok.png")))
        b = self.bb.addButton(_("Forget library"), self.bb.RejectRole)
        b.setIcon(QIcon(I("edit-clear.png")))
        b.clicked.connect(self.forget_library)
        self.bb.accepted.connect(self.accept)
        self.bb.rejected.connect(self.reject)
        l.addWidget(self.bb, 3, 0, 1, ncols)
        self.resize(self.sizeHint() + QSize(100, 50))

    def choose_dir(self):
        d = choose_dir(self, "library moved choose new loc", _("New library location"), default_dir=self.oldloc)
        if d is not None:
            self.loc.setText(d)

    def forget_library(self):
        self.stats.remove(self.location)

    def accept(self):
        newloc = unicode(self.loc.text())
        if not db_class().exists_at(newloc):
            error_dialog(self, _("No library found"), _("No existing calibre library found at %s") % newloc, show=True)
            return
        self.stats.rename(self.location, newloc)
        self.newloc = newloc
        QDialog.accept(self)
예제 #5
0
 def __init__(self, *args):
     QToolButton.__init__(self, *args)
     self._icon_size = -1
     QToolButton.setIcon(self, QIcon(I('donate.png')))
     self.setText('\xa0')
     self.animation = QPropertyAnimation(self, b'icon_size', self)
     self.animation.setDuration(60/72.*1000)
     self.animation.setLoopCount(4)
     self.animation.valueChanged.connect(self.value_changed)
     self.setCursor(Qt.PointingHandCursor)
     self.animation.finished.connect(self.animation_finished)
예제 #6
0
 def __init__(self, *args):
     QToolButton.__init__(self, *args)
     # vertically size policy must be expanding for it to align inside a
     # toolbar
     self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
     self._icon_size = -1
     QToolButton.setIcon(self, QIcon(I('donate.png')))
     self.setText('\xa0')
     self.animation = QPropertyAnimation(self, b'icon_size', self)
     self.animation.setDuration(60 / 72. * 1000)
     self.animation.setLoopCount(4)
     self.animation.valueChanged.connect(self.value_changed)
     self.setCursor(Qt.PointingHandCursor)
     self.animation.finished.connect(self.animation_finished)
예제 #7
0
 def __init__(self, *args):
     QToolButton.__init__(self, *args)
     # vertically size policy must be expanding for it to align inside a
     # toolbar
     self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
     self._icon_size = -1
     QToolButton.setIcon(self, QIcon(I('donate.png')))
     self.setText('\xa0')
     self.animation = QPropertyAnimation(self, b'icon_size', self)
     self.animation.setDuration(60/72.*1000)
     self.animation.setLoopCount(4)
     self.animation.valueChanged.connect(self.value_changed)
     self.setCursor(Qt.PointingHandCursor)
     self.animation.finished.connect(self.animation_finished)
예제 #8
0
 def makeButton(self, label, callback=None, width=None, icon=None):
     btn = QToolButton(self)
     #    btn.setAutoRaise(True)
     label and btn.setText(label)
     icon and btn.setIcon(icon)
     #    btn = QPushButton(label,self)
     #   btn.setFlat(True)
     if width:
         btn.setMinimumWidth(width)
         btn.setMaximumWidth(width)
     if icon:
         btn.setIcon(icon)
     if callback:
         btn.clicked.connect(callback)
     return btn
예제 #9
0
class MultipleWidget(QWidget):

    def __init__(self, parent):
        QWidget.__init__(self, parent)
        layout = QHBoxLayout()
        layout.setSpacing(5)
        layout.setContentsMargins(0, 0, 0, 0)

        self.tags_box = EditWithComplete(parent)
        layout.addWidget(self.tags_box, stretch=1000)
        self.editor_button = QToolButton(self)
        self.editor_button.setToolTip(_('Open Item Editor'))
        self.editor_button.setIcon(QIcon(I('chapters.png')))
        layout.addWidget(self.editor_button)
        self.setLayout(layout)

    def get_editor_button(self):
        return self.editor_button

    def update_items_cache(self, values):
        self.tags_box.update_items_cache(values)

    def clear(self):
        self.tags_box.clear()

    def setEditText(self):
        self.tags_box.setEditText()

    def addItem(self, itm):
        self.tags_box.addItem(itm)

    def set_separator(self, sep):
        self.tags_box.set_separator(sep)

    def set_add_separator(self, sep):
        self.tags_box.set_add_separator(sep)

    def set_space_before_sep(self, v):
        self.tags_box.set_space_before_sep(v)

    def setSizePolicy(self, v1, v2):
        self.tags_box.setSizePolicy(v1, v2)

    def setText(self, v):
        self.tags_box.setText(v)

    def text(self):
        return self.tags_box.text()
예제 #10
0
class FontFamilyChooser(QWidget):

    family_changed = pyqtSignal(object)

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.l = l = QHBoxLayout()
        l.setContentsMargins(0, 0, 0, 0)
        self.setLayout(l)
        self.button = QPushButton(self)
        self.button.setIcon(QIcon(I('font.png')))
        self.button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        l.addWidget(self.button)
        self.default_text = _('Choose &font family')
        self.font_family = None
        self.button.clicked.connect(self.show_chooser)
        self.clear_button = QToolButton(self)
        self.clear_button.setIcon(QIcon(I('clear_left.png')))
        self.clear_button.clicked.connect(self.clear_family)
        l.addWidget(self.clear_button)
        self.setToolTip = self.button.setToolTip
        self.toolTip = self.button.toolTip
        self.clear_button.setToolTip(_('Clear the font family'))
        l.addStretch(1)

    def clear_family(self):
        self.font_family = None

    @dynamic_property
    def font_family(self):
        def fget(self):
            return self._current_family

        def fset(self, val):
            if not val:
                val = None
            self._current_family = val
            self.button.setText(val or self.default_text)
            self.family_changed.emit(val)

        return property(fget=fget, fset=fset)

    def show_chooser(self):
        d = FontFamilyDialog(self.font_family, self)
        if d.exec_() == d.Accepted:
            self.font_family = d.font_family
예제 #11
0
    def __init__(self, parent=None):
        QFrame.__init__(self, parent)
        self.setFocusPolicy(Qt.StrongFocus)
        self.setAutoFillBackground(True)
        self.capture = 0

        self.setFrameShape(self.StyledPanel)
        self.setFrameShadow(self.Raised)
        self._layout = l = QGridLayout(self)
        self.setLayout(l)

        self.header = QLabel('')
        l.addWidget(self.header, 0, 0, 1, 2)

        self.use_default = QRadioButton('')
        self.use_custom = QRadioButton(_('Custom'))
        l.addWidget(self.use_default, 1, 0, 1, 3)
        l.addWidget(self.use_custom, 2, 0, 1, 3)
        self.use_custom.toggled.connect(self.custom_toggled)

        off = 2
        for which in (1, 2):
            text = _('&Shortcut:') if which == 1 else _('&Alternate shortcut:')
            la = QLabel(text)
            la.setStyleSheet('QLabel { margin-left: 1.5em }')
            l.addWidget(la, off+which, 0, 1, 3)
            setattr(self, 'label%d'%which, la)
            button = QPushButton(_('None'), self)
            button.clicked.connect(partial(self.capture_clicked, which=which))
            button.keyPressEvent = partial(self.key_press_event, which=which)
            setattr(self, 'button%d'%which, button)
            clear = QToolButton(self)
            clear.setIcon(QIcon(I('clear_left.png')))
            clear.clicked.connect(partial(self.clear_clicked, which=which))
            setattr(self, 'clear%d'%which, clear)
            l.addWidget(button, off+which, 1, 1, 1)
            l.addWidget(clear, off+which, 2, 1, 1)
            la.setBuddy(button)

        self.done_button = doneb = QPushButton(_('Done'), self)
        l.addWidget(doneb, 0, 2, 1, 1)
        doneb.clicked.connect(lambda : self.editing_done.emit(self))
        l.setColumnStretch(0, 100)

        self.custom_toggled(False)
예제 #12
0
    def __init__(self, parent=None):
        QFrame.__init__(self, parent)
        self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
        self.setAutoFillBackground(True)
        self.capture = 0

        self.setFrameShape(self.StyledPanel)
        self.setFrameShadow(self.Raised)
        self._layout = l = QGridLayout(self)
        self.setLayout(l)

        self.header = QLabel('')
        l.addWidget(self.header, 0, 0, 1, 2)

        self.use_default = QRadioButton('')
        self.use_custom = QRadioButton(_('&Custom'))
        l.addWidget(self.use_default, 1, 0, 1, 3)
        l.addWidget(self.use_custom, 2, 0, 1, 3)
        self.use_custom.toggled.connect(self.custom_toggled)

        off = 2
        for which in (1, 2):
            text = _('&Shortcut:') if which == 1 else _('&Alternate shortcut:')
            la = QLabel(text)
            la.setStyleSheet('QLabel { margin-left: 1.5em }')
            l.addWidget(la, off + which, 0, 1, 3)
            setattr(self, 'label%d' % which, la)
            button = QPushButton(_('None'), self)
            button.clicked.connect(partial(self.capture_clicked, which=which))
            button.installEventFilter(self)
            setattr(self, 'button%d' % which, button)
            clear = QToolButton(self)
            clear.setIcon(QIcon(I('clear_left.png')))
            clear.clicked.connect(partial(self.clear_clicked, which=which))
            setattr(self, 'clear%d' % which, clear)
            l.addWidget(button, off + which, 1, 1, 1)
            l.addWidget(clear, off + which, 2, 1, 1)
            la.setBuddy(button)

        self.done_button = doneb = QPushButton(_('Done'), self)
        l.addWidget(doneb, 0, 2, 1, 1)
        doneb.clicked.connect(lambda: self.editing_done.emit(self))
        l.setColumnStretch(0, 100)

        self.custom_toggled(False)
예제 #13
0
class FontFamilyChooser(QWidget):

    family_changed = pyqtSignal(object)

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.l = l = QHBoxLayout()
        l.setContentsMargins(0, 0, 0, 0)
        self.setLayout(l)
        self.button = QPushButton(self)
        self.button.setIcon(QIcon(I('font.png')))
        self.button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        l.addWidget(self.button)
        self.default_text = _('Choose &font family')
        self.font_family = None
        self.button.clicked.connect(self.show_chooser)
        self.clear_button = QToolButton(self)
        self.clear_button.setIcon(QIcon(I('clear_left.png')))
        self.clear_button.clicked.connect(self.clear_family)
        l.addWidget(self.clear_button)
        self.setToolTip = self.button.setToolTip
        self.toolTip = self.button.toolTip
        self.clear_button.setToolTip(_('Clear the font family'))
        l.addStretch(1)

    def clear_family(self):
        self.font_family = None

    @dynamic_property
    def font_family(self):
        def fget(self):
            return self._current_family

        def fset(self, val):
            if not val:
                val = None
            self._current_family = val
            self.button.setText(val or self.default_text)
            self.family_changed.emit(val)
        return property(fget=fget, fset=fset)

    def show_chooser(self):
        d = FontFamilyDialog(self.font_family, self)
        if d.exec_() == d.Accepted:
            self.font_family = d.font_family
예제 #14
0
 def __init__(self, index, dup_check, parent=None):
     QFrame.__init__(self, parent)
     self.setFrameShape(self.StyledPanel)
     self.setFrameShadow(self.Raised)
     self.setFocusPolicy(Qt.StrongFocus)
     self.setAutoFillBackground(True)
     self.l = l = QVBoxLayout(self)
     self.header = la = QLabel(self)
     la.setWordWrap(True)
     l.addWidget(la)
     self.default_shortcuts = QRadioButton(_("&Default"), self)
     self.custom = QRadioButton(_("&Custom"), self)
     self.custom.toggled.connect(self.custom_toggled)
     l.addWidget(self.default_shortcuts)
     l.addWidget(self.custom)
     for which in 1, 2:
         la = QLabel(
             _("&Shortcut:") if which == 1 else _("&Alternate shortcut:"))
         setattr(self, 'label%d' % which, la)
         h = QHBoxLayout()
         l.addLayout(h)
         h.setContentsMargins(25, -1, -1, -1)
         h.addWidget(la)
         b = QPushButton(_("Click to change"), self)
         la.setBuddy(b)
         b.clicked.connect(partial(self.capture_clicked, which=which))
         b.installEventFilter(self)
         setattr(self, 'button%d' % which, b)
         h.addWidget(b)
         c = QToolButton(self)
         c.setIcon(QIcon(I('clear_left.png')))
         c.setToolTip(_('Clear'))
         h.addWidget(c)
         c.clicked.connect(partial(self.clear_clicked, which=which))
         setattr(self, 'clear%d' % which, c)
     self.data_model = index.model()
     self.capture = 0
     self.key = None
     self.shorcut1 = self.shortcut2 = None
     self.dup_check = dup_check
     self.custom_toggled(False)
예제 #15
0
 def __init__(self, index, dup_check, parent=None):
     QFrame.__init__(self, parent)
     self.setFrameShape(self.StyledPanel)
     self.setFrameShadow(self.Raised)
     self.setFocusPolicy(Qt.StrongFocus)
     self.setAutoFillBackground(True)
     self.l = l = QVBoxLayout(self)
     self.header = la = QLabel(self)
     la.setWordWrap(True)
     l.addWidget(la)
     self.default_shortcuts = QRadioButton(_("&Default"), self)
     self.custom = QRadioButton(_("&Custom"), self)
     self.custom.toggled.connect(self.custom_toggled)
     l.addWidget(self.default_shortcuts)
     l.addWidget(self.custom)
     for which in 1, 2:
         la = QLabel(_("&Shortcut:") if which == 1 else _("&Alternate shortcut:"))
         setattr(self, 'label%d' % which, la)
         h = QHBoxLayout()
         l.addLayout(h)
         h.setContentsMargins(25, -1, -1, -1)
         h.addWidget(la)
         b = QPushButton(_("Click to change"), self)
         la.setBuddy(b)
         b.clicked.connect(partial(self.capture_clicked, which=which))
         b.installEventFilter(self)
         setattr(self, 'button%d' % which, b)
         h.addWidget(b)
         c = QToolButton(self)
         c.setIcon(QIcon(I('clear_left.png')))
         c.setToolTip(_('Clear'))
         h.addWidget(c)
         c.clicked.connect(partial(self.clear_clicked, which=which))
         setattr(self, 'clear%d' % which, c)
     self.data_model = index.model()
     self.capture = 0
     self.key = None
     self.shorcut1 = self.shortcut2 = None
     self.dup_check = dup_check
     self.custom_toggled(False)
예제 #16
0
    def __init__(self,
                 field_metadata,
                 parent=None,
                 revert_tooltip=None,
                 datetime_fmt='MMMM yyyy',
                 blank_as_equal=True,
                 fields=('title', 'authors', 'series', 'tags', 'rating',
                         'publisher', 'pubdate', 'identifiers', 'languages',
                         'comments', 'cover')):
        QWidget.__init__(self, parent)
        self.l = l = QGridLayout()
        l.setContentsMargins(0, 0, 0, 0)
        self.setLayout(l)
        revert_tooltip = revert_tooltip or _('Revert %s')
        self.current_mi = None
        self.changed_font = QFont(QApplication.font())
        self.changed_font.setBold(True)
        self.changed_font.setItalic(True)
        self.blank_as_equal = blank_as_equal

        self.widgets = OrderedDict()
        row = 0

        for field in fields:
            m = field_metadata[field]
            dt = m['datatype']
            extra = None
            if 'series' in {field, dt}:
                cls = SeriesEdit
            elif field == 'identifiers':
                cls = IdentifiersEdit
            elif field == 'languages':
                cls = LanguagesEdit
            elif 'comments' in {field, dt}:
                cls = CommentsEdit
            elif 'rating' in {field, dt}:
                cls = RatingsEdit
            elif dt == 'datetime':
                extra = datetime_fmt
                cls = DateEdit
            elif field == 'cover':
                cls = CoverView
            elif dt in {'text', 'enum'}:
                cls = LineEdit
            else:
                continue
            neww = cls(field, True, self, m, extra)
            neww.changed.connect(partial(self.changed, field))
            oldw = cls(field, False, self, m, extra)
            newl = QLabel('&%s:' % m['name'])
            newl.setBuddy(neww)
            button = QToolButton(self)
            button.setIcon(QIcon(I('back.png')))
            button.clicked.connect(partial(self.revert, field))
            button.setToolTip(revert_tooltip % m['name'])
            self.widgets[field] = Widgets(neww, oldw, newl, button)
            for i, w in enumerate((newl, neww, button, oldw)):
                c = i if i < 2 else i + 1
                if w is oldw:
                    c += 1
                l.addWidget(w, row, c)
            row += 1

        self.sep = f = QFrame(self)
        f.setFrameShape(f.VLine)
        l.addWidget(f, 0, 2, row, 1)
        self.sep2 = f = QFrame(self)
        f.setFrameShape(f.VLine)
        l.addWidget(f, 0, 4, row, 1)
        if 'comments' in self.widgets and not gprefs.get(
                'diff_widget_show_comments_controls', True):
            self.widgets['comments'].new.hide_toolbars()
예제 #17
0
class EnumValuesEdit(QDialog):
    def __init__(self, parent, db, key):
        QDialog.__init__(self, parent)

        self.setWindowTitle(_('Edit permissible values for {0}').format(key))
        self.db = db
        l = QGridLayout()

        bbox = QVBoxLayout()
        bbox.addStretch(10)
        self.del_button = QToolButton()
        self.del_button.setIcon(QIcon(I('trash.png')))
        self.del_button.setToolTip(_('Remove the currently selected value'))
        self.ins_button = QToolButton()
        self.ins_button.setIcon(QIcon(I('plus.png')))
        self.ins_button.setToolTip(_('Add a new permissible value'))
        self.move_up_button = QToolButton()
        self.move_up_button.setIcon(QIcon(I('arrow-up.png')))
        self.move_down_button = QToolButton()
        self.move_down_button.setIcon(QIcon(I('arrow-down.png')))
        bbox.addWidget(self.del_button)
        bbox.addStretch(1)
        bbox.addWidget(self.ins_button)
        bbox.addStretch(1)
        bbox.addWidget(self.move_up_button)
        bbox.addStretch(1)
        bbox.addWidget(self.move_down_button)
        bbox.addStretch(10)
        l.addItem(bbox, 0, 0)

        self.del_button.clicked.connect(self.del_line)

        self.all_colors = {unicode_type(s) for s in list(QColor.colorNames())}

        tl = QVBoxLayout()
        l.addItem(tl, 0, 1)
        self.table = t = QTableWidget(parent)
        t.setColumnCount(2)
        t.setRowCount(1)
        t.setHorizontalHeaderLabels([_('Value'), _('Color')])
        t.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
        tl.addWidget(t)

        self.fm = fm = db.field_metadata[key]
        permitted_values = fm.get('display', {}).get('enum_values', '')
        colors = fm.get('display', {}).get('enum_colors', '')
        t.setRowCount(len(permitted_values))
        for i, v in enumerate(permitted_values):
            t.setItem(i, 0, QTableWidgetItem(v))
            c = self.make_color_combobox(i, -1)
            if colors:
                c.setCurrentIndex(c.findText(colors[i]))
            else:
                c.setCurrentIndex(0)

        t.horizontalHeader().setSectionResizeMode(
            QHeaderView.ResizeMode.Stretch)

        self.setLayout(l)

        self.bb = bb = QDialogButtonBox(
            QDialogButtonBox.StandardButton.Ok
            | QDialogButtonBox.StandardButton.Cancel)
        bb.accepted.connect(self.accept)
        bb.rejected.connect(self.reject)
        l.addWidget(bb, 1, 0, 1, 2)

        self.ins_button.clicked.connect(self.ins_button_clicked)
        self.move_down_button.clicked.connect(self.move_down_clicked)
        self.move_up_button.clicked.connect(self.move_up_clicked)
        geom = gprefs.get('enum-values-edit-geometry')
        if geom is not None:
            QApplication.instance().safe_restore_geometry(self, geom)

    def sizeHint(self):
        sz = QDialog.sizeHint(self)
        sz.setWidth(max(sz.width(), 600))
        sz.setHeight(max(sz.height(), 400))
        return sz

    def make_color_combobox(self, row, dex):
        c = QComboBox(self)
        c.addItem('')
        c.addItems(QColor.colorNames())
        self.table.setCellWidget(row, 1, c)
        if dex >= 0:
            c.setCurrentIndex(dex)
        return c

    def move_up_clicked(self):
        row = self.table.currentRow()
        if row < 0:
            error_dialog(self,
                         _('Select a cell'),
                         _('Select a cell before clicking the button'),
                         show=True)
            return
        if row == 0:
            return
        self.move_row(row, -1)

    def move_row(self, row, direction):
        t = self.table.item(row, 0).text()
        c = self.table.cellWidget(row, 1).currentIndex()
        self.table.removeRow(row)
        row += direction
        self.table.insertRow(row)
        self.table.setItem(row, 0, QTableWidgetItem(t))
        self.make_color_combobox(row, c)
        self.table.setCurrentCell(row, 0)

    def move_down_clicked(self):
        row = self.table.currentRow()
        if row < 0:
            error_dialog(self,
                         _('Select a cell'),
                         _('Select a cell before clicking the button'),
                         show=True)
            return
        if row >= self.table.rowCount() - 1:
            return
        self.move_row(row, 1)

    def del_line(self):
        if self.table.currentRow() >= 0:
            self.table.removeRow(self.table.currentRow())

    def ins_button_clicked(self):
        row = self.table.currentRow()
        if row < 0:
            error_dialog(self,
                         _('Select a cell'),
                         _('Select a cell before clicking the button'),
                         show=True)
            return
        self.table.insertRow(row)
        self.table.setItem(row, 0, QTableWidgetItem())
        c = QComboBox(self)
        c.addItem('')
        c.addItems(QColor.colorNames())
        self.table.setCellWidget(row, 1, c)

    def save_geometry(self):
        gprefs.set('enum-values-edit-geometry', bytearray(self.saveGeometry()))

    def accept(self):
        disp = self.fm['display']
        values = []
        colors = []
        for i in range(0, self.table.rowCount()):
            v = unicode_type(self.table.item(i, 0).text())
            if not v:
                error_dialog(self,
                             _('Empty value'),
                             _('Empty values are not allowed'),
                             show=True)
                return
            values.append(v)
            c = unicode_type(self.table.cellWidget(i, 1).currentText())
            if c:
                colors.append(c)

        l_lower = [v.lower() for v in values]
        for i, v in enumerate(l_lower):
            if v in l_lower[i + 1:]:
                error_dialog(self,
                             _('Duplicate value'),
                             _('The value "{0}" is in the list more than '
                               'once, perhaps with different case').format(
                                   values[i]),
                             show=True)
                return

        if colors and len(colors) != len(values):
            error_dialog(self,
                         _('Invalid colors specification'),
                         _('Either all values or no values must have colors'),
                         show=True)
            return

        disp['enum_values'] = values
        disp['enum_colors'] = colors
        self.db.set_custom_column_metadata(self.fm['colnum'],
                                           display=disp,
                                           update_last_modified=True)
        self.save_geometry()
        return QDialog.accept(self)

    def reject(self):
        return QDialog.reject(self)
class AnnotationElementsTable(QTableWidget):
    '''
    QTableWidget managing CSS rules
    '''
    DEBUG = True
    #MAXIMUM_TABLE_HEIGHT = 113
    ELEMENT_FIELD_WIDTH = 250
    if isosx:
        FONT = QFont('Monaco', 11)
    elif iswindows:
        FONT = QFont('Lucida Console', 9)
    elif islinux:
        FONT = QFont('Monospace', 9)
        FONT.setStyleHint(QFont.TypeWriter)

    COLUMNS = {
                'ELEMENT':   {'ordinal': 0, 'name': 'Element'},
                'CSS':  {'ordinal': 1, 'name': 'CSS'},
                }

    # Sample content for preview
    sample_ann_1 = {
        'text': [
            ("What really knocks me out is a book that, when you're all done reading it, "
             "you wish the author that wrote it was a terrific friend of yours and "
             "you could call him up on the phone whenever you felt like it. "
             "That doesn't happen much, though.")],
        'note': ['— J.D. Salinger'],
        'highlightcolor': 'Yellow',
        'timestamp': time.mktime(time.localtime()),
        'location': 'Chapter 4',
        'location_sort': 0
        }
    sample_ann_2 = {
        'text': [
            ("Literature is a luxury; fiction is a necessity.")],
        'note': ['— G.K. Chesterton'],
        'highlightcolor': 'Pink',
        'timestamp': time.mktime(time.localtime()),
        'location': 'Chapter 12',
        'location_sort': 1
        }
    sample_ann_3 = {
        'text': [
            ("There is no surer foundation for a beautiful friendship "
             "than a mutual taste in literature.")],
        'note': ['— P.G. Wodehouse'],
        'highlightcolor': 'Purple',
        'timestamp': time.mktime(time.localtime()),
        'location': 'Chapter 53',
        'location_sort': 2
        }
    sample_book_notes = [
        "Create book notes from Marvin’s Library view by swiping left on a cover, "
        "then touching <b>Notes</b>.<br/>"
        "Create book notes from Marvin’s Home screen by long-touching a cover, then "
        "touching <b>Notes</b>."
        ]
    sample_bookmark_notes = {
        "1": {'color': 'bookmark_red',
              'location': 'Chapter 1',
              'note': ('Create bookmark notes from Marvin’s <b>Bookmarks</b> screen. '
                       'Access the Table of Contents screen by tapping the book icon '
                       'in the top toolbar of the Book screen, then tapping <b>Bookmarks</b> '
                       'in the bottom toolbar. '
                       'Swipe left on a bookmark to edit.')},
        "2": {'color': 'bookmark_green',
              'location': 'Chapter 2',
              'note': ('Enable multi-color bookmarks from Marvin’s '
                       '<b>Options | More</b> screen.')},
        }

    def __init__(self, parent, object_name):
        self.parent = parent
        self.prefs = parent.prefs
        self.elements = self.prefs.get('appearance_css', None)
        if not self.elements:
            self.elements = default_elements

        QTableWidget.__init__(self)
        self.setObjectName(object_name)
        self.layout = parent.elements_hl.layout()

        # Add ourselves to the layout
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
        sizePolicy.setHorizontalStretch(0)
        #sizePolicy.setVerticalStretch(0)
        #sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth())
        self.setSizePolicy(sizePolicy)
        #self.setMaximumSize(QSize(16777215, self.MAXIMUM_TABLE_HEIGHT))

        self.setColumnCount(0)
        self.setRowCount(0)
        self.layout.addWidget(self)

    def _init_controls(self):
        # Add the control set
        vbl = QVBoxLayout()
        self.move_element_up_tb = QToolButton()
        self.move_element_up_tb.setObjectName("move_element_up_tb")
        self.move_element_up_tb.setToolTip('Move element up')
        self.move_element_up_tb.setIcon(QIcon(I('arrow-up.png')))
        self.move_element_up_tb.clicked.connect(self.move_row_up)
        vbl.addWidget(self.move_element_up_tb)

        self.undo_css_tb = QToolButton()
        self.undo_css_tb.setObjectName("undo_css_tb")
        self.undo_css_tb.setToolTip('Restore CSS to last saved')
        self.undo_css_tb.setIcon(QIcon(I('edit-undo.png')))
        self.undo_css_tb.clicked.connect(partial(self.undo_reset_button_clicked, 'undo'))
        vbl.addWidget(self.undo_css_tb)

        self.reset_css_tb = QToolButton()
        self.reset_css_tb.setObjectName("reset_css_tb")
        self.reset_css_tb.setToolTip('Reset CSS to default')
        self.reset_css_tb.setIcon(QIcon(I('trash.png')))
        self.reset_css_tb.clicked.connect(partial(self.undo_reset_button_clicked, 'reset'))
        vbl.addWidget(self.reset_css_tb)

        self.move_element_down_tb = QToolButton()
        self.move_element_down_tb.setObjectName("move_element_down_tb")
        self.move_element_down_tb.setToolTip('Move element down')
        self.move_element_down_tb.setIcon(QIcon(I('arrow-down.png')))
        self.move_element_down_tb.clicked.connect(self.move_row_down)
        vbl.addWidget(self.move_element_down_tb)

        self.layout.addLayout(vbl)

    def _init_table_widget(self):
        header_labels = [self.COLUMNS[index]['name'] \
            for index in sorted(self.COLUMNS.keys(), key=lambda c: self.COLUMNS[c]['ordinal'])]
        self.setColumnCount(len(header_labels))
        self.setHorizontalHeaderLabels(header_labels)
        self.verticalHeader().setVisible(False)

        self.setSortingEnabled(False)

        # Select single rows
        self.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.setSelectionMode(QAbstractItemView.SingleSelection)

    def convert_row_to_data(self, row):
        data = {}
        data['ordinal'] = row
        data['name'] = unicode(self.cellWidget(row, self.COLUMNS['ELEMENT']['ordinal']).text()).strip()
        data['css'] = unicode(self.cellWidget(row, self.COLUMNS['CSS']['ordinal']).toPlainText()).strip()
        return data

    def css_edited(self, row):
        self.select_and_scroll_to_row(row)
        col = self.COLUMNS['CSS']['ordinal']
        widget = self.cellWidget(row, col)
        css = unicode(widget.toPlainText())
        lines = []
        for line in css.split('\n'):
            lines.append(re.sub('^\s*', '', line))
        self.resize_row_height(lines, row)
        self.prefs.set('appearance_css', self.get_data())
        widget.setFocus()
        self.preview_css()

    def get_data(self):
        data_items = []
        for row in range(self.rowCount()):
            data = self.convert_row_to_data(row)
            data_items.append(
                               {'ordinal': data['ordinal'],
                                'name': data['name'],
                                'css': data['css'],
                                })
        return data_items

    def initialize(self):
        self._init_table_widget()
        self._init_controls()
        self.populate_table()
        self.resizeColumnsToContents()
        self.horizontalHeader().setStretchLastSection(True)

        # Update preview window, select first row
        self.css_edited(0)

    def move_row(self, source, dest):

        self.blockSignals(True)
        # Save the contents of the destination row
        saved_data = self.convert_row_to_data(dest)

        # Remove the destination row
        self.removeRow(dest)

        # Insert a new row at the original location
        self.insertRow(source)

        # Populate it with the saved data
        self.populate_table_row(source, saved_data)

        self.select_and_scroll_to_row(dest)
        self.blockSignals(False)

        self.css_edited(dest)

    def move_row_down(self):
        src_row = self.currentRow()
        dest_row = src_row + 1
        if dest_row == self.rowCount():
            return
        self.move_row(src_row, dest_row)

    def move_row_up(self):
        src_row = self.currentRow()
        dest_row = src_row - 1
        if dest_row < 0:
            return
        self.move_row(src_row, dest_row)

    def populate_table(self):
        # Format of rules list is different if default values vs retrieved JSON
        # Hack to normalize list style
        elements = self.elements
        if elements and type(elements[0]) is list:
            elements = elements[0]
        self.setFocus()
        elements = sorted(elements, key=lambda k: k['ordinal'])
        for row, element in enumerate(elements):
            self.insertRow(row)
            self.select_and_scroll_to_row(row)
            self.populate_table_row(row, element)
        self.selectRow(0)

    def populate_table_row(self, row, data):
        self.blockSignals(True)
        self.set_element_name_in_row(row, self.COLUMNS['ELEMENT']['ordinal'], data['name'])
        self.set_css_in_row(row, self.COLUMNS['CSS']['ordinal'], data['css'])
        self.blockSignals(False)

    def preview_css(self):
        '''
        Construct a dummy set of notes and annotation for preview purposes
        Modeled after book_status:_get_formatted_annotations()
        '''
        from calibre_plugins.marvin_manager.annotations import (
            ANNOTATIONS_HTML_TEMPLATE, Annotation, Annotations, BookNotes, BookmarkNotes)

        # Assemble the preview soup
        soup = BeautifulSoup(ANNOTATIONS_HTML_TEMPLATE)

        # Load the CSS from MXD resources
        path = os.path.join(self.parent.opts.resources_path, 'css', 'annotations.css')
        with open(path, 'rb') as f:
            css = f.read().decode('utf-8')
        style_tag = Tag(soup, 'style')
        style_tag.insert(0, css)
        soup.head.style.replaceWith(style_tag)

        # Assemble the sample Book notes
        book_notes_soup = BookNotes().construct(self.sample_book_notes)
        soup.body.append(book_notes_soup)
        cd_tag = Tag(soup, 'div', [('class', "divider")])
        soup.body.append(cd_tag)

        # Assemble the sample Bookmark notes
        bookmark_notes_soup = BookmarkNotes().construct(self.sample_bookmark_notes)
        soup.body.append(bookmark_notes_soup)
        cd_tag = Tag(soup, 'div', [('class', "divider")])
        soup.body.append(cd_tag)

        # Assemble the sample annotations
        pas = Annotations(None, title="Preview")
        pas.annotations.append(Annotation(self.sample_ann_1))
        pas.annotations.append(Annotation(self.sample_ann_2))
        pas.annotations.append(Annotation(self.sample_ann_3))
        annotations_soup = pas.to_HTML(pas.create_soup())
        soup.body.append(annotations_soup)

        self.parent.wv.setHtml(unicode(soup.renderContents()))

    def resize_row_height(self, lines, row):
        point_size = self.FONT.pointSize()
        if isosx:
            height = 30 + (len(lines) - 1) * (point_size + 4)
        elif iswindows:
            height = 26 + (len(lines) - 1) * (point_size + 3)
        elif islinux:
            height = 30 + (len(lines) - 1) * (point_size + 6)

        self.verticalHeader().resizeSection(row, height)

    def select_and_scroll_to_row(self, row):
        self.setFocus()
        self.selectRow(row)
        self.scrollToItem(self.currentItem())

    def set_element_name_in_row(self, row, col, name):
        rule_name = QLabel(" %s " % name)
        rule_name.setFont(self.FONT)
        self.setCellWidget(row, col, rule_name)

    def set_css_in_row(self, row, col, css):
        # Clean up multi-line css formatting
        # A single line is 30px tall, subsequent lines add 16px

        lines = []
        for line in css.split('\n'):
            lines.append(re.sub('^\s*', '', line))
        css_content = QPlainTextEdit('\n'.join(lines))
        css_content.setFont(self.FONT)
        css_content.textChanged.connect(partial(self.css_edited, row))
        self.setCellWidget(row, col, css_content)
        self.resize_row_height(lines, row)

    def undo_reset_button_clicked(self, mode):
        """
        Figure out which element is being reset
        Reset to last save or default
        """
        row = self.currentRow()
        data = self.convert_row_to_data(row)

        # Get default
        default_css = None
        for de in default_elements:
            if de['name'] == data['name']:
                default_css = de
                break

        # Get last saved
        last_saved_css = None
        saved_elements = self.prefs.get('appearance_css', None)
        last_saved_css = default_css
        if saved_elements:
            for se in saved_elements:
                if se['name'] == data['name']:
                    last_saved_css = se
                    break

        # Restore css
        if mode == 'reset':
            self.populate_table_row(row, default_css)
        elif mode == 'undo':
            self.populate_table_row(row, last_saved_css)

        # Refresh the stored data
        #self.prefs.set('appearance_css', self.get_data())
        self.css_edited(row)
예제 #19
0
class AddEmptyBookDialog(QDialog):

    def __init__(self, parent, db, author, series=None, title=None, dup_title=None):
        QDialog.__init__(self, parent)
        self.db = db

        self.setWindowTitle(_('How many empty books?'))

        self._layout = QGridLayout(self)
        self.setLayout(self._layout)

        self.qty_label = QLabel(_('How many empty books should be added?'))
        self._layout.addWidget(self.qty_label, 0, 0, 1, 2)

        self.qty_spinbox = QSpinBox(self)
        self.qty_spinbox.setRange(1, 10000)
        self.qty_spinbox.setValue(1)
        self._layout.addWidget(self.qty_spinbox, 1, 0, 1, 2)

        self.author_label = QLabel(_('Set the author of the new books to:'))
        self._layout.addWidget(self.author_label, 2, 0, 1, 2)

        self.authors_combo = EditWithComplete(self)
        self.authors_combo.setSizeAdjustPolicy(
                self.authors_combo.AdjustToMinimumContentsLengthWithIcon)
        self.authors_combo.setEditable(True)
        self._layout.addWidget(self.authors_combo, 3, 0, 1, 1)
        self.initialize_authors(db, author)

        self.clear_button = QToolButton(self)
        self.clear_button.setIcon(QIcon(I('trash.png')))
        self.clear_button.setToolTip(_('Reset author to Unknown'))
        self.clear_button.clicked.connect(self.reset_author)
        self._layout.addWidget(self.clear_button, 3, 1, 1, 1)

        self.series_label = QLabel(_('Set the series of the new books to:'))
        self._layout.addWidget(self.series_label, 4, 0, 1, 2)

        self.series_combo = EditWithComplete(self)
        self.series_combo.setSizeAdjustPolicy(
                self.authors_combo.AdjustToMinimumContentsLengthWithIcon)
        self.series_combo.setEditable(True)
        self._layout.addWidget(self.series_combo, 5, 0, 1, 1)
        self.initialize_series(db, series)

        self.sclear_button = QToolButton(self)
        self.sclear_button.setIcon(QIcon(I('trash.png')))
        self.sclear_button.setToolTip(_('Reset series'))
        self.sclear_button.clicked.connect(self.reset_series)
        self._layout.addWidget(self.sclear_button, 5, 1, 1, 1)

        self.title_label = QLabel(_('Set the title of the new books to:'))
        self._layout.addWidget(self.title_label, 6, 0, 1, 2)

        self.title_edit = QLineEdit(self)
        self.title_edit.setText(title or '')
        self._layout.addWidget(self.title_edit, 7, 0, 1, 1)

        self.tclear_button = QToolButton(self)
        self.tclear_button.setIcon(QIcon(I('trash.png')))
        self.tclear_button.setToolTip(_('Reset title'))
        self.tclear_button.clicked.connect(self.title_edit.clear)
        self._layout.addWidget(self.tclear_button, 7, 1, 1, 1)

        self.format_label = QLabel(_('Also create an empty e-book in format:'))
        self._layout.addWidget(self.format_label, 8, 0, 1, 2)
        c = self.format_value = QComboBox(self)
        from calibre.ebooks.oeb.polish.create import valid_empty_formats
        possible_formats = [''] + sorted(x.upper() for x in valid_empty_formats)
        c.addItems(possible_formats)
        c.setToolTip(_('Also create an empty book format file that you can subsequently edit'))
        if gprefs.get('create_empty_epub_file', False):
            # Migration of the check box
            gprefs.set('create_empty_format_file', 'epub')
            del gprefs['create_empty_epub_file']
        use_format = gprefs.get('create_empty_format_file', '').upper()
        try:
            c.setCurrentIndex(possible_formats.index(use_format))
        except Exception:
            pass
        self._layout.addWidget(c, 9, 0, 1, 1)

        self.copy_formats = cf = QCheckBox(_('Also copy book &formats when duplicating a book'), self)
        cf.setToolTip(_(
            'Also copy all e-book files into the newly created duplicate'
            ' books.'))
        cf.setChecked(gprefs.get('create_empty_copy_dup_formats', False))
        self._layout.addWidget(cf, 10, 0, 1, -1)

        button_box = self.bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
        button_box.accepted.connect(self.accept)
        button_box.rejected.connect(self.reject)
        self._layout.addWidget(button_box, 11, 0, 1, -1)
        if dup_title:
            self.dup_button = b = button_box.addButton(_('&Duplicate current book'), button_box.ActionRole)
            b.clicked.connect(self.do_duplicate_book)
            b.setIcon(QIcon(I('edit-copy.png')))
            b.setToolTip(_(
                'Make the new empty book records exact duplicates\n'
                'of the current book "%s", with all metadata identical'
            ) % dup_title)
        self.resize(self.sizeHint())
        self.duplicate_current_book = False

    def do_duplicate_book(self):
        self.duplicate_current_book = True
        self.accept()

    def accept(self):
        self.save_settings()
        return QDialog.accept(self)

    def save_settings(self):
        gprefs['create_empty_format_file'] = self.format_value.currentText().lower()
        gprefs['create_empty_copy_dup_formats'] = self.copy_formats.isChecked()

    def reject(self):
        self.save_settings()
        return QDialog.reject(self)

    def reset_author(self, *args):
        self.authors_combo.setEditText(_('Unknown'))

    def reset_series(self):
        self.series_combo.setEditText('')

    def initialize_authors(self, db, author):
        au = author
        if not au:
            au = _('Unknown')
        self.authors_combo.show_initial_value(au.replace('|', ','))

        self.authors_combo.set_separator('&')
        self.authors_combo.set_space_before_sep(True)
        self.authors_combo.set_add_separator(tweaks['authors_completer_append_separator'])
        self.authors_combo.update_items_cache(db.all_author_names())

    def initialize_series(self, db, series):
        self.series_combo.show_initial_value(series or '')
        self.series_combo.update_items_cache(db.all_series_names())
        self.series_combo.set_separator(None)

    @property
    def qty_to_add(self):
        return self.qty_spinbox.value()

    @property
    def selected_authors(self):
        return string_to_authors(unicode_type(self.authors_combo.text()))

    @property
    def selected_series(self):
        return unicode_type(self.series_combo.text())

    @property
    def selected_title(self):
        return self.title_edit.text().strip()
class FindAnnotationsDialog(SizePersistedDialog, Logger):

    GENERIC_STYLE = 'Any style'
    GENERIC_READER = 'Any reader'

    def __init__(self, opts):
        self.matched_ids = set()
        self.opts = opts
        self.prefs = opts.prefs
        super(FindAnnotationsDialog, self).__init__(self.opts.gui, 'find_annotations_dialog')
        self.setWindowTitle('Find Annotations')
        self.setWindowIcon(self.opts.icon)
        self.l = QVBoxLayout(self)
        self.setLayout(self.l)

        self.search_criteria_gb = QGroupBox(self)
        self.search_criteria_gb.setTitle("Search criteria")
        self.scgl = QGridLayout(self.search_criteria_gb)
        self.l.addWidget(self.search_criteria_gb)
        # addWidget(widget, row, col, rowspan, colspan)

        row = 0
        # ~~~~~~~~ Create the Readers comboBox ~~~~~~~~
        self.reader_label = QLabel('Reader')
        self.reader_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
        self.scgl.addWidget(self.reader_label, row, 0, 1, 1)

        self.find_annotations_reader_comboBox = QComboBox()
        self.find_annotations_reader_comboBox.setObjectName('find_annotations_reader_comboBox')
        self.find_annotations_reader_comboBox.setToolTip('Reader annotations to search for')

        self.find_annotations_reader_comboBox.addItem(self.GENERIC_READER)
        racs = ReaderApp.get_reader_app_classes()
        for ra in sorted(racs.keys()):
            self.find_annotations_reader_comboBox.addItem(ra)
        self.scgl.addWidget(self.find_annotations_reader_comboBox, row, 1, 1, 4)
        row += 1

        # ~~~~~~~~ Create the Styles comboBox ~~~~~~~~
        self.style_label = QLabel('Style')
        self.style_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
        self.scgl.addWidget(self.style_label, row, 0, 1, 1)

        self.find_annotations_color_comboBox = QComboBox()
        self.find_annotations_color_comboBox.setObjectName('find_annotations_color_comboBox')
        self.find_annotations_color_comboBox.setToolTip('Annotation style to search for')

        self.find_annotations_color_comboBox.addItem(self.GENERIC_STYLE)
        all_colors = COLOR_MAP.keys()
        all_colors.remove('Default')
        for color in sorted(all_colors):
            self.find_annotations_color_comboBox.addItem(color)
        self.scgl.addWidget(self.find_annotations_color_comboBox, row, 1, 1, 4)
        row += 1

        # ~~~~~~~~ Create the Text LineEdit control ~~~~~~~~
        self.text_label = QLabel('Text')
        self.text_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
        self.scgl.addWidget(self.text_label, row, 0, 1, 1)
        self.find_annotations_text_lineEdit = MyLineEdit()
        self.find_annotations_text_lineEdit.setObjectName('find_annotations_text_lineEdit')
        self.scgl.addWidget(self.find_annotations_text_lineEdit, row, 1, 1, 3)
        self.reset_text_tb = QToolButton()
        self.reset_text_tb.setObjectName('reset_text_tb')
        self.reset_text_tb.setToolTip('Clear search criteria')
        self.reset_text_tb.setIcon(QIcon(I('trash.png')))
        self.reset_text_tb.clicked.connect(self.clear_text_field)
        self.scgl.addWidget(self.reset_text_tb, row, 4, 1, 1)
        row += 1

        # ~~~~~~~~ Create the Note LineEdit control ~~~~~~~~
        self.note_label = QLabel('Note')
        self.note_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
        self.scgl.addWidget(self.note_label, row, 0, 1, 1)
        self.find_annotations_note_lineEdit = MyLineEdit()
        self.find_annotations_note_lineEdit.setObjectName('find_annotations_note_lineEdit')
        self.scgl.addWidget(self.find_annotations_note_lineEdit, row, 1, 1, 3)
        self.reset_note_tb = QToolButton()
        self.reset_note_tb.setObjectName('reset_note_tb')
        self.reset_note_tb.setToolTip('Clear search criteria')
        self.reset_note_tb.setIcon(QIcon(I('trash.png')))
        self.reset_note_tb.clicked.connect(self.clear_note_field)
        self.scgl.addWidget(self.reset_note_tb, row, 4, 1, 1)
        row += 1

        # ~~~~~~~~ Create the Date range controls ~~~~~~~~
        self.date_range_label = QLabel('Date range')
        self.date_range_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
        self.scgl.addWidget(self.date_range_label, row, 0, 1, 1)

        # Date 'From'
        self.find_annotations_date_from_dateEdit = MyDateEdit(self, datetime(1970,1,1))
        self.find_annotations_date_from_dateEdit.setObjectName('find_annotations_date_from_dateEdit')
        #self.find_annotations_date_from_dateEdit.current_val = datetime(1970,1,1)
        self.find_annotations_date_from_dateEdit.clear_button.clicked.connect(self.find_annotations_date_from_dateEdit.reset_from_date)
        self.scgl.addWidget(self.find_annotations_date_from_dateEdit, row, 1, 1, 1)
        self.scgl.addWidget(self.find_annotations_date_from_dateEdit.clear_button, row, 2, 1, 1)

        # Date 'To'
        self.find_annotations_date_to_dateEdit = MyDateEdit(self, datetime.today())
        self.find_annotations_date_to_dateEdit.setObjectName('find_annotations_date_to_dateEdit')
        #self.find_annotations_date_to_dateEdit.current_val = datetime.today()
        self.find_annotations_date_to_dateEdit.clear_button.clicked.connect(self.find_annotations_date_to_dateEdit.reset_to_date)
        self.scgl.addWidget(self.find_annotations_date_to_dateEdit, row, 3, 1, 1)
        self.scgl.addWidget(self.find_annotations_date_to_dateEdit.clear_button, row, 4, 1, 1)
        row += 1

        # ~~~~~~~~ Create a horizontal line ~~~~~~~~
        self.hl = QFrame(self)
        self.hl.setGeometry(QRect(0, 0, 1, 3))
        self.hl.setFrameShape(QFrame.HLine)
        self.hl.setFrameShadow(QFrame.Raised)
        self.scgl.addWidget(self.hl, row, 0, 1, 5)
        row += 1

        # ~~~~~~~~ Create the results label field ~~~~~~~~
        self.result_label = QLabel('<p style="color:red">scanning…</p>')
        self.result_label.setAlignment(Qt.AlignCenter)
        self.result_label.setWordWrap(False)

        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Maximum)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.result_label.sizePolicy().hasHeightForWidth())
        self.result_label.setSizePolicy(sizePolicy)
        self.result_label.setMinimumSize(QtCore.QSize(250, 0))
        self.scgl.addWidget(self.result_label, row, 0, 1, 5)
        row += 1

        # ~~~~~~~~ Create the ButtonBox ~~~~~~~~
        self.dialogButtonBox = QDialogButtonBox(self)
        self.dialogButtonBox.setOrientation(Qt.Horizontal)
        if False:
            self.update_button = QPushButton('Update results')
            self.update_button.setDefault(True)
            self.update_button.setVisible(False)
            self.dialogButtonBox.addButton(self.update_button, QDialogButtonBox.ActionRole)

        self.cancel_button = self.dialogButtonBox.addButton(self.dialogButtonBox.Cancel)
        self.find_button = self.dialogButtonBox.addButton(self.dialogButtonBox.Ok)
        self.find_button.setText('Find Matching Books')

        self.l.addWidget(self.dialogButtonBox)
        self.dialogButtonBox.clicked.connect(self.find_annotations_dialog_clicked)

        # ~~~~~~~~ Add a spacer ~~~~~~~~
        self.spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
        self.l.addItem(self.spacerItem)

        # ~~~~~~~~ Restore previously saved settings ~~~~~~~~
        self.restore_settings()

        # ~~~~~~~~ Declare sizing ~~~~~~~~
        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth())
        self.setSizePolicy(sizePolicy)
        self.resize_dialog()

        # ~~~~~~~~ Connect all signals ~~~~~~~~
        self.find_annotations_reader_comboBox.currentIndexChanged.connect(partial(self.update_results, 'reader'))
        self.find_annotations_color_comboBox.currentIndexChanged.connect(partial(self.update_results, 'color'))
        self.find_annotations_text_lineEdit.editingFinished.connect(partial(self.update_results, 'text'))
        self.find_annotations_note_lineEdit.editingFinished.connect(partial(self.update_results, 'note'))
#        self.connect(self.find_annotations_text_lineEdit, pyqtSignal("return_pressed"), self.return_pressed)
        self.find_annotations_text_lineEdit.return_pressed.connect(self.return_pressed)
#        self.connect(self.find_annotations_note_lineEdit, pyqtSignal("return_pressed"), self.return_pressed)
        self.find_annotations_note_lineEdit.return_pressed.connect(self.return_pressed)

        # Date range signals connected in inventory_available()

        # ~~~~~~~~ Allow dialog to render before doing inventory ~~~~~~~~
        #field = self.prefs.get('cfg_annotations_destination_field', None)
        field = get_cc_mapping('annotations', 'field', None)
        self.annotated_books_scanner = InventoryAnnotatedBooks(self.opts.gui, field, get_date_range=True)
        self.annotated_books_scanner.signal.connect(self.inventory_available)
        QTimer.singleShot(1, self.start_inventory_scan)

    def clear_note_field(self):
        if str(self.find_annotations_note_lineEdit.text()) > '':
            self.find_annotations_note_lineEdit.setText('')
            self.update_results('clear_note_field')

    def clear_text_field(self):
        if str(self.find_annotations_text_lineEdit.text()) > '':
            self.find_annotations_text_lineEdit.setText('')
            self.update_results('clear_text_field')

    def find_annotations_dialog_clicked(self, button):
        if self.dialogButtonBox.buttonRole(button) == QDialogButtonBox.AcceptRole:
            self.save_settings()
            self.accept()
        elif self.dialogButtonBox.buttonRole(button) == QDialogButtonBox.RejectRole:
            self.close()

    def inventory_available(self):
        '''
        Update the Date range widgets with the rounded oldest, newest dates
        Don't connect date signals until date range available
        '''
        self._log_location()

        # Reset the date range based on available annotations
        oldest = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.oldest_annotation))
        oldest_day = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.oldest_annotation).replace(hour=0, minute=0, second=0))
        newest = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.newest_annotation))
        newest_day = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.newest_annotation).replace(hour=23, minute=59, second=59))

        # Set 'From' date limits to inventory values
        self.find_annotations_date_from_dateEdit.setMinimumDateTime(oldest_day)
        self.find_annotations_date_from_dateEdit.current_val = oldest
        self.find_annotations_date_from_dateEdit.setMaximumDateTime(newest_day)

        # Set 'To' date limits to inventory values
        self.find_annotations_date_to_dateEdit.setMinimumDateTime(oldest_day)
        self.find_annotations_date_to_dateEdit.current_val = newest_day
        self.find_annotations_date_to_dateEdit.setMaximumDateTime(newest_day)

        # Connect the signals for date range changes
        self.find_annotations_date_from_dateEdit.dateTimeChanged.connect(partial(self.update_results, 'from_date'))
        self.find_annotations_date_to_dateEdit.dateTimeChanged.connect(partial(self.update_results, 'to_date'))

        self.update_results('inventory_available')

    def restore_settings(self):
        self.blockSignals(True)

        ra = self.prefs.get('find_annotations_reader_comboBox', self.GENERIC_READER)
        ra_index = self.find_annotations_reader_comboBox.findText(ra)
        self.find_annotations_reader_comboBox.setCurrentIndex(ra_index)

        color = self.prefs.get('find_annotations_color_comboBox', self.GENERIC_STYLE)
        color_index = self.find_annotations_color_comboBox.findText(color)
        self.find_annotations_color_comboBox.setCurrentIndex(color_index)

        text = self.prefs.get('find_annotations_text_lineEdit', '')
        self.find_annotations_text_lineEdit.setText(text)

        note = self.prefs.get('find_annotations_note_lineEdit', '')
        self.find_annotations_note_lineEdit.setText(note)

        if False:
            from_date = self.prefs.get('find_annotations_date_from_dateEdit', datetime(1970,1,1))
            self.find_annotations_date_from_dateEdit.current_val = from_date
            to_date = self.prefs.get('find_annotations_date_to_dateEdit', datetime.today())
            self.find_annotations_date_to_dateEdit.current_val = to_date

        self.blockSignals(False)

    def return_pressed(self):
        self.update_results("return_pressed")

    def save_settings(self):
        ra = str(self.find_annotations_reader_comboBox.currentText())
        self.prefs.set('find_annotations_reader_comboBox', ra)

        color = str(self.find_annotations_color_comboBox.currentText())
        self.prefs.set('find_annotations_color_comboBox', color)

        text = str(self.find_annotations_text_lineEdit.text())
        self.prefs.set('find_annotations_text_lineEdit', text)

        note = str(self.find_annotations_note_lineEdit.text())
        self.prefs.set('find_annotations_note_lineEdit', note)

        if False:
            from_date = self.find_annotations_date_from_dateEdit.current_val
            self.prefs.set('find_annotations_date_from_dateEdit', from_date)

            to_date = self.find_annotations_date_to_dateEdit.current_val
            self.prefs.set('find_annotations_date_to_dateEdit', to_date)

    def start_inventory_scan(self):
        self._log_location()
        self.annotated_books_scanner.start()

    def update_results(self, trigger):
        #self._log_location(trigger)
        reader_to_match = str(self.find_annotations_reader_comboBox.currentText())
        color_to_match = str(self.find_annotations_color_comboBox.currentText())
        text_to_match = str(self.find_annotations_text_lineEdit.text())
        note_to_match = str(self.find_annotations_note_lineEdit.text())

        from_date = self.find_annotations_date_from_dateEdit.dateTime().toTime_t()
        to_date = self.find_annotations_date_to_dateEdit.dateTime().toTime_t()

        annotation_map = self.annotated_books_scanner.annotation_map
        #field = self.prefs.get("cfg_annotations_destination_field", None)
        field = get_cc_mapping('annotations', 'field', None)

        db = self.opts.gui.current_db
        matched_titles = []
        self.matched_ids = set()

        for cid in annotation_map:
            mi = db.get_metadata(cid, index_is_id=True)
            soup = None
            if field == 'Comments':
                if mi.comments:
                    soup = BeautifulSoup(mi.comments)
            else:
                if mi.get_user_metadata(field, False)['#value#'] is not None:
                    soup = BeautifulSoup(mi.get_user_metadata(field, False)['#value#'])
            if soup:
                uas = soup.findAll('div', 'annotation')
                for ua in uas:
                    # Are we already logged?
                    if cid in self.matched_ids:
                        continue

                    # Check reader
                    if reader_to_match != self.GENERIC_READER:
                        this_reader = ua['reader']
                        if this_reader != reader_to_match:
                            continue

                    # Check color
                    if color_to_match != self.GENERIC_STYLE:
                        this_color = ua.find('table')['color']
                        if this_color != color_to_match:
                            continue

                    # Check date range, allow for mangled timestamp
                    try:
                        timestamp = float(ua.find('td', 'timestamp')['uts'])
                        if timestamp < from_date or timestamp > to_date:
                            continue
                    except:
                        continue

                    highlight_text = ''
                    try:
                        pels = ua.findAll('p', 'highlight')
                        for pel in pels:
                            highlight_text += pel.string + '\n'
                    except:
                        pass
                    if text_to_match > '':
                        if not re.search(text_to_match, highlight_text, flags=re.IGNORECASE):
                            continue

                    note_text = ''
                    try:
                        nels = ua.findAll('p', 'note')
                        for nel in nels:
                            note_text += nel.string + '\n'
                    except:
                        pass
                    if note_to_match > '':
                        if not re.search(note_to_match, note_text, flags=re.IGNORECASE):
                            continue

                    # If we made it this far, add the id to matched_ids
                    self.matched_ids.add(cid)
                    matched_titles.append(mi.title)

        # Update the results box
        matched_titles.sort()
        if len(annotation_map):
            if len(matched_titles):
                first_match = ("<i>%s</i>" % matched_titles[0])
                if len(matched_titles) == 1:
                    results = first_match
                else:
                    results = first_match + (" and %d more." % (len(matched_titles) - 1))
                self.result_label.setText('<p style="color:blue">{0}</p>'.format(results))
            else:
                self.result_label.setText('<p style="color:red">no matches</p>')
        else:
            self.result_label.setText('<p style="color:red">no annotated books in library</p>')

        self.resize_dialog()
예제 #21
0
파일: layout.py 프로젝트: sj660/litebrary
    def __init__(self, parent):
        QWidget.__init__(self, parent)
        self._layout = l = QHBoxLayout()
        self.setLayout(self._layout)
        self._layout.setContentsMargins(0,5,0,0)

        x = QToolButton(self)
        x.setText(_('Vi&rtual Library'))
        x.setIcon(QIcon(I('lt.png')))
        x.setObjectName("virtual_library")
        x.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        l.addWidget(x)
        parent.virtual_library = x

        x = QToolButton(self)
        x.setIcon(QIcon(I('minus.png')))
        x.setObjectName('clear_vl')
        l.addWidget(x)
        x.setVisible(False)
        x.setToolTip(_('Close the Virtual Library'))
        parent.clear_vl = x

        x = QLabel(self)
        x.setObjectName("search_count")
        l.addWidget(x)
        parent.search_count = x
        x.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)

        parent.advanced_search_button = x = QToolButton(self)
        parent.advanced_search_toggle_action = ac = QAction(parent)
        parent.addAction(ac)
        parent.keyboard.register_shortcut('advanced search toggle',
                _('Advanced search'), default_keys=("Shift+Ctrl+F",),
                action=ac)
        ac.triggered.connect(x.click)
        x.setIcon(QIcon(I('search.png')))
        l.addWidget(x)
        x.setToolTip(_("Advanced search"))

        x = parent.search = SearchBox2(self)
        x.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
        x.setObjectName("search")
        x.setToolTip(_("<p>Search the list of books by title, author, publisher, "
                       "tags, comments, etc.<br><br>Words separated by spaces are ANDed"))
        x.setMinimumContentsLength(10)
        l.addWidget(x)

        self.search_button = QToolButton()
        self.search_button.setToolButtonStyle(Qt.ToolButtonTextOnly)
        self.search_button.setText(_('&Go!'))
        l.addWidget(self.search_button)
        self.search_button.setSizePolicy(QSizePolicy.Minimum,
                QSizePolicy.Minimum)
        self.search_button.clicked.connect(parent.do_search_button)
        self.search_button.setToolTip(
            _('Do Quick Search (you can also press the Enter key)'))

        x = parent.clear_button = QToolButton(self)
        x.setIcon(QIcon(I('clear_left.png')))
        x.setObjectName("clear_button")
        l.addWidget(x)
        x.setToolTip(_("Reset Quick Search"))

        x = parent.highlight_only_button = QToolButton(self)
        x.setIcon(QIcon(I('arrow-down.png')))
        l.addWidget(x)

        x = parent.saved_search = SavedSearchBox(self)
        x.setMaximumSize(QSize(150, 16777215))
        x.setMinimumContentsLength(10)
        x.setObjectName("saved_search")
        l.addWidget(x)

        x = parent.copy_search_button = QToolButton(self)
        x.setIcon(QIcon(I("search_copy_saved.png")))
        x.setObjectName("copy_search_button")
        l.addWidget(x)
        x.setToolTip(_("Copy current search text (instead of search name)"))

        x = parent.save_search_button = RightClickButton(self)
        x.setIcon(QIcon(I("search_add_saved.png")))
        x.setObjectName("save_search_button")
        l.addWidget(x)
예제 #22
0
    def __init__(self, text, colors, color_caption):
        TestableWidget.__init__(self)
        self.setWindowFlags(Qt.Popup)

        main_layout = QVBoxLayout()
        main_layout.setContentsMargins(0, 0, 0, 0)
        border = "border: 1px solid #9b9b9b;"
        self.setStyleSheet('QFrame[frameShape="4"]{  color: #9b9b9b;}')

        # -- CAPTION ----------------------------------
        caption = QFrame(self, flags=Qt.WindowFlags())
        caption_layout = QHBoxLayout()
        self._lbl_caption = QLabel(text)
        self._lbl_caption.setStyleSheet("border: 0px solid blue;")
        caption_layout.addWidget(self._lbl_caption, alignment=Qt.Alignment())
        caption.setLayout(caption_layout)
        caption_layout.setContentsMargins(9, 5, 9, 5)
        caption.setStyleSheet(f"background-color: {color_caption}; {border}")

        # -- COLORS GRID ------------------------------
        colorbox = QFrame(self, flags=Qt.WindowFlags())
        color_layout = QGridLayout()
        colorbox.setLayout(color_layout)
        color_layout.setContentsMargins(9, 0, 9, 0)
        nn = 7
        self.clrbtn = []
        for i, color in enumerate(colors):
            self.clrbtn.append(QToolButton())
            css = (f"QToolButton {{{border}background-color:{color};}}"
                   f"QToolButton:hover {{border: 1px solid red; }}")
            self.clrbtn[-1].setStyleSheet(css)
            self.clrbtn[-1].clicked.connect(
                lambda x, c=color: self.select_color_(c))
            # noinspection PyArgumentList
            color_layout.addWidget(self.clrbtn[-1], i // nn, i % nn)

        # -- SPLITTER ---------------------------------
        h_frame = QFrame(None, flags=Qt.WindowFlags())
        h_frame.setFrameShape(QFrame.HLine)
        h_frame.setContentsMargins(0, 0, 0, 0)

        # -- BOTTOM (other color) ---------------------
        btns = QFrame(self, flags=Qt.WindowFlags())
        btn_layout = QHBoxLayout()
        other = QToolButton()
        other.clicked.connect(self.other_color_)
        other.setAutoRaise(True)
        other.setIcon(QIcon(img("editor/colorwheel")))
        btn_layout.addWidget(other, alignment=Qt.Alignment())
        self._lbl_other = QLabel(self.tr("other colors"))
        btn_layout.addWidget(self._lbl_other, alignment=Qt.Alignment())
        btns.setLayout(btn_layout)
        btn_layout.setContentsMargins(9, 0, 9, 9)
        self.clrbtn.append(other)

        # ---------------------------------------------
        main_layout.addWidget(caption, alignment=Qt.Alignment())
        main_layout.addWidget(colorbox, alignment=Qt.Alignment())
        main_layout.addWidget(h_frame, alignment=Qt.Alignment())
        main_layout.addWidget(btns, alignment=Qt.Alignment())

        self.setLayout(main_layout)
예제 #23
0
    def __init__(self, parent):
        QFrame.__init__(self, parent)
        self.setFrameStyle(QFrame.NoFrame)
        self.setObjectName('search_bar')
        self._layout = l = QHBoxLayout(self)
        l.setContentsMargins(0, 4, 0, 4)

        x = parent.virtual_library = QToolButton(self)
        x.setCursor(Qt.PointingHandCursor)
        x.setPopupMode(x.InstantPopup)
        x.setText(_('Virtual library'))
        x.setAutoRaise(True)
        x.setIcon(QIcon(I('lt.png')))
        x.setObjectName("virtual_library")
        x.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        l.addWidget(x)

        x = QToolButton(self)
        x.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        x.setAutoRaise(True)
        x.setIcon(QIcon(I('minus.png')))
        x.setObjectName('clear_vl')
        l.addWidget(x)
        x.setVisible(False)
        x.setToolTip(_('Close the Virtual library'))
        parent.clear_vl = x
        self.vl_sep = QFrame(self)
        self.vl_sep.setFrameStyle(QFrame.VLine | QFrame.Sunken)
        l.addWidget(self.vl_sep)

        parent.sort_sep = QFrame(self)
        parent.sort_sep.setFrameStyle(QFrame.VLine | QFrame.Sunken)
        parent.sort_sep.setVisible(False)
        parent.sort_button = self.sort_button = sb = QToolButton(self)
        sb.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        sb.setToolTip(_('Change how the displayed books are sorted'))
        sb.setCursor(Qt.PointingHandCursor)
        sb.setPopupMode(QToolButton.InstantPopup)
        sb.setAutoRaise(True)
        sb.setText(_('Sort'))
        sb.setIcon(QIcon(I('sort.png')))
        sb.setMenu(QMenu())
        sb.menu().aboutToShow.connect(self.populate_sort_menu)
        sb.setVisible(False)
        l.addWidget(sb)
        l.addWidget(parent.sort_sep)

        x = parent.search = SearchBox2(self)
        x.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
        x.setObjectName("search")
        x.setToolTip(_("<p>Search the list of books by title, author, publisher, "
                       "tags, comments, etc.<br><br>Words separated by spaces are ANDed"))
        x.setMinimumContentsLength(10)
        l.addWidget(x)

        parent.advanced_search_toggle_action = ac = parent.search.add_action('gear.png', QLineEdit.LeadingPosition)
        parent.addAction(ac)
        ac.setToolTip(_('Advanced search'))
        parent.keyboard.register_shortcut('advanced search toggle',
                _('Advanced search'), default_keys=("Shift+Ctrl+F",),
                action=ac)

        self.search_button = QToolButton()
        self.search_button.setToolButtonStyle(Qt.ToolButtonTextOnly)
        self.search_button.setIcon(QIcon(I('search.png')))
        self.search_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.search_button.setText(_('Search'))
        self.search_button.setAutoRaise(True)
        self.search_button.setCursor(Qt.PointingHandCursor)
        l.addWidget(self.search_button)
        self.search_button.setSizePolicy(QSizePolicy.Minimum,
                QSizePolicy.Minimum)
        self.search_button.clicked.connect(parent.do_search_button)
        self.search_button.setToolTip(
            _('Do Quick Search (you can also press the Enter key)'))

        x = parent.highlight_only_button = QToolButton(self)
        x.setAutoRaise(True)
        x.setText(_('Highlight'))
        x.setCursor(Qt.PointingHandCursor)
        x.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        x.setIcon(QIcon(I('arrow-down.png')))
        l.addWidget(x)

        x = parent.saved_search = SavedSearchBox(self)
        x.setMaximumSize(QSize(150, 16777215))
        x.setMinimumContentsLength(10)
        x.setObjectName("saved_search")
        l.addWidget(x)
        x.setVisible(tweaks['show_saved_search_box'])

        x = parent.copy_search_button = QToolButton(self)
        x.setAutoRaise(True)
        x.setCursor(Qt.PointingHandCursor)
        x.setIcon(QIcon(I("search_copy_saved.png")))
        x.setObjectName("copy_search_button")
        l.addWidget(x)
        x.setToolTip(_("Copy current search text (instead of search name)"))
        x.setVisible(tweaks['show_saved_search_box'])

        x = parent.save_search_button = RightClickButton(self)
        x.setAutoRaise(True)
        x.setCursor(Qt.PointingHandCursor)
        x.setIcon(QIcon(I("search_add_saved.png")))
        x.setObjectName("save_search_button")
        l.addWidget(x)
        x.setVisible(tweaks['show_saved_search_box'])

        x = parent.add_saved_search_button = RightClickButton(self)
        x.setToolTip(_(
            'Use an existing Saved search or create a new one'
        ))
        x.setText(_('Saved search'))
        x.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        x.setCursor(Qt.PointingHandCursor)
        x.setPopupMode(x.InstantPopup)
        x.setAutoRaise(True)
        x.setIcon(QIcon(I("bookmarks.png")))
        l.addWidget(x)
        x.setVisible(not tweaks['show_saved_search_box'])
class AnnotationsAppearance(SizePersistedDialog):
    '''
    Dialog for managing CSS rules, including Preview window
    '''
    if isosx:
        FONT = QFont('Monaco', 12)
    elif iswindows:
        FONT = QFont('Lucida Console', 9)
    elif islinux:
        FONT = QFont('Monospace', 9)
        FONT.setStyleHint(QFont.TypeWriter)

    def __init__(self, parent, icon, prefs):

        self.opts = parent.opts
        self.parent = parent
        self.prefs = prefs
        self.icon = icon
        super(AnnotationsAppearance, self).__init__(parent, 'appearance_dialog')
        self.setWindowTitle('Annotations appearance')
        self.setWindowIcon(icon)
        self.l = QVBoxLayout(self)
        self.setLayout(self.l)

        # Add a label for description
        #self.description_label = QLabel("Descriptive text here")
        #self.l.addWidget(self.description_label)

        # Add a group box, vertical layout for preview window
        self.preview_gb = QGroupBox(self)
        self.preview_gb.setTitle("Preview")
        self.preview_vl = QVBoxLayout(self.preview_gb)
        self.l.addWidget(self.preview_gb)

        self.wv = QWebView()
        self.wv.setHtml('<p></p>')
        self.wv.setMinimumHeight(100)
        self.wv.setMaximumHeight(16777215)
        self.wv.setGeometry(0, 0, 200, 100)
        self.wv.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.preview_vl.addWidget(self.wv)

        # Create a group box, horizontal layout for the table
        self.css_table_gb = QGroupBox(self)
        self.css_table_gb.setTitle("Annotation elements")
        self.elements_hl = QHBoxLayout(self.css_table_gb)
        self.l.addWidget(self.css_table_gb)

        # Add the group box to the main layout
        self.elements_table = AnnotationElementsTable(self, 'annotation_elements_tw')
        self.elements_hl.addWidget(self.elements_table)
        self.elements_table.initialize()

        # Options
        self.options_gb = QGroupBox(self)
        self.options_gb.setTitle("Options")
        self.options_gl = QGridLayout(self.options_gb)
        self.l.addWidget(self.options_gb)
        current_row = 0

        # <hr/> separator
        # addWidget(widget, row, col, rowspan, colspan)
        self.hr_checkbox = QCheckBox('Add horizontal rule between annotations')
        self.hr_checkbox.stateChanged.connect(self.hr_checkbox_changed)
        self.hr_checkbox.setCheckState(
            prefs.get('appearance_hr_checkbox', False))
        self.options_gl.addWidget(self.hr_checkbox, current_row, 0, 1, 4)
        current_row += 1

        # Timestamp
        self.timestamp_fmt_label = QLabel("Timestamp format:")
        self.options_gl.addWidget(self.timestamp_fmt_label, current_row, 0)

        self.timestamp_fmt_le = QLineEdit(
            prefs.get('appearance_timestamp_format', default_timestamp),
            parent=self)
        self.timestamp_fmt_le.textEdited.connect(self.timestamp_fmt_changed)
        self.timestamp_fmt_le.setFont(self.FONT)
        self.timestamp_fmt_le.setObjectName('timestamp_fmt_le')
        self.timestamp_fmt_le.setToolTip('Format string for timestamp')
        self.timestamp_fmt_le.setMaximumWidth(16777215)
        self.timestamp_fmt_le.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
        self.options_gl.addWidget(self.timestamp_fmt_le, current_row, 1)

        self.timestamp_fmt_reset_tb = QToolButton(self)
        self.timestamp_fmt_reset_tb.setToolTip("Reset to default")
        self.timestamp_fmt_reset_tb.setIcon(QIcon(I('trash.png')))
        self.timestamp_fmt_reset_tb.clicked.connect(self.reset_timestamp_to_default)
        self.options_gl.addWidget(self.timestamp_fmt_reset_tb, current_row, 2)

        self.timestamp_fmt_help_tb = QToolButton(self)
        self.timestamp_fmt_help_tb.setToolTip("Format string reference")
        self.timestamp_fmt_help_tb.setIcon(QIcon(I('help.png')))
        self.timestamp_fmt_help_tb.clicked.connect(self.show_help)
        self.options_gl.addWidget(self.timestamp_fmt_help_tb, current_row, 3)

        # Button box
        bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
        bb.accepted.connect(self.accept)
        bb.rejected.connect(self.reject)
        self.l.addWidget(bb)

        # Spacer
        #self.spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
        #self.l.addItem(self.spacerItem)

        # Sizing
        #sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
        #sizePolicy.setHorizontalStretch(0)
        #sizePolicy.setVerticalStretch(0)
        #sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth())
        #self.setSizePolicy(sizePolicy)
        self.resize_dialog()

    def hr_checkbox_changed(self, state):
        self.prefs.set('appearance_hr_checkbox', state)
        self.prefs.commit()
        self.elements_table.preview_css()

    def reset_timestamp_to_default(self):
        from calibre_plugins.marvin_manager.appearance import default_timestamp
        self.timestamp_fmt_le.setText(default_timestamp)
        self.timestamp_fmt_changed()

    def show_help(self):
        '''
        Display strftime help file
        '''
        from calibre.gui2 import open_url
        path = os.path.join(self.parent.resources_path, 'help/timestamp_formats.html')
        open_url(QUrl.fromLocalFile(path))

    def sizeHint(self):
        return QtCore.QSize(600, 200)

    def timestamp_fmt_changed(self):
        self.prefs.set('appearance_timestamp_format', str(self.timestamp_fmt_le.text()))
        self.prefs.commit()
        self.elements_table.preview_css()
예제 #25
0
class ConfigWidget(QWidget, Logger):
    # Manually managed controls when saving/restoring
    EXCLUDED_CONTROLS = [
        'cfg_annotations_destination_comboBox'
        ]

    #LOCATION_TEMPLATE = "{cls}:{func}({arg1}) {arg2}"

    WIZARD_PROFILES = {
        'Annotations': {
            'label': 'mm_annotations',
            'datatype': 'comments',
            'display': {},
            'is_multiple': False
            }
        }

    def __init__(self, plugin_action):
        self.gui = plugin_action.gui
        self.opts = plugin_action.opts

        QWidget.__init__(self)
        self.l = QVBoxLayout()
        self.setLayout(self.l)

        # ~~~~~~~~ Create the runtime options group box ~~~~~~~~
        self.cfg_runtime_options_gb = QGroupBox(self)
        self.cfg_runtime_options_gb.setTitle(_('Runtime options'))
        self.l.addWidget(self.cfg_runtime_options_gb)
        self.cfg_runtime_options_qvl = QVBoxLayout(self.cfg_runtime_options_gb)

        # ~~~~~~~~ Disable caching checkbox ~~~~~~~~
        self.cfg_disable_caching_checkbox = QCheckBox(_('Disable caching'))
        self.cfg_disable_caching_checkbox.setObjectName('cfg_disable_caching_checkbox')
        self.cfg_disable_caching_checkbox.setToolTip(_('Force reload of reader database'))
        self.cfg_disable_caching_checkbox.setChecked(False)
        self.cfg_runtime_options_qvl.addWidget(self.cfg_disable_caching_checkbox)

        # ~~~~~~~~ plugin logging checkbox ~~~~~~~~
        self.cfg_plugin_debug_log_checkbox = QCheckBox(_('Enable debug logging for Annotations plugin'))
        self.cfg_plugin_debug_log_checkbox.setObjectName('cfg_plugin_debug_log_checkbox')
        self.cfg_plugin_debug_log_checkbox.setToolTip(_('Print plugin diagnostic messages to console'))
        self.cfg_plugin_debug_log_checkbox.setChecked(False)
        self.cfg_runtime_options_qvl.addWidget(self.cfg_plugin_debug_log_checkbox)

        # ~~~~~~~~ libiMobileDevice logging checkbox ~~~~~~~~
        self.cfg_libimobiledevice_debug_log_checkbox = QCheckBox(_('Enable debug logging for libiMobileDevice'))
        self.cfg_libimobiledevice_debug_log_checkbox.setObjectName('cfg_libimobiledevice_debug_log_checkbox')
        self.cfg_libimobiledevice_debug_log_checkbox.setToolTip(_('Print libiMobileDevice debug messages to console'))
        self.cfg_libimobiledevice_debug_log_checkbox.setChecked(False)
        self.cfg_libimobiledevice_debug_log_checkbox.setEnabled(LIBIMOBILEDEVICE_AVAILABLE)
        self.cfg_runtime_options_qvl.addWidget(self.cfg_libimobiledevice_debug_log_checkbox)

        # ~~~~~~~~ Create the Annotations options group box ~~~~~~~~
        self.cfg_annotation_options_gb = QGroupBox(self)
        self.cfg_annotation_options_gb.setTitle(_('Annotation options'))
        self.l.addWidget(self.cfg_annotation_options_gb)

        self.cfg_annotation_options_qgl = QGridLayout(self.cfg_annotation_options_gb)
        current_row = 0

        # Add the label/combobox for annotations destination
        self.cfg_annotations_destination_label = QLabel(_('<b>Add fetched annotations to<b>'))
        self.cfg_annotations_destination_label.setAlignment(Qt.AlignLeft)
        self.cfg_annotation_options_qgl.addWidget(self.cfg_annotations_destination_label, current_row, 0)
        current_row += 1

        self.cfg_annotations_destination_comboBox = QComboBox(self.cfg_annotation_options_gb)
        self.cfg_annotations_destination_comboBox.setObjectName('cfg_annotations_destination_comboBox')
        self.cfg_annotations_destination_comboBox.setToolTip(_('Custom field to store annotations'))
        self.cfg_annotation_options_qgl.addWidget(self.cfg_annotations_destination_comboBox, current_row, 0)

        # Populate annotations_field combobox
        db = self.gui.current_db
        all_custom_fields = db.custom_field_keys()
        self.custom_fields = {}
        for custom_field in all_custom_fields:
            field_md = db.metadata_for_field(custom_field)
            if field_md['datatype'] in ['comments']:
                self.custom_fields[field_md['name']] = {'field': custom_field,
                                                   'datatype': field_md['datatype']}

        all_fields = self.custom_fields.keys() + ['Comments']
        for cf in sorted(all_fields):
            self.cfg_annotations_destination_comboBox.addItem(cf)

        # Add CC Wizard
        self.cfg_annotations_wizard = QToolButton()
        self.cfg_annotations_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_annotations_wizard.setToolTip(_("Create a custom column to store annotations"))
        self.cfg_annotations_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Annotations'))
        self.cfg_annotation_options_qgl.addWidget(self.cfg_annotations_wizard, current_row, 2)

        current_row += 1

        # ~~~~~~~~ Add a horizontal line ~~~~~~~~
        self.cfg_appearance_hl = QFrame(self)
        self.cfg_appearance_hl.setGeometry(QRect(0, 0, 1, 3))
        self.cfg_appearance_hl.setFrameShape(QFrame.HLine)
        self.cfg_appearance_hl.setFrameShadow(QFrame.Raised)
        self.cfg_annotation_options_qgl.addWidget(self.cfg_appearance_hl, current_row, 0)
        current_row += 1

        # ~~~~~~~~ Add the Modify… button ~~~~~~~~
        self.cfg_annotations_appearance_pushbutton = QPushButton(_("Modify appearance…"))
        self.cfg_annotations_appearance_pushbutton.clicked.connect(self.configure_appearance)
        self.cfg_annotation_options_qgl.addWidget(self.cfg_annotations_appearance_pushbutton, current_row, 0)
        current_row += 1

        self.spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
        self.cfg_annotation_options_qgl.addItem(self.spacerItem, current_row, 0, 1, 1)

        # ~~~~~~~~ Compilations group box ~~~~~~~~
        self.cfg_compilation_options_gb = QGroupBox(self)
        self.cfg_compilation_options_gb.setTitle(_('Compilations'))
        self.l.addWidget(self.cfg_compilation_options_gb)
        self.cfg_compilation_options_qgl = QGridLayout(self.cfg_compilation_options_gb)
        current_row = 0

        #   News clippings
        self.cfg_news_clippings_checkbox = QCheckBox(_('Collect News clippings'))
        self.cfg_news_clippings_checkbox.setObjectName('cfg_news_clippings_checkbox')
        self.cfg_compilation_options_qgl.addWidget(self.cfg_news_clippings_checkbox,
            current_row, 0)

        self.cfg_news_clippings_lineEdit = QLineEdit()
        self.cfg_news_clippings_lineEdit.setObjectName('cfg_news_clippings_lineEdit')
        self.cfg_news_clippings_lineEdit.setToolTip(_('Title for collected news clippings'))
        self.cfg_compilation_options_qgl.addWidget(self.cfg_news_clippings_lineEdit,
            current_row, 1)

        # ~~~~~~~~ End of construction zone ~~~~~~~~

        self.resize(self.sizeHint())

        # Restore state of controls, populate annotations combobox
        self.controls = inventory_controls(self, dump_controls=False)
        restore_state(self)
        self.populate_annotations()

        # Hook changes to annotations_destination_combobox
#        self.connect(self.cfg_annotations_destination_comboBox,
#                     pyqtSignal('currentIndexChanged(const QString &)'),
#                     self.annotations_destination_changed)
        self.cfg_annotations_destination_comboBox.currentIndexChanged.connect(self.annotations_destination_changed)

        # Hook changes to diagnostic checkboxes
        self.cfg_disable_caching_checkbox.stateChanged.connect(self.restart_required)
        self.cfg_libimobiledevice_debug_log_checkbox.stateChanged.connect(self.restart_required)
        self.cfg_plugin_debug_log_checkbox.stateChanged.connect(self.restart_required)

        # Hook changes to News clippings, initialize
        self.cfg_news_clippings_checkbox.stateChanged.connect(self.news_clippings_toggled)
        self.news_clippings_toggled(self.cfg_news_clippings_checkbox.checkState())
        self.cfg_news_clippings_lineEdit.editingFinished.connect(self.news_clippings_destination_changed)

        # Launch the annotated_books_scanner
        field = get_cc_mapping('annotations', 'field', 'Comments')
        self.annotated_books_scanner = InventoryAnnotatedBooks(self.gui, field)
        self.annotated_books_scanner.signal.connect(self.inventory_complete)
#        self.connect(self.annotated_books_scanner, self.annotated_books_scanner.signal,
#            self.inventory_complete)
        QTimer.singleShot(1, self.start_inventory)

    def annotations_destination_changed(self, qs_new_destination_name):
        '''
        If the destination field changes, move all existing annotations from old to new
        '''
        self._log_location(repr(qs_new_destination_name))
        self._log("self.custom_fields: %s" % self.custom_fields)

        old_destination_field = get_cc_mapping('annotations', 'field', None)
        if old_destination_field and not (old_destination_field in self.gui.current_db.custom_field_keys() or old_destination_field == 'Comments'):
            return
        old_destination_name = get_cc_mapping('annotations', 'combobox', None)

        self._log("old_destination_field: %s" % old_destination_field)
        self._log("old_destination_name: %s" % old_destination_name)

        # Catch initial change from None to Comments - first run only
        if old_destination_field is None:
            return

#        new_destination_name = unicode(qs_new_destination_name)
        new_destination_name = unicode(self.cfg_annotations_destination_comboBox.currentText())
        self._log("new_destination_name: %s" % new_destination_name)

        if old_destination_name == new_destination_name:
            self._log_location("old_destination_name = new_destination_name, no changes")
            return

        new_destination_field = None
        if new_destination_name == 'Comments':
            new_destination_field = 'Comments'
        else:
            new_destination_field = self.custom_fields[new_destination_name]['field']

        if existing_annotations(self.opts.parent, old_destination_field):
            command = self.launch_new_destination_dialog(old_destination_name, new_destination_name)

            if command == 'move':
                set_cc_mapping('annotations', field=new_destination_field, combobox=new_destination_name)

                if self.annotated_books_scanner.isRunning():
                    self.annotated_books_scanner.wait()
                move_annotations(self, self.annotated_books_scanner.annotation_map,
                    old_destination_field, new_destination_field)

            elif command == 'change':
                # Keep the updated destination field, but don't move annotations
                pass

            elif command == 'cancel':
                # Restore previous destination
                self.cfg_annotations_destination_comboBox.blockSignals(True)
                old_index = self.cfg_annotations_destination_comboBox.findText(old_destination_name)
                self.cfg_annotations_destination_comboBox.setCurrentIndex(old_index)
                self.cfg_annotations_destination_comboBox.blockSignals(False)

            """
            # Warn user that change will move existing annotations to new field
            title = 'Move annotations?'
            msg = ("<p>Existing annotations will be moved from <b>%s</b> to <b>%s</b>.</p>" %
                    (old_destination_name, new_destination_name) +
                   "<p>New annotations will be added to <b>%s</b>.</p>" %
                    new_destination_name +
                   "<p>Proceed?</p>")
            d = MessageBox(MessageBox.QUESTION,
                           title, msg,
                           show_copy_button=False)
            self._log_location("QUESTION: %s" % msg)
            if d.exec_():
                set_cc_mapping('annotations', field=new_destination_field, combobox=new_destination_name)

                if self.annotated_books_scanner.isRunning():
                    self.annotated_books_scanner.wait()
                move_annotations(self, self.annotated_books_scanner.annotation_map,
                    old_destination_field, new_destination_field)

            else:
                self.cfg_annotations_destination_comboBox.blockSignals(True)
                old_index = self.cfg_annotations_destination_comboBox.findText(old_destination_name)
                self.cfg_annotations_destination_comboBox.setCurrentIndex(old_index)
                self.cfg_annotations_destination_comboBox.blockSignals(False)
            """

        else:
            # No existing annotations, just update prefs
            set_cc_mapping('annotations', field=new_destination_field, combobox=new_destination_name)

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

    def inventory_complete(self, msg):
        self._log_location(msg)

    def launch_cc_wizard(self, column_type):
        '''
        '''
        def _update_combo_box(comboBox, destination, previous):
            '''
            '''
            self._log_location()

            cb = getattr(self, comboBox)
            cb.blockSignals(True)
            all_items = [str(cb.itemText(i))
                         for i in range(cb.count())]
            if previous and previous in all_items:
                all_items.remove(previous)
            all_items.append(destination)

            cb.clear()
            cb.addItems(sorted(all_items, key=lambda s: s.lower()))

            # Select the new destination in the comboBox
            idx = cb.findText(destination)
            if idx > -1:
                cb.setCurrentIndex(idx)

            # Process the changed destination
            self.annotations_destination_changed(destination)

            cb.blockSignals(False)


        klass = os.path.join(dialog_resources_path, 'cc_wizard.py')
        if os.path.exists(klass):
            #self._log("importing CC Wizard dialog from '%s'" % klass)
            sys.path.insert(0, dialog_resources_path)
            this_dc = importlib.import_module('cc_wizard')
            sys.path.remove(dialog_resources_path)
            dlg = this_dc.CustomColumnWizard(self,
                                             column_type,
                                             self.WIZARD_PROFILES[column_type],
                                             verbose=True)
            dlg.exec_()

            if dlg.modified_column:
                self._log("modified_column: %s" % dlg.modified_column)

                destination = dlg.modified_column['destination']
                label = dlg.modified_column['label']
                previous = dlg.modified_column['previous']
                source = dlg.modified_column['source']

                self._log("destination: %s" % destination)
                self._log("label: %s" % label)
                self._log("previous: %s" % previous)
                self._log("source: %s" % source)

                if source == "Annotations":
                    # Add/update the new destination so save_settings() can find it
                    if destination in self.custom_fields:
                        self.custom_fields[destination]['field'] = label
                    else:
                        self.custom_fields[destination] = {'field': label}

                    _update_combo_box('cfg_annotations_destination_comboBox', destination, previous)

                    # Save field manually in case user cancels
                    #self.prefs.set('cfg_annotations_destination_comboBox', destination)
                    #self.prefs.set('cfg_annotations_destination_field', label)
                    set_cc_mapping('annotations', field=label, combobox=destination)

                    # Inform user to restart
                    self.restart_required('custom_column')

        else:
            self._log("ERROR: Can't import from '%s'" % klass)

    def launch_new_destination_dialog(self, old, new):
        '''
        Return 'move', 'change' or 'cancel'
        '''
        self._log_location()

        klass = os.path.join(dialog_resources_path, 'new_destination.py')
        if os.path.exists(klass):
            self._log("importing new destination dialog from '%s'" % klass)
            sys.path.insert(0, dialog_resources_path)
            this_dc = importlib.import_module('new_destination')
            sys.path.remove(dialog_resources_path)
            dlg = this_dc.NewDestinationDialog(self, old, new)
            dlg.exec_()
            return dlg.command

    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_()

    def news_clippings_toggled(self, state):
        if state == Qt.Checked:
            self.cfg_news_clippings_lineEdit.setEnabled(True)
        else:
            self.cfg_news_clippings_lineEdit.setEnabled(False)

    def populate_annotations(self):
        '''
        Restore annotations combobox
        '''
        self._log_location()
        target = 'Comments'
        existing = get_cc_mapping('annotations', 'combobox')
        if existing:
            target = existing
        ci = self.cfg_annotations_destination_comboBox.findText(target)
        self.cfg_annotations_destination_comboBox.setCurrentIndex(ci)

    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_()

    def save_settings(self):
        save_state(self)

        # Save the annotation destination field
        ann_dest = unicode(self.cfg_annotations_destination_comboBox.currentText())
        self._log_location("INFO: ann_dest=%s" % (ann_dest))
        self._log_location("INFO: self.custom_fields=%s" % (self.custom_fields))
        if ann_dest == 'Comments':
            set_cc_mapping('annotations', field='Comments', combobox='Comments')
        elif ann_dest:
            set_cc_mapping('annotations', field=self.custom_fields[ann_dest]['field'], combobox=ann_dest)

    def start_inventory(self):
        self.annotated_books_scanner.start()
예제 #26
0
class AddEmptyBookDialog(QDialog):

    def __init__(self, parent, db, author, series=None):
        QDialog.__init__(self, parent)
        self.db = db

        self.setWindowTitle(_('How many empty books?'))

        self._layout = QGridLayout(self)
        self.setLayout(self._layout)

        self.qty_label = QLabel(_('How many empty books should be added?'))
        self._layout.addWidget(self.qty_label, 0, 0, 1, 2)

        self.qty_spinbox = QSpinBox(self)
        self.qty_spinbox.setRange(1, 10000)
        self.qty_spinbox.setValue(1)
        self._layout.addWidget(self.qty_spinbox, 1, 0, 1, 2)

        self.author_label = QLabel(_('Set the author of the new books to:'))
        self._layout.addWidget(self.author_label, 2, 0, 1, 2)

        self.authors_combo = EditWithComplete(self)
        self.authors_combo.setSizeAdjustPolicy(
                self.authors_combo.AdjustToMinimumContentsLengthWithIcon)
        self.authors_combo.setEditable(True)
        self._layout.addWidget(self.authors_combo, 3, 0, 1, 1)
        self.initialize_authors(db, author)

        self.clear_button = QToolButton(self)
        self.clear_button.setIcon(QIcon(I('trash.png')))
        self.clear_button.setToolTip(_('Reset author to Unknown'))
        self.clear_button.clicked.connect(self.reset_author)
        self._layout.addWidget(self.clear_button, 3, 1, 1, 1)

        self.series_label = QLabel(_('Set the series of the new books to:'))
        self._layout.addWidget(self.series_label, 4, 0, 1, 2)

        self.series_combo = EditWithComplete(self)
        self.authors_combo.setSizeAdjustPolicy(
                self.authors_combo.AdjustToMinimumContentsLengthWithIcon)
        self.series_combo.setEditable(True)
        self._layout.addWidget(self.series_combo, 5, 0, 1, 1)
        self.initialize_series(db, series)

        self.sclear_button = QToolButton(self)
        self.sclear_button.setIcon(QIcon(I('trash.png')))
        self.sclear_button.setToolTip(_('Reset series'))
        self.sclear_button.clicked.connect(self.reset_series)
        self._layout.addWidget(self.sclear_button, 5, 1, 1, 1)

        self.create_epub = c = QCheckBox(_('Create an empty EPUB file as well'))
        c.setChecked(gprefs.get('create_empty_epub_file', False))
        c.setToolTip(_('Also create an empty EPUB file that you can subsequently edit'))
        self._layout.addWidget(c, 6, 0, 1, -1)

        button_box = self.bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        button_box.accepted.connect(self.accept)
        button_box.rejected.connect(self.reject)
        self._layout.addWidget(button_box, 7, 0, 1, -1)
        self.resize(self.sizeHint())

    def accept(self):
        oval = gprefs.get('create_empty_epub_file', False)
        if self.create_epub.isChecked() != oval:
            gprefs['create_empty_epub_file'] = self.create_epub.isChecked()
        return QDialog.accept(self)

    def reset_author(self, *args):
        self.authors_combo.setEditText(_('Unknown'))

    def reset_series(self):
        self.series_combo.setEditText('')

    def initialize_authors(self, db, author):
        au = author
        if not au:
            au = _('Unknown')
        self.authors_combo.show_initial_value(au.replace('|', ','))

        self.authors_combo.set_separator('&')
        self.authors_combo.set_space_before_sep(True)
        self.authors_combo.set_add_separator(tweaks['authors_completer_append_separator'])
        self.authors_combo.update_items_cache(db.all_author_names())

    def initialize_series(self, db, series):
        self.series_combo.show_initial_value(series or '')
        self.series_combo.update_items_cache(db.all_series_names())
        self.series_combo.set_separator(None)

    @property
    def qty_to_add(self):
        return self.qty_spinbox.value()

    @property
    def selected_authors(self):
        return string_to_authors(unicode(self.authors_combo.text()))

    @property
    def selected_series(self):
        return unicode(self.series_combo.text())
예제 #27
0
파일: main.py 프로젝트: Josh0729/Viper
class MainWindow(QMainWindow, mainwindow_auto.Ui_MainWindow):
    # access variables inside of the UI's file

    CachedWeather = WeatherApi('Initial')
    #AlarmAnimation = QtCore.QPropertyAnimation()
    AlarmInfo = Alarm()
    Pandora = Pandora()
    AlarmCurrentState = 'Closed'
    PandoraCurrentState = 'Closed'
    CalendarCurrentState = 'Closed'
    ANIMATIONDURATION = 650

    def __init__(self):
        super(self.__class__, self).__init__()
        self.setupUi(self)  # gets defined in the UI file
        self.initUI()

    def initUI(self):
        self.alarmWidget.setGeometry(QtCore.QRect(800, 310, 300, 100))
        self.pandoraWidget.setGeometry(QtCore.QRect(800, 240, 300, 100))
        self.calendarWidget.setGeometry(QtCore.QRect(800, 170, 300, 100))

        self.setupTimers()

        self.getTime()
        self.getWeather()
        self.setupScrollers()
        #self.alarmSlider.setStyleSheet(self.styleAlarmSlider())
        self.pauseToolButton.hide()
        self.alarmOnToolButton.hide()

        self.setupHooks()

        self.hourListWidget.setCurrentRow(2)
        self.minuteListWidget.setCurrentRow(2)
        self.amPmListWidget.setCurrentRow(2)

    def setupTimers(self):
        self.timer = QtCore.QTimer(self)
        self.timer.timeout.connect(self.getTime)
        self.timer.start(10)
        self.alarmTimer = QtCore.QTimer(self)
        self.alarmTimer.timeout.connect(self.checkAlarm)
        self.alarmTimer.start(10)
        self.weatherTimer = QtCore.QTimer(self)
        self.weatherTimer.timeout.connect(self.getWeather)
        self.weatherTimer.start(600000)

    def setupHooks(self):
        self.alarmToolButton.clicked.connect(lambda: self.pressedAlarmButton())
        self.hourListWidget.itemSelectionChanged.connect(
            lambda: self.hourListWidgetSelectionChange())
        self.minuteListWidget.itemSelectionChanged.connect(
            lambda: self.minuteListWidgetSelectionChange())
        self.amPmListWidget.itemSelectionChanged.connect(
            lambda: self.amPmListWidgetSelectionChange())
        self.alarmOnToolButton.clicked.connect(
            lambda: self.pressedAlarmOnButton())
        self.alarmOffToolButton.clicked.connect(
            lambda: self.pressedAlarmOffButton())
        self.pandoraToolButton.clicked.connect(
            lambda: self.pressedPandoraButton())
        self.playToolButton.clicked.connect(lambda: self.pressedPlayButton())
        self.pauseToolButton.clicked.connect(lambda: self.pressedPauseButton())
        self.stopToolButton.clicked.connect(lambda: self.pressedStopButton())
        self.skipToolButton.clicked.connect(lambda: self.pressedSkipButton())
        self.calendarToolButton.clicked.connect(
            lambda: self.pressedCalendarButton())

    def setupStatusbar(self):
        self.alarmStatusWidget = QToolButton()
        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap("images/alarm-clock.png"),
                       QtGui.QIcon.Normal, QtGui.QIcon.On)
        self.alarmStatusWidget.setIcon(icon)
        self.alarmStatusWidget.setFixedSize(QSize(24, 24))
        self.alarmStatusWidget.setIconSize(QSize(24, 24))

        self.statusBar.setStyleSheet("""QStatusBar::item {
                                        border: none;
                                        }""")

    def setupScrollers(self):
        sp = QScrollerProperties()
        sp.setScrollMetric(QScrollerProperties.DragVelocitySmoothingFactor,
                           0.6)
        sp.setScrollMetric(QScrollerProperties.MinimumVelocity, 0.0)
        sp.setScrollMetric(QScrollerProperties.MaximumVelocity, 0.5)
        sp.setScrollMetric(QScrollerProperties.AcceleratingFlickMaximumTime,
                           0.4)
        sp.setScrollMetric(QScrollerProperties.AcceleratingFlickSpeedupFactor,
                           1.2)
        sp.setScrollMetric(QScrollerProperties.SnapPositionRatio, 0.2)
        sp.setScrollMetric(QScrollerProperties.MaximumClickThroughVelocity, 0)
        sp.setScrollMetric(QScrollerProperties.DragStartDistance, 0.001)
        sp.setScrollMetric(QScrollerProperties.MousePressEventDelay, 0.5)

        hourListScroller = QScroller.scroller(self.hourListWidget)
        hourListScroller.grabGesture(self.hourListWidget,
                                     QScroller.LeftMouseButtonGesture)
        hourListScroller.setScrollerProperties(sp)

        minuteListScroller = QScroller.scroller(self.minuteListWidget)
        minuteListScroller.grabGesture(self.minuteListWidget,
                                       QScroller.LeftMouseButtonGesture)
        minuteListScroller.setScrollerProperties(sp)

        amPmListScroller = QScroller.scroller(self.amPmListWidget)
        amPmListScroller.grabGesture(self.amPmListWidget,
                                     QScroller.LeftMouseButtonGesture)
        amPmListScroller.setScrollerProperties(sp)

    def getTime(self):
        self.timeDisplay.display(strftime("%I" + ":" + "%M").lstrip('0'))
        self.dateLabel.setText(
            strftime("%A" + " " + "%B" + " " + "%d" + ", " + "%Y"))

    def getWeather(self):
        successfulResponse = False
        weatherResponse = WeatherApi('Fetch')
        if weatherResponse.response.status_code == 200:
            weatherConditions = WeatherConditions(
                weatherResponse.response.text)
            successfulResponse = True
        else:
            if self.CachedWeather.response is not None:
                if self.CachedWeather.response.status_code == 200:
                    weatherConditions = WeatherConditions(
                        self.CachedWeather.response.text)
                    successfulResponse = True
        if successfulResponse == True:
            self.currentWeatherIcon.setPixmap(weatherConditions.pixMap)
            self.temperatureLabel.setText(weatherConditions.temperature +
                                          u'\u00b0' + 'F')
            self.currentWeatherDescription.setText(
                weatherConditions.description)
            self.CachedWeather = weatherResponse

    def pressedAlarmButton(self):
        try:
            alarmButtonCurrentGeometry = QtCore.QRect(
                self.alarmToolButton.geometry())
            alarmButtonAnimation = QPropertyAnimation(
                self.alarmToolButton, 'geometry'.encode(encoding='utf_8'))
            alarmButtonAnimation.setDuration(self.ANIMATIONDURATION)
            alarmButtonAnimation.setStartValue(alarmButtonCurrentGeometry)

            alarmWidgetCurrentGeometry = QtCore.QRect(
                self.alarmWidget.geometry())
            alarmWidgetAnimation = QPropertyAnimation(
                self.alarmWidget, 'geometry'.encode(encoding='utf_8'))
            alarmWidgetAnimation.setDuration(self.ANIMATIONDURATION)
            alarmWidgetAnimation.setStartValue(alarmWidgetCurrentGeometry)

            if self.AlarmCurrentState == 'Closed':
                alarmButtonEndTopLeftCorner = QtCore.QPoint(
                    self.alarmToolButton.pos() - QtCore.QPoint(275, 0))
                alarmWidgetEndTopLeftCorner = QtCore.QPoint(
                    self.alarmWidget.pos() - QtCore.QPoint(275, 0))
                self.AlarmCurrentState = 'Open'
            else:
                alarmButtonEndTopLeftCorner = QtCore.QPoint(
                    self.alarmToolButton.pos() + QtCore.QPoint(275, 0))
                alarmWidgetEndTopLeftCorner = QtCore.QPoint(
                    self.alarmWidget.pos() + QtCore.QPoint(275, 0))
                self.AlarmCurrentState = 'Closed'

            alarmButtonFinalGeometry = QtCore.QRect(
                alarmButtonEndTopLeftCorner,
                QtCore.QSize(self.alarmToolButton.width(),
                             self.alarmToolButton.height()))
            alarmButtonAnimation.setEndValue(alarmButtonFinalGeometry)

            alarmWidgetFinalGeometry = QtCore.QRect(
                alarmWidgetEndTopLeftCorner,
                QtCore.QSize(self.alarmWidget.width(),
                             self.alarmWidget.height()))
            alarmWidgetAnimation.setEndValue(alarmWidgetFinalGeometry)

            alarmButtonAnimation.start()
            alarmWidgetAnimation.start()
            #self.setAlarmWidgetStyleSheet()
            self.AlarmIconAnimation = alarmButtonAnimation
            self.AlarmWidgetAnimation = alarmWidgetAnimation
        except Exception as e:
            print(e.strerror)

    def setAlarmWidgetStyleSheet(self):
        style1 = "background-color: white"
        style2 = "background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0, stop:0 rgba(0, 0, 0, 255), stop:1 rgba(255, 255, 255, 255))"

        # animation doesn't work for strings but provides an appropriate delay
        animation = QtCore.QPropertyAnimation(
            self.alarmWidget, 'styleSheet'.encode(encoding='utf_8'))
        animation.setDuration(500)

        state1 = QtCore.QState()
        state2 = QtCore.QState()
        state3 = QtCore.QState()
        state1.assignProperty(self.alarmWidget, 'styleSheet', style1)
        state2.assignProperty(self.alarmWidget, 'styleSheet', style2)
        state3.assignProperty(self.alarmWidget, 'styleSheet', style2)
        #              change a state after an animation has played
        #                               v
        state1.addTransition(state1.propertiesAssigned, state2)
        state2.addTransition(state2.propertiesAssigned, state3)

        self.alarmWidget.machine = QtCore.QStateMachine()
        self.alarmWidget.machine.addDefaultAnimation(animation)
        self.alarmWidget.machine.addState(state1)
        self.alarmWidget.machine.addState(state2)
        self.alarmWidget.machine.setInitialState(state1)
        self.alarmWidget.machine.start()

    def hourListWidgetSelectionChange(self):
        value = self.hourListWidget.currentRow()
        if value < 2:
            value = 2
        if value > 13:
            value = 13
        item = self.hourListWidget.item(value)
        self.hourListWidget.scrollToItem(item,
                                         QAbstractItemView.PositionAtCenter)
        self.hourListWidget.setCurrentRow(value)

    def minuteListWidgetSelectionChange(self):
        value = self.minuteListWidget.currentRow()
        if value < 2:
            value = 2
        if value > 61:
            value = 61
        item = self.minuteListWidget.item(value)
        self.minuteListWidget.scrollToItem(item,
                                           QAbstractItemView.PositionAtCenter)
        self.minuteListWidget.setCurrentRow(value)

    def amPmListWidgetSelectionChange(self):
        value = self.amPmListWidget.currentRow()
        if value < 2:
            value = 2
        if value > 3:
            value = 3
        item = self.amPmListWidget.item(value)
        self.amPmListWidget.scrollToItem(item,
                                         QAbstractItemView.PositionAtCenter)
        self.amPmListWidget.setCurrentRow(value)

    def styleAlarmSlider(self):
        return """
            QSlider::groove:horizontal {
                height: 20px; /* the groove expands to the size of the slider by default. by giving it a height, it has a fixed size */
                background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #B1B1B1, stop:1 #c4c4c4);
                margin: 2px 0;
            }
            
            QSlider::sub-page:horizontal {
                background: qlineargradient(x1: 0, y1: 0,    x2: 0, y2: 1,
                    stop: 0 #66e, stop: 1 #bbf);
                background: qlineargradient(x1: 0, y1: 0.2, x2: 1, y2: 1,
                    stop: 0 #bbf, stop: 1 #55f);
                height: 40px;
            }

            QSlider::handle:horizontal {
                background: #bbf;
                border: 0px;
                width: 5px;
                margin-top: 0px;
                margin-bottom: 0px;
                border-radius: 0px;
            }
        """

#    def alarmSliderValueChange(self):
#        if self.alarmSlider.value() == 0:
#            self.AlarmInfo.disableAlarm()
#        else:
#            hourItem = self.hourListWidget.currentItem()
#            minuteItem = self.minuteListWidget.currentItem()
#            amPmItem = self.amPmListWidget.currentItem()
#            timeToSet = hourItem.text() + ':' + minuteItem.text() + ' ' + amPmItem.text()
#            self.AlarmInfo.setAlarm(timeToSet)

    def pressedAlarmOffButton(self):
        self.alarmOffToolButton.hide()
        self.alarmOnToolButton.show()
        hourItem = self.hourListWidget.currentItem()
        minuteItem = self.minuteListWidget.currentItem()
        amPmItem = self.amPmListWidget.currentItem()
        timeToSet = hourItem.text() + ':' + minuteItem.text(
        ) + ' ' + amPmItem.text()
        self.AlarmInfo.setAlarm(timeToSet)

        self.statusBar.addWidget(self.alarmStatusWidget)
        self.alarmStatusWidget.show()

    def pressedAlarmOnButton(self):
        self.alarmOnToolButton.hide()
        self.alarmOffToolButton.show()
        self.AlarmInfo.disableAlarm()
        self.statusBar.removeWidget(self.alarmStatusWidget)

    def pressedPandoraButton(self):
        pandoraButtonCurrentGeometry = QtCore.QRect(
            self.pandoraToolButton.geometry())
        pandoraButtonAnimation = QPropertyAnimation(
            self.pandoraToolButton, 'geometry'.encode(encoding='utf_8'))
        pandoraButtonAnimation.setDuration(self.ANIMATIONDURATION)
        pandoraButtonAnimation.setStartValue(pandoraButtonCurrentGeometry)

        pandoraWidgetCurrentGeometry = QtCore.QRect(
            self.pandoraWidget.geometry())
        pandoraWidgetAnimation = QPropertyAnimation(
            self.pandoraWidget, 'geometry'.encode(encoding='utf_8'))
        pandoraWidgetAnimation.setDuration(self.ANIMATIONDURATION)
        pandoraWidgetAnimation.setStartValue(pandoraWidgetCurrentGeometry)

        if self.PandoraCurrentState == 'Closed':
            pandoraButtonEndTopLeftCorner = QtCore.QPoint(
                self.pandoraToolButton.pos() - QtCore.QPoint(275, 0))
            pandoraWidgetEndTopLeftCorner = QtCore.QPoint(
                self.pandoraWidget.pos() - QtCore.QPoint(275, 0))
            self.PandoraCurrentState = 'Open'
            if self.Pandora.started == True:
                self.playToolButton.hide()
                self.pauseToolButton.show()
        else:
            pandoraButtonEndTopLeftCorner = QtCore.QPoint(
                self.pandoraToolButton.pos() + QtCore.QPoint(275, 0))
            pandoraWidgetEndTopLeftCorner = QtCore.QPoint(
                self.pandoraWidget.pos() + QtCore.QPoint(275, 0))
            self.PandoraCurrentState = 'Closed'

        pandoraButtonFinalGeometry = QtCore.QRect(
            pandoraButtonEndTopLeftCorner,
            QtCore.QSize(self.pandoraToolButton.width(),
                         self.pandoraToolButton.height()))
        pandoraButtonAnimation.setEndValue(pandoraButtonFinalGeometry)

        pandoraWidgetFinalGeometry = QtCore.QRect(
            pandoraWidgetEndTopLeftCorner,
            QtCore.QSize(self.pandoraWidget.width(),
                         self.pandoraWidget.height()))
        pandoraWidgetAnimation.setEndValue(pandoraWidgetFinalGeometry)

        pandoraButtonAnimation.start()
        pandoraWidgetAnimation.start()
        #self.setpandoraWidgetStyleSheet()
        self.PandoraIconAnimation = pandoraButtonAnimation
        self.PandoraWidgetAnimation = pandoraWidgetAnimation

    def pressedPlayButton(self):
        if self.Pandora.started == False:
            self.songLabel.setText('Loading...')
            self.Pandora.start('*****@*****.**', 'bgc7y9l')
            self.playToolButton.hide()
            self.pauseToolButton.show()
            self.Pandora.read()
            self.songLabel.setText(self.Pandora.output)
        else:
            if self.playToolButton.isvisible():
                self.Pandora.play()
                self.playToolButton.hide()
                self.pauseToolButton.show()
                self.Pandora.read()
                self.songLabel.setText(self.Pandora.output)

    def pressedPauseButton(self):
        self.Pandora.pause()
        self.pauseToolButton.hide()
        self.playToolButton.show()

    def pressedStopButton(self):
        self.Pandora.stop()
        self.pauseToolButton.hide()
        self.playToolButton.show()

    def pressedSkipButton(self):
        self.Pandora.skip()

    def checkAlarm(self):
        if self.AlarmInfo.alarmSet == True:
            self.AlarmInfo.checkAlarm()
            if self.AlarmInfo.alarmSet == False:
                self.alarmOnToolButton.hide()
                self.alarmOffToolButton.show()
                #self.pressedPlayButton()
                self.statusBar.removeWidget(self.alarmStatusWidget)

    def pressedCalendarButton(self):
        try:
            calendarButtonCurrentGeometry = QtCore.QRect(
                self.calendarToolButton.geometry())
            calendarButtonAnimation = QPropertyAnimation(
                self.calendarToolButton, 'geometry'.encode(encoding='utf_8'))
            calendarButtonAnimation.setDuration(self.ANIMATIONDURATION)
            calendarButtonAnimation.setStartValue(
                calendarButtonCurrentGeometry)

            calendarWidgetCurrentGeometry = QtCore.QRect(
                self.calendarWidget.geometry())
            calendarWidgetAnimation = QPropertyAnimation(
                self.calendarWidget, 'geometry'.encode(encoding='utf_8'))
            calendarWidgetAnimation.setDuration(self.ANIMATIONDURATION)
            calendarWidgetAnimation.setStartValue(
                calendarWidgetCurrentGeometry)

            if self.CalendarCurrentState == 'Closed':
                calendarButtonEndTopLeftCorner = QtCore.QPoint(
                    self.calendarToolButton.pos() - QtCore.QPoint(275, 0))
                calendarWidgetEndTopLeftCorner = QtCore.QPoint(
                    self.calendarWidget.pos() - QtCore.QPoint(275, 0))
                self.CalendarCurrentState = 'Open'
                self.Calendar = Calendar()
                self.calendarLabel.setText(self.Calendar.EventList)
            else:
                calendarButtonEndTopLeftCorner = QtCore.QPoint(
                    self.calendarToolButton.pos() + QtCore.QPoint(275, 0))
                calendarWidgetEndTopLeftCorner = QtCore.QPoint(
                    self.calendarWidget.pos() + QtCore.QPoint(275, 0))
                self.CalendarCurrentState = 'Closed'

            calendarButtonFinalGeometry = QtCore.QRect(
                calendarButtonEndTopLeftCorner,
                QtCore.QSize(self.calendarToolButton.width(),
                             self.calendarToolButton.height()))
            calendarButtonAnimation.setEndValue(calendarButtonFinalGeometry)

            calendarWidgetFinalGeometry = QtCore.QRect(
                calendarWidgetEndTopLeftCorner,
                QtCore.QSize(self.calendarWidget.width(),
                             self.calendarWidget.height()))
            calendarWidgetAnimation.setEndValue(calendarWidgetFinalGeometry)

            calendarButtonAnimation.start()
            calendarWidgetAnimation.start()
            #self.setcalendarWidgetStyleSheet()
            self.CalendarIconAnimation = calendarButtonAnimation
            self.CalendarWidgetAnimation = calendarWidgetAnimation
        except Exception as e:
            print(e.strerror)
예제 #28
0
class AddEmptyBookDialog(QDialog):
    def __init__(self, parent, db, author, series=None):
        QDialog.__init__(self, parent)
        self.db = db

        self.setWindowTitle(_('How many empty books?'))

        self._layout = QGridLayout(self)
        self.setLayout(self._layout)

        self.qty_label = QLabel(_('How many empty books should be added?'))
        self._layout.addWidget(self.qty_label, 0, 0, 1, 2)

        self.qty_spinbox = QSpinBox(self)
        self.qty_spinbox.setRange(1, 10000)
        self.qty_spinbox.setValue(1)
        self._layout.addWidget(self.qty_spinbox, 1, 0, 1, 2)

        self.author_label = QLabel(_('Set the author of the new books to:'))
        self._layout.addWidget(self.author_label, 2, 0, 1, 2)

        self.authors_combo = EditWithComplete(self)
        self.authors_combo.setSizeAdjustPolicy(
            self.authors_combo.AdjustToMinimumContentsLengthWithIcon)
        self.authors_combo.setEditable(True)
        self._layout.addWidget(self.authors_combo, 3, 0, 1, 1)
        self.initialize_authors(db, author)

        self.clear_button = QToolButton(self)
        self.clear_button.setIcon(QIcon(I('trash.png')))
        self.clear_button.setToolTip(_('Reset author to Unknown'))
        self.clear_button.clicked.connect(self.reset_author)
        self._layout.addWidget(self.clear_button, 3, 1, 1, 1)

        self.series_label = QLabel(_('Set the series of the new books to:'))
        self._layout.addWidget(self.series_label, 4, 0, 1, 2)

        self.series_combo = EditWithComplete(self)
        self.authors_combo.setSizeAdjustPolicy(
            self.authors_combo.AdjustToMinimumContentsLengthWithIcon)
        self.series_combo.setEditable(True)
        self._layout.addWidget(self.series_combo, 5, 0, 1, 1)
        self.initialize_series(db, series)

        self.sclear_button = QToolButton(self)
        self.sclear_button.setIcon(QIcon(I('trash.png')))
        self.sclear_button.setToolTip(_('Reset series'))
        self.sclear_button.clicked.connect(self.reset_series)
        self._layout.addWidget(self.sclear_button, 5, 1, 1, 1)

        self.create_epub = c = QCheckBox(
            _('Create an empty EPUB file as well'))
        c.setChecked(gprefs.get('create_empty_epub_file', False))
        c.setToolTip(
            _('Also create an empty EPUB file that you can subsequently edit'))
        self._layout.addWidget(c, 6, 0, 1, -1)

        button_box = self.bb = QDialogButtonBox(QDialogButtonBox.Ok
                                                | QDialogButtonBox.Cancel)
        button_box.accepted.connect(self.accept)
        button_box.rejected.connect(self.reject)
        self._layout.addWidget(button_box, 7, 0, 1, -1)
        self.resize(self.sizeHint())

    def accept(self):
        oval = gprefs.get('create_empty_epub_file', False)
        if self.create_epub.isChecked() != oval:
            gprefs['create_empty_epub_file'] = self.create_epub.isChecked()
        return QDialog.accept(self)

    def reset_author(self, *args):
        self.authors_combo.setEditText(_('Unknown'))

    def reset_series(self):
        self.series_combo.setEditText('')

    def initialize_authors(self, db, author):
        au = author
        if not au:
            au = _('Unknown')
        self.authors_combo.show_initial_value(au.replace('|', ','))

        self.authors_combo.set_separator('&')
        self.authors_combo.set_space_before_sep(True)
        self.authors_combo.set_add_separator(
            tweaks['authors_completer_append_separator'])
        self.authors_combo.update_items_cache(db.all_author_names())

    def initialize_series(self, db, series):
        self.series_combo.show_initial_value(series or '')
        self.series_combo.update_items_cache(db.all_series_names())
        self.series_combo.set_separator(None)

    @property
    def qty_to_add(self):
        return self.qty_spinbox.value()

    @property
    def selected_authors(self):
        return string_to_authors(unicode(self.authors_combo.text()))

    @property
    def selected_series(self):
        return unicode(self.series_combo.text())
예제 #29
0
class FindAnnotationsDialog(SizePersistedDialog, Logger):

    GENERIC_STYLE = 'Any style'
    GENERIC_READER = 'Any reader'

    def __init__(self, opts):
        self.matched_ids = set()
        self.opts = opts
        self.prefs = opts.prefs
        super(FindAnnotationsDialog, self).__init__(self.opts.gui, 'find_annotations_dialog')
        self.setWindowTitle(_('Find Annotations'))
        self.setWindowIcon(self.opts.icon)
        self.l = QVBoxLayout(self)
        self.setLayout(self.l)

        self.search_criteria_gb = QGroupBox(self)
        self.search_criteria_gb.setTitle(_("Search criteria"))
        self.scgl = QGridLayout(self.search_criteria_gb)
        self.l.addWidget(self.search_criteria_gb)
        # addWidget(widget, row, col, rowspan, colspan)

        row = 0
        # ~~~~~~~~ Create the Readers comboBox ~~~~~~~~
        self.reader_label = QLabel(_('Reader'))
        self.reader_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
        self.scgl.addWidget(self.reader_label, row, 0, 1, 1)

        self.find_annotations_reader_comboBox = QComboBox()
        self.find_annotations_reader_comboBox.setObjectName('find_annotations_reader_comboBox')
        self.find_annotations_reader_comboBox.setToolTip(_('Reader annotations to search for'))

        self.find_annotations_reader_comboBox.addItem(self.GENERIC_READER)
        racs = ReaderApp.get_reader_app_classes()
        for ra in sorted(racs.keys()):
            self.find_annotations_reader_comboBox.addItem(ra)
        self.scgl.addWidget(self.find_annotations_reader_comboBox, row, 1, 1, 4)
        row += 1

        # ~~~~~~~~ Create the Styles comboBox ~~~~~~~~
        self.style_label = QLabel(_('Style'))
        self.style_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
        self.scgl.addWidget(self.style_label, row, 0, 1, 1)

        self.find_annotations_color_comboBox = QComboBox()
        self.find_annotations_color_comboBox.setObjectName('find_annotations_color_comboBox')
        self.find_annotations_color_comboBox.setToolTip(_('Annotation style to search for'))

        self.find_annotations_color_comboBox.addItem(self.GENERIC_STYLE)
        all_colors = COLOR_MAP.keys()
        all_colors.remove('Default')
        for color in sorted(all_colors):
            self.find_annotations_color_comboBox.addItem(color)
        self.scgl.addWidget(self.find_annotations_color_comboBox, row, 1, 1, 4)
        row += 1

        # ~~~~~~~~ Create the Text LineEdit control ~~~~~~~~
        self.text_label = QLabel(_('Text'))
        self.text_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
        self.scgl.addWidget(self.text_label, row, 0, 1, 1)
        self.find_annotations_text_lineEdit = MyLineEdit()
        self.find_annotations_text_lineEdit.setObjectName('find_annotations_text_lineEdit')
        self.scgl.addWidget(self.find_annotations_text_lineEdit, row, 1, 1, 3)
        self.reset_text_tb = QToolButton()
        self.reset_text_tb.setObjectName('reset_text_tb')
        self.reset_text_tb.setToolTip(_('Clear search criteria'))
        self.reset_text_tb.setIcon(QIcon(I('trash.png')))
        self.reset_text_tb.clicked.connect(self.clear_text_field)
        self.scgl.addWidget(self.reset_text_tb, row, 4, 1, 1)
        row += 1

        # ~~~~~~~~ Create the Note LineEdit control ~~~~~~~~
        self.note_label = QLabel(_('Note'))
        self.note_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
        self.scgl.addWidget(self.note_label, row, 0, 1, 1)
        self.find_annotations_note_lineEdit = MyLineEdit()
        self.find_annotations_note_lineEdit.setObjectName('find_annotations_note_lineEdit')
        self.scgl.addWidget(self.find_annotations_note_lineEdit, row, 1, 1, 3)
        self.reset_note_tb = QToolButton()
        self.reset_note_tb.setObjectName('reset_note_tb')
        self.reset_note_tb.setToolTip(_('Clear search criteria'))
        self.reset_note_tb.setIcon(QIcon(I('trash.png')))
        self.reset_note_tb.clicked.connect(self.clear_note_field)
        self.scgl.addWidget(self.reset_note_tb, row, 4, 1, 1)
        row += 1

        # ~~~~~~~~ Create the Date range controls ~~~~~~~~
        self.date_range_label = QLabel(_('Date range'))
        self.date_range_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
        self.scgl.addWidget(self.date_range_label, row, 0, 1, 1)

        # Date 'From'
        self.find_annotations_date_from_dateEdit = MyDateEdit(self, datetime(1970,1,1))
        self.find_annotations_date_from_dateEdit.setObjectName('find_annotations_date_from_dateEdit')
        #self.find_annotations_date_from_dateEdit.current_val = datetime(1970,1,1)
        self.find_annotations_date_from_dateEdit.clear_button.clicked.connect(self.find_annotations_date_from_dateEdit.reset_from_date)
        self.scgl.addWidget(self.find_annotations_date_from_dateEdit, row, 1, 1, 1)
        self.scgl.addWidget(self.find_annotations_date_from_dateEdit.clear_button, row, 2, 1, 1)

        # Date 'To'
        self.find_annotations_date_to_dateEdit = MyDateEdit(self, datetime.today())
        self.find_annotations_date_to_dateEdit.setObjectName('find_annotations_date_to_dateEdit')
        #self.find_annotations_date_to_dateEdit.current_val = datetime.today()
        self.find_annotations_date_to_dateEdit.clear_button.clicked.connect(self.find_annotations_date_to_dateEdit.reset_to_date)
        self.scgl.addWidget(self.find_annotations_date_to_dateEdit, row, 3, 1, 1)
        self.scgl.addWidget(self.find_annotations_date_to_dateEdit.clear_button, row, 4, 1, 1)
        row += 1

        # ~~~~~~~~ Create a horizontal line ~~~~~~~~
        self.hl = QFrame(self)
        self.hl.setGeometry(QRect(0, 0, 1, 3))
        self.hl.setFrameShape(QFrame.HLine)
        self.hl.setFrameShadow(QFrame.Raised)
        self.scgl.addWidget(self.hl, row, 0, 1, 5)
        row += 1

        # ~~~~~~~~ Create the results label field ~~~~~~~~
        self.result_label = QLabel('<p style="color:red">{0}</p>'.format(_('scanning…')))
        self.result_label.setAlignment(Qt.AlignCenter)
        self.result_label.setWordWrap(False)

        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Maximum)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.result_label.sizePolicy().hasHeightForWidth())
        self.result_label.setSizePolicy(sizePolicy)
        self.result_label.setMinimumSize(QtCore.QSize(250, 0))
        self.scgl.addWidget(self.result_label, row, 0, 1, 5)
        row += 1

        # ~~~~~~~~ Create the ButtonBox ~~~~~~~~
        self.dialogButtonBox = QDialogButtonBox(self)
        self.dialogButtonBox.setOrientation(Qt.Horizontal)
        if False:
            self.update_button = QPushButton(_('Update results'))
            self.update_button.setDefault(True)
            self.update_button.setVisible(False)
            self.dialogButtonBox.addButton(self.update_button, QDialogButtonBox.ActionRole)

        self.cancel_button = self.dialogButtonBox.addButton(self.dialogButtonBox.Cancel)
        self.find_button = self.dialogButtonBox.addButton(self.dialogButtonBox.Ok)
        self.find_button.setText(_('Find Matching Books'))

        self.l.addWidget(self.dialogButtonBox)
        self.dialogButtonBox.clicked.connect(self.find_annotations_dialog_clicked)

        # ~~~~~~~~ Add a spacer ~~~~~~~~
        self.spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
        self.l.addItem(self.spacerItem)

        # ~~~~~~~~ Restore previously saved settings ~~~~~~~~
        self.restore_settings()

        # ~~~~~~~~ Declare sizing ~~~~~~~~
        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth())
        self.setSizePolicy(sizePolicy)
        self.resize_dialog()

        # ~~~~~~~~ Connect all signals ~~~~~~~~
        self.find_annotations_reader_comboBox.currentIndexChanged.connect(partial(self.update_results, 'reader'))
        self.find_annotations_color_comboBox.currentIndexChanged.connect(partial(self.update_results, 'color'))
        self.find_annotations_text_lineEdit.editingFinished.connect(partial(self.update_results, 'text'))
        self.find_annotations_note_lineEdit.editingFinished.connect(partial(self.update_results, 'note'))
#        self.connect(self.find_annotations_text_lineEdit, pyqtSignal("return_pressed"), self.return_pressed)
        self.find_annotations_text_lineEdit.return_pressed.connect(self.return_pressed)
#        self.connect(self.find_annotations_note_lineEdit, pyqtSignal("return_pressed"), self.return_pressed)
        self.find_annotations_note_lineEdit.return_pressed.connect(self.return_pressed)

        # Date range signals connected in inventory_available()

        # ~~~~~~~~ Allow dialog to render before doing inventory ~~~~~~~~
        #field = self.prefs.get('cfg_annotations_destination_field', None)
        field = get_cc_mapping('annotations', 'field', None)
        self.annotated_books_scanner = InventoryAnnotatedBooks(self.opts.gui, field, get_date_range=True)
        self.annotated_books_scanner.signal.connect(self.inventory_available)
        QTimer.singleShot(1, self.start_inventory_scan)

    def clear_note_field(self):
        if str(self.find_annotations_note_lineEdit.text()) > '':
            self.find_annotations_note_lineEdit.setText('')
            self.update_results('clear_note_field')

    def clear_text_field(self):
        if str(self.find_annotations_text_lineEdit.text()) > '':
            self.find_annotations_text_lineEdit.setText('')
            self.update_results('clear_text_field')

    def find_annotations_dialog_clicked(self, button):
        if self.dialogButtonBox.buttonRole(button) == QDialogButtonBox.AcceptRole:
            self.save_settings()
            self.accept()
        elif self.dialogButtonBox.buttonRole(button) == QDialogButtonBox.RejectRole:
            self.close()

    def inventory_available(self):
        '''
        Update the Date range widgets with the rounded oldest, newest dates
        Don't connect date signals until date range available
        '''
        self._log_location()

        # Reset the date range based on available annotations
        oldest = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.oldest_annotation))
        oldest_day = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.oldest_annotation).replace(hour=0, minute=0, second=0))
        newest = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.newest_annotation))
        newest_day = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.newest_annotation).replace(hour=23, minute=59, second=59))

        # Set 'From' date limits to inventory values
        self.find_annotations_date_from_dateEdit.setMinimumDateTime(oldest_day)
        self.find_annotations_date_from_dateEdit.current_val = oldest
        self.find_annotations_date_from_dateEdit.setMaximumDateTime(newest_day)

        # Set 'To' date limits to inventory values
        self.find_annotations_date_to_dateEdit.setMinimumDateTime(oldest_day)
        self.find_annotations_date_to_dateEdit.current_val = newest_day
        self.find_annotations_date_to_dateEdit.setMaximumDateTime(newest_day)

        # Connect the signals for date range changes
        self.find_annotations_date_from_dateEdit.dateTimeChanged.connect(partial(self.update_results, 'from_date'))
        self.find_annotations_date_to_dateEdit.dateTimeChanged.connect(partial(self.update_results, 'to_date'))

        self.update_results('inventory_available')

    def restore_settings(self):
        self.blockSignals(True)

        ra = self.prefs.get('find_annotations_reader_comboBox', self.GENERIC_READER)
        ra_index = self.find_annotations_reader_comboBox.findText(ra)
        self.find_annotations_reader_comboBox.setCurrentIndex(ra_index)

        color = self.prefs.get('find_annotations_color_comboBox', self.GENERIC_STYLE)
        color_index = self.find_annotations_color_comboBox.findText(color)
        self.find_annotations_color_comboBox.setCurrentIndex(color_index)

        text = self.prefs.get('find_annotations_text_lineEdit', '')
        self.find_annotations_text_lineEdit.setText(text)

        note = self.prefs.get('find_annotations_note_lineEdit', '')
        self.find_annotations_note_lineEdit.setText(note)

        if False:
            from_date = self.prefs.get('find_annotations_date_from_dateEdit', datetime(1970,1,1))
            self.find_annotations_date_from_dateEdit.current_val = from_date
            to_date = self.prefs.get('find_annotations_date_to_dateEdit', datetime.today())
            self.find_annotations_date_to_dateEdit.current_val = to_date

        self.blockSignals(False)

    def return_pressed(self):
        self.update_results("return_pressed")

    def save_settings(self):
        ra = str(self.find_annotations_reader_comboBox.currentText())
        self.prefs.set('find_annotations_reader_comboBox', ra)

        color = str(self.find_annotations_color_comboBox.currentText())
        self.prefs.set('find_annotations_color_comboBox', color)

        text = str(self.find_annotations_text_lineEdit.text())
        self.prefs.set('find_annotations_text_lineEdit', text)

        note = str(self.find_annotations_note_lineEdit.text())
        self.prefs.set('find_annotations_note_lineEdit', note)

        if False:
            from_date = self.find_annotations_date_from_dateEdit.current_val
            self.prefs.set('find_annotations_date_from_dateEdit', from_date)

            to_date = self.find_annotations_date_to_dateEdit.current_val
            self.prefs.set('find_annotations_date_to_dateEdit', to_date)

    def start_inventory_scan(self):
        self._log_location()
        self.annotated_books_scanner.start()

    def update_results(self, trigger):
        #self._log_location(trigger)
        reader_to_match = str(self.find_annotations_reader_comboBox.currentText())
        color_to_match = str(self.find_annotations_color_comboBox.currentText())
        text_to_match = str(self.find_annotations_text_lineEdit.text())
        note_to_match = str(self.find_annotations_note_lineEdit.text())

        from_date = self.find_annotations_date_from_dateEdit.dateTime().toTime_t()
        to_date = self.find_annotations_date_to_dateEdit.dateTime().toTime_t()

        annotation_map = self.annotated_books_scanner.annotation_map
        #field = self.prefs.get("cfg_annotations_destination_field", None)
        field = get_cc_mapping('annotations', 'field', None)

        db = self.opts.gui.current_db
        matched_titles = []
        self.matched_ids = set()

        for cid in annotation_map:
            mi = db.get_metadata(cid, index_is_id=True)
            soup = None
            if field == 'Comments':
                if mi.comments:
                    soup = BeautifulSoup(mi.comments)
            else:
                if mi.get_user_metadata(field, False)['#value#'] is not None:
                    soup = BeautifulSoup(mi.get_user_metadata(field, False)['#value#'])
            if soup:
                uas = soup.findAll('div', 'annotation')
                for ua in uas:
                    # Are we already logged?
                    if cid in self.matched_ids:
                        continue

                    # Check reader
                    if reader_to_match != self.GENERIC_READER:
                        this_reader = ua['reader']
                        if this_reader != reader_to_match:
                            continue

                    # Check color
                    if color_to_match != self.GENERIC_STYLE:
                        this_color = ua.find('table')['color']
                        if this_color != color_to_match:
                            continue

                    # Check date range, allow for mangled timestamp
                    try:
                        timestamp = float(ua.find('td', 'timestamp')['uts'])
                        if timestamp < from_date or timestamp > to_date:
                            continue
                    except:
                        continue

                    highlight_text = ''
                    try:
                        pels = ua.findAll('p', 'highlight')
                        highlight_text = '\n'.join([p.string for p in pels])
                    except:
                        pass
                    if text_to_match > '':
                        if not re.search(text_to_match, highlight_text, flags=re.IGNORECASE):
                            continue

                    note_text = ''
                    try:
                        nels = ua.findAll('p', 'note')
                        note_text = '\n'.join([n.string for n in nels])
                    except:
                        pass
                    if note_to_match > '':
                        if not re.search(note_to_match, note_text, flags=re.IGNORECASE):
                            continue

                    # If we made it this far, add the id to matched_ids
                    self.matched_ids.add(cid)
                    matched_titles.append(mi.title)

        # Update the results box
        matched_titles.sort()
        if len(annotation_map):
            if len(matched_titles):
                first_match = ("<i>%s</i>" % matched_titles[0])
                if len(matched_titles) == 1:
                    results = first_match
                else:
                    results = first_match + (_(" and {0} more.").format(len(matched_titles) - 1))
                self.result_label.setText('<p style="color:blue">{0}</p>'.format(results))
            else:
                self.result_label.setText('<p style="color:red">{0}</p>'.format(_('no matches')))
        else:
            self.result_label.setText('<p style="color:red">{0}</p>'.format(_('no annotated books in library')))

        self.resize_dialog()
예제 #30
0
파일: ui.py 프로젝트: j-howell/calibre
class TagBrowserBar(QWidget):  # {{{

    def __init__(self, parent):
        QWidget.__init__(self, parent)
        self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Preferred)
        parent = parent.parent()
        self.l = l = QHBoxLayout(self)
        l.setContentsMargins(0, 0, 0, 0)
        self.alter_tb = parent.alter_tb = b = QToolButton(self)
        b.setAutoRaise(True)
        b.setText(_('Configure')), b.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        b.setCursor(Qt.PointingHandCursor)
        b.setPopupMode(b.InstantPopup)
        b.setToolTip(textwrap.fill(_(
            'Change how the Tag browser works, such as,'
            ' how it is sorted, what happens when you click'
            ' items, etc.'
        )))
        b.setIcon(QIcon(I('config.png')))
        b.m = QMenu()
        b.setMenu(b.m)

        self.item_search = FindBox(parent)
        self.item_search.setMinimumContentsLength(5)
        self.item_search.setSizeAdjustPolicy(self.item_search.AdjustToMinimumContentsLengthWithIcon)
        self.item_search.initialize('tag_browser_search')
        self.item_search.completer().setCaseSensitivity(Qt.CaseSensitive)
        self.item_search.setToolTip(_(
        'Search for items. This is a "contains" search; items containing the\n'
        'text anywhere in the name will be found. You can limit the search\n'
        'to particular categories using syntax similar to search. For example,\n'
        'tags:foo will find foo in any tag, but not in authors etc. Entering\n'
        '*foo will filter all categories at once, showing only those items\n'
        'containing the text "foo"'))
        ac = QAction(parent)
        parent.addAction(ac)
        parent.keyboard.register_shortcut('tag browser find box',
                _('Find next match'), default_keys=(),
                action=ac, group=_('Tag browser'))
        ac.triggered.connect(self.set_focus_to_find_box)

        self.search_button = QToolButton()
        self.search_button.setAutoRaise(True)
        self.search_button.setCursor(Qt.PointingHandCursor)
        self.search_button.setIcon(QIcon(I('search.png')))
        self.search_button.setToolTip(_('Find the first/next matching item'))
        ac = QAction(parent)
        parent.addAction(ac)
        parent.keyboard.register_shortcut('tag browser find button',
                _('Find in Tag browser'), default_keys=(),
                action=ac, group=_('Tag browser'))
        ac.triggered.connect(self.search_button.click)

        self.toggle_search_button = b = QToolButton(self)
        le = self.item_search.lineEdit()
        le.addAction(QIcon(I('window-close.png')), le.LeadingPosition).triggered.connect(self.close_find_box)
        b.setText(_('Find')), b.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        b.setCursor(Qt.PointingHandCursor)
        b.setIcon(QIcon(I('search.png')))
        b.setCheckable(True)
        b.setChecked(gprefs.get('tag browser search box visible', False))
        b.setToolTip(_('Search for items in the Tag browser'))
        b.setAutoRaise(True)
        b.toggled.connect(self.update_searchbar_state)
        self.update_searchbar_state()

    def close_find_box(self):
        self.item_search.setCurrentIndex(0)
        self.item_search.setCurrentText('')
        self.toggle_search_button.click()

    def set_focus_to_find_box(self):
        self.toggle_search_button.setChecked(True)
        self.item_search.setFocus()
        self.item_search.lineEdit().selectAll()

    def update_searchbar_state(self):
        find_shown = self.toggle_search_button.isChecked()
        self.toggle_search_button.setVisible(not find_shown)
        l = self.layout()
        items = [l.itemAt(i) for i in range(l.count())]
        tuple(map(l.removeItem, items))
        if find_shown:
            l.addWidget(self.alter_tb)
            self.alter_tb.setToolButtonStyle(Qt.ToolButtonIconOnly)
            l.addWidget(self.item_search, 10)
            l.addWidget(self.search_button)
            self.item_search.setFocus(Qt.OtherFocusReason)
            self.toggle_search_button.setVisible(False)
            self.search_button.setVisible(True)
            self.item_search.setVisible(True)
        else:
            l.addWidget(self.alter_tb)
            self.alter_tb.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
            l.addStretch(10)
            l.addStretch(10)
            l.addWidget(self.toggle_search_button)
            self.toggle_search_button.setVisible(True)
            self.search_button.setVisible(False)
            self.item_search.setVisible(False)
예제 #31
0
    def __init__(self, parent, rc, imgman):
        """An ImageControlDialog is initialized with a parent widget, a RenderControl object,
        and an ImageManager object"""
        QDialog.__init__(self, parent)
        image = rc.image
        self.setWindowTitle("%s: Colour Controls" % image.name)
        self.setWindowIcon(pixmaps.colours.icon())
        self.setModal(False)
        self.image = image
        self._rc = rc
        self._imgman = imgman
        self._currier = PersistentCurrier()

        # init internal state
        self._prev_range = self._display_range = None, None
        self._hist = None
        self._geometry = None

        # create layouts
        lo0 = QVBoxLayout(self)
        #    lo0.setContentsMargins(0,0,0,0)

        # histogram plot
        whide = self.makeButton("Hide", self.hide, width=128)
        whide.setShortcut(Qt.Key_F9)
        lo0.addWidget(Separator(self, "Histogram and ITF", extra_widgets=[whide]))
        lo1 = QHBoxLayout()
        lo1.setContentsMargins(0, 0, 0, 0)
        self._histplot = QwtPlot(self)
        self._histplot.setAutoDelete(False)
        lo1.addWidget(self._histplot, 1)
        lo2 = QHBoxLayout()
        lo2.setContentsMargins(0, 0, 0, 0)
        lo2.setSpacing(2)
        lo0.addLayout(lo2)
        lo0.addLayout(lo1)
        self._wautozoom = QCheckBox("autozoom", self)
        self._wautozoom.setChecked(True)
        self._wautozoom.setToolTip("""<P>If checked, then the histrogram plot will zoom in automatically when
      you narrow the current intensity range.</P>""")
        self._wlogy = QCheckBox("log Y", self)
        self._wlogy.setChecked(True)
        self._ylogscale = True
        self._wlogy.setToolTip(
            """<P>If checked, a log-scale Y axis is used for the histogram plot instead of a linear one.""")
        self._wlogy.toggled[bool].connect(self._setHistLogScale)
        self._whistunzoom = self.makeButton("", self._unzoomHistogram, icon=pixmaps.full_range.icon())
        self._whistzoomout = self.makeButton("-", self._currier.curry(self._zoomHistogramByFactor, math.sqrt(.1)))
        self._whistzoomin = self.makeButton("+", self._currier.curry(self._zoomHistogramByFactor, math.sqrt(10)))
        self._whistzoomin.setToolTip("""<P>Click to zoom into the histogram plot by one step. This does not
      change the current intensity range.</P>""")
        self._whistzoomout.setToolTip("""<P>Click to zoom out of the histogram plot by one step. This does not
      change the current intensity range.</P>""")
        self._whistunzoom.setToolTip("""<P>Click to reset the histogram plot back to its full extent.
      This does not change the current intensity range.</P>""")
        self._whistzoom = QwtWheel(self)
        self._whistzoom.setMass(0.5)
        self._whistzoom.setOrientation(Qt.Horizontal)
        self._whistzoom.setMaximumWidth(80)
        self._whistzoom.setRange(0, 10)
        self._whistzoom.setSingleStep(0.1)
        self._whistzoom.setPageStepCount(1)
        self._whistzoom.setTickCount(30)
        self._whistzoom.setTracking(False)
        self._whistzoom.valueChanged['double'].connect(self._zoomHistogramFinalize)
        self._whistzoom.wheelMoved['double'].connect(self._zoomHistogramPreview)
        self._whistzoom.setToolTip("""<P>Use this wheel control to zoom in/out of the histogram plot.
      This does not change the current intensity range.
      Note that the zoom wheel should also respond to your mouse wheel, if you have one.</P>""")
        # This works around a stupid bug in QwtSliders -- when using the mousewheel, only sliderMoved() signals are emitted,
        # with no final  valueChanged(). If we want to do a fast preview of something on sliderMoved(), and a "slow" final
        # step on valueChanged(), we're in trouble. So we start a timer on sliderMoved(), and if the timer expires without
        # anything else happening, do a valueChanged().
        # Here we use a timer to call zoomHistogramFinalize() w/o an argument.
        self._whistzoom_timer = QTimer(self)
        self._whistzoom_timer.setSingleShot(True)
        self._whistzoom_timer.setInterval(500)
        self._whistzoom_timer.timeout.connect(self._zoomHistogramFinalize)
        # set same size for all buttons and controls
        width = 24
        for w in self._whistunzoom, self._whistzoomin, self._whistzoomout:
            w.setMinimumSize(width, width)
            w.setMaximumSize(width, width)
        self._whistzoom.setMinimumSize(80, width)
        self._wlab_histpos_text = "(hover for help)"
        self._wlab_histpos = QLabel(self._wlab_histpos_text, self)
        help_font = QFont()
        help_font.setPointSize(8)
        self._wlab_histpos.setFont(help_font)
        self._wlab_histpos.setToolTip("""
      <P>The plot shows a histogram of either the full image or its selected subset
      (as per the "Data subset" section below).</P>
      <P>The current intensity range is indicated by the grey box
      in the plot.</P>
      <P>Use the left mouse button to change the low intensity limit, and the right
      button (on Macs, use Ctrl-click) to change the high limit.</P>
      <P>Use Shift with the left mouse button to zoom into an area of the histogram,
      or else use the "zoom wheel" control or the plus/minus toolbuttons above the histogram to zoom in or out.
      To zoom back out to the full extent of the histogram, click on the rightmost button above the histogram.</P>
      """)
        lo2.addWidget(self._wlab_histpos, 1)
        lo2.addWidget(self._wautozoom)
        lo2.addWidget(self._wlogy, 0)
        lo2.addWidget(self._whistzoomin, 0)
        lo2.addWidget(self._whistzoom, 0)
        lo2.addWidget(self._whistzoomout, 0)
        lo2.addWidget(self._whistunzoom, 0)
        self._zooming_histogram = False

        sliced_axes = rc.slicedAxes()
        dprint(1, "sliced axes are", sliced_axes)
        self._stokes_axis = None

        # subset indication
        lo0.addWidget(Separator(self, "Data subset"))
        # sliced axis selectors
        self._wslicers = []
        if sliced_axes:
            lo1 = QHBoxLayout()
            lo1.setContentsMargins(0, 0, 0, 0)
            lo1.setSpacing(2)
            lo0.addLayout(lo1)
            lo1.addWidget(QLabel("Current slice:  ", self))
            for i, (iextra, name, labels) in enumerate(sliced_axes):
                lo1.addWidget(QLabel("%s:" % name, self))
                if name == "STOKES":
                    self._stokes_axis = iextra
                # add controls
                wslicer = QComboBox(self)
                self._wslicers.append(wslicer)
                wslicer.addItems(labels)
                wslicer.setToolTip("""<P>Selects current slice along the %s axis.</P>""" % name)
                wslicer.setCurrentIndex(self._rc.currentSlice()[iextra])
                wslicer.activated[int].connect(self._currier.curry(self._rc.changeSlice, iextra))
                lo2 = QVBoxLayout()
                lo1.addLayout(lo2)
                lo2.setContentsMargins(0, 0, 0, 0)
                lo2.setSpacing(0)
                wminus = QToolButton(self)
                wminus.setArrowType(Qt.UpArrow)
                wminus.clicked.connect(self._currier.curry(self._rc.incrementSlice, iextra, 1))
                if i == 0:
                    wminus.setShortcut(Qt.SHIFT + Qt.Key_F7)
                elif i == 1:
                    wminus.setShortcut(Qt.SHIFT + Qt.Key_F8)
                wplus = QToolButton(self)
                wplus.setArrowType(Qt.DownArrow)
                wplus.clicked.connect(self._currier.curry(self._rc.incrementSlice, iextra, -1))
                if i == 0:
                    wplus.setShortcut(Qt.Key_F7)
                elif i == 1:
                    wplus.setShortcut(Qt.Key_F8)
                wminus.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
                wplus.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
                sz = QSize(12, 8)
                wminus.setMinimumSize(sz)
                wplus.setMinimumSize(sz)
                wminus.resize(sz)
                wplus.resize(sz)
                lo2.addWidget(wminus)
                lo2.addWidget(wplus)
                lo1.addWidget(wslicer)
                lo1.addSpacing(5)
            lo1.addStretch(1)
        # subset indicator
        lo1 = QHBoxLayout()
        lo1.setContentsMargins(0, 0, 0, 0)
        lo1.setSpacing(2)
        lo0.addLayout(lo1)
        self._wlab_subset = QLabel("Subset: xxx", self)
        self._wlab_subset.setToolTip("""<P>This indicates the current data subset to which the histogram
      and the stats given here apply. Use the "Reset to" control on the right to change the
      current subset and recompute the histogram and stats.</P>""")
        lo1.addWidget(self._wlab_subset, 1)

        self._wreset_full = self.makeButton("\u2192 full", self._rc.setFullSubset)
        lo1.addWidget(self._wreset_full)
        if sliced_axes:
            #      if self._stokes_axis is not None and len(sliced_axes)>1:
            #        self._wreset_stokes = self.makeButton(u"\u21920Stokes",self._rc.setFullSubset)
            self._wreset_slice = self.makeButton("\u2192 slice", self._rc.setSliceSubset)
            lo1.addWidget(self._wreset_slice)
        else:
            self._wreset_slice = None

        # min/max controls
        lo1 = QHBoxLayout()
        lo1.setContentsMargins(0, 0, 0, 0)
        lo0.addLayout(lo1, 0)
        self._wlab_stats = QLabel(self)
        self._wlab_stats.setWordWrap(True)
        self._wlab_stats.setMinimumWidth(384)
        lo1.addWidget(self._wlab_stats, 0)
        self._wmore_stats = self.makeButton("more...", self._showMeanStd)
        self._wlab_stats.setMinimumHeight(self._wmore_stats.height())
        lo1.addWidget(self._wmore_stats, 0)
        lo1.addStretch(1)

        # intensity controls
        lo0.addWidget(Separator(self, "Intensity mapping"))
        lo1 = QHBoxLayout()
        lo1.setContentsMargins(0, 0, 0, 0)
        lo1.setSpacing(2)
        lo0.addLayout(lo1, 0)
        self._range_validator = FloatValidator(self)
        self._wrange = QLineEdit(self), QLineEdit(self)
        self._wrange[0].setToolTip("""<P>This is the low end of the intensity range.</P>""")
        self._wrange[1].setToolTip("""<P>This is the high end of the intensity range.</P>""")
        for w in self._wrange:
            w.setValidator(self._range_validator)
            w.editingFinished.connect(self._changeDisplayRange)
        lo1.addWidget(QLabel("low:", self), 0)
        lo1.addWidget(self._wrange[0], 1)
        self._wrangeleft0 = self.makeButton("\u21920", self._setZeroLeftLimit, width=32)
        self._wrangeleft0.setToolTip("""<P>Click this to set the low end of the intensity range to 0.</P>""")
        lo1.addWidget(self._wrangeleft0, 0)
        lo1.addSpacing(8)
        lo1.addWidget(QLabel("high:", self), 0)
        lo1.addWidget(self._wrange[1], 1)
        lo1.addSpacing(8)
        self._wrange_full = self.makeButton(None, self._setHistDisplayRange, icon=pixmaps.intensity_graph.icon())
        lo1.addWidget(self._wrange_full)
        self._wrange_full.setToolTip(
            """<P>Click this to reset the intensity range to the current extent of the histogram plot.</P>""")
        # add menu for display range
        range_menu = QMenu(self)
        wrange_menu = QToolButton(self)
        wrange_menu.setText("Reset to")
        wrange_menu.setToolTip("""<P>Use this to reset the intensity range to various pre-defined settings.</P>""")
        lo1.addWidget(wrange_menu)
        self._qa_range_full = range_menu.addAction(pixmaps.full_range.icon(), "Full subset",
                                                   self._rc.resetSubsetDisplayRange)
        self._qa_range_hist = range_menu.addAction(pixmaps.intensity_graph.icon(), "Current histogram limits",
                                                   self._setHistDisplayRange)
        for percent in (99.99, 99.9, 99.5, 99, 98, 95):
            range_menu.addAction("%g%%" % percent, self._currier.curry(self._changeDisplayRangeToPercent, percent))
        wrange_menu.setMenu(range_menu)
        wrange_menu.setPopupMode(QToolButton.InstantPopup)

        lo1 = QGridLayout()
        lo1.setContentsMargins(0, 0, 0, 0)
        lo0.addLayout(lo1, 0)
        self._wimap = QComboBox(self)
        lo1.addWidget(QLabel("Intensity policy:", self), 0, 0)
        lo1.addWidget(self._wimap, 1, 0)
        self._wimap.addItems(rc.getIntensityMapNames())
        self._wimap.currentIndexChanged[int].connect(self._rc.setIntensityMapNumber)
        self._wimap.setToolTip("""<P>Use this to change the type of the intensity transfer function (ITF).</P>""")

        # log cycles control
        lo1.setColumnStretch(1, 1)
        self._wlogcycles_label = QLabel("Log cycles: ", self)
        lo1.addWidget(self._wlogcycles_label, 0, 1)
        #    self._wlogcycles = QwtWheel(self)
        #    self._wlogcycles.setTotalAngle(360)
        self._wlogcycles = QwtSlider(self)
        self._wlogcycles.setToolTip(
            """<P>Use this to change the log-base for the logarithmic intensity transfer function (ITF).</P>""")
        # This works around a stupid bug in QwtSliders -- see comments on histogram zoom wheel above
        self._wlogcycles_timer = QTimer(self)
        self._wlogcycles_timer.setSingleShot(True)
        self._wlogcycles_timer.setInterval(500)
        self._wlogcycles_timer.timeout.connect(self._setIntensityLogCycles)
        lo1.addWidget(self._wlogcycles, 1, 1)
        # self._wlogcycles.setRange(1., 10)  # need to find 6.1.5 change from v5
        self._wlogcycles.setScale(1., 10)
        # self._wlogcycles.setStep(0.1)  # need to find 6.1.5 change from v5
        # self._wlogcycles.setScaleStepSize(0.1)
        self._wlogcycles.setTracking(False)
        self._wlogcycles.valueChanged.connect(self._setIntensityLogCycles)
        self._wlogcycles.sliderMoved.connect(self._previewIntensityLogCycles)
        self._updating_imap = False

        # lock intensity map
        lo1 = QHBoxLayout()
        lo1.setContentsMargins(0, 0, 0, 0)
        lo0.addLayout(lo1, 0)
        #    lo1.addWidget(QLabel("Lock range accross",self))
        wlock = QCheckBox("Lock display range", self)
        wlock.setMinimumWidth(192)
        wlock.setToolTip("""<P>If checked, then the intensity range will be locked. The ranges of all locked images
      change simultaneously.</P>""")
        lo1.addWidget(wlock)
        wlockall = QToolButton(self)
        wlockall.setIcon(pixmaps.locked.icon())
        wlockall.setText("Lock all to this")
        wlockall.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        wlockall.setAutoRaise(True)
        wlockall.setToolTip("""<P>Click this to lock together the intensity ranges of all images.</P>""")
        lo1.addWidget(wlockall)
        wunlockall = QToolButton(self)
        wunlockall.setIcon(pixmaps.unlocked.icon())
        wunlockall.setText("Unlock all")
        wunlockall.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        wunlockall.setAutoRaise(True)
        wunlockall.setToolTip("""<P>Click this to unlock the intensity ranges of all images.</P>""")
        lo1.addWidget(wunlockall)
        wlock.setChecked(self._rc.isDisplayRangeLocked())
        wlock.clicked[bool].connect(self._rc.lockDisplayRange)
        wlockall.clicked.connect(self._currier.curry(self._imgman.lockAllDisplayRanges, self._rc))
        wunlockall.clicked.connect(self._imgman.unlockAllDisplayRanges)
        self._rc.displayRangeLocked.connect(wlock.setChecked)

        #    self._wlock_imap_axis = [ QCheckBox(name,self) for iaxis,name,labels in sliced_axes ]
        #    for iw,w in enumerate(self._wlock_imap_axis):
        #      QObject.connect(w,pyqtSignal("toggled(bool)"),self._currier.curry(self._rc.lockDisplayRangeForAxis,iw))
        #      lo1.addWidget(w,0)
        lo1.addStretch(1)

        # lo0.addWidget(Separator(self,"Colourmap"))
        # color bar
        self._colorbar = QwtPlot(self)
        lo0.addWidget(self._colorbar)
        self._colorbar.setAutoDelete(False)
        self._colorbar.setMinimumHeight(32)
        self._colorbar.enableAxis(QwtPlot.yLeft, False)
        self._colorbar.enableAxis(QwtPlot.xBottom, False)
        # color plot
        self._colorplot = QwtPlot(self)
        lo0.addWidget(self._colorplot)
        self._colorplot.setAutoDelete(False)
        self._colorplot.setMinimumHeight(64)
        self._colorplot.enableAxis(QwtPlot.yLeft, False)
        self._colorplot.enableAxis(QwtPlot.xBottom, False)
        # self._colorplot.setSizePolicy(QSizePolicy.Expanding,QSizePolicy.Preferred)
        self._colorbar.hide()
        self._colorplot.hide()
        # color controls
        lo1 = QHBoxLayout()
        lo1.setContentsMargins(0, 0, 0, 0)
        lo0.addLayout(lo1, 1)
        lo1.addWidget(QLabel("Colourmap:", self))
        # colormap list
        ### NB: use setIconSize() and icons in QComboBox!!!
        self._wcolmaps = QComboBox(self)
        self._wcolmaps.setIconSize(QSize(128, 16))
        self._wcolmaps.setToolTip("""<P>Use this to select a different colourmap.</P>""")
        for cmap in self._rc.getColormapList():
            self._wcolmaps.addItem(QIcon(cmap.makeQPixmap(128, 16)), cmap.name)
        lo1.addWidget(self._wcolmaps)
        self._wcolmaps.activated[int].connect(self._rc.setColorMapNumber)
        # add widgetstack for colormap controls
        self._wcolmap_control_stack = QStackedWidget(self)
        self._wcolmap_control_blank = QWidget(self._wcolmap_control_stack)
        self._wcolmap_control_stack.addWidget(self._wcolmap_control_blank)
        lo0.addWidget(self._wcolmap_control_stack)
        self._colmap_controls = []
        # add controls to stack
        for index, cmap in enumerate(self._rc.getColormapList()):
            if isinstance(cmap, Colormaps.ColormapWithControls):
                controls = cmap.makeControlWidgets(self._wcolmap_control_stack)
                self._wcolmap_control_stack.addWidget(controls)
                cmap.colormapChanged.connect(self._currier.curry(self._previewColormapParameters, index, cmap))
                cmap.colormapPreviewed.connect(self._currier.curry(self._previewColormapParameters, index, cmap))
                self._colmap_controls.append(controls)
            else:
                self._colmap_controls.append(self._wcolmap_control_blank)

        # connect updates from renderControl and image
        self.image.signalSlice.connect(self._updateImageSlice)
        self._rc.intensityMapChanged.connect(self._updateIntensityMap)
        self._rc.colorMapChanged.connect(self._updateColorMap)
        self._rc.dataSubsetChanged.connect(self._updateDataSubset)
        self._rc.displayRangeChanged.connect(self._updateDisplayRange)

        # update widgets
        self._setupHistogramPlot()
        self._updateDataSubset(*self._rc.currentSubset())
        self._updateColorMap(image.colorMap())
        self._updateIntensityMap(rc.currentIntensityMap(), rc.currentIntensityMapNumber())
        self._updateDisplayRange(*self._rc.displayRange())
예제 #32
0
파일: main.py 프로젝트: Josh0729/Viper
class MainWindow(QMainWindow, mainwindow_auto.Ui_MainWindow):
    # access variables inside of the UI's file

    CachedWeather = WeatherApi('Initial')
    #AlarmAnimation = QtCore.QPropertyAnimation()
    AlarmInfo = Alarm()
    Pandora = Pandora()
    AlarmCurrentState = 'Closed'
    PandoraCurrentState = 'Closed'
    CalendarCurrentState = 'Closed'
    ANIMATIONDURATION = 650

    def __init__(self):
        super(self.__class__, self).__init__()
        self.setupUi(self) # gets defined in the UI file
        self.initUI()
        
    def initUI(self):
        self.alarmWidget.setGeometry(QtCore.QRect(800, 310, 300, 100))
        self.pandoraWidget.setGeometry(QtCore.QRect(800, 240, 300, 100))
        self.calendarWidget.setGeometry(QtCore.QRect(800, 170, 300, 100))
        
        self.setupTimers()
        
        self.getTime()
        self.getWeather()
        self.setupScrollers()
        #self.alarmSlider.setStyleSheet(self.styleAlarmSlider())
        self.pauseToolButton.hide()
        self.alarmOnToolButton.hide()
           
        self.setupHooks()
        
        self.hourListWidget.setCurrentRow(2)
        self.minuteListWidget.setCurrentRow(2)
        self.amPmListWidget.setCurrentRow(2)
        
    def setupTimers(self):
        self.timer = QtCore.QTimer(self)
        self.timer.timeout.connect(self.getTime)
        self.timer.start(10)
        self.alarmTimer = QtCore.QTimer(self)
        self.alarmTimer.timeout.connect(self.checkAlarm)
        self.alarmTimer.start(10)
        self.weatherTimer = QtCore.QTimer(self)
        self.weatherTimer.timeout.connect(self.getWeather)
        self.weatherTimer.start(600000)
        
    def setupHooks(self):
        self.alarmToolButton.clicked.connect(lambda: self.pressedAlarmButton())
        self.hourListWidget.itemSelectionChanged.connect(lambda: self.hourListWidgetSelectionChange())
        self.minuteListWidget.itemSelectionChanged.connect(lambda: self.minuteListWidgetSelectionChange())
        self.amPmListWidget.itemSelectionChanged.connect(lambda: self.amPmListWidgetSelectionChange())
        self.alarmOnToolButton.clicked.connect(lambda: self.pressedAlarmOnButton())
        self.alarmOffToolButton.clicked.connect(lambda: self.pressedAlarmOffButton())
        self.pandoraToolButton.clicked.connect(lambda: self.pressedPandoraButton())
        self.playToolButton.clicked.connect(lambda: self.pressedPlayButton())
        self.pauseToolButton.clicked.connect(lambda: self.pressedPauseButton())
        self.stopToolButton.clicked.connect(lambda: self.pressedStopButton())
        self.skipToolButton.clicked.connect(lambda: self.pressedSkipButton())
        self.calendarToolButton.clicked.connect(lambda: self.pressedCalendarButton())
        
    def setupStatusbar(self):
        self.alarmStatusWidget = QToolButton()
        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap("images/alarm-clock.png"), QtGui.QIcon.Normal, QtGui.QIcon.On)
        self.alarmStatusWidget.setIcon(icon)
        self.alarmStatusWidget.setFixedSize(QSize(24,24))
        self.alarmStatusWidget.setIconSize(QSize(24,24))
        
        self.statusBar.setStyleSheet("""QStatusBar::item {
                                        border: none;
                                        }""")

    def setupScrollers(self):
        sp = QScrollerProperties()
        sp.setScrollMetric(QScrollerProperties.DragVelocitySmoothingFactor, 0.6)
        sp.setScrollMetric(QScrollerProperties.MinimumVelocity, 0.0)
        sp.setScrollMetric(QScrollerProperties.MaximumVelocity, 0.5)
        sp.setScrollMetric(QScrollerProperties.AcceleratingFlickMaximumTime, 0.4)
        sp.setScrollMetric(QScrollerProperties.AcceleratingFlickSpeedupFactor, 1.2)
        sp.setScrollMetric(QScrollerProperties.SnapPositionRatio, 0.2)
        sp.setScrollMetric(QScrollerProperties.MaximumClickThroughVelocity, 0)
        sp.setScrollMetric(QScrollerProperties.DragStartDistance, 0.001)
        sp.setScrollMetric(QScrollerProperties.MousePressEventDelay, 0.5)
        
        hourListScroller = QScroller.scroller(self.hourListWidget) 
        hourListScroller.grabGesture(self.hourListWidget, QScroller.LeftMouseButtonGesture)
        hourListScroller.setScrollerProperties(sp)
        
        minuteListScroller = QScroller.scroller(self.minuteListWidget)
        minuteListScroller.grabGesture(self.minuteListWidget, QScroller.LeftMouseButtonGesture)
        minuteListScroller.setScrollerProperties(sp)
        
        amPmListScroller = QScroller.scroller(self.amPmListWidget)
        amPmListScroller.grabGesture(self.amPmListWidget, QScroller.LeftMouseButtonGesture)
        amPmListScroller.setScrollerProperties(sp)
                
    def getTime(self):
        self.timeDisplay.display(strftime("%I"+":"+"%M").lstrip('0'))
        self.dateLabel.setText(strftime("%A"+" "+"%B"+" "+"%d"+", "+"%Y"))
        
    def getWeather(self):
        successfulResponse = False
        weatherResponse = WeatherApi('Fetch')
        if weatherResponse.response.status_code == 200:
            weatherConditions = WeatherConditions(weatherResponse.response.text)
            successfulResponse = True
        else:
            if self.CachedWeather.response is not None:
                if self.CachedWeather.response.status_code == 200:
                    weatherConditions = WeatherConditions(self.CachedWeather.response.text)
                    successfulResponse = True
        if successfulResponse == True:
            self.currentWeatherIcon.setPixmap(weatherConditions.pixMap)
            self.temperatureLabel.setText(weatherConditions.temperature + u'\u00b0' + 'F')
            self.currentWeatherDescription.setText(weatherConditions.description)
            self.CachedWeather = weatherResponse

    def pressedAlarmButton(self):
        try:
            alarmButtonCurrentGeometry = QtCore.QRect(self.alarmToolButton.geometry())
            alarmButtonAnimation = QPropertyAnimation(self.alarmToolButton,'geometry'.encode(encoding='utf_8'))
            alarmButtonAnimation.setDuration(self.ANIMATIONDURATION)
            alarmButtonAnimation.setStartValue(alarmButtonCurrentGeometry)
            
            alarmWidgetCurrentGeometry = QtCore.QRect(self.alarmWidget.geometry())
            alarmWidgetAnimation = QPropertyAnimation(self.alarmWidget,'geometry'.encode(encoding='utf_8'))
            alarmWidgetAnimation.setDuration(self.ANIMATIONDURATION)
            alarmWidgetAnimation.setStartValue(alarmWidgetCurrentGeometry)
            
            if self.AlarmCurrentState == 'Closed':
                alarmButtonEndTopLeftCorner = QtCore.QPoint(self.alarmToolButton.pos() - QtCore.QPoint(275, 0))
                alarmWidgetEndTopLeftCorner = QtCore.QPoint(self.alarmWidget.pos() - QtCore.QPoint(275, 0))
                self.AlarmCurrentState = 'Open'
            else:
                alarmButtonEndTopLeftCorner = QtCore.QPoint(self.alarmToolButton.pos() + QtCore.QPoint(275, 0))
                alarmWidgetEndTopLeftCorner = QtCore.QPoint(self.alarmWidget.pos() + QtCore.QPoint(275, 0))
                self.AlarmCurrentState = 'Closed'
            
            alarmButtonFinalGeometry = QtCore.QRect(alarmButtonEndTopLeftCorner, QtCore.QSize(self.alarmToolButton.width(), self.alarmToolButton.height()))
            alarmButtonAnimation.setEndValue(alarmButtonFinalGeometry)
            
            alarmWidgetFinalGeometry = QtCore.QRect(alarmWidgetEndTopLeftCorner, QtCore.QSize(self.alarmWidget.width(), self.alarmWidget.height()))
            alarmWidgetAnimation.setEndValue(alarmWidgetFinalGeometry)
            
            alarmButtonAnimation.start()
            alarmWidgetAnimation.start()
            #self.setAlarmWidgetStyleSheet()
            self.AlarmIconAnimation = alarmButtonAnimation
            self.AlarmWidgetAnimation = alarmWidgetAnimation
        except Exception as e:
            print (e.strerror)
            
    def setAlarmWidgetStyleSheet(self):
        style1 = "background-color: white"
        style2 = "background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0, stop:0 rgba(0, 0, 0, 255), stop:1 rgba(255, 255, 255, 255))"

        # animation doesn't work for strings but provides an appropriate delay
        animation = QtCore.QPropertyAnimation(self.alarmWidget, 'styleSheet'.encode(encoding='utf_8'))
        animation.setDuration(500)

        state1 = QtCore.QState()
        state2 = QtCore.QState()
        state3 = QtCore.QState()
        state1.assignProperty(self.alarmWidget, 'styleSheet', style1)
        state2.assignProperty(self.alarmWidget, 'styleSheet', style2)
        state3.assignProperty(self.alarmWidget, 'styleSheet', style2)
        #              change a state after an animation has played
        #                               v
        state1.addTransition(state1.propertiesAssigned, state2)
        state2.addTransition(state2.propertiesAssigned, state3)

        self.alarmWidget.machine = QtCore.QStateMachine()
        self.alarmWidget.machine.addDefaultAnimation(animation)
        self.alarmWidget.machine.addState(state1)
        self.alarmWidget.machine.addState(state2)
        self.alarmWidget.machine.setInitialState(state1)
        self.alarmWidget.machine.start()

    def hourListWidgetSelectionChange(self):
        value = self.hourListWidget.currentRow()
        if value < 2:
            value = 2
        if value > 13:
            value = 13
        item = self.hourListWidget.item(value)
        self.hourListWidget.scrollToItem(item,QAbstractItemView.PositionAtCenter)
        self.hourListWidget.setCurrentRow(value)
        
    def minuteListWidgetSelectionChange(self):
        value = self.minuteListWidget.currentRow()
        if value < 2:
            value = 2
        if value > 61:
            value = 61
        item = self.minuteListWidget.item(value)
        self.minuteListWidget.scrollToItem(item,QAbstractItemView.PositionAtCenter)
        self.minuteListWidget.setCurrentRow(value)
        
    def amPmListWidgetSelectionChange(self):
        value = self.amPmListWidget.currentRow()
        if value < 2:
            value = 2
        if value > 3:
            value = 3
        item = self.amPmListWidget.item(value)
        self.amPmListWidget.scrollToItem(item,QAbstractItemView.PositionAtCenter)
        self.amPmListWidget.setCurrentRow(value)
        
    def styleAlarmSlider(self):
        return """
            QSlider::groove:horizontal {
                height: 20px; /* the groove expands to the size of the slider by default. by giving it a height, it has a fixed size */
                background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #B1B1B1, stop:1 #c4c4c4);
                margin: 2px 0;
            }
            
            QSlider::sub-page:horizontal {
                background: qlineargradient(x1: 0, y1: 0,    x2: 0, y2: 1,
                    stop: 0 #66e, stop: 1 #bbf);
                background: qlineargradient(x1: 0, y1: 0.2, x2: 1, y2: 1,
                    stop: 0 #bbf, stop: 1 #55f);
                height: 40px;
            }

            QSlider::handle:horizontal {
                background: #bbf;
                border: 0px;
                width: 5px;
                margin-top: 0px;
                margin-bottom: 0px;
                border-radius: 0px;
            }
        """
    
#    def alarmSliderValueChange(self):
#        if self.alarmSlider.value() == 0:
#            self.AlarmInfo.disableAlarm()
#        else:
#            hourItem = self.hourListWidget.currentItem()
#            minuteItem = self.minuteListWidget.currentItem()
#            amPmItem = self.amPmListWidget.currentItem()
#            timeToSet = hourItem.text() + ':' + minuteItem.text() + ' ' + amPmItem.text()
#            self.AlarmInfo.setAlarm(timeToSet)
    
    def pressedAlarmOffButton(self):
        self.alarmOffToolButton.hide()
        self.alarmOnToolButton.show()
        hourItem = self.hourListWidget.currentItem()
        minuteItem = self.minuteListWidget.currentItem()
        amPmItem = self.amPmListWidget.currentItem()
        timeToSet = hourItem.text() + ':' + minuteItem.text() + ' ' + amPmItem.text()
        self.AlarmInfo.setAlarm(timeToSet)
        
        self.statusBar.addWidget(self.alarmStatusWidget)
        self.alarmStatusWidget.show()
            
    def pressedAlarmOnButton(self):
        self.alarmOnToolButton.hide()
        self.alarmOffToolButton.show()
        self.AlarmInfo.disableAlarm()
        self.statusBar.removeWidget(self.alarmStatusWidget)
    
    def pressedPandoraButton(self):
        pandoraButtonCurrentGeometry = QtCore.QRect(self.pandoraToolButton.geometry())
        pandoraButtonAnimation = QPropertyAnimation(self.pandoraToolButton,'geometry'.encode(encoding='utf_8'))
        pandoraButtonAnimation.setDuration(self.ANIMATIONDURATION)
        pandoraButtonAnimation.setStartValue(pandoraButtonCurrentGeometry)
            
        pandoraWidgetCurrentGeometry = QtCore.QRect(self.pandoraWidget.geometry())
        pandoraWidgetAnimation = QPropertyAnimation(self.pandoraWidget,'geometry'.encode(encoding='utf_8'))
        pandoraWidgetAnimation.setDuration(self.ANIMATIONDURATION)
        pandoraWidgetAnimation.setStartValue(pandoraWidgetCurrentGeometry)
            
        if self.PandoraCurrentState == 'Closed':
            pandoraButtonEndTopLeftCorner = QtCore.QPoint(self.pandoraToolButton.pos() - QtCore.QPoint(275, 0))
            pandoraWidgetEndTopLeftCorner = QtCore.QPoint(self.pandoraWidget.pos() - QtCore.QPoint(275, 0))
            self.PandoraCurrentState = 'Open'
            if self.Pandora.started == True:
                self.playToolButton.hide()
                self.pauseToolButton.show()
        else:
            pandoraButtonEndTopLeftCorner = QtCore.QPoint(self.pandoraToolButton.pos() + QtCore.QPoint(275, 0))
            pandoraWidgetEndTopLeftCorner = QtCore.QPoint(self.pandoraWidget.pos() + QtCore.QPoint(275, 0))
            self.PandoraCurrentState = 'Closed'
            
        pandoraButtonFinalGeometry = QtCore.QRect(pandoraButtonEndTopLeftCorner, QtCore.QSize(self.pandoraToolButton.width(), self.pandoraToolButton.height()))
        pandoraButtonAnimation.setEndValue(pandoraButtonFinalGeometry)
            
        pandoraWidgetFinalGeometry = QtCore.QRect(pandoraWidgetEndTopLeftCorner, QtCore.QSize(self.pandoraWidget.width(), self.pandoraWidget.height()))
        pandoraWidgetAnimation.setEndValue(pandoraWidgetFinalGeometry)
            
        pandoraButtonAnimation.start()
        pandoraWidgetAnimation.start()
            #self.setpandoraWidgetStyleSheet()
        self.PandoraIconAnimation = pandoraButtonAnimation
        self.PandoraWidgetAnimation = pandoraWidgetAnimation
        
    def pressedPlayButton(self):
        if self.Pandora.started == False:
            self.songLabel.setText('Loading...')
            self.Pandora.start('*****@*****.**', 'bgc7y9l')
            self.playToolButton.hide()
            self.pauseToolButton.show()
            self.Pandora.read()
            self.songLabel.setText(self.Pandora.output)
        else:
            if self.playToolButton.isvisible():
                self.Pandora.play()
                self.playToolButton.hide()
                self.pauseToolButton.show()
                self.Pandora.read()
                self.songLabel.setText(self.Pandora.output)
            
    def pressedPauseButton(self):
        self.Pandora.pause()
        self.pauseToolButton.hide()
        self.playToolButton.show()
    
    def pressedStopButton(self):
        self.Pandora.stop()
        self.pauseToolButton.hide()
        self.playToolButton.show()
        
    def pressedSkipButton(self):
        self.Pandora.skip()
        
    def checkAlarm(self):
        if self.AlarmInfo.alarmSet == True:
            self.AlarmInfo.checkAlarm()
            if self.AlarmInfo.alarmSet == False:
                self.alarmOnToolButton.hide()
                self.alarmOffToolButton.show()
                #self.pressedPlayButton()
                self.statusBar.removeWidget(self.alarmStatusWidget)
    
    def pressedCalendarButton(self):
        try:
            calendarButtonCurrentGeometry = QtCore.QRect(self.calendarToolButton.geometry())
            calendarButtonAnimation = QPropertyAnimation(self.calendarToolButton,'geometry'.encode(encoding='utf_8'))
            calendarButtonAnimation.setDuration(self.ANIMATIONDURATION)
            calendarButtonAnimation.setStartValue(calendarButtonCurrentGeometry)
            
            calendarWidgetCurrentGeometry = QtCore.QRect(self.calendarWidget.geometry())
            calendarWidgetAnimation = QPropertyAnimation(self.calendarWidget,'geometry'.encode(encoding='utf_8'))
            calendarWidgetAnimation.setDuration(self.ANIMATIONDURATION)
            calendarWidgetAnimation.setStartValue(calendarWidgetCurrentGeometry)
            
            if self.CalendarCurrentState == 'Closed':
                calendarButtonEndTopLeftCorner = QtCore.QPoint(self.calendarToolButton.pos() - QtCore.QPoint(275, 0))
                calendarWidgetEndTopLeftCorner = QtCore.QPoint(self.calendarWidget.pos() - QtCore.QPoint(275, 0))
                self.CalendarCurrentState = 'Open'
                self.Calendar = Calendar()
                self.calendarLabel.setText(self.Calendar.EventList)
            else:
                calendarButtonEndTopLeftCorner = QtCore.QPoint(self.calendarToolButton.pos() + QtCore.QPoint(275, 0))
                calendarWidgetEndTopLeftCorner = QtCore.QPoint(self.calendarWidget.pos() + QtCore.QPoint(275, 0))
                self.CalendarCurrentState = 'Closed'
            
            calendarButtonFinalGeometry = QtCore.QRect(calendarButtonEndTopLeftCorner, QtCore.QSize(self.calendarToolButton.width(), self.calendarToolButton.height()))
            calendarButtonAnimation.setEndValue(calendarButtonFinalGeometry)
            
            calendarWidgetFinalGeometry = QtCore.QRect(calendarWidgetEndTopLeftCorner, QtCore.QSize(self.calendarWidget.width(), self.calendarWidget.height()))
            calendarWidgetAnimation.setEndValue(calendarWidgetFinalGeometry)
            
            calendarButtonAnimation.start()
            calendarWidgetAnimation.start()
            #self.setcalendarWidgetStyleSheet()
            self.CalendarIconAnimation = calendarButtonAnimation
            self.CalendarWidgetAnimation = calendarWidgetAnimation
        except Exception as e:
            print (e.strerror)
예제 #33
0
파일: diff.py 프로젝트: Cykooz/calibre
    def __init__(
            self, field_metadata, parent=None, revert_tooltip=None,
            datetime_fmt='MMMM yyyy', blank_as_equal=True,
            fields=('title', 'authors', 'series', 'tags', 'rating', 'publisher', 'pubdate', 'identifiers', 'languages', 'comments', 'cover')):
        QWidget.__init__(self, parent)
        self.l = l = QGridLayout()
        l.setContentsMargins(0, 0, 0, 0)
        self.setLayout(l)
        revert_tooltip = revert_tooltip or _('Revert %s')
        self.current_mi = None
        self.changed_font = QFont(QApplication.font())
        self.changed_font.setBold(True)
        self.changed_font.setItalic(True)
        self.blank_as_equal = blank_as_equal

        self.widgets = OrderedDict()
        row = 0

        for field in fields:
            m = field_metadata[field]
            dt = m['datatype']
            extra = None
            if 'series' in {field, dt}:
                cls = SeriesEdit
            elif field == 'identifiers':
                cls = IdentifiersEdit
            elif field == 'languages':
                cls = LanguagesEdit
            elif 'comments' in {field, dt}:
                cls = CommentsEdit
            elif 'rating' in {field, dt}:
                cls = RatingsEdit
            elif dt == 'datetime':
                extra = datetime_fmt
                cls = DateEdit
            elif field == 'cover':
                cls = CoverView
            elif dt in {'text', 'enum'}:
                cls = LineEdit
            else:
                continue
            neww = cls(field, True, self, m, extra)
            neww.changed.connect(partial(self.changed, field))
            oldw = cls(field, False, self, m, extra)
            newl = QLabel('&%s:' % m['name'])
            newl.setBuddy(neww)
            button = QToolButton(self)
            button.setIcon(QIcon(I('back.png')))
            button.clicked.connect(partial(self.revert, field))
            button.setToolTip(revert_tooltip % m['name'])
            self.widgets[field] = Widgets(neww, oldw, newl, button)
            for i, w in enumerate((newl, neww, button, oldw)):
                c = i if i < 2 else i + 1
                if w is oldw:
                    c += 1
                l.addWidget(w, row, c)
            row += 1

        self.sep = f = QFrame(self)
        f.setFrameShape(f.VLine)
        l.addWidget(f, 0, 2, row, 1)
        self.sep2 = f = QFrame(self)
        f.setFrameShape(f.VLine)
        l.addWidget(f, 0, 4, row, 1)
        if 'comments' in self.widgets and not gprefs.get('diff_widget_show_comments_controls', True):
            self.widgets['comments'].new.hide_toolbars()
예제 #34
0
class FontFamilyDialog(QDialog):

    def __init__(self, current_family, parent=None):
        QDialog.__init__(self, parent)
        self.setWindowTitle(_('Choose font family'))
        self.setWindowIcon(QIcon(I('font.png')))
        from calibre.utils.fonts.scanner import font_scanner
        self.font_scanner = font_scanner

        self.m = QStringListModel(self)
        self.build_font_list()
        self.l = l = QGridLayout()
        self.setLayout(l)
        self.view = FontsView(self)
        self.view.setModel(self.m)
        self.view.setCurrentIndex(self.m.index(0))
        if current_family:
            for i, val in enumerate(self.families):
                if icu_lower(val) == icu_lower(current_family):
                    self.view.setCurrentIndex(self.m.index(i))
                    break
        self.view.doubleClicked.connect(self.accept, type=Qt.QueuedConnection)
        self.view.changed.connect(self.current_changed,
                type=Qt.QueuedConnection)
        self.faces = Typefaces(self)
        self.bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
        self.bb.accepted.connect(self.accept)
        self.bb.rejected.connect(self.reject)
        self.add_fonts_button = afb = self.bb.addButton(_('Add &fonts'),
                self.bb.ActionRole)
        afb.setIcon(QIcon(I('plus.png')))
        afb.clicked.connect(self.add_fonts)
        self.ml = QLabel(_('Choose a font family from the list below:'))
        self.search = QLineEdit(self)
        self.search.setPlaceholderText(_('Search'))
        self.search.returnPressed.connect(self.find)
        self.nb = QToolButton(self)
        self.nb.setIcon(QIcon(I('arrow-down.png')))
        self.nb.setToolTip(_('Find next'))
        self.pb = QToolButton(self)
        self.pb.setIcon(QIcon(I('arrow-up.png')))
        self.pb.setToolTip(_('Find previous'))
        self.nb.clicked.connect(self.find_next)
        self.pb.clicked.connect(self.find_previous)

        l.addWidget(self.ml, 0, 0, 1, 4)
        l.addWidget(self.search, 1, 0, 1, 1)
        l.addWidget(self.nb, 1, 1, 1, 1)
        l.addWidget(self.pb, 1, 2, 1, 1)
        l.addWidget(self.view, 2, 0, 1, 3)
        l.addWidget(self.faces, 1, 3, 2, 1)
        l.addWidget(self.bb, 3, 0, 1, 4)
        l.setAlignment(self.faces, Qt.AlignTop)

        self.resize(800, 600)

    def set_current(self, i):
        self.view.setCurrentIndex(self.m.index(i))

    def keyPressEvent(self, e):
        if e.key() == Qt.Key_Return:
            return
        return QDialog.keyPressEvent(self, e)

    def find(self, backwards=False):
        i = self.view.currentIndex().row()
        if i < 0:
            i = 0
        q = icu_lower(unicode(self.search.text())).strip()
        if not q:
            return
        r = (xrange(i-1, -1, -1) if backwards else xrange(i+1,
            len(self.families)))
        for j in r:
            f = self.families[j]
            if q in icu_lower(f):
                self.set_current(j)
                return

    def find_next(self):
        self.find()

    def find_previous(self):
        self.find(backwards=True)

    def build_font_list(self):
        try:
            self.families = list(self.font_scanner.find_font_families())
        except:
            self.families = []
            print ('WARNING: Could not load fonts')
            import traceback
            traceback.print_exc()
        self.families.insert(0, _('None'))
        self.m.setStringList(self.families)

    def add_fonts(self):
        families = add_fonts(self)
        if not families:
            return
        self.font_scanner.do_scan()
        self.m.beginResetModel()
        self.build_font_list()
        self.m.endResetModel()
        self.view.setCurrentIndex(self.m.index(0))
        if families:
            for i, val in enumerate(self.families):
                if icu_lower(val) == icu_lower(families[0]):
                    self.view.setCurrentIndex(self.m.index(i))
                    break

        info_dialog(self, _('Added fonts'),
                _('Added font families: %s')%(
                    ', '.join(families)), show=True)

    @property
    def font_family(self):
        idx = self.view.currentIndex().row()
        if idx == 0:
            return None
        return self.families[idx]

    def current_changed(self):
        fam = self.font_family
        self.faces.show_family(fam, self.font_scanner.fonts_for_family(fam)
                if fam else None)
예제 #35
0
class TagBrowserBar(QWidget):  # {{{

    clear_find = pyqtSignal()

    def __init__(self, parent):
        QWidget.__init__(self, parent)
        self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Preferred)
        parent = parent.parent()
        self.l = l = QHBoxLayout(self)
        l.setContentsMargins(0, 0, 0, 0)
        self.alter_tb = parent.alter_tb = b = QToolButton(self)
        b.setAutoRaise(True)
        b.setText(_('Configure')), b.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        b.setCursor(Qt.PointingHandCursor)
        b.setPopupMode(b.InstantPopup)
        b.setToolTip(textwrap.fill(_(
            'Change how the Tag browser works, such as,'
            ' how it is sorted, what happens when you click'
            ' items, etc.'
        )))
        b.setIcon(QIcon(I('config.png')))
        b.m = QMenu()
        b.setMenu(b.m)

        self.item_search = FindBox(parent)
        self.item_search.setMinimumContentsLength(5)
        self.item_search.setSizeAdjustPolicy(self.item_search.AdjustToMinimumContentsLengthWithIcon)
        self.item_search.initialize('tag_browser_search')
        self.item_search.completer().setCaseSensitivity(Qt.CaseSensitive)
        self.item_search.setToolTip(
            '<p>' +_(
                'Search for items. If the text begins with equals (=) the search is '
                'exact match, otherwise it is "contains" finding items containing '
                'the text anywhere in the item name. Both exact and contains '
                'searches ignore case. You can limit the search to particular '
                'categories using syntax similar to search. For example, '
                'tags:foo will find foo in any tag, but not in authors etc. Entering '
                '*foo will collapse all categories then showing only those categories '
                'with items containing the text "foo"') + '</p')
        ac = QAction(parent)
        parent.addAction(ac)
        parent.keyboard.register_shortcut('tag browser find box',
                _('Find next match'), default_keys=(),
                action=ac, group=_('Tag browser'))
        ac.triggered.connect(self.set_focus_to_find_box)

        self.search_button = QToolButton()
        self.search_button.setAutoRaise(True)
        self.search_button.setCursor(Qt.PointingHandCursor)
        self.search_button.setIcon(QIcon(I('search.png')))
        self.search_button.setToolTip(_('Find the first/next matching item'))
        ac = QAction(parent)
        parent.addAction(ac)
        parent.keyboard.register_shortcut('tag browser find button',
                _('Find in Tag browser'), default_keys=(),
                action=ac, group=_('Tag browser'))
        ac.triggered.connect(self.search_button.click)

        self.toggle_search_button = b = QToolButton(self)
        le = self.item_search.lineEdit()
        le.addAction(QIcon(I('window-close.png')), le.LeadingPosition).triggered.connect(self.close_find_box)
        b.setText(_('Find')), b.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        b.setCursor(Qt.PointingHandCursor)
        b.setIcon(QIcon(I('search.png')))
        b.setCheckable(True)
        b.setChecked(gprefs.get('tag browser search box visible', False))
        b.setToolTip(_('Search for items in the Tag browser'))
        b.setAutoRaise(True)
        b.toggled.connect(self.update_searchbar_state)
        self.update_searchbar_state()

    def close_find_box(self):
        self.item_search.setCurrentIndex(0)
        self.item_search.setCurrentText('')
        self.toggle_search_button.click()
        self.clear_find.emit()

    def set_focus_to_find_box(self):
        self.toggle_search_button.setChecked(True)
        self.item_search.setFocus()
        self.item_search.lineEdit().selectAll()

    def update_searchbar_state(self):
        find_shown = self.toggle_search_button.isChecked()
        self.toggle_search_button.setVisible(not find_shown)
        l = self.layout()
        items = [l.itemAt(i) for i in range(l.count())]
        tuple(map(l.removeItem, items))
        if find_shown:
            l.addWidget(self.alter_tb)
            self.alter_tb.setToolButtonStyle(Qt.ToolButtonIconOnly)
            l.addWidget(self.item_search, 10)
            l.addWidget(self.search_button)
            self.item_search.setFocus(Qt.OtherFocusReason)
            self.toggle_search_button.setVisible(False)
            self.search_button.setVisible(True)
            self.item_search.setVisible(True)
        else:
            l.addWidget(self.alter_tb)
            self.alter_tb.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
            l.addStretch(10)
            l.addStretch(10)
            l.addWidget(self.toggle_search_button)
            self.toggle_search_button.setVisible(True)
            self.search_button.setVisible(False)
            self.item_search.setVisible(False)
예제 #36
0
class MetadataSingleDialogBase(ResizableDialog):

    view_format = pyqtSignal(object, object)
    cc_two_column = tweaks['metadata_single_use_2_cols_for_custom_fields']
    one_line_comments_toolbar = False
    use_toolbutton_for_config_metadata = True

    def __init__(self, db, parent=None):
        self.db = db
        self.changed = set()
        self.books_to_refresh = set()
        self.rows_to_refresh = set()
        self.metadata_before_fetch = None
        ResizableDialog.__init__(self, parent)

    def setupUi(self, *args):  # {{{
        self.resize(990, 670)

        self.download_shortcut = QShortcut(self)
        self.download_shortcut.setKey(
            QKeySequence('Ctrl+D', QKeySequence.PortableText))
        p = self.parent()
        if hasattr(p, 'keyboard'):
            kname = u'Interface Action: Edit Metadata (Edit Metadata) : menu action : download'
            sc = p.keyboard.keys_map.get(kname, None)
            if sc:
                self.download_shortcut.setKey(sc[0])

        self.button_box = bb = QDialogButtonBox(self)
        self.button_box.accepted.connect(self.accept)
        self.button_box.rejected.connect(self.reject)
        self.next_button = QPushButton(QIcon(I('forward.png')), _('Next'),
                                       self)
        self.next_button.setShortcut(QKeySequence('Alt+Right'))
        self.next_button.clicked.connect(self.next_clicked)
        self.prev_button = QPushButton(QIcon(I('back.png')), _('Previous'),
                                       self)
        self.prev_button.setShortcut(QKeySequence('Alt+Left'))

        self.button_box.addButton(self.prev_button, bb.ActionRole)
        self.button_box.addButton(self.next_button, bb.ActionRole)
        self.prev_button.clicked.connect(self.prev_clicked)
        bb.setStandardButtons(bb.Ok | bb.Cancel)
        bb.button(bb.Ok).setDefault(True)

        self.scroll_area = QScrollArea(self)
        self.scroll_area.setFrameShape(QScrollArea.NoFrame)
        self.scroll_area.setWidgetResizable(True)
        self.central_widget = QTabWidget(self)
        self.scroll_area.setWidget(self.central_widget)

        self.l = QVBoxLayout(self)
        self.setLayout(self.l)
        self.l.addWidget(self.scroll_area)
        ll = self.button_box_layout = QHBoxLayout()
        self.l.addLayout(ll)
        ll.addSpacing(10)
        ll.addWidget(self.button_box)

        self.setWindowIcon(QIcon(I('edit_input.png')))
        self.setWindowTitle(BASE_TITLE)

        self.create_basic_metadata_widgets()

        if len(self.db.custom_column_label_map):
            self.create_custom_metadata_widgets()

        self.do_layout()
        geom = gprefs.get('metasingle_window_geometry3', None)
        if geom is not None:
            self.restoreGeometry(bytes(geom))

    # }}}

    def create_basic_metadata_widgets(self):  # {{{
        self.basic_metadata_widgets = []

        self.languages = LanguagesEdit(self)
        self.basic_metadata_widgets.append(self.languages)

        self.title = TitleEdit(self)
        self.title.textChanged.connect(self.update_window_title)
        self.deduce_title_sort_button = QToolButton(self)
        self.deduce_title_sort_button.setToolTip(
            _('Automatically create the title sort entry based on the current '
              'title entry.\nUsing this button to create title sort will '
              'change title sort from red to green.'))
        self.deduce_title_sort_button.setWhatsThis(
            self.deduce_title_sort_button.toolTip())
        self.title_sort = TitleSortEdit(self, self.title,
                                        self.deduce_title_sort_button,
                                        self.languages)
        self.basic_metadata_widgets.extend([self.title, self.title_sort])

        self.deduce_author_sort_button = b = QToolButton(self)
        b.setToolTip('<p>' + _(
            'Automatically create the author sort entry based on the current '
            'author entry. Using this button to create author sort will '
            'change author sort from red to green.  There is a menu of '
            'functions available under this button. Click and hold '
            'on the button to see it.') + '</p>')
        if isosx:
            # Workaround for https://bugreports.qt-project.org/browse/QTBUG-41017
            class Menu(QMenu):
                def mouseReleaseEvent(self, ev):
                    ac = self.actionAt(ev.pos())
                    if ac is not None:
                        ac.trigger()
                    return QMenu.mouseReleaseEvent(self, ev)

            b.m = m = Menu()
        else:
            b.m = m = QMenu()
        ac = m.addAction(QIcon(I('forward.png')),
                         _('Set author sort from author'))
        ac2 = m.addAction(QIcon(I('back.png')),
                          _('Set author from author sort'))
        ac3 = m.addAction(QIcon(I('user_profile.png')), _('Manage authors'))
        ac4 = m.addAction(QIcon(I('next.png')),
                          _('Copy author to author sort'))
        ac5 = m.addAction(QIcon(I('previous.png')),
                          _('Copy author sort to author'))

        b.setMenu(m)
        self.authors = AuthorsEdit(self, ac3)
        self.author_sort = AuthorSortEdit(self, self.authors, b, self.db, ac,
                                          ac2, ac4, ac5)
        self.basic_metadata_widgets.extend([self.authors, self.author_sort])

        self.swap_title_author_button = QToolButton(self)
        self.swap_title_author_button.setIcon(QIcon(I('swap.png')))
        self.swap_title_author_button.setToolTip(
            _('Swap the author and title'))
        self.swap_title_author_button.clicked.connect(self.swap_title_author)

        self.manage_authors_button = QToolButton(self)
        self.manage_authors_button.setIcon(QIcon(I('user_profile.png')))
        self.manage_authors_button.setToolTip(
            '<p>' + _('Manage authors. Use to rename authors and correct '
                      'individual author\'s sort values') + '</p>')
        self.manage_authors_button.clicked.connect(self.authors.manage_authors)

        self.series = SeriesEdit(self)
        self.clear_series_button = QToolButton(self)
        self.clear_series_button.setToolTip(_('Clear series'))
        self.clear_series_button.clicked.connect(self.series.clear)
        self.series_index = SeriesIndexEdit(self, self.series)
        self.basic_metadata_widgets.extend([self.series, self.series_index])

        self.formats_manager = FormatsManager(self, self.copy_fmt)
        # We want formats changes to be committed before title/author, as
        # otherwise we could have data loss if the title/author changed and the
        # user was trying to add an extra file from the old books directory.
        self.basic_metadata_widgets.insert(0, self.formats_manager)
        self.formats_manager.metadata_from_format_button.clicked.connect(
            self.metadata_from_format)
        self.formats_manager.cover_from_format_button.clicked.connect(
            self.cover_from_format)
        self.cover = Cover(self)
        self.cover.download_cover.connect(self.download_cover)
        self.basic_metadata_widgets.append(self.cover)

        self.comments = CommentsEdit(self, self.one_line_comments_toolbar)
        self.basic_metadata_widgets.append(self.comments)

        self.rating = RatingEdit(self)
        self.clear_ratings_button = QToolButton(self)
        self.clear_ratings_button.setToolTip(_('Clear rating'))
        self.clear_ratings_button.setIcon(QIcon(I('trash.png')))
        self.clear_ratings_button.clicked.connect(self.rating.zero)

        self.basic_metadata_widgets.append(self.rating)

        self.tags = TagsEdit(self)
        self.tags_editor_button = QToolButton(self)
        self.tags_editor_button.setToolTip(_('Open Tag Editor'))
        self.tags_editor_button.setIcon(QIcon(I('chapters.png')))
        self.tags_editor_button.clicked.connect(self.tags_editor)
        self.clear_tags_button = QToolButton(self)
        self.clear_tags_button.setToolTip(_('Clear all tags'))
        self.clear_tags_button.setIcon(QIcon(I('trash.png')))
        self.clear_tags_button.clicked.connect(self.tags.clear)
        self.basic_metadata_widgets.append(self.tags)

        self.identifiers = IdentifiersEdit(self)
        self.basic_metadata_widgets.append(self.identifiers)
        self.clear_identifiers_button = QToolButton(self)
        self.clear_identifiers_button.setIcon(QIcon(I('trash.png')))
        self.clear_identifiers_button.setToolTip(_('Clear Ids'))
        self.clear_identifiers_button.clicked.connect(self.identifiers.clear)
        self.paste_isbn_button = QToolButton(self)
        self.paste_isbn_button.setToolTip(
            '<p>' + _('Paste the contents of the clipboard into the '
                      'identifiers box prefixed with isbn:') + '</p>')
        self.paste_isbn_button.setIcon(QIcon(I('edit-paste.png')))
        self.paste_isbn_button.clicked.connect(self.identifiers.paste_isbn)

        self.publisher = PublisherEdit(self)
        self.basic_metadata_widgets.append(self.publisher)

        self.timestamp = DateEdit(self)
        self.pubdate = PubdateEdit(self)
        self.basic_metadata_widgets.extend([self.timestamp, self.pubdate])

        self.fetch_metadata_button = b = QToolButton(self)
        b.setText(_('&Download metadata')), b.setPopupMode(b.DelayedPopup)
        b.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed))
        self.fetch_metadata_button.clicked.connect(self.fetch_metadata)
        self.fetch_metadata_menu = m = QMenu(self.fetch_metadata_button)
        m.addAction(QIcon(I('edit-undo.png')),
                    _('Undo last metadata download'), self.undo_fetch_metadata)
        self.fetch_metadata_button.setMenu(m)
        self.download_shortcut.activated.connect(
            self.fetch_metadata_button.click)
        font = self.fmb_font = QFont()
        font.setBold(True)
        self.fetch_metadata_button.setFont(font)

        if self.use_toolbutton_for_config_metadata:
            self.config_metadata_button = QToolButton(self)
            self.config_metadata_button.setIcon(QIcon(I('config.png')))
        else:
            self.config_metadata_button = QPushButton(self)
            self.config_metadata_button.setText(
                _('Configure download metadata'))
        self.config_metadata_button.setIcon(QIcon(I('config.png')))
        self.config_metadata_button.clicked.connect(self.configure_metadata)
        self.config_metadata_button.setToolTip(
            _('Change how calibre downloads metadata'))

    # }}}

    def create_custom_metadata_widgets(self):  # {{{
        self.custom_metadata_widgets_parent = w = QWidget(self)
        layout = QGridLayout()
        w.setLayout(layout)
        self.custom_metadata_widgets, self.__cc_spacers = \
            populate_metadata_page(layout, self.db, None, parent=w, bulk=False,
                two_column=self.cc_two_column)
        self.__custom_col_layouts = [layout]

    # }}}

    def set_custom_metadata_tab_order(self, before=None, after=None):  # {{{
        sto = QWidget.setTabOrder
        if getattr(self, 'custom_metadata_widgets', []):
            ans = self.custom_metadata_widgets
            for i in range(len(ans) - 1):
                if before is not None and i == 0:
                    pass
                if len(ans[i + 1].widgets) == 2:
                    sto(ans[i].widgets[-1], ans[i + 1].widgets[1])
                else:
                    sto(ans[i].widgets[-1], ans[i + 1].widgets[0])
                for c in range(2, len(ans[i].widgets), 2):
                    sto(ans[i].widgets[c - 1], ans[i].widgets[c + 1])
            if after is not None:
                pass

    # }}}

    def do_view_format(self, path, fmt):
        if path:
            self.view_format.emit(None, path)
        else:
            self.view_format.emit(self.book_id, fmt)

    def copy_fmt(self, fmt, f):
        self.db.copy_format_to(self.book_id, fmt, f, index_is_id=True)

    def do_layout(self):
        raise NotImplementedError()

    def __call__(self, id_):
        self.book_id = id_
        self.books_to_refresh = set([])
        self.metadata_before_fetch = None
        for widget in self.basic_metadata_widgets:
            widget.initialize(self.db, id_)
        for widget in getattr(self, 'custom_metadata_widgets', []):
            widget.initialize(id_)
        if callable(self.set_current_callback):
            self.set_current_callback(id_)
        # Commented out as it doesn't play nice with Next, Prev buttons
        # self.fetch_metadata_button.setFocus(Qt.OtherFocusReason)

    # Miscellaneous interaction methods {{{
    def update_window_title(self, *args):
        title = self.title.current_val
        if len(title) > 50:
            title = title[:50] + u'\u2026'
        self.setWindowTitle(
            BASE_TITLE + ' - ' + title + ' - ' + _(' [%(num)d of %(tot)d]') %
            dict(num=self.current_row + 1, tot=len(self.row_list)))

    def swap_title_author(self, *args):
        title = self.title.current_val
        self.title.current_val = authors_to_string(self.authors.current_val)
        self.authors.current_val = string_to_authors(title)
        self.title_sort.auto_generate()
        self.author_sort.auto_generate()

    def tags_editor(self, *args):
        self.tags.edit(self.db, self.book_id)

    def metadata_from_format(self, *args):
        mi, ext = self.formats_manager.get_selected_format_metadata(
            self.db, self.book_id)
        if mi is not None:
            self.update_from_mi(mi)

    def get_pdf_cover(self):
        pdfpath = self.formats_manager.get_format_path(self.db, self.book_id,
                                                       'pdf')
        from calibre.gui2.metadata.pdf_covers import PDFCovers
        d = PDFCovers(pdfpath, parent=self)
        if d.exec_() == d.Accepted:
            cpath = d.cover_path
            if cpath:
                with open(cpath, 'rb') as f:
                    self.update_cover(f.read(), 'PDF')
        d.cleanup()

    def cover_from_format(self, *args):
        ext = self.formats_manager.get_selected_format()
        if ext is None:
            return
        if ext == 'pdf':
            return self.get_pdf_cover()
        try:
            mi, ext = self.formats_manager.get_selected_format_metadata(
                self.db, self.book_id)
        except (IOError, OSError) as err:
            if getattr(err, 'errno',
                       None) == errno.EACCES:  # Permission denied
                import traceback
                fname = err.filename if err.filename else 'file'
                error_dialog(self,
                             _('Permission denied'),
                             _('Could not open %s. Is it being used by another'
                               ' program?') % fname,
                             det_msg=traceback.format_exc(),
                             show=True)
                return
            raise
        if mi is None:
            return
        cdata = None
        if mi.cover and os.access(mi.cover, os.R_OK):
            cdata = open(mi.cover).read()
        elif mi.cover_data[1] is not None:
            cdata = mi.cover_data[1]
        if cdata is None:
            error_dialog(self, _('Could not read cover'),
                         _('Could not read cover from %s format') %
                         ext).exec_()
            return
        self.update_cover(cdata, ext)

    def update_cover(self, cdata, fmt):
        orig = self.cover.current_val
        self.cover.current_val = cdata
        if self.cover.current_val is None:
            self.cover.current_val = orig
            return error_dialog(self,
                                _('Could not read cover'),
                                _('The cover in the %s format is invalid') %
                                fmt,
                                show=True)
            return

    def update_from_mi(self,
                       mi,
                       update_sorts=True,
                       merge_tags=True,
                       merge_comments=False):
        fw = self.focusWidget()
        if not mi.is_null('title'):
            self.title.set_value(mi.title)
            if update_sorts:
                self.title_sort.auto_generate()
        if not mi.is_null('authors'):
            self.authors.set_value(mi.authors)
        if not mi.is_null('author_sort'):
            self.author_sort.set_value(mi.author_sort)
        elif update_sorts:
            self.author_sort.auto_generate()
        if not mi.is_null('rating'):
            try:
                self.rating.set_value(mi.rating)
            except:
                pass
        if not mi.is_null('publisher'):
            self.publisher.set_value(mi.publisher)
        if not mi.is_null('tags'):
            old_tags = self.tags.current_val
            tags = mi.tags if mi.tags else []
            if old_tags and merge_tags:
                ltags, lotags = {t.lower()
                                 for t in tags}, {t.lower()
                                                  for t in old_tags}
                tags = [t for t in tags if t.lower() in ltags - lotags
                        ] + old_tags
            self.tags.set_value(tags)
        if not mi.is_null('identifiers'):
            current = self.identifiers.current_val
            current.update(mi.identifiers)
            self.identifiers.set_value(current)
        if not mi.is_null('pubdate'):
            self.pubdate.set_value(mi.pubdate)
        if not mi.is_null('series') and mi.series.strip():
            self.series.set_value(mi.series)
            if mi.series_index is not None:
                self.series_index.reset_original()
                self.series_index.set_value(float(mi.series_index))
        if not mi.is_null('languages'):
            langs = [canonicalize_lang(x) for x in mi.languages]
            langs = [x for x in langs if x is not None]
            if langs:
                self.languages.set_value(langs)
        if mi.comments and mi.comments.strip():
            val = mi.comments
            if val and merge_comments:
                cval = self.comments.current_val
                if cval:
                    val = merge_two_comments(cval, val)
            self.comments.set_value(val)
        if fw is not None:
            fw.setFocus(Qt.OtherFocusReason)

    def fetch_metadata(self, *args):
        d = FullFetch(self.cover.pixmap(), self)
        ret = d.start(title=self.title.current_val,
                      authors=self.authors.current_val,
                      identifiers=self.identifiers.current_val)
        if ret == d.Accepted:
            self.metadata_before_fetch = {
                f: getattr(self, f).current_val
                for f in fetched_fields
            }
            from calibre.ebooks.metadata.sources.prefs import msprefs
            mi = d.book
            dummy = Metadata(_('Unknown'))
            for f in msprefs['ignore_fields']:
                if ':' not in f:
                    setattr(mi, f, getattr(dummy, f))
            if mi is not None:
                pd = mi.pubdate
                if pd is not None:
                    # Put the downloaded published date into the local timezone
                    # as we discard time info and the date is timezone
                    # invariant. This prevents the as_local_timezone() call in
                    # update_from_mi from changing the pubdate
                    mi.pubdate = datetime(pd.year,
                                          pd.month,
                                          pd.day,
                                          tzinfo=local_tz)
                self.update_from_mi(mi,
                                    merge_comments=msprefs['append_comments'])
            if d.cover_pixmap is not None:
                self.metadata_before_fetch['cover'] = self.cover.current_val
                self.cover.current_val = pixmap_to_data(d.cover_pixmap)

    def undo_fetch_metadata(self):
        if self.metadata_before_fetch is None:
            return error_dialog(self,
                                _('No downloaded metadata'),
                                _('There is no downloaded metadata to undo'),
                                show=True)
        for field, val in self.metadata_before_fetch.iteritems():
            getattr(self, field).current_val = val
        self.metadata_before_fetch = None

    def configure_metadata(self):
        from calibre.gui2.preferences import show_config_widget
        gui = self.parent()
        show_config_widget('Sharing',
                           'Metadata download',
                           parent=self,
                           gui=gui,
                           never_shutdown=True)

    def download_cover(self, *args):
        from calibre.gui2.metadata.single_download import CoverFetch
        d = CoverFetch(self.cover.pixmap(), self)
        ret = d.start(self.title.current_val, self.authors.current_val,
                      self.identifiers.current_val)
        if ret == d.Accepted:
            if d.cover_pixmap is not None:
                self.cover.current_val = pixmap_to_data(d.cover_pixmap)

    # }}}

    def to_book_metadata(self):
        mi = Metadata(_('Unknown'))
        if self.db is None:
            return mi
        mi.set_all_user_metadata(
            self.db.field_metadata.custom_field_metadata())
        for widget in self.basic_metadata_widgets:
            widget.apply_to_metadata(mi)
        for widget in getattr(self, 'custom_metadata_widgets', []):
            widget.apply_to_metadata(mi)
        return mi

    def apply_changes(self):
        self.changed.add(self.book_id)
        if self.db is None:
            # break_cycles has already been called, don't know why this should
            # happen but a user reported it
            return True
        for widget in self.basic_metadata_widgets:
            try:
                if hasattr(widget, 'validate_for_commit'):
                    title, msg, det_msg = widget.validate_for_commit()
                    if title is not None:
                        error_dialog(self,
                                     title,
                                     msg,
                                     det_msg=det_msg,
                                     show=True)
                        return False
                widget.commit(self.db, self.book_id)
                self.books_to_refresh |= getattr(widget, 'books_to_refresh',
                                                 set())
            except (IOError, OSError) as err:
                if getattr(err, 'errno',
                           None) == errno.EACCES:  # Permission denied
                    import traceback
                    fname = getattr(err, 'filename', None)
                    p = 'Locked file: %s\n\n' % fname if fname else ''
                    error_dialog(
                        self,
                        _('Permission denied'),
                        _('Could not change the on disk location of this'
                          ' book. Is it open in another program?'),
                        det_msg=p + traceback.format_exc(),
                        show=True)
                    return False
                raise
        for widget in getattr(self, 'custom_metadata_widgets', []):
            self.books_to_refresh |= widget.commit(self.book_id)

        self.db.commit()
        rows = self.db.refresh_ids(list(self.books_to_refresh))
        if rows:
            self.rows_to_refresh |= set(rows)

        return True

    def accept(self):
        self.save_state()
        if not self.apply_changes():
            return
        ResizableDialog.accept(self)

    def reject(self):
        self.save_state()
        ResizableDialog.reject(self)

    def save_state(self):
        try:
            gprefs['metasingle_window_geometry3'] = bytearray(
                self.saveGeometry())
        except:
            # Weird failure, see https://bugs.launchpad.net/bugs/995271
            import traceback
            traceback.print_exc()

    # Dialog use methods {{{
    def start(self,
              row_list,
              current_row,
              view_slot=None,
              set_current_callback=None):
        self.row_list = row_list
        self.current_row = current_row
        if view_slot is not None:
            self.view_format.connect(view_slot)
        self.set_current_callback = set_current_callback
        self.do_one(apply_changes=False)
        ret = self.exec_()
        self.break_cycles()
        return ret

    def next_clicked(self):
        if not self.apply_changes():
            return
        self.do_one(delta=1, apply_changes=False)

    def prev_clicked(self):
        if not self.apply_changes():
            return
        self.do_one(delta=-1, apply_changes=False)

    def do_one(self, delta=0, apply_changes=True):
        if apply_changes:
            self.apply_changes()
        self.current_row += delta
        prev = next_ = None
        if self.current_row > 0:
            prev = self.db.title(self.row_list[self.current_row - 1])
        if self.current_row < len(self.row_list) - 1:
            next_ = self.db.title(self.row_list[self.current_row + 1])

        if next_ is not None:
            tip = (_('Save changes and edit the metadata of %s') +
                   ' [Alt+Right]') % next_
            self.next_button.setToolTip(tip)
        self.next_button.setEnabled(next_ is not None)
        if prev is not None:
            tip = (_('Save changes and edit the metadata of %s') +
                   ' [Alt+Left]') % prev
            self.prev_button.setToolTip(tip)
        self.prev_button.setEnabled(prev is not None)
        self.button_box.button(self.button_box.Ok).setDefault(True)
        self.button_box.button(self.button_box.Ok).setFocus(
            Qt.OtherFocusReason)
        self(self.db.id(self.row_list[self.current_row]))

    def break_cycles(self):
        # Break any reference cycles that could prevent python
        # from garbage collecting this dialog
        self.set_current_callback = self.db = None
        self.metadata_before_fetch = None

        def disconnect(signal):
            try:
                signal.disconnect()
            except:
                pass  # Fails if view format was never connected

        disconnect(self.view_format)
        for b in ('next_button', 'prev_button'):
            x = getattr(self, b, None)
            if x is not None:
                disconnect(x.clicked)
        for widget in self.basic_metadata_widgets:
            bc = getattr(widget, 'break_cycles', None)
            if bc is not None and callable(bc):
                bc()
        for widget in getattr(self, 'custom_metadata_widgets', []):
            widget.break_cycles()
예제 #37
0
class MetadataSingleDialogBase(ResizableDialog):

    view_format = pyqtSignal(object, object)
    cc_two_column = tweaks["metadata_single_use_2_cols_for_custom_fields"]
    one_line_comments_toolbar = False
    use_toolbutton_for_config_metadata = True

    def __init__(self, db, parent=None):
        self.db = db
        self.changed = set()
        self.books_to_refresh = set()
        self.rows_to_refresh = set()
        ResizableDialog.__init__(self, parent)

    def setupUi(self, *args):  # {{{
        self.resize(990, 670)

        self.download_shortcut = QShortcut(self)
        self.download_shortcut.setKey(QKeySequence("Ctrl+D", QKeySequence.PortableText))
        p = self.parent()
        if hasattr(p, "keyboard"):
            kname = "Interface Action: Edit Metadata (Edit Metadata) : menu action : download"
            sc = p.keyboard.keys_map.get(kname, None)
            if sc:
                self.download_shortcut.setKey(sc[0])

        self.button_box = bb = QDialogButtonBox(self)
        self.button_box.accepted.connect(self.accept)
        self.button_box.rejected.connect(self.reject)
        self.next_button = QPushButton(QIcon(I("forward.png")), _("Next"), self)
        self.next_button.setShortcut(QKeySequence("Alt+Right"))
        self.next_button.clicked.connect(self.next_clicked)
        self.prev_button = QPushButton(QIcon(I("back.png")), _("Previous"), self)
        self.prev_button.setShortcut(QKeySequence("Alt+Left"))

        self.button_box.addButton(self.prev_button, bb.ActionRole)
        self.button_box.addButton(self.next_button, bb.ActionRole)
        self.prev_button.clicked.connect(self.prev_clicked)
        bb.setStandardButtons(bb.Ok | bb.Cancel)
        bb.button(bb.Ok).setDefault(True)

        self.scroll_area = QScrollArea(self)
        self.scroll_area.setFrameShape(QScrollArea.NoFrame)
        self.scroll_area.setWidgetResizable(True)
        self.central_widget = QTabWidget(self)
        self.scroll_area.setWidget(self.central_widget)

        self.l = QVBoxLayout(self)
        self.setLayout(self.l)
        self.l.addWidget(self.scroll_area)
        ll = self.button_box_layout = QHBoxLayout()
        self.l.addLayout(ll)
        ll.addSpacing(10)
        ll.addWidget(self.button_box)

        self.setWindowIcon(QIcon(I("edit_input.png")))
        self.setWindowTitle(BASE_TITLE)

        self.create_basic_metadata_widgets()

        if len(self.db.custom_column_label_map):
            self.create_custom_metadata_widgets()

        self.do_layout()
        geom = gprefs.get("metasingle_window_geometry3", None)
        if geom is not None:
            self.restoreGeometry(bytes(geom))

    # }}}

    def create_basic_metadata_widgets(self):  # {{{
        self.basic_metadata_widgets = []

        self.languages = LanguagesEdit(self)
        self.basic_metadata_widgets.append(self.languages)

        self.title = TitleEdit(self)
        self.title.textChanged.connect(self.update_window_title)
        self.deduce_title_sort_button = QToolButton(self)
        self.deduce_title_sort_button.setToolTip(
            _(
                "Automatically create the title sort entry based on the current "
                "title entry.\nUsing this button to create title sort will "
                "change title sort from red to green."
            )
        )
        self.deduce_title_sort_button.setWhatsThis(self.deduce_title_sort_button.toolTip())
        self.title_sort = TitleSortEdit(self, self.title, self.deduce_title_sort_button, self.languages)
        self.basic_metadata_widgets.extend([self.title, self.title_sort])

        self.deduce_author_sort_button = b = QToolButton(self)
        b.setToolTip(
            "<p>"
            + _(
                "Automatically create the author sort entry based on the current "
                "author entry. Using this button to create author sort will "
                "change author sort from red to green.  There is a menu of "
                "functions available under this button. Click and hold "
                "on the button to see it."
            )
            + "</p>"
        )
        b.m = m = QMenu()
        ac = m.addAction(QIcon(I("forward.png")), _("Set author sort from author"))
        ac2 = m.addAction(QIcon(I("back.png")), _("Set author from author sort"))
        ac3 = m.addAction(QIcon(I("user_profile.png")), _("Manage authors"))
        ac4 = m.addAction(QIcon(I("next.png")), _("Copy author to author sort"))
        ac5 = m.addAction(QIcon(I("previous.png")), _("Copy author sort to author"))

        b.setMenu(m)
        self.authors = AuthorsEdit(self, ac3)
        self.author_sort = AuthorSortEdit(self, self.authors, b, self.db, ac, ac2, ac4, ac5)
        self.basic_metadata_widgets.extend([self.authors, self.author_sort])

        self.swap_title_author_button = QToolButton(self)
        self.swap_title_author_button.setIcon(QIcon(I("swap.png")))
        self.swap_title_author_button.setToolTip(_("Swap the author and title"))
        self.swap_title_author_button.clicked.connect(self.swap_title_author)

        self.manage_authors_button = QToolButton(self)
        self.manage_authors_button.setIcon(QIcon(I("user_profile.png")))
        self.manage_authors_button.setToolTip(
            "<p>" + _("Manage authors. Use to rename authors and correct " "individual author's sort values") + "</p>"
        )
        self.manage_authors_button.clicked.connect(self.authors.manage_authors)

        self.series = SeriesEdit(self)
        self.clear_series_button = QToolButton(self)
        self.clear_series_button.setToolTip(_("Clear series"))
        self.clear_series_button.clicked.connect(self.series.clear)
        self.series_index = SeriesIndexEdit(self, self.series)
        self.basic_metadata_widgets.extend([self.series, self.series_index])

        self.formats_manager = FormatsManager(self, self.copy_fmt)
        # We want formats changes to be committed before title/author, as
        # otherwise we could have data loss if the title/author changed and the
        # user was trying to add an extra file from the old books directory.
        self.basic_metadata_widgets.insert(0, self.formats_manager)
        self.formats_manager.metadata_from_format_button.clicked.connect(self.metadata_from_format)
        self.formats_manager.cover_from_format_button.clicked.connect(self.cover_from_format)
        self.cover = Cover(self)
        self.cover.download_cover.connect(self.download_cover)
        self.basic_metadata_widgets.append(self.cover)

        self.comments = CommentsEdit(self, self.one_line_comments_toolbar)
        self.basic_metadata_widgets.append(self.comments)

        self.rating = RatingEdit(self)
        self.clear_ratings_button = QToolButton(self)
        self.clear_ratings_button.setToolTip(_("Clear rating"))
        self.clear_ratings_button.setIcon(QIcon(I("trash.png")))
        self.clear_ratings_button.clicked.connect(self.rating.zero)

        self.basic_metadata_widgets.append(self.rating)

        self.tags = TagsEdit(self)
        self.tags_editor_button = QToolButton(self)
        self.tags_editor_button.setToolTip(_("Open Tag Editor"))
        self.tags_editor_button.setIcon(QIcon(I("chapters.png")))
        self.tags_editor_button.clicked.connect(self.tags_editor)
        self.clear_tags_button = QToolButton(self)
        self.clear_tags_button.setToolTip(_("Clear all tags"))
        self.clear_tags_button.setIcon(QIcon(I("trash.png")))
        self.clear_tags_button.clicked.connect(self.tags.clear)
        self.basic_metadata_widgets.append(self.tags)

        self.identifiers = IdentifiersEdit(self)
        self.basic_metadata_widgets.append(self.identifiers)
        self.clear_identifiers_button = QToolButton(self)
        self.clear_identifiers_button.setIcon(QIcon(I("trash.png")))
        self.clear_identifiers_button.setToolTip(_("Clear Ids"))
        self.clear_identifiers_button.clicked.connect(self.identifiers.clear)
        self.paste_isbn_button = QToolButton(self)
        self.paste_isbn_button.setToolTip(
            "<p>" + _("Paste the contents of the clipboard into the " "identifiers box prefixed with isbn:") + "</p>"
        )
        self.paste_isbn_button.setIcon(QIcon(I("edit-paste.png")))
        self.paste_isbn_button.clicked.connect(self.identifiers.paste_isbn)

        self.publisher = PublisherEdit(self)
        self.basic_metadata_widgets.append(self.publisher)

        self.timestamp = DateEdit(self)
        self.pubdate = PubdateEdit(self)
        self.basic_metadata_widgets.extend([self.timestamp, self.pubdate])

        self.fetch_metadata_button = QPushButton(_("&Download metadata"), self)
        self.fetch_metadata_button.clicked.connect(self.fetch_metadata)
        self.download_shortcut.activated.connect(self.fetch_metadata_button.click)
        font = self.fmb_font = QFont()
        font.setBold(True)
        self.fetch_metadata_button.setFont(font)

        if self.use_toolbutton_for_config_metadata:
            self.config_metadata_button = QToolButton(self)
            self.config_metadata_button.setIcon(QIcon(I("config.png")))
        else:
            self.config_metadata_button = QPushButton(self)
            self.config_metadata_button.setText(_("Configure download metadata"))
        self.config_metadata_button.setIcon(QIcon(I("config.png")))
        self.config_metadata_button.clicked.connect(self.configure_metadata)
        self.config_metadata_button.setToolTip(_("Change how calibre downloads metadata"))

    # }}}

    def create_custom_metadata_widgets(self):  # {{{
        self.custom_metadata_widgets_parent = w = QWidget(self)
        layout = QGridLayout()
        w.setLayout(layout)
        self.custom_metadata_widgets, self.__cc_spacers = populate_metadata_page(
            layout, self.db, None, parent=w, bulk=False, two_column=self.cc_two_column
        )
        self.__custom_col_layouts = [layout]

    # }}}

    def set_custom_metadata_tab_order(self, before=None, after=None):  # {{{
        sto = QWidget.setTabOrder
        if getattr(self, "custom_metadata_widgets", []):
            ans = self.custom_metadata_widgets
            for i in range(len(ans) - 1):
                if before is not None and i == 0:
                    pass
                if len(ans[i + 1].widgets) == 2:
                    sto(ans[i].widgets[-1], ans[i + 1].widgets[1])
                else:
                    sto(ans[i].widgets[-1], ans[i + 1].widgets[0])
                for c in range(2, len(ans[i].widgets), 2):
                    sto(ans[i].widgets[c - 1], ans[i].widgets[c + 1])
            if after is not None:
                pass

    # }}}

    def do_view_format(self, path, fmt):
        if path:
            self.view_format.emit(None, path)
        else:
            self.view_format.emit(self.book_id, fmt)

    def copy_fmt(self, fmt, f):
        self.db.copy_format_to(self.book_id, fmt, f, index_is_id=True)

    def do_layout(self):
        raise NotImplementedError()

    def __call__(self, id_):
        self.book_id = id_
        self.books_to_refresh = set([])
        for widget in self.basic_metadata_widgets:
            widget.initialize(self.db, id_)
        for widget in getattr(self, "custom_metadata_widgets", []):
            widget.initialize(id_)
        if callable(self.set_current_callback):
            self.set_current_callback(id_)
        # Commented out as it doesn't play nice with Next, Prev buttons
        # self.fetch_metadata_button.setFocus(Qt.OtherFocusReason)

    # Miscellaneous interaction methods {{{
    def update_window_title(self, *args):
        title = self.title.current_val
        if len(title) > 50:
            title = title[:50] + "\u2026"
        self.setWindowTitle(
            BASE_TITLE
            + " - "
            + title
            + " - "
            + _(" [%(num)d of %(tot)d]") % dict(num=self.current_row + 1, tot=len(self.row_list))
        )

    def swap_title_author(self, *args):
        title = self.title.current_val
        self.title.current_val = authors_to_string(self.authors.current_val)
        self.authors.current_val = string_to_authors(title)
        self.title_sort.auto_generate()
        self.author_sort.auto_generate()

    def tags_editor(self, *args):
        self.tags.edit(self.db, self.book_id)

    def metadata_from_format(self, *args):
        mi, ext = self.formats_manager.get_selected_format_metadata(self.db, self.book_id)
        if mi is not None:
            self.update_from_mi(mi)

    def get_pdf_cover(self):
        pdfpath = self.formats_manager.get_format_path(self.db, self.book_id, "pdf")
        from calibre.gui2.metadata.pdf_covers import PDFCovers

        d = PDFCovers(pdfpath, parent=self)
        if d.exec_() == d.Accepted:
            cpath = d.cover_path
            if cpath:
                with open(cpath, "rb") as f:
                    self.update_cover(f.read(), "PDF")
        d.cleanup()

    def cover_from_format(self, *args):
        ext = self.formats_manager.get_selected_format()
        if ext is None:
            return
        if ext == "pdf":
            return self.get_pdf_cover()
        try:
            mi, ext = self.formats_manager.get_selected_format_metadata(self.db, self.book_id)
        except (IOError, OSError) as err:
            if getattr(err, "errno", None) == errno.EACCES:  # Permission denied
                import traceback

                fname = err.filename if err.filename else "file"
                error_dialog(
                    self,
                    _("Permission denied"),
                    _("Could not open %s. Is it being used by another" " program?") % fname,
                    det_msg=traceback.format_exc(),
                    show=True,
                )
                return
            raise
        if mi is None:
            return
        cdata = None
        if mi.cover and os.access(mi.cover, os.R_OK):
            cdata = open(mi.cover).read()
        elif mi.cover_data[1] is not None:
            cdata = mi.cover_data[1]
        if cdata is None:
            error_dialog(self, _("Could not read cover"), _("Could not read cover from %s format") % ext).exec_()
            return
        self.update_cover(cdata, ext)

    def update_cover(self, cdata, fmt):
        orig = self.cover.current_val
        self.cover.current_val = cdata
        if self.cover.current_val is None:
            self.cover.current_val = orig
            return error_dialog(
                self, _("Could not read cover"), _("The cover in the %s format is invalid") % fmt, show=True
            )
            return

    def update_from_mi(self, mi, update_sorts=True, merge_tags=True, merge_comments=False):
        if not mi.is_null("title"):
            self.title.current_val = mi.title
            if update_sorts:
                self.title_sort.auto_generate()
        if not mi.is_null("authors"):
            self.authors.current_val = mi.authors
        if not mi.is_null("author_sort"):
            self.author_sort.current_val = mi.author_sort
        elif update_sorts:
            self.author_sort.auto_generate()
        if not mi.is_null("rating"):
            try:
                self.rating.current_val = mi.rating
            except:
                pass
        if not mi.is_null("publisher"):
            self.publisher.current_val = mi.publisher
        if not mi.is_null("tags"):
            old_tags = self.tags.current_val
            tags = mi.tags if mi.tags else []
            if old_tags and merge_tags:
                ltags, lotags = {t.lower() for t in tags}, {t.lower() for t in old_tags}
                tags = [t for t in tags if t.lower() in ltags - lotags] + old_tags
            self.tags.current_val = tags
        if not mi.is_null("identifiers"):
            current = self.identifiers.current_val
            current.update(mi.identifiers)
            self.identifiers.current_val = current
        if not mi.is_null("pubdate"):
            self.pubdate.current_val = mi.pubdate
        if not mi.is_null("series") and mi.series.strip():
            self.series.current_val = mi.series
            if mi.series_index is not None:
                self.series_index.reset_original()
                self.series_index.current_val = float(mi.series_index)
        if not mi.is_null("languages"):
            langs = [canonicalize_lang(x) for x in mi.languages]
            langs = [x for x in langs if x is not None]
            if langs:
                self.languages.current_val = langs
        if mi.comments and mi.comments.strip():
            val = mi.comments
            if val and merge_comments:
                cval = self.comments.current_val
                if cval:
                    val = merge_two_comments(cval, val)
            self.comments.current_val = val

    def fetch_metadata(self, *args):
        d = FullFetch(self.cover.pixmap(), self)
        ret = d.start(
            title=self.title.current_val, authors=self.authors.current_val, identifiers=self.identifiers.current_val
        )
        if ret == d.Accepted:
            from calibre.ebooks.metadata.sources.prefs import msprefs

            mi = d.book
            dummy = Metadata(_("Unknown"))
            for f in msprefs["ignore_fields"]:
                if ":" not in f:
                    setattr(mi, f, getattr(dummy, f))
            if mi is not None:
                pd = mi.pubdate
                if pd is not None:
                    # Put the downloaded published date into the local timezone
                    # as we discard time info and the date is timezone
                    # invariant. This prevents the as_local_timezone() call in
                    # update_from_mi from changing the pubdate
                    mi.pubdate = datetime(pd.year, pd.month, pd.day, tzinfo=local_tz)
                self.update_from_mi(mi, merge_comments=msprefs["append_comments"])
            if d.cover_pixmap is not None:
                self.cover.current_val = pixmap_to_data(d.cover_pixmap)

    def configure_metadata(self):
        from calibre.gui2.preferences import show_config_widget

        gui = self.parent()
        show_config_widget("Sharing", "Metadata download", parent=self, gui=gui, never_shutdown=True)

    def download_cover(self, *args):
        from calibre.gui2.metadata.single_download import CoverFetch

        d = CoverFetch(self.cover.pixmap(), self)
        ret = d.start(self.title.current_val, self.authors.current_val, self.identifiers.current_val)
        if ret == d.Accepted:
            if d.cover_pixmap is not None:
                self.cover.current_val = pixmap_to_data(d.cover_pixmap)

    # }}}

    def apply_changes(self):
        self.changed.add(self.book_id)
        if self.db is None:
            # break_cycles has already been called, don't know why this should
            # happen but a user reported it
            return True
        for widget in self.basic_metadata_widgets:
            try:
                if hasattr(widget, "validate_for_commit"):
                    title, msg, det_msg = widget.validate_for_commit()
                    if title is not None:
                        error_dialog(self, title, msg, det_msg=det_msg, show=True)
                        return False
                widget.commit(self.db, self.book_id)
                self.books_to_refresh |= getattr(widget, "books_to_refresh", set())
            except (IOError, OSError) as err:
                if getattr(err, "errno", None) == errno.EACCES:  # Permission denied
                    import traceback

                    fname = getattr(err, "filename", None)
                    p = "Locked file: %s\n\n" % fname if fname else ""
                    error_dialog(
                        self,
                        _("Permission denied"),
                        _("Could not change the on disk location of this" " book. Is it open in another program?"),
                        det_msg=p + traceback.format_exc(),
                        show=True,
                    )
                    return False
                raise
        for widget in getattr(self, "custom_metadata_widgets", []):
            self.books_to_refresh |= widget.commit(self.book_id)

        self.db.commit()
        rows = self.db.refresh_ids(list(self.books_to_refresh))
        if rows:
            self.rows_to_refresh |= set(rows)

        return True

    def accept(self):
        self.save_state()
        if not self.apply_changes():
            return
        ResizableDialog.accept(self)

    def reject(self):
        self.save_state()
        ResizableDialog.reject(self)

    def save_state(self):
        try:
            gprefs["metasingle_window_geometry3"] = bytearray(self.saveGeometry())
        except:
            # Weird failure, see https://bugs.launchpad.net/bugs/995271
            import traceback

            traceback.print_exc()

    # Dialog use methods {{{
    def start(self, row_list, current_row, view_slot=None, set_current_callback=None):
        self.row_list = row_list
        self.current_row = current_row
        if view_slot is not None:
            self.view_format.connect(view_slot)
        self.set_current_callback = set_current_callback
        self.do_one(apply_changes=False)
        ret = self.exec_()
        self.break_cycles()
        return ret

    def next_clicked(self):
        if not self.apply_changes():
            return
        self.do_one(delta=1, apply_changes=False)

    def prev_clicked(self):
        if not self.apply_changes():
            return
        self.do_one(delta=-1, apply_changes=False)

    def do_one(self, delta=0, apply_changes=True):
        if apply_changes:
            self.apply_changes()
        self.current_row += delta
        prev = next_ = None
        if self.current_row > 0:
            prev = self.db.title(self.row_list[self.current_row - 1])
        if self.current_row < len(self.row_list) - 1:
            next_ = self.db.title(self.row_list[self.current_row + 1])

        if next_ is not None:
            tip = (_("Save changes and edit the metadata of %s") + " [Alt+Right]") % next_
            self.next_button.setToolTip(tip)
        self.next_button.setEnabled(next_ is not None)
        if prev is not None:
            tip = (_("Save changes and edit the metadata of %s") + " [Alt+Left]") % prev
            self.prev_button.setToolTip(tip)
        self.prev_button.setEnabled(prev is not None)
        self.button_box.button(self.button_box.Ok).setDefault(True)
        self.button_box.button(self.button_box.Ok).setFocus(Qt.OtherFocusReason)
        self(self.db.id(self.row_list[self.current_row]))

    def break_cycles(self):
        # Break any reference cycles that could prevent python
        # from garbage collecting this dialog
        self.set_current_callback = self.db = None

        def disconnect(signal):
            try:
                signal.disconnect()
            except:
                pass  # Fails if view format was never connected

        disconnect(self.view_format)
        for b in ("next_button", "prev_button"):
            x = getattr(self, b, None)
            if x is not None:
                disconnect(x.clicked)
        for widget in self.basic_metadata_widgets:
            bc = getattr(widget, "break_cycles", None)
            if bc is not None and callable(bc):
                bc()
        for widget in getattr(self, "custom_metadata_widgets", []):
            widget.break_cycles()
예제 #38
0
class AnnotationsAppearance(SizePersistedDialog):
    '''
    Dialog for managing CSS rules, including Preview window
    '''
    if isosx:
        FONT = QFont('Monaco', 12)
    elif iswindows:
        FONT = QFont('Lucida Console', 9)
    elif islinux:
        FONT = QFont('Monospace', 9)
        FONT.setStyleHint(QFont.TypeWriter)

    def __init__(self, parent, icon, prefs):

        self.parent = parent
        self.prefs = prefs
        self.icon = icon
        super(AnnotationsAppearance, self).__init__(parent, 'appearance_dialog')
        self.setWindowTitle(_('Modify appearance'))
        self.setWindowIcon(icon)
        self.l = QVBoxLayout(self)
        self.setLayout(self.l)

        # Add a label for description
        #self.description_label = QLabel(_("Descriptive text here"))
        #self.l.addWidget(self.description_label)

        # Add a group box, vertical layout for preview window
        self.preview_gb = QGroupBox(self)
        self.preview_gb.setTitle(_("Preview"))
        self.preview_vl = QVBoxLayout(self.preview_gb)
        self.l.addWidget(self.preview_gb)

        self.wv = QWebView()
        self.wv.setHtml('<p></p>')
        self.wv.setMinimumHeight(100)
        self.wv.setMaximumHeight(16777215)
        self.wv.setGeometry(0, 0, 200, 100)
        self.wv.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.preview_vl.addWidget(self.wv)

        # Create a group box, horizontal layout for the table
        self.css_table_gb = QGroupBox(self)
        self.css_table_gb.setTitle(_("Annotation elements"))
        self.elements_hl = QHBoxLayout(self.css_table_gb)
        self.l.addWidget(self.css_table_gb)

        # Add the group box to the main layout
        self.elements_table = AnnotationElementsTable(self, 'annotation_elements_tw')
        self.elements_hl.addWidget(self.elements_table)
        self.elements_table.initialize()

        # Options
        self.options_gb = QGroupBox(self)
        self.options_gb.setTitle(_("Options"))
        self.options_gl = QGridLayout(self.options_gb)
        self.l.addWidget(self.options_gb)
        current_row = 0

        # <hr/> separator
        # addWidget(widget, row, col, rowspan, colspan)
        self.hr_checkbox = QCheckBox(_('Add horizontal rule between annotations'))
        self.hr_checkbox.stateChanged.connect(self.hr_checkbox_changed)
        self.hr_checkbox.setCheckState(
            JSONConfig('plugins/annotations').get('appearance_hr_checkbox', False))
        self.options_gl.addWidget(self.hr_checkbox, current_row, 0, 1, 4)
        current_row += 1

        # Timestamp
        self.timestamp_fmt_label = QLabel(_("Timestamp format:"))
        self.options_gl.addWidget(self.timestamp_fmt_label, current_row, 0)

        self.timestamp_fmt_le = QLineEdit(
            JSONConfig('plugins/annotations').get('appearance_timestamp_format', default_timestamp),
            parent=self)
        self.timestamp_fmt_le.textEdited.connect(self.timestamp_fmt_changed)
        self.timestamp_fmt_le.setFont(self.FONT)
        self.timestamp_fmt_le.setObjectName('timestamp_fmt_le')
        self.timestamp_fmt_le.setToolTip(_('Format string for timestamp'))
        self.timestamp_fmt_le.setMaximumWidth(16777215)
        self.timestamp_fmt_le.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
        self.options_gl.addWidget(self.timestamp_fmt_le, current_row, 1)

        self.timestamp_fmt_reset_tb = QToolButton(self)
        self.timestamp_fmt_reset_tb.setToolTip(_("Reset to default"))
        self.timestamp_fmt_reset_tb.setIcon(QIcon(I('trash.png')))
        self.timestamp_fmt_reset_tb.clicked.connect(self.reset_timestamp_to_default)
        self.options_gl.addWidget(self.timestamp_fmt_reset_tb, current_row, 2)

        self.timestamp_fmt_help_tb = QToolButton(self)
        self.timestamp_fmt_help_tb.setToolTip(_("Format string reference"))
        self.timestamp_fmt_help_tb.setIcon(QIcon(I('help.png')))
        self.timestamp_fmt_help_tb.clicked.connect(self.show_help)
        self.options_gl.addWidget(self.timestamp_fmt_help_tb, current_row, 3)

        # Button box
        bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
        bb.accepted.connect(self.accept)
        bb.rejected.connect(self.reject)
        self.l.addWidget(bb)

        # Spacer
        self.spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
        self.l.addItem(self.spacerItem)

        # Sizing
        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth())
        self.setSizePolicy(sizePolicy)
        self.resize_dialog()

    def hr_checkbox_changed(self, state):
        self.prefs.set('appearance_hr_checkbox', state)
        self.elements_table.preview_css()

    def reset_timestamp_to_default(self):
        from calibre_plugins.annotations.appearance import default_timestamp
        self.timestamp_fmt_le.setText(default_timestamp)
        self.timestamp_fmt_changed()

    def show_help(self):
        '''
        Display strftime help file
        '''
        hv = HelpView(self, self.icon, self.prefs,
            html=get_resources('help/timestamp_formats.html'), title=_("Timestamp formats"))
        hv.show()

    def sizeHint(self):
        return QtCore.QSize(600, 200)

    def timestamp_fmt_changed(self):
        self.prefs.set('appearance_timestamp_format', str(self.timestamp_fmt_le.text()))
        self.elements_table.preview_css()
예제 #39
0
    def __init__(self, parent):
        QWidget.__init__(self, parent)
        self._layout = l = QHBoxLayout()
        self.setLayout(self._layout)
        self._layout.setContentsMargins(0, 5, 0, 0)

        x = QToolButton(self)
        x.setText(_('Vi&rtual Library'))
        x.setIcon(QIcon(I('lt.png')))
        x.setObjectName("virtual_library")
        x.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        l.addWidget(x)
        parent.virtual_library = x

        x = QToolButton(self)
        x.setIcon(QIcon(I('minus.png')))
        x.setObjectName('clear_vl')
        l.addWidget(x)
        x.setVisible(False)
        x.setToolTip(_('Close the Virtual Library'))
        parent.clear_vl = x

        x = QLabel(self)
        x.setObjectName("search_count")
        l.addWidget(x)
        parent.search_count = x
        x.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)

        parent.advanced_search_button = x = QToolButton(self)
        parent.advanced_search_toggle_action = ac = QAction(parent)
        parent.addAction(ac)
        parent.keyboard.register_shortcut('advanced search toggle',
                                          _('Advanced search'),
                                          default_keys=("Shift+Ctrl+F", ),
                                          action=ac)
        ac.triggered.connect(x.click)
        x.setIcon(QIcon(I('search.png')))
        l.addWidget(x)
        x.setToolTip(_("Advanced search"))

        x = parent.search = SearchBox2(self)
        x.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
        x.setObjectName("search")
        x.setToolTip(
            _("<p>Search the list of books by title, author, publisher, "
              "tags, comments, etc.<br><br>Words separated by spaces are ANDed"
              ))
        x.setMinimumContentsLength(10)
        l.addWidget(x)

        self.search_button = QToolButton()
        self.search_button.setToolButtonStyle(Qt.ToolButtonTextOnly)
        self.search_button.setText(_('&Go!'))
        l.addWidget(self.search_button)
        self.search_button.setSizePolicy(QSizePolicy.Minimum,
                                         QSizePolicy.Minimum)
        self.search_button.clicked.connect(parent.do_search_button)
        self.search_button.setToolTip(
            _('Do Quick Search (you can also press the Enter key)'))

        x = parent.clear_button = QToolButton(self)
        x.setIcon(QIcon(I('clear_left.png')))
        x.setObjectName("clear_button")
        l.addWidget(x)
        x.setToolTip(_("Reset Quick Search"))

        x = parent.highlight_only_button = QToolButton(self)
        x.setIcon(QIcon(I('arrow-down.png')))
        l.addWidget(x)

        x = parent.saved_search = SavedSearchBox(self)
        x.setMaximumSize(QSize(150, 16777215))
        x.setMinimumContentsLength(10)
        x.setObjectName("saved_search")
        l.addWidget(x)

        x = parent.copy_search_button = QToolButton(self)
        x.setIcon(QIcon(I("search_copy_saved.png")))
        x.setObjectName("copy_search_button")
        l.addWidget(x)
        x.setToolTip(_("Copy current search text (instead of search name)"))

        x = parent.save_search_button = RightClickButton(self)
        x.setIcon(QIcon(I("search_add_saved.png")))
        x.setObjectName("save_search_button")
        l.addWidget(x)
예제 #40
0
class AnnotationElementsTable(QTableWidget):
    '''
    QTableWidget managing CSS rules
    '''
    DEBUG = True
    #MAXIMUM_TABLE_HEIGHT = 113
    ELEMENT_FIELD_WIDTH = 250
    if isosx:
        FONT = QFont('Monaco', 11)
    elif iswindows:
        FONT = QFont('Lucida Console', 9)
    elif islinux:
        FONT = QFont('Monospace', 9)
        FONT.setStyleHint(QFont.TypeWriter)

    COLUMNS = {
                'ELEMENT_NAME': {'ordinal': 0, 'name': 'Element Name'},
                'ELEMENT':      {'ordinal': 1, 'name': _('Element')},
                'CSS':          {'ordinal': 2, 'name': _('CSS')},
                }

    sample_ann_1 = {
        'text': ['Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean placerat condimentum semper. Aliquam hendrerit nisl mauris, nec laoreet orci. Donec rutrum consequat ultricies.',
                 'Curabitur sollicitudin euismod felis, vitae mollis magna vestibulum id.'],
        'note': [_('This is a note appended to the highlight.'),
                 _('And additional comments after a linebreak.')],
        'highlightcolor': 'Yellow',
        'timestamp': time.mktime(time.localtime()),
        'location': 'Chapter 4',
        'location_sort': 0
        }
    sample_ann_2 = {
        'text': ['Phasellus sit amet ipsum id velit commodo convallis. In dictum felis non tellus volutpat in tincidunt neque varius. Sed at mauris augue. Vestibulum ligula nunc, ullamcorper id suscipit sed, auctor quis erat. In hac habitasse platea dictumst. Aliquam sit amet nulla dolor, ut tempus libero. In hac habitasse platea dictumst. Etiam consectetur orci vel massa eleifend in vestibulum odio auctor. Praesent orci turpis, aliquet non eleifend sit amet, sollicitudin sit amet augue.'],
        'highlightcolor': 'Green',
        'timestamp': time.mktime(time.localtime()),
        'location': 'Chapter 12',
        'location_sort': 1
        }
    sample_ann_3 = {
        'text': ['Morbi massa tellus, laoreet id pretium sed, volutpat in felis.',
                 'Donec massa nulla, malesuada vitae volutpat quis, accumsan ut tellus.'],
        'note': [_('This is a note appended to the highlight.'),
                 _('And additional comments after a linebreak.')],
        'highlightcolor': 'Purple',
        'timestamp': time.mktime(time.localtime()),
        'location': 'Chapter 53',
        'location_sort': 2
        }


    def __init__(self, parent, object_name):
        self.parent = parent
        self.prefs = parent.prefs
        self.elements = self.prefs.get('appearance_css', None)
        debug_print("AnnotationElementsTable::__init__ - self.elements", self.elements)
        if not self.elements:
            self.elements = default_elements
            debug_print("AnnotationElementsTable::__init__ - self.elements", self.elements)

        QTableWidget.__init__(self)
        self.setObjectName(object_name)
        self.layout = parent.elements_hl.layout()

        # Add ourselves to the layout
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
        sizePolicy.setHorizontalStretch(0)
        #sizePolicy.setVerticalStretch(0)
        #sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth())
        self.setSizePolicy(sizePolicy)
        #self.setMaximumSize(QSize(16777215, self.MAXIMUM_TABLE_HEIGHT))

        self.setColumnCount(0)
        self.setRowCount(0)
        self.layout.addWidget(self)

    def _init_controls(self):
        # Add the control set
        vbl = QVBoxLayout()
        self.move_element_up_tb = QToolButton()
        self.move_element_up_tb.setObjectName("move_element_up_tb")
        self.move_element_up_tb.setToolTip(_('Move element up'))
        self.move_element_up_tb.setIcon(QIcon(I('arrow-up.png')))
        self.move_element_up_tb.clicked.connect(self.move_row_up)
        vbl.addWidget(self.move_element_up_tb)

        self.undo_css_tb = QToolButton()
        self.undo_css_tb.setObjectName("undo_css_tb")
        self.undo_css_tb.setToolTip(_('Restore CSS to last saved'))
        self.undo_css_tb.setIcon(QIcon(I('edit-undo.png')))
        self.undo_css_tb.clicked.connect(partial(self.undo_reset_button_clicked, 'undo'))
        vbl.addWidget(self.undo_css_tb)

        self.reset_css_tb = QToolButton()
        self.reset_css_tb.setObjectName("reset_css_tb")
        self.reset_css_tb.setToolTip(_('Reset CSS to default'))
        self.reset_css_tb.setIcon(QIcon(I('trash.png')))
        self.reset_css_tb.clicked.connect(partial(self.undo_reset_button_clicked, 'reset'))
        vbl.addWidget(self.reset_css_tb)

        self.move_element_down_tb = QToolButton()
        self.move_element_down_tb.setObjectName("move_element_down_tb")
        self.move_element_down_tb.setToolTip(_('Move element down'))
        self.move_element_down_tb.setIcon(QIcon(I('arrow-down.png')))
        self.move_element_down_tb.clicked.connect(self.move_row_down)
        vbl.addWidget(self.move_element_down_tb)

        self.layout.addLayout(vbl)

    def _init_table_widget(self):
        header_labels = [self.COLUMNS[index]['name'] \
            for index in sorted(self.COLUMNS.keys(), key=lambda c: self.COLUMNS[c]['ordinal'])]
        self.setColumnCount(len(header_labels))
        self.setHorizontalHeaderLabels(header_labels)
        self.verticalHeader().setVisible(False)

        self.setSortingEnabled(False)

        # Select single rows
        self.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.setSelectionMode(QAbstractItemView.SingleSelection)

    def convert_row_to_data(self, row):
        data = {}
        data['ordinal'] = row
#         data['name'] = unicode(self.cellWidget(row, self.COLUMNS['ELEMENT']['ordinal']).text()).strip()
        data['name'] = unicode(self.cellWidget(row, self.COLUMNS['ELEMENT_NAME']['ordinal']).text()).strip()
        data['css'] = unicode(self.cellWidget(row, self.COLUMNS['CSS']['ordinal']).toPlainText()).strip()
        return data

    def css_edited(self, row):
        self.select_and_scroll_to_row(row)
        col = self.COLUMNS['CSS']['ordinal']
        widget = self.cellWidget(row, col)
        css = unicode(widget.toPlainText())
        lines = []
        for line in css.split('\n'):
            lines.append(re.sub('^\s*', '', line))
        self.resize_row_height(lines, row)
#         self.prefs.set('appearance_css', self.get_data())
        widget.setFocus()
        self.preview_css()

    def get_data(self):
        data_items = []
        for row in range(self.rowCount()):
            data = self.convert_row_to_data(row)
            data_items.append(
                               {'ordinal': data['ordinal'],
                                'name': data['name'],
#                                 'translatable_name': translatable_element_names[data['name']],
                                'css': data['css'],
                                })
        return data_items

    def initialize(self):
        self._init_table_widget()
        self._init_controls()
        self.populate_table()
        self.resizeColumnsToContents()
        self.horizontalHeader().setStretchLastSection(True)

        # Update preview window, select first row
        self.css_edited(0)

    def move_row(self, source, dest):

        self.blockSignals(True)
        # Save the contents of the destination row
        saved_data = self.convert_row_to_data(dest)
        debug_print("Annotations::appearance.py::move_row - saved_data", saved_data)

        # Remove the destination row
        self.removeRow(dest)

        # Insert a new row at the original location
        self.insertRow(source)

        # Populate it with the saved data
        self.populate_table_row(source, saved_data)

        self.select_and_scroll_to_row(dest)
        self.blockSignals(False)

        self.css_edited(dest)

    def move_row_down(self):
        src_row = self.currentRow()
        dest_row = src_row + 1
        if dest_row == self.rowCount():
            return
        self.move_row(src_row, dest_row)

    def move_row_up(self):
        src_row = self.currentRow()
        dest_row = src_row - 1
        if dest_row < 0:
            return
        self.move_row(src_row, dest_row)

    def populate_table(self):
        # Format of rules list is different if default values vs retrieved JSON
        # Hack to normalize list style
        elements = self.elements
        if elements and type(elements[0]) is list:
            elements = elements[0]
        self.setFocus()
        elements = sorted(elements, key=lambda k: k['ordinal'])
        for row, element in enumerate(elements):
            self.insertRow(row)
            self.select_and_scroll_to_row(row)
            self.populate_table_row(row, element)
        self.selectRow(0)
        self.setColumnHidden(0, True)

    def populate_table_row(self, row, data):
        self.blockSignals(True)
        self.setCellWidget(row, self.COLUMNS['ELEMENT_NAME']['ordinal'], QLabel(data['name']))
        translatable_name = translatable_element_names[data['name']]
        self.set_element_name_in_row(row, self.COLUMNS['ELEMENT']['ordinal'], translatable_name)
        self.set_css_in_row(row, self.COLUMNS['CSS']['ordinal'], data['css'])
        self.blockSignals(False)

    def preview_css(self):
        '''
        Construct a dummy annotation for preview purposes
        '''
        from calibre_plugins.annotations.annotations import Annotation, Annotations

        pas = Annotations(None, title=_("Preview"))
        pas.annotations.append(Annotation(self.sample_ann_1))
        pas.annotations.append(Annotation(self.sample_ann_2))
        pas.annotations.append(Annotation(self.sample_ann_3))
        self.parent.wv.setHtml(pas.to_HTML())

    def resize_row_height(self, lines, row):
        point_size = self.FONT.pointSize()
        if isosx:
            height = 30 + (len(lines) - 1) * (point_size + 4)
        elif iswindows:
            height = 26 + (len(lines) - 1) * (point_size + 3)
        elif islinux:
            height = 30 + (len(lines) - 1) * (point_size + 6)

        self.verticalHeader().resizeSection(row, height)

    def select_and_scroll_to_row(self, row):
        self.setFocus()
        self.selectRow(row)
        self.scrollToItem(self.currentItem())

    def set_element_name_in_row(self, row, col, name):
        rule_name = QLabel(" %s " % name)
        rule_name.setFont(self.FONT)
        self.setCellWidget(row, col, rule_name)

    def set_css_in_row(self, row, col, css):
        # Clean up multi-line css formatting
        # A single line is 30px tall, subsequent lines add 16px

        lines = []
        for line in css.split('\n'):
            lines.append(re.sub('^\s*', '', line))
        css_content = QPlainTextEdit('\n'.join(lines))
        css_content.setFont(self.FONT)
        css_content.textChanged.connect(partial(self.css_edited, row))
        self.setCellWidget(row, col, css_content)
        self.resize_row_height(lines, row)

    def undo_reset_button_clicked(self, mode):
        """
        Figure out which element is being reset
        Reset to last save or default
        """
        debug_print("undo_reset_button_clicked - mode=", mode)
        row = self.currentRow()
        data = self.convert_row_to_data(row)
        debug_print("undo_reset_button_clicked - data=", data)

        # Get default
        default_css = None
        for de in default_elements:
            if de['name'] == data['name']:
                default_css = de
                break

        # Get last saved
        last_saved_css = None
        saved_elements = self.prefs.get('appearance_css', None)
        last_saved_css = default_css
        debug_print("undo_reset_button_clicked - saved_elements=", saved_elements)
        debug_print("undo_reset_button_clicked - last_saved_css=", last_saved_css)
        if saved_elements:
            for se in saved_elements:
                if se['name'] == data['name']:
                    debug_print("undo_reset_button_clicked - se=", se)
                    last_saved_css = se
                    break
        debug_print("undo_reset_button_clicked - last_saved_css=", last_saved_css)

        # Restore css
        if mode == 'reset':
            self.populate_table_row(row, default_css)
        elif mode == 'undo':
            self.populate_table_row(row, last_saved_css)

        # Refresh the stored data
        #self.prefs.set('appearance_css', self.get_data())
        self.css_edited(row)
예제 #41
0
파일: instable.py 프로젝트: ligm74/LiGM
    def __init__(self, color_caption):
        TestableWidget.__init__(self)
        self.setWindowFlags(Qt.Popup)
        self._text = self.tr("Table")

        main_layout = QVBoxLayout()
        main_layout.setContentsMargins(0, 0, 0, 0)

        b = "border: 1px solid #9b9b9b;"
        self._styles = {
            "border": b,
            "border_blue": "border: 0px solid blue;",
            "selection": f"QToolButton {{{b}background-color:#fee5e2;}}",
            "white": f"QToolButton {{{b}background-color:white;}}",
            "frame": 'QFrame[frameShape="4"]{color: #9b9b9b;}'}

        self.setStyleSheet(self._styles["frame"])

        # -- CAPTION ----------------------------------
        caption = QFrame(self, flags=Qt.WindowFlags())
        caption_layout = QHBoxLayout()
        self._lbl_caption = QLabel(self._text)
        self._lbl_caption.setStyleSheet(self._styles["border_blue"])
        caption_layout.addWidget(self._lbl_caption, alignment=Qt.Alignment())
        caption.setLayout(caption_layout)
        caption_layout.setContentsMargins(9, 5, 9, 5)
        caption.setStyleSheet(
            f"background-color: {color_caption}; {self._styles['border']}")

        # -- CELLS GRID -------------------------------
        cellsbox = QFrame(self, flags=Qt.WindowFlags())
        cells = QGridLayout()
        cells.setSpacing(1)
        cellsbox.setLayout(cells)
        cells.setContentsMargins(9, 0, 9, 0)
        self._nn = 10
        self._clrbtn = []
        colors = ["white" for _ in range(self._nn ** 2)]
        cellsbox.leaveEvent = lambda x: self._leave_cell()
        for i, color in enumerate(colors):
            self._clrbtn.append(QToolButton())
            # noinspection PyPep8Naming
            self._clrbtn[-1].enterEvent = lambda x, n=i: self._enter_cell(n)
            self._clrbtn[-1].setStyleSheet(self._styles["white"])
            self._clrbtn[-1].clicked.connect(
                lambda x, n=i: self.select_size_(n))
            # noinspection PyArgumentList
            cells.addWidget(self._clrbtn[-1], i // self._nn, i % self._nn)

        # -- SPLITTER ---------------------------------
        h_frame = QFrame(None, flags=Qt.WindowFlags())
        h_frame.setFrameShape(QFrame.HLine)
        h_frame.setContentsMargins(0, 0, 0, 0)

        # -- BOTTOM (other color) ---------------------
        btns = QFrame(self, flags=Qt.WindowFlags())
        btn_layout = QHBoxLayout()
        other = QToolButton()
        other.clicked.connect(self.other_size_)
        other.setAutoRaise(True)
        other.setIcon(QIcon(img("editor/table_gray")))
        btn_layout.addWidget(other, alignment=Qt.Alignment())
        self._lbl_other = QLabel(self.tr("insert table"))
        btn_layout.addWidget(self._lbl_other, alignment=Qt.Alignment())
        btns.setLayout(btn_layout)
        btn_layout.setContentsMargins(9, 0, 9, 9)
        self._clrbtn.append(other)

        # ---------------------------------------------
        main_layout.addWidget(caption, alignment=Qt.Alignment())
        main_layout.addWidget(cellsbox, alignment=Qt.Alignment())
        main_layout.addWidget(h_frame, alignment=Qt.Alignment())
        main_layout.addWidget(btns, alignment=Qt.Alignment())

        self.setLayout(main_layout)
예제 #42
0
class ImageController(QFrame):
    """An ImageController is a widget for controlling the display of one image.
    It can emit the following signals from the image:
    raise                     raise button was clicked
    center                  center-on-image option was selected
    unload                  unload option was selected
    slice                     image slice has changed, need to redraw (emitted by SkyImage automatically)
    repaint                 image display range or colormap has changed, need to redraw (emitted by SkyImage automatically)
    """

    # image signals
    imageSignalRepaint = pyqtSignal()
    imageSignalSlice = pyqtSignal(tuple)
    imageSignalRaise = pyqtSignal([FITSImagePlotItem])
    imageSignalUnload = pyqtSignal(object)
    imageSignalCenter = pyqtSignal(object)

    def __init__(self, image, parent, imgman, name=None, save=False):
        QFrame.__init__(self, parent)
        self.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
        # init state
        self._border_pen = None
        self._image_label_text = None
        self._subset = None
        self.image = image
        self._imgman = imgman
        self._currier = PersistentCurrier()
        self._control_dialog = None
        # create widgets
        self._lo = lo = QHBoxLayout(self)
        lo.setContentsMargins(0, 0, 0, 0)
        lo.setSpacing(2)
        # raise button
        self._wraise = QToolButton(self)
        lo.addWidget(self._wraise)
        self._wraise.setIcon(pixmaps.raise_up.icon())
        self._wraise.setAutoRaise(True)
        self._can_raise = False
        self._wraise.clicked.connect(self._raiseButtonPressed)
        self._wraise.setToolTip(
            """<P>Click here to raise this image above other images. Hold the button down briefly to
      show a menu of image operations.</P>""")
        # center label
        self._wcenter = QLabel(self)
        self._wcenter.setPixmap(pixmaps.center_image.pm())
        self._wcenter.setToolTip(
            "<P>The plot is currently centered on (the reference pixel %d,%d) of this image.</P>"
            % self.image.referencePixel())
        lo.addWidget(self._wcenter)
        # name/filename label
        self.name = image.name
        self._wlabel = QLabel(self.name, self)
        self._number = 0
        self.setName(self.name)
        self._wlabel.setToolTip(
            "%s %s" %
            (image.filename, "\u00D7".join(map(str,
                                               image.data().shape))))
        lo.addWidget(self._wlabel, 1)
        # if 'save' is specified, create a "save" button
        if save:
            self._wsave = QToolButton(self)
            lo.addWidget(self._wsave)
            self._wsave.setText("save")
            self._wsave.setAutoRaise(True)
            self._save_dir = save if isinstance(save, str) else "."
            self._wsave.clicked.connect(self._saveImage)
            self._wsave.setToolTip(
                """<P>Click here to write this image to a FITS file.</P>""")
        # render control
        self.image.connectRepaint(self.imageSignalRepaint)
        self.image.connectSlice(self.imageSignalSlice)
        self.image.connectRaise(self.imageSignalRaise)
        self.image.connectUnload(self.imageSignalUnload)
        self.image.connectCenter(self.imageSignalCenter)
        dprint(2, "creating RenderControl")
        self._rc = RenderControl(image, self)
        dprint(2, "done")
        # selectors for extra axes
        self._wslicers = []
        curslice = self._rc.currentSlice(
        )  # this may be loaded from config, so not necessarily 0
        for iextra, axisname, labels in self._rc.slicedAxes():
            if axisname.upper() not in ["STOKES", "COMPLEX"]:
                lbl = QLabel("%s:" % axisname, self)
                lo.addWidget(lbl)
            else:
                lbl = None
            slicer = QComboBox(self)
            self._wslicers.append(slicer)
            lo.addWidget(slicer)
            slicer.addItems(labels)
            slicer.setToolTip(
                """<P>Selects current slice along the %s axis.</P>""" %
                axisname)
            slicer.setCurrentIndex(curslice[iextra])
            slicer.activated[int].connect(
                self._currier.curry(self._rc.changeSlice, iextra))
        # min/max display ranges
        lo.addSpacing(5)
        self._wrangelbl = QLabel(self)
        lo.addWidget(self._wrangelbl)
        self._minmaxvalidator = FloatValidator(self)
        self._wmin = QLineEdit(self)
        self._wmax = QLineEdit(self)
        width = self._wmin.fontMetrics().width("1.234567e-05")
        for w in self._wmin, self._wmax:
            lo.addWidget(w, 0)
            w.setValidator(self._minmaxvalidator)
            w.setMaximumWidth(width)
            w.setMinimumWidth(width)
            w.editingFinished.connect(self._changeDisplayRange)
        # full-range button
        self._wfullrange = QToolButton(self)
        lo.addWidget(self._wfullrange, 0)
        self._wfullrange.setIcon(pixmaps.zoom_range.icon())
        self._wfullrange.setAutoRaise(True)
        self._wfullrange.clicked.connect(
            self.renderControl().resetSubsetDisplayRange)
        rangemenu = QMenu(self)
        rangemenu.addAction(pixmaps.full_range.icon(), "Full subset",
                            self.renderControl().resetSubsetDisplayRange)
        for percent in (99.99, 99.9, 99.5, 99, 98, 95):
            rangemenu.addAction(
                "%g%%" % percent,
                self._currier.curry(self._changeDisplayRangeToPercent,
                                    percent))
        self._wfullrange.setPopupMode(QToolButton.DelayedPopup)
        self._wfullrange.setMenu(rangemenu)
        # update widgets from current display range
        self._updateDisplayRange(*self._rc.displayRange())
        # lock button
        self._wlock = QToolButton(self)
        self._wlock.setIcon(pixmaps.unlocked.icon())
        self._wlock.setAutoRaise(True)
        self._wlock.setToolTip(
            """<P>Click to lock or unlock the intensity range. When the intensity range is locked across multiple images, any changes in the intensity
          range of one are propagated to the others. Hold the button down briefly for additional options.</P>"""
        )
        lo.addWidget(self._wlock)
        self._wlock.clicked.connect(self._toggleDisplayRangeLock)
        self.renderControl().displayRangeLocked.connect(
            self._setDisplayRangeLock)
        self.renderControl().dataSubsetChanged.connect(self._dataSubsetChanged)
        lockmenu = QMenu(self)
        lockmenu.addAction(
            pixmaps.locked.icon(), "Lock all to this",
            self._currier.curry(imgman.lockAllDisplayRanges,
                                self.renderControl()))
        lockmenu.addAction(pixmaps.unlocked.icon(), "Unlock all",
                           imgman.unlockAllDisplayRanges)
        self._wlock.setPopupMode(QToolButton.DelayedPopup)
        self._wlock.setMenu(lockmenu)
        self._setDisplayRangeLock(self.renderControl().isDisplayRangeLocked())
        # dialog button
        self._wshowdialog = QToolButton(self)
        lo.addWidget(self._wshowdialog)
        self._wshowdialog.setIcon(pixmaps.colours.icon())
        self._wshowdialog.setAutoRaise(True)
        self._wshowdialog.setToolTip(
            """<P>Click for colourmap and intensity policy options.</P>""")
        self._wshowdialog.clicked.connect(self.showRenderControls)
        tooltip = """<P>You can change the currently displayed intensity range by entering low and high limits here.</P>
            <TABLE>
            <TR><TD><NOBR>Image min:</NOBR></TD><TD>%g</TD><TD>max:</TD><TD>%g</TD></TR>
            </TABLE>""" % self.image.imageMinMax()
        for w in self._wmin, self._wmax, self._wrangelbl:
            w.setToolTip(tooltip)
        # create image operations menu
        self._menu = QMenu(self.name, self)
        self._qa_raise = self._menu.addAction(
            pixmaps.raise_up.icon(), "Raise image",
            self._currier.curry(self.image.signalRaise.emit, None))
        self._qa_center = self._menu.addAction(
            pixmaps.center_image.icon(), "Center plot on image",
            self._currier.curry(self.image.signalCenter.emit, True))
        self._qa_show_rc = self._menu.addAction(pixmaps.colours.icon(),
                                                "Colours && Intensities...",
                                                self.showRenderControls)
        if save:
            self._qa_save = self._menu.addAction("Save image...",
                                                 self._saveImage)
        self._menu.addAction("Export image to PNG file...",
                             self._exportImageToPNG)
        self._export_png_dialog = None
        self._menu.addAction(
            "Unload image",
            self._currier.curry(self.image.signalUnload.emit, None))
        self._wraise.setMenu(self._menu)
        self._wraise.setPopupMode(QToolButton.DelayedPopup)
        # connect updates from renderControl and image
        self.image.signalSlice.connect(self._updateImageSlice)
        self._rc.displayRangeChanged.connect(self._updateDisplayRange)
        # default plot depth of image markers
        self._z_markers = None
        # and the markers themselves
        self._image_border = QwtPlotCurve()
        self._image_border.setRenderHint(QwtPlotItem.RenderAntialiased)
        self._image_label = QwtPlotMarker()
        self._image_label.setRenderHint(QwtPlotItem.RenderAntialiased)
        # subset markers
        self._subset_pen = QPen(QColor("Light Blue"))
        self._subset_border = QwtPlotCurve()
        self._subset_border.setRenderHint(QwtPlotItem.RenderAntialiased)
        self._subset_border.setPen(self._subset_pen)
        self._subset_border.setVisible(False)
        self._subset_label = QwtPlotMarker()
        self._subset_label.setRenderHint(QwtPlotItem.RenderAntialiased)
        text = QwtText("subset")
        text.setColor(self._subset_pen.color())
        self._subset_label.setLabel(text)
        self._subset_label.setLabelAlignment(Qt.AlignRight | Qt.AlignBottom)
        self._subset_label.setVisible(False)
        self._setting_lmrect = False
        self._all_markers = [
            self._image_border, self._image_label, self._subset_border,
            self._subset_label
        ]
        self._exportMaxRes = False
        self._dockable_colour_ctrl = None

    def close(self):
        if self._control_dialog:
            self._control_dialog.close()
            self._control_dialog = None

    def __del__(self):
        self.close()

    def __eq__(self, other):
        return self is other

    def renderControl(self):
        return self._rc

    def getMenu(self):
        return self._menu

    def getFilename(self):
        return self.image.filename

    def setName(self, name):
        self.name = name
        self._wlabel.setText("%s: %s" %
                             (chr(ord('a') + self._number), self.name))

    def setNumber(self, num):
        self._number = num
        self._menu.menuAction().setText(
            "%s: %s" % (chr(ord('a') + self._number), self.name))
        self._qa_raise.setShortcut(QKeySequence("Alt+" + chr(ord('A') + num)))
        self.setName(self.name)

    def getNumber(self):
        return self._number

    def setPlotProjection(self, proj):
        self.image.setPlotProjection(proj)
        sameproj = proj == self.image.projection
        self._wcenter.setVisible(sameproj)
        self._qa_center.setVisible(not sameproj)
        if self._image_border:
            (l0, l1), (m0, m1) = self.image.getExtents()
            path = numpy.array([l0, l0, l1, l1,
                                l0]), numpy.array([m0, m1, m1, m0, m0])
            self._image_border.setSamples(*path)
            if self._image_label:
                self._image_label.setValue(path[0][2], path[1][2])

    def addPlotBorder(self,
                      border_pen,
                      label,
                      label_color=None,
                      bg_brush=None):
        # make plot items for image frame
        # make curve for image borders
        (l0, l1), (m0, m1) = self.image.getExtents()
        self._border_pen = QPen(border_pen)
        self._image_border.show()
        self._image_border.setSamples([l0, l0, l1, l1, l0],
                                      [m0, m1, m1, m0, m0])
        self._image_border.setPen(self._border_pen)
        self._image_border.setZ(
            self.image.z() + 1 if self._z_markers is None else self._z_markers)
        if label:
            self._image_label.show()
            self._image_label_text = text = QwtText(" %s " % label)
            text.setColor(label_color)
            text.setBackgroundBrush(bg_brush)
            self._image_label.setValue(l1, m1)
            self._image_label.setLabel(text)
            self._image_label.setLabelAlignment(Qt.AlignRight
                                                | Qt.AlignVCenter)
            self._image_label.setZ(
                self.image.z() +
                2 if self._z_markers is None else self._z_markers)

    def setPlotBorderStyle(self, border_color=None, label_color=None):
        if border_color:
            self._border_pen.setColor(border_color)
            self._image_border.setPen(self._border_pen)
        if label_color:
            self._image_label_text.setColor(label_color)
            self._image_label.setLabel(self._image_label_text)

    def showPlotBorder(self, show=True):
        self._image_border.setVisible(show)
        self._image_label.setVisible(show)

    def attachToPlot(self, plot, z_markers=None):
        for item in [self.image] + self._all_markers:
            if item.plot() != plot:
                item.attach(plot)

    def setImageVisible(self, visible):
        self.image.setVisible(visible)

    def showRenderControls(self):
        if not self._control_dialog:
            dprint(1, "creating control dialog")
            self._control_dialog = ImageControlDialog(self, self._rc,
                                                      self._imgman)
            # line below allows window to be resized by the user
            self._control_dialog.setSizeGripEnabled(True)
            # get and set sizing
            self._control_dialog.setMinimumWidth(396)
            # create size policy for control dialog
            colour_ctrl_policy = QSizePolicy()
            colour_ctrl_policy.setHorizontalPolicy(QSizePolicy.Minimum)
            self._control_dialog.setSizePolicy(colour_ctrl_policy)
            # setup dockable colour control dialog
            self._dockable_colour_ctrl = TDockWidget(
                title=f"{self._rc.image.name}",
                parent=self.parent().mainwin,
                bind_widget=self._control_dialog,
                close_slot=self.colourctrl_dockwidget_closed,
                toggle_slot=self.colourctrl_dockwidget_toggled)
            self.addDockWidgetToTab()
            dprint(1, "done")
        # set dockable widget visibility in sync with control dialog
        if not self._control_dialog.isVisible():
            dprint(1, "showing control dialog")
            self._control_dialog.show()
            self._dockable_colour_ctrl.setVisible(True)
            self.addDockWidgetToTab()
            self._dockable_colour_ctrl.show()
            self._dockable_colour_ctrl.raise_()
        else:
            self._control_dialog.hide()
            self._dockable_colour_ctrl.setVisible(False)
            self.parent().mainwin.setMaximumWidth(
                self.parent().mainwin.width() +
                self._dockable_colour_ctrl.width())

    def addDockWidgetToTab(self):
        # Add dockable widget to main window.
        # This needs to itterate through the widgets to find DockWidgets already in the right side area,
        # then tabifydockwidget when adding, or add to the right area if empty
        widget_list = self.parent().mainwin.findChildren(QDockWidget)
        for widget in widget_list:
            if self.parent().mainwin.dockWidgetArea(
                    widget) == 2:  # if in right dock area
                if widget.isVisible() and not widget.isFloating(
                ):  # if widget active and not a window
                    if self._dockable_colour_ctrl is not widget:  # check not itself
                        # add dock widget in tab on top of current widget in right area
                        self.parent().mainwin.tabifyDockWidget(
                            widget, self._dockable_colour_ctrl)
                        self.parent().mainwin.resizeDocks(
                            [widget], [widget.bind_widget.width()],
                            Qt.Horizontal)
            elif self.parent().mainwin.dockWidgetArea(
                    widget
            ) == 0:  # if not in any dock area assume we have new dock widget
                # no previous widget in this area then add
                self.parent().mainwin.addDockWidget(Qt.RightDockWidgetArea,
                                                    self._dockable_colour_ctrl)
                self.parent().mainwin.resizeDocks([widget],
                                                  [widget.bind_widget.width()],
                                                  Qt.Horizontal)

    def removeDockWidget(self):
        # remove image control dock widget
        self.parent().mainwin.removeDockWidget(self._dockable_colour_ctrl)
        # get widgets to resize
        widget_list = self.parent().mainwin.findChildren(QDockWidget)
        size_list = []
        result = []
        for widget in widget_list:
            if not isinstance(widget.bind_widget, ImageControlDialog):
                size_list.append(widget.bind_widget.width())
                result.append(widget)
                dprint(2, f"{widget} width {widget.width()}")
                dprint(
                    2,
                    f"{widget} bind_widget width {widget.bind_widget.width()}")
                if isinstance(widget.bind_widget, LiveImageZoom):
                    widget.bind_widget.setMinimumWidth(widget.width())
        widget_list = result
        # resize dock areas
        self.parent().mainwin.resizeDocks(widget_list, size_list,
                                          Qt.Horizontal)

    def colourctrl_dockwidget_closed(self):
        self._dockable_colour_ctrl.setVisible(False)
        self.parent().mainwin.setMaximumWidth(
            self.parent().mainwin.width() - self._dockable_colour_ctrl.width())

    def colourctrl_dockwidget_toggled(self):
        if self._dockable_colour_ctrl.isVisible():
            if self._dockable_colour_ctrl.isWindow():
                self._dockable_colour_ctrl.setFloating(False)
            else:
                self._dockable_colour_ctrl.setFloating(True)
                self.parent().mainwin.setMaximumWidth(
                    self.parent().mainwin.width() +
                    self._dockable_colour_ctrl.width())

    def _changeDisplayRangeToPercent(self, percent):
        if not self._control_dialog:
            self._control_dialog = ImageControlDialog(self, self._rc,
                                                      self._imgman)
        self._control_dialog._changeDisplayRangeToPercent(percent)

    def _updateDisplayRange(self, dmin, dmax):
        """Updates display range widgets."""
        self._wmin.setText("%.4g" % dmin)
        self._wmax.setText("%.4g" % dmax)
        self._updateFullRangeIcon()

    def _changeDisplayRange(self):
        """Gets display range from widgets and updates the image with it."""
        try:
            newrange = float(str(self._wmin.text())), float(
                str(self._wmax.text()))
        except ValueError:
            return
        self._rc.setDisplayRange(*newrange)

    def _dataSubsetChanged(self, subset, minmax, desc, subset_type):
        """Called when the data subset changes (or is reset)"""
        # hide the subset indicator -- unless we're invoked while we're actually setting the subset itself
        if not self._setting_lmrect:
            self._subset = None
            self._subset_border.setVisible(False)
            self._subset_label.setVisible(False)

    def setLMRectSubset(self, rect):
        self._subset = rect
        l0, m0, l1, m1 = rect.getCoords()
        self._subset_border.setSamples([l0, l0, l1, l1, l0],
                                       [m0, m1, m1, m0, m0])
        self._subset_border.setVisible(True)
        self._subset_label.setValue(max(l0, l1), max(m0, m1))
        self._subset_label.setVisible(True)
        self._setting_lmrect = True
        self.renderControl().setLMRectSubset(rect)
        self._setting_lmrect = False

    def currentSlice(self):
        return self._rc.currentSlice()

    def _updateImageSlice(self, _slice):
        dprint(2, _slice)
        for i, (iextra, name, labels) in enumerate(self._rc.slicedAxes()):
            slicer = self._wslicers[i]
            if slicer.currentIndex() != _slice[iextra]:
                dprint(3, "setting widget", i, "to", _slice[iextra])
                slicer.setCurrentIndex(_slice[iextra])

    def setMarkersZ(self, z):
        self._z_markers = z
        for i, elem in enumerate(self._all_markers):
            elem.setZ(z + i)

    def setZ(self, z, top=False, depthlabel=None, can_raise=True):
        self.image.setZ(z)
        if self._z_markers is None:
            for i, elem in enumerate(self._all_markers):
                elem.setZ(z + i + i)
        # set the depth label, if any
        label = "%s: %s" % (chr(ord('a') + self._number), self.name)
        # label = "%s %s"%(depthlabel,self.name) if depthlabel else self.name
        if top:
            label = "%s: <B>%s</B>" % (chr(ord('a') + self._number), self.name)
        self._wlabel.setText(label)
        # set hotkey
        self._qa_show_rc.setShortcut(Qt.Key_F9 if top else QKeySequence())
        # set raise control
        self._can_raise = can_raise
        self._qa_raise.setVisible(can_raise)
        self._wlock.setVisible(can_raise)
        if can_raise:
            self._wraise.setToolTip(
                "<P>Click here to raise this image to the top. Click on the down-arrow to access the image menu.</P>"
            )
        else:
            self._wraise.setToolTip("<P>Click to access the image menu.</P>")

    def _raiseButtonPressed(self):
        if self._can_raise:
            self.image.signalRaise.emit(self.image)
        else:
            self._wraise.showMenu()

    def _saveImage(self):
        filename = QFileDialog.getSaveFileName(
            self,
            "Save FITS file",
            self._save_dir,
            "FITS files(*.fits *.FITS *fts *FTS)",
            options=QFileDialog.DontUseNativeDialog)
        filename = str(filename[0])
        if not filename:
            return
        busy = BusyIndicator()
        self._imgman.signalShowMessage.emit(
            """Writing FITS image %s""" % filename, 3000)
        QApplication.flush()
        try:
            self.image.save(filename)
        except Exception as exc:
            busy.reset_cursor()
            traceback.print_exc()
            self._imgman.signalShowErrorMessage.emit(
                """Error writing FITS image %s: %s""" %
                (filename, str(sys.exc_info()[1])))
            return None
        self.renderControl().startSavingConfig(filename)
        self.setName(self.image.name)
        self._qa_save.setVisible(False)
        self._wsave.hide()
        busy.reset_cursor()

    def _exportImageResolution(self):
        sender = self.sender()
        if isinstance(sender, QCheckBox):
            if sender.isChecked():
                self._exportMaxRes = True
            else:
                self._exportMaxRes = False

    def _exportImageToPNG(self, filename=None):
        if not filename:
            if not self._export_png_dialog:
                dialog = self._export_png_dialog = QFileDialog(
                    self, "Export image to PNG", ".", "*.png")
                dialog.setDefaultSuffix("png")
                dialog.setFileMode(QFileDialog.AnyFile)
                dialog.setAcceptMode(QFileDialog.AcceptSave)
                dialog.setModal(True)
                dialog.filesSelected['QStringList'].connect(
                    self._exportImageToPNG)
                # attempt to add limit 4K option - not available on Ubuntu Unity
                layout = dialog.layout()
                if layout is not None:
                    checkbox = QCheckBox("Limit to 4K image")
                    checkbox.setChecked(False)
                    checkbox.setToolTip("Limits the image output to 4K")
                    checkbox.toggled.connect(self._exportImageResolution)
                    layout.addWidget(checkbox)
                    dialog.setLayout(layout)
            return self._export_png_dialog.exec_() == QDialog.Accepted
        busy = BusyIndicator()
        if isinstance(filename, QStringList):
            filename = filename[0]
        filename = str(filename)
        # get image dimensions
        nx, ny = self.image.imageDims()
        # export either max resolution possible or default to 4K. If image is small then no scaling occurs.
        if not self._exportMaxRes:
            # get free memory. Note: Linux only!
            import os
            total_memory, used_memory, free_memory = map(
                int,
                os.popen('free -t -m').readlines()[-1].split()[1:])
            # use 90% of free memory available
            free_memory = free_memory * 0.9
            # use an approximation to find the max image size that can be generated
            if nx >= ny and nx > free_memory:
                scale_factor = round(free_memory / nx, 1)
            elif ny > nx and ny > free_memory:
                scale_factor = round(free_memory / ny, 1)
            else:
                scale_factor = 1
        else:
            # default to 4K
            if nx > 4000:
                scale_factor = 4000 / nx
            elif ny > nx and ny > 4000:
                scale_factor = 4000 / ny
            else:
                scale_factor = 1

        # make QPixmap
        nx = nx * scale_factor
        ny = ny * scale_factor
        (l0, l1), (m0, m1) = self.image.getExtents()
        pixmap = QPixmap(nx, ny)
        painter = QPainter(pixmap)
        # use QwtPlot implementation of draw canvas, since we want to avoid caching
        xmap = QwtScaleMap()
        xmap.setPaintInterval(0, nx)
        xmap.setScaleInterval(l1, l0)
        ymap = QwtScaleMap()
        ymap.setPaintInterval(ny, 0)
        ymap.setScaleInterval(m0, m1)
        # call painter with clear cache option for consistent file size output.
        self.image.draw(painter, xmap, ymap, pixmap.rect(), use_cache=False)
        painter.end()
        # save to file
        try:
            pixmap.save(filename, "PNG")
            # clean up export items
            pixmap.detach()
            del xmap
            del ymap
            del pixmap
            del painter
        except Exception as exc:
            self._imgman.signalShowErrorMessage[str, int].emit(
                "Error writing %s: %s" % (filename, str(exc)), 3000)
            busy.reset_cursor()
        else:
            busy.reset_cursor()
            self._imgman.signalShowMessage[str, int].emit(
                "Exported image to file %s" % filename, 3000)

    def _toggleDisplayRangeLock(self):
        self.renderControl().lockDisplayRange(
            not self.renderControl().isDisplayRangeLocked())

    def _setDisplayRangeLock(self, locked):
        self._wlock.setIcon(
            pixmaps.locked.icon() if locked else pixmaps.unlocked.icon())

    def _updateFullRangeIcon(self):
        if self._rc.isSubsetDisplayRange():
            self._wfullrange.setIcon(pixmaps.zoom_range.icon())
            self._wfullrange.setToolTip(
                """<P>The current intensity range is the full range. Hold this button down briefly for additional options.</P>"""
            )
        else:
            self._wfullrange.setIcon(pixmaps.full_range.icon())
            self._wfullrange.setToolTip(
                """<P>Click to reset to a full intensity range. Hold the button down briefly for additional options.</P>"""
            )
예제 #43
0
    def __init__(self, parent):
        QFrame.__init__(self, parent)
        self.setFrameStyle(QFrame.Shape.NoFrame)
        self.setObjectName('search_bar')
        self._layout = l = QHBoxLayout(self)
        l.setContentsMargins(0, 4, 0, 4)

        x = parent.virtual_library = QToolButton(self)
        x.setCursor(Qt.CursorShape.PointingHandCursor)
        x.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
        x.setText(_('Virtual library'))
        x.setAutoRaise(True)
        x.setIcon(QIcon(I('vl.png')))
        x.setObjectName("virtual_library")
        x.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon)
        l.addWidget(x)

        x = QToolButton(self)
        x.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon)
        x.setAutoRaise(True)
        x.setIcon(QIcon(I('minus.png')))
        x.setObjectName('clear_vl')
        l.addWidget(x)
        x.setVisible(False)
        x.setToolTip(_('Close the Virtual library'))
        parent.clear_vl = x
        self.vl_sep = QFrame(self)
        self.vl_sep.setFrameStyle(QFrame.Shape.VLine | QFrame.Shadow.Sunken)
        l.addWidget(self.vl_sep)

        parent.sort_sep = QFrame(self)
        parent.sort_sep.setFrameStyle(QFrame.Shape.VLine | QFrame.Shadow.Sunken)
        parent.sort_sep.setVisible(False)
        parent.sort_button = self.sort_button = sb = QToolButton(self)
        sb.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon)
        sb.setToolTip(_('Change how the displayed books are sorted'))
        sb.setCursor(Qt.CursorShape.PointingHandCursor)
        sb.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
        sb.setAutoRaise(True)
        sb.setText(_('Sort'))
        sb.setIcon(QIcon(I('sort.png')))
        sb.setMenu(QMenu(sb))
        sb.menu().aboutToShow.connect(self.populate_sort_menu)
        sb.setVisible(False)
        l.addWidget(sb)
        l.addWidget(parent.sort_sep)

        x = parent.search = SearchBox2(self, as_url=search_as_url)
        x.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
        x.setObjectName("search")
        x.setToolTip(_("<p>Search the list of books by title, author, publisher, "
                       "tags, comments, etc.<br><br>Words separated by spaces are ANDed"))
        x.setMinimumContentsLength(10)
        l.addWidget(x)

        parent.advanced_search_toggle_action = ac = parent.search.add_action('gear.png', QLineEdit.ActionPosition.LeadingPosition)
        parent.addAction(ac)
        ac.setToolTip(_('Advanced search'))
        parent.keyboard.register_shortcut('advanced search toggle',
                _('Advanced search'), default_keys=("Shift+Ctrl+F",),
                action=ac)

        self.search_button = QToolButton()
        self.search_button.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextOnly)
        self.search_button.setIcon(QIcon(I('search.png')))
        self.search_button.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon)
        self.search_button.setText(_('Search'))
        self.search_button.setAutoRaise(True)
        self.search_button.setCursor(Qt.CursorShape.PointingHandCursor)
        l.addWidget(self.search_button)
        self.search_button.setSizePolicy(QSizePolicy.Policy.Minimum,
                QSizePolicy.Policy.Minimum)
        self.search_button.clicked.connect(parent.do_search_button)
        self.search_button.setToolTip(
            _('Do quick search (you can also press the Enter key)'))

        x = parent.highlight_only_button = QToolButton(self)
        x.setAutoRaise(True)
        x.setText(_('Highlight'))
        x.setCursor(Qt.CursorShape.PointingHandCursor)
        x.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon)
        x.setIcon(QIcon(I('arrow-down.png')))
        l.addWidget(x)

        x = parent.saved_search = SavedSearchBox(self)
        x.setMaximumSize(QSize(150, 16777215))
        x.setMinimumContentsLength(10)
        x.setObjectName("saved_search")
        l.addWidget(x)
        x.setVisible(tweaks['show_saved_search_box'])

        x = parent.copy_search_button = QToolButton(self)
        x.setAutoRaise(True)
        x.setCursor(Qt.CursorShape.PointingHandCursor)
        x.setIcon(QIcon(I("search_copy_saved.png")))
        x.setObjectName("copy_search_button")
        l.addWidget(x)
        x.setToolTip(_("Copy current search text (instead of search name)"))
        x.setVisible(tweaks['show_saved_search_box'])

        x = parent.save_search_button = RightClickButton(self)
        x.setAutoRaise(True)
        x.setCursor(Qt.CursorShape.PointingHandCursor)
        x.setIcon(QIcon(I("search_add_saved.png")))
        x.setObjectName("save_search_button")
        l.addWidget(x)
        x.setVisible(tweaks['show_saved_search_box'])

        x = parent.add_saved_search_button = RightClickButton(self)
        x.setToolTip(_(
            'Use an existing Saved search or create a new one'
        ))
        x.setText(_('Saved search'))
        x.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon)
        x.setCursor(Qt.CursorShape.PointingHandCursor)
        x.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
        x.setAutoRaise(True)
        x.setIcon(QIcon(I("bookmarks.png")))
        l.addWidget(x)
        x.setVisible(not tweaks['show_saved_search_box'])
예제 #44
0
파일: single.py 프로젝트: JapaChin/calibre
class MetadataSingleDialogBase(ResizableDialog):

    view_format = pyqtSignal(object, object)
    cc_two_column = tweaks['metadata_single_use_2_cols_for_custom_fields']
    one_line_comments_toolbar = False
    use_toolbutton_for_config_metadata = True

    def __init__(self, db, parent=None, editing_multiple=False):
        self.db = db
        self.changed = set()
        self.books_to_refresh = set()
        self.rows_to_refresh = set()
        self.metadata_before_fetch = None
        self.editing_multiple = editing_multiple
        self.comments_edit_state_at_apply = {}
        ResizableDialog.__init__(self, parent)

    def setupUi(self, *args):  # {{{
        self.resize(990, 670)

        self.download_shortcut = QShortcut(self)
        self.download_shortcut.setKey(QKeySequence('Ctrl+D',
            QKeySequence.PortableText))
        p = self.parent()
        if hasattr(p, 'keyboard'):
            kname = u'Interface Action: Edit Metadata (Edit Metadata) : menu action : download'
            sc = p.keyboard.keys_map.get(kname, None)
            if sc:
                self.download_shortcut.setKey(sc[0])
        self.swap_title_author_shortcut = s = QShortcut(self)
        s.setKey(QKeySequence('Alt+Down', QKeySequence.PortableText))

        self.button_box = bb = QDialogButtonBox(self)
        self.button_box.accepted.connect(self.accept)
        self.button_box.rejected.connect(self.reject)
        self.next_button = QPushButton(QIcon(I('forward.png')), _('Next'),
                self)
        self.next_button.setShortcut(QKeySequence('Alt+Right'))
        self.next_button.clicked.connect(self.next_clicked)
        self.prev_button = QPushButton(QIcon(I('back.png')), _('Previous'),
                self)
        self.prev_button.setShortcut(QKeySequence('Alt+Left'))

        self.button_box.addButton(self.prev_button, bb.ActionRole)
        self.button_box.addButton(self.next_button, bb.ActionRole)
        self.prev_button.clicked.connect(self.prev_clicked)
        bb.setStandardButtons(bb.Ok|bb.Cancel)
        bb.button(bb.Ok).setDefault(True)

        self.scroll_area = QScrollArea(self)
        self.scroll_area.setFrameShape(QScrollArea.NoFrame)
        self.scroll_area.setWidgetResizable(True)
        self.central_widget = QTabWidget(self)
        self.scroll_area.setWidget(self.central_widget)

        self.l = QVBoxLayout(self)
        self.setLayout(self.l)
        self.l.addWidget(self.scroll_area)
        ll = self.button_box_layout = QHBoxLayout()
        self.l.addLayout(ll)
        ll.addSpacing(10)
        ll.addWidget(self.button_box)

        self.setWindowIcon(QIcon(I('edit_input.png')))
        self.setWindowTitle(BASE_TITLE)

        self.create_basic_metadata_widgets()

        if len(self.db.custom_column_label_map):
            self.create_custom_metadata_widgets()
        self.comments_edit_state_at_apply = {self.comments:None}

        self.do_layout()
        geom = gprefs.get('metasingle_window_geometry3', None)
        if geom is not None:
            self.restoreGeometry(bytes(geom))
    # }}}

    def create_basic_metadata_widgets(self):  # {{{
        self.basic_metadata_widgets = []

        self.languages = LanguagesEdit(self)
        self.basic_metadata_widgets.append(self.languages)

        self.title = TitleEdit(self)
        self.title.textChanged.connect(self.update_window_title)
        self.deduce_title_sort_button = QToolButton(self)
        self.deduce_title_sort_button.setToolTip(
            _('Automatically create the title sort entry based on the current '
                'title entry.\nUsing this button to create title sort will '
                'change title sort from red to green.'))
        self.deduce_title_sort_button.setWhatsThis(
                self.deduce_title_sort_button.toolTip())
        self.title_sort = TitleSortEdit(self, self.title,
                self.deduce_title_sort_button, self.languages)
        self.basic_metadata_widgets.extend([self.title, self.title_sort])

        self.deduce_author_sort_button = b = RightClickButton(self)
        b.setToolTip('<p>' +
            _('Automatically create the author sort entry based on the current '
              'author entry. Using this button to create author sort will '
              'change author sort from red to green.  There is a menu of '
              'functions available under this button. Click and hold '
              'on the button to see it.') + '</p>')
        if isosx:
            # Workaround for https://bugreports.qt-project.org/browse/QTBUG-41017
            class Menu(QMenu):

                def mouseReleaseEvent(self, ev):
                    ac = self.actionAt(ev.pos())
                    if ac is not None:
                        ac.trigger()
                    return QMenu.mouseReleaseEvent(self, ev)
            b.m = m = Menu()
        else:
            b.m = m = QMenu()
        ac = m.addAction(QIcon(I('forward.png')), _('Set author sort from author'))
        ac2 = m.addAction(QIcon(I('back.png')), _('Set author from author sort'))
        ac3 = m.addAction(QIcon(I('user_profile.png')), _('Manage authors'))
        ac4 = m.addAction(QIcon(I('next.png')),
                _('Copy author to author sort'))
        ac5 = m.addAction(QIcon(I('previous.png')),
                _('Copy author sort to author'))

        b.setMenu(m)
        self.authors = AuthorsEdit(self, ac3)
        self.author_sort = AuthorSortEdit(self, self.authors, b, self.db, ac,
                ac2, ac4, ac5)
        self.basic_metadata_widgets.extend([self.authors, self.author_sort])

        self.swap_title_author_button = QToolButton(self)
        self.swap_title_author_button.setIcon(QIcon(I('swap.png')))
        self.swap_title_author_button.setToolTip(_(
            'Swap the author and title') + ' [%s]' % self.swap_title_author_shortcut.key().toString(QKeySequence.NativeText))
        self.swap_title_author_button.clicked.connect(self.swap_title_author)
        self.swap_title_author_shortcut.activated.connect(self.swap_title_author_button.click)

        self.manage_authors_button = QToolButton(self)
        self.manage_authors_button.setIcon(QIcon(I('user_profile.png')))
        self.manage_authors_button.setToolTip('<p>' + _(
            'Manage authors. Use to rename authors and correct '
            'individual author\'s sort values') + '</p>')
        self.manage_authors_button.clicked.connect(self.authors.manage_authors)

        self.series = SeriesEdit(self)
        self.clear_series_button = QToolButton(self)
        self.clear_series_button.setToolTip(
               _('Clear series'))
        self.clear_series_button.clicked.connect(self.series.clear)
        self.series_index = SeriesIndexEdit(self, self.series)
        self.basic_metadata_widgets.extend([self.series, self.series_index])

        self.formats_manager = FormatsManager(self, self.copy_fmt)
        # We want formats changes to be committed before title/author, as
        # otherwise we could have data loss if the title/author changed and the
        # user was trying to add an extra file from the old books directory.
        self.basic_metadata_widgets.insert(0, self.formats_manager)
        self.formats_manager.metadata_from_format_button.clicked.connect(
                self.metadata_from_format)
        self.formats_manager.cover_from_format_button.clicked.connect(
                self.cover_from_format)
        self.cover = Cover(self)
        self.cover.download_cover.connect(self.download_cover)
        self.basic_metadata_widgets.append(self.cover)

        self.comments = CommentsEdit(self, self.one_line_comments_toolbar)
        self.basic_metadata_widgets.append(self.comments)

        self.rating = RatingEdit(self)
        self.clear_ratings_button = QToolButton(self)
        self.clear_ratings_button.setToolTip(_('Clear rating'))
        self.clear_ratings_button.setIcon(QIcon(I('trash.png')))
        self.clear_ratings_button.clicked.connect(self.rating.zero)

        self.basic_metadata_widgets.append(self.rating)

        self.tags = TagsEdit(self)
        self.tags_editor_button = QToolButton(self)
        self.tags_editor_button.setToolTip(_('Open Tag Editor'))
        self.tags_editor_button.setIcon(QIcon(I('chapters.png')))
        self.tags_editor_button.clicked.connect(self.tags_editor)
        self.clear_tags_button = QToolButton(self)
        self.clear_tags_button.setToolTip(_('Clear all tags'))
        self.clear_tags_button.setIcon(QIcon(I('trash.png')))
        self.clear_tags_button.clicked.connect(self.tags.clear)
        self.basic_metadata_widgets.append(self.tags)

        self.identifiers = IdentifiersEdit(self)
        self.basic_metadata_widgets.append(self.identifiers)
        self.clear_identifiers_button = QToolButton(self)
        self.clear_identifiers_button.setIcon(QIcon(I('trash.png')))
        self.clear_identifiers_button.setToolTip(_('Clear Ids'))
        self.clear_identifiers_button.clicked.connect(self.identifiers.clear)
        self.paste_isbn_button = QToolButton(self)
        self.paste_isbn_button.setToolTip('<p>' +
                    _('Paste the contents of the clipboard into the '
                      'identifiers box prefixed with isbn:') + '</p>')
        self.paste_isbn_button.setIcon(QIcon(I('edit-paste.png')))
        self.paste_isbn_button.clicked.connect(self.identifiers.paste_isbn)

        self.publisher = PublisherEdit(self)
        self.basic_metadata_widgets.append(self.publisher)

        self.timestamp = DateEdit(self)
        self.pubdate = PubdateEdit(self)
        self.basic_metadata_widgets.extend([self.timestamp, self.pubdate])

        self.fetch_metadata_button = b = RightClickButton(self)
        # The following rigmarole is needed so that Qt gives the button the
        # same height as the other buttons in the dialog. There is no way to
        # center the text in a QToolButton with an icon, so we cant just set an
        # icon
        b.setIcon(QIcon(I('download-metadata.png')))
        b.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        b.setMinimumHeight(b.sizeHint().height())
        b.setIcon(QIcon())
        b.setText(_('&Download metadata')), b.setPopupMode(b.DelayedPopup)
        b.setToolTip(_('Download metadata for this book [%s]') % self.download_shortcut.key().toString(QKeySequence.NativeText))
        b.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed))
        self.fetch_metadata_button.clicked.connect(self.fetch_metadata)
        self.fetch_metadata_menu = m = QMenu(self.fetch_metadata_button)
        m.addAction(QIcon(I('edit-undo.png')), _('Undo last metadata download'), self.undo_fetch_metadata)
        self.fetch_metadata_button.setMenu(m)
        self.download_shortcut.activated.connect(self.fetch_metadata_button.click)
        font = self.fmb_font = QFont()
        font.setBold(True)
        self.fetch_metadata_button.setFont(font)

        if self.use_toolbutton_for_config_metadata:
            self.config_metadata_button = QToolButton(self)
            self.config_metadata_button.setIcon(QIcon(I('config.png')))
        else:
            self.config_metadata_button = QPushButton(self)
            self.config_metadata_button.setText(_('Configure download metadata'))
        self.config_metadata_button.setIcon(QIcon(I('config.png')))
        self.config_metadata_button.clicked.connect(self.configure_metadata)
        self.config_metadata_button.setToolTip(
            _('Change how calibre downloads metadata'))

    # }}}

    def create_custom_metadata_widgets(self):  # {{{
        self.custom_metadata_widgets_parent = w = QWidget(self)
        layout = QGridLayout()
        w.setLayout(layout)
        self.custom_metadata_widgets, self.__cc_spacers = \
            populate_metadata_page(layout, self.db, None, parent=w, bulk=False,
                two_column=self.cc_two_column)
        self.__custom_col_layouts = [layout]
        for widget in self.custom_metadata_widgets:
            if isinstance(widget, Comments):
                self.comments_edit_state_at_apply[widget] = None
    # }}}

    def set_custom_metadata_tab_order(self, before=None, after=None):  # {{{
        sto = QWidget.setTabOrder
        if getattr(self, 'custom_metadata_widgets', []):
            ans = self.custom_metadata_widgets
            for i in range(len(ans)-1):
                if before is not None and i == 0:
                    pass
                if len(ans[i+1].widgets) == 2:
                    sto(ans[i].widgets[-1], ans[i+1].widgets[1])
                else:
                    sto(ans[i].widgets[-1], ans[i+1].widgets[0])
                for c in range(2, len(ans[i].widgets), 2):
                    sto(ans[i].widgets[c-1], ans[i].widgets[c+1])
            if after is not None:
                pass
    # }}}

    def do_view_format(self, path, fmt):
        if path:
            self.view_format.emit(None, path)
        else:
            self.view_format.emit(self.book_id, fmt)

    def copy_fmt(self, fmt, f):
        self.db.copy_format_to(self.book_id, fmt, f, index_is_id=True)

    def do_layout(self):
        raise NotImplementedError()

    def __call__(self, id_):
        self.book_id = id_
        self.books_to_refresh = set([])
        self.metadata_before_fetch = None
        for widget in self.basic_metadata_widgets:
            widget.initialize(self.db, id_)
        for widget in getattr(self, 'custom_metadata_widgets', []):
            widget.initialize(id_)
        if callable(self.set_current_callback):
            self.set_current_callback(id_)
        # Commented out as it doesn't play nice with Next, Prev buttons
        # self.fetch_metadata_button.setFocus(Qt.OtherFocusReason)

    # Miscellaneous interaction methods {{{
    def update_window_title(self, *args):
        title = self.title.current_val
        if len(title) > 50:
            title = title[:50] + u'\u2026'
        self.setWindowTitle(BASE_TITLE + ' - ' +
                title + ' - ' +
                _(' [%(num)d of %(tot)d]')%dict(num=self.current_row+1,
                tot=len(self.row_list)))

    def swap_title_author(self, *args):
        title = self.title.current_val
        self.title.current_val = authors_to_string(self.authors.current_val)
        self.authors.current_val = string_to_authors(title)
        self.title_sort.auto_generate()
        self.author_sort.auto_generate()

    def tags_editor(self, *args):
        self.tags.edit(self.db, self.book_id)

    def metadata_from_format(self, *args):
        mi, ext = self.formats_manager.get_selected_format_metadata(self.db,
                self.book_id)
        if mi is not None:
            self.update_from_mi(mi)

    def get_pdf_cover(self):
        pdfpath = self.formats_manager.get_format_path(self.db, self.book_id,
                                                       'pdf')
        from calibre.gui2.metadata.pdf_covers import PDFCovers
        d = PDFCovers(pdfpath, parent=self)
        if d.exec_() == d.Accepted:
            cpath = d.cover_path
            if cpath:
                with open(cpath, 'rb') as f:
                    self.update_cover(f.read(), 'PDF')
        d.cleanup()

    def cover_from_format(self, *args):
        ext = self.formats_manager.get_selected_format()
        if ext is None:
            return
        if ext == 'pdf':
            return self.get_pdf_cover()
        try:
            mi, ext = self.formats_manager.get_selected_format_metadata(self.db,
                    self.book_id)
        except (IOError, OSError) as err:
            if getattr(err, 'errno', None) == errno.EACCES:  # Permission denied
                import traceback
                fname = err.filename if err.filename else 'file'
                error_dialog(self, _('Permission denied'),
                        _('Could not open %s. Is it being used by another'
                        ' program?')%fname, det_msg=traceback.format_exc(),
                        show=True)
                return
            raise
        if mi is None:
            return
        cdata = None
        if mi.cover and os.access(mi.cover, os.R_OK):
            cdata = open(mi.cover).read()
        elif mi.cover_data[1] is not None:
            cdata = mi.cover_data[1]
        if cdata is None:
            error_dialog(self, _('Could not read cover'),
                         _('Could not read cover from %s format')%ext).exec_()
            return
        self.update_cover(cdata, ext)

    def update_cover(self, cdata, fmt):
        orig = self.cover.current_val
        self.cover.current_val = cdata
        if self.cover.current_val is None:
            self.cover.current_val = orig
            return error_dialog(self, _('Could not read cover'),
                         _('The cover in the %s format is invalid')%fmt,
                         show=True)
            return

    def update_from_mi(self, mi, update_sorts=True, merge_tags=True, merge_comments=False):
        fw = self.focusWidget()
        if not mi.is_null('title'):
            self.title.set_value(mi.title)
            if update_sorts:
                self.title_sort.auto_generate()
        if not mi.is_null('authors'):
            self.authors.set_value(mi.authors)
        if not mi.is_null('author_sort'):
            self.author_sort.set_value(mi.author_sort)
        elif update_sorts and not mi.is_null('authors'):
            self.author_sort.auto_generate()
        if not mi.is_null('rating'):
            try:
                self.rating.set_value(mi.rating)
            except:
                pass
        if not mi.is_null('publisher'):
            self.publisher.set_value(mi.publisher)
        if not mi.is_null('tags'):
            old_tags = self.tags.current_val
            tags = mi.tags if mi.tags else []
            if old_tags and merge_tags:
                ltags, lotags = {t.lower() for t in tags}, {t.lower() for t in
                        old_tags}
                tags = [t for t in tags if t.lower() in ltags-lotags] + old_tags
            self.tags.set_value(tags)
        if not mi.is_null('identifiers'):
            current = self.identifiers.current_val
            current.update(mi.identifiers)
            self.identifiers.set_value(current)
        if not mi.is_null('pubdate'):
            self.pubdate.set_value(mi.pubdate)
        if not mi.is_null('series') and mi.series.strip():
            self.series.set_value(mi.series)
            if mi.series_index is not None:
                self.series_index.reset_original()
                self.series_index.set_value(float(mi.series_index))
        if not mi.is_null('languages'):
            langs = [canonicalize_lang(x) for x in mi.languages]
            langs = [x for x in langs if x is not None]
            if langs:
                self.languages.set_value(langs)
        if mi.comments and mi.comments.strip():
            val = mi.comments
            if val and merge_comments:
                cval = self.comments.current_val
                if cval:
                    val = merge_two_comments(cval, val)
            self.comments.set_value(val)
        if fw is not None:
            fw.setFocus(Qt.OtherFocusReason)

    def fetch_metadata(self, *args):
        d = FullFetch(self.cover.pixmap(), self)
        ret = d.start(title=self.title.current_val, authors=self.authors.current_val,
                identifiers=self.identifiers.current_val)
        if ret == d.Accepted:
            self.metadata_before_fetch = {f:getattr(self, f).current_val for f in fetched_fields}
            from calibre.ebooks.metadata.sources.prefs import msprefs
            mi = d.book
            dummy = Metadata(_('Unknown'))
            for f in msprefs['ignore_fields']:
                if ':' not in f:
                    setattr(mi, f, getattr(dummy, f))
            if mi is not None:
                pd = mi.pubdate
                if pd is not None:
                    # Put the downloaded published date into the local timezone
                    # as we discard time info and the date is timezone
                    # invariant. This prevents the as_local_timezone() call in
                    # update_from_mi from changing the pubdate
                    mi.pubdate = datetime(pd.year, pd.month, pd.day,
                            tzinfo=local_tz)
                self.update_from_mi(mi, merge_comments=msprefs['append_comments'])
            if d.cover_pixmap is not None:
                self.metadata_before_fetch['cover'] = self.cover.current_val
                self.cover.current_val = pixmap_to_data(d.cover_pixmap)

    def undo_fetch_metadata(self):
        if self.metadata_before_fetch is None:
            return error_dialog(self, _('No downloaded metadata'), _(
                'There is no downloaded metadata to undo'), show=True)
        for field, val in self.metadata_before_fetch.iteritems():
            getattr(self, field).current_val = val
        self.metadata_before_fetch = None

    def configure_metadata(self):
        from calibre.gui2.preferences import show_config_widget
        gui = self.parent()
        show_config_widget('Sharing', 'Metadata download', parent=self,
                gui=gui, never_shutdown=True)

    def download_cover(self, *args):
        from calibre.gui2.metadata.single_download import CoverFetch
        d = CoverFetch(self.cover.pixmap(), self)
        ret = d.start(self.title.current_val, self.authors.current_val,
                self.identifiers.current_val)
        if ret == d.Accepted:
            if d.cover_pixmap is not None:
                self.cover.current_val = pixmap_to_data(d.cover_pixmap)

    # }}}

    def to_book_metadata(self):
        mi = Metadata(_('Unknown'))
        if self.db is None:
            return mi
        mi.set_all_user_metadata(self.db.field_metadata.custom_field_metadata())
        for widget in self.basic_metadata_widgets:
            widget.apply_to_metadata(mi)
        for widget in getattr(self, 'custom_metadata_widgets', []):
            widget.apply_to_metadata(mi)
        return mi

    def apply_changes(self):
        self.changed.add(self.book_id)
        if self.db is None:
            # break_cycles has already been called, don't know why this should
            # happen but a user reported it
            return True
        self.comments_edit_state_at_apply = {w:w.tab for w in self.comments_edit_state_at_apply}
        for widget in self.basic_metadata_widgets:
            try:
                if hasattr(widget, 'validate_for_commit'):
                    title, msg, det_msg = widget.validate_for_commit()
                    if title is not None:
                        error_dialog(self, title, msg, det_msg=det_msg, show=True)
                        return False
                widget.commit(self.db, self.book_id)
                self.books_to_refresh |= getattr(widget, 'books_to_refresh', set())
            except (IOError, OSError) as err:
                if getattr(err, 'errno', None) == errno.EACCES:  # Permission denied
                    import traceback
                    fname = getattr(err, 'filename', None)
                    p = 'Locked file: %s\n\n'%fname if fname else ''
                    error_dialog(self, _('Permission denied'),
                            _('Could not change the on disk location of this'
                                ' book. Is it open in another program?'),
                            det_msg=p+traceback.format_exc(), show=True)
                    return False
                raise
        for widget in getattr(self, 'custom_metadata_widgets', []):
            self.books_to_refresh |= widget.commit(self.book_id)

        self.db.commit()
        rows = self.db.refresh_ids(list(self.books_to_refresh))
        if rows:
            self.rows_to_refresh |= set(rows)

        return True

    def accept(self):
        self.save_state()
        if not self.apply_changes():
            return
        if self.editing_multiple and self.current_row != len(self.row_list) - 1:
            num = len(self.row_list) - 1 - self.current_row
            from calibre.gui2 import question_dialog
            if not question_dialog(
                    self, _('Are you sure?'),
                    _('There are still %d more books to edit in this set.'
                      ' Are you sure you want to stop? Use the Next button'
                      ' instead of the OK button to move through books in the set.') % num,
                    yes_text=_('&Stop editing'), no_text=_('&Continue editing'),
                    yes_icon='dot_red.png', no_icon='dot_green.png',
                    default_yes=False, skip_dialog_name='edit-metadata-single-confirm-ok-on-multiple'):
                return self.do_one(delta=1, apply_changes=False)
        ResizableDialog.accept(self)

    def reject(self):
        self.save_state()
        ResizableDialog.reject(self)

    def save_state(self):
        try:
            gprefs['metasingle_window_geometry3'] = bytearray(self.saveGeometry())
        except:
            # Weird failure, see https://bugs.launchpad.net/bugs/995271
            import traceback
            traceback.print_exc()

    # Dialog use methods {{{
    def start(self, row_list, current_row, view_slot=None,
            set_current_callback=None):
        self.row_list = row_list
        self.current_row = current_row
        if view_slot is not None:
            self.view_format.connect(view_slot)
        self.set_current_callback = set_current_callback
        self.do_one(apply_changes=False)
        ret = self.exec_()
        self.break_cycles()
        return ret

    def next_clicked(self):
        if not self.apply_changes():
            return
        self.do_one(delta=1, apply_changes=False)

    def prev_clicked(self):
        if not self.apply_changes():
            return
        self.do_one(delta=-1, apply_changes=False)

    def do_one(self, delta=0, apply_changes=True):
        if apply_changes:
            self.apply_changes()
        self.current_row += delta
        self.update_window_title()
        prev = next_ = None
        if self.current_row > 0:
            prev = self.db.title(self.row_list[self.current_row-1])
        if self.current_row < len(self.row_list) - 1:
            next_ = self.db.title(self.row_list[self.current_row+1])

        if next_ is not None:
            tip = (_('Save changes and edit the metadata of %s')+
                    ' [Alt+Right]')%next_
            self.next_button.setToolTip(tip)
        self.next_button.setEnabled(next_ is not None)
        if prev is not None:
            tip = (_('Save changes and edit the metadata of %s')+
                    ' [Alt+Left]')%prev
            self.prev_button.setToolTip(tip)
        self.prev_button.setEnabled(prev is not None)
        self.button_box.button(self.button_box.Ok).setDefault(True)
        self.button_box.button(self.button_box.Ok).setFocus(Qt.OtherFocusReason)
        self(self.db.id(self.row_list[self.current_row]))
        for w, state in self.comments_edit_state_at_apply.iteritems():
            if state == 'code':
                w.tab = 'code'

    def break_cycles(self):
        # Break any reference cycles that could prevent python
        # from garbage collecting this dialog
        self.set_current_callback = self.db = None
        self.metadata_before_fetch = None
        def disconnect(signal):
            try:
                signal.disconnect()
            except:
                pass  # Fails if view format was never connected
        disconnect(self.view_format)
        for b in ('next_button', 'prev_button'):
            x = getattr(self, b, None)
            if x is not None:
                disconnect(x.clicked)
        for widget in self.basic_metadata_widgets:
            bc = getattr(widget, 'break_cycles', None)
            if bc is not None and callable(bc):
                bc()
        for widget in getattr(self, 'custom_metadata_widgets', []):
            widget.break_cycles()
예제 #45
0
class FontFamilyDialog(QDialog):
    def __init__(self, current_family, parent=None):
        QDialog.__init__(self, parent)
        self.setWindowTitle(_('Choose font family'))
        self.setWindowIcon(QIcon(I('font.png')))
        from calibre.utils.fonts.scanner import font_scanner
        self.font_scanner = font_scanner

        self.m = QStringListModel(self)
        self.build_font_list()
        self.l = l = QGridLayout()
        self.setLayout(l)
        self.view = FontsView(self)
        self.view.setModel(self.m)
        self.view.setCurrentIndex(self.m.index(0))
        if current_family:
            for i, val in enumerate(self.families):
                if icu_lower(val) == icu_lower(current_family):
                    self.view.setCurrentIndex(self.m.index(i))
                    break
        self.view.doubleClicked.connect(self.accept, type=Qt.QueuedConnection)
        self.view.changed.connect(self.current_changed,
                                  type=Qt.QueuedConnection)
        self.faces = Typefaces(self)
        self.bb = QDialogButtonBox(QDialogButtonBox.Ok
                                   | QDialogButtonBox.Cancel)
        self.bb.accepted.connect(self.accept)
        self.bb.rejected.connect(self.reject)
        self.add_fonts_button = afb = self.bb.addButton(
            _('Add &fonts'), self.bb.ActionRole)
        afb.setIcon(QIcon(I('plus.png')))
        afb.clicked.connect(self.add_fonts)
        self.ml = QLabel(_('Choose a font family from the list below:'))
        self.search = QLineEdit(self)
        self.search.setPlaceholderText(_('Search'))
        self.search.returnPressed.connect(self.find)
        self.nb = QToolButton(self)
        self.nb.setIcon(QIcon(I('arrow-down.png')))
        self.nb.setToolTip(_('Find Next'))
        self.pb = QToolButton(self)
        self.pb.setIcon(QIcon(I('arrow-up.png')))
        self.pb.setToolTip(_('Find Previous'))
        self.nb.clicked.connect(self.find_next)
        self.pb.clicked.connect(self.find_previous)

        l.addWidget(self.ml, 0, 0, 1, 4)
        l.addWidget(self.search, 1, 0, 1, 1)
        l.addWidget(self.nb, 1, 1, 1, 1)
        l.addWidget(self.pb, 1, 2, 1, 1)
        l.addWidget(self.view, 2, 0, 1, 3)
        l.addWidget(self.faces, 1, 3, 2, 1)
        l.addWidget(self.bb, 3, 0, 1, 4)
        l.setAlignment(self.faces, Qt.AlignTop)

        self.resize(800, 600)

    def set_current(self, i):
        self.view.setCurrentIndex(self.m.index(i))

    def keyPressEvent(self, e):
        if e.key() == Qt.Key_Return:
            return
        return QDialog.keyPressEvent(self, e)

    def find(self, backwards=False):
        i = self.view.currentIndex().row()
        if i < 0:
            i = 0
        q = icu_lower(unicode(self.search.text())).strip()
        if not q:
            return
        r = (xrange(i - 1, -1, -1) if backwards else xrange(
            i + 1, len(self.families)))
        for j in r:
            f = self.families[j]
            if q in icu_lower(f):
                self.set_current(j)
                return

    def find_next(self):
        self.find()

    def find_previous(self):
        self.find(backwards=True)

    def build_font_list(self):
        try:
            self.families = list(self.font_scanner.find_font_families())
        except:
            self.families = []
            print('WARNING: Could not load fonts')
            import traceback
            traceback.print_exc()
        self.families.insert(0, _('None'))
        self.m.setStringList(self.families)

    def add_fonts(self):
        families = add_fonts(self)
        if not families:
            return
        self.font_scanner.do_scan()
        self.m.beginResetModel()
        self.build_font_list()
        self.m.endResetModel()
        self.view.setCurrentIndex(self.m.index(0))
        if families:
            for i, val in enumerate(self.families):
                if icu_lower(val) == icu_lower(families[0]):
                    self.view.setCurrentIndex(self.m.index(i))
                    break

        info_dialog(self,
                    _('Added fonts'),
                    _('Added font families: %s') % (', '.join(families)),
                    show=True)

    @property
    def font_family(self):
        idx = self.view.currentIndex().row()
        if idx == 0:
            return None
        return self.families[idx]

    def current_changed(self):
        fam = self.font_family
        self.faces.show_family(
            fam,
            self.font_scanner.fonts_for_family(fam) if fam else None)
예제 #46
0
class BulkBase(Base):

    @property
    def gui_val(self):
        if not hasattr(self, '_cached_gui_val_'):
            self._cached_gui_val_ = self.getter()
        return self._cached_gui_val_

    def get_initial_value(self, book_ids):
        values = set([])
        for book_id in book_ids:
            val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True)
            if isinstance(val, list):
                val = frozenset(val)
            values.add(val)
            if len(values) > 1:
                break
        ans = None
        if len(values) == 1:
            ans = iter(values).next()
        if isinstance(ans, frozenset):
            ans = list(ans)
        return ans

    def initialize(self, book_ids):
        self.initial_val = val = self.get_initial_value(book_ids)
        val = self.normalize_db_val(val)
        self.setter(val)

    def commit(self, book_ids, notify=False):
        if not self.a_c_checkbox.isChecked():
            return
        val = self.gui_val
        val = self.normalize_ui_val(val)
        self.db.set_custom_bulk(book_ids, val, num=self.col_id, notify=notify)

    def make_widgets(self, parent, main_widget_class, extra_label_text='',
                     add_tags_edit_button=False):
        w = QWidget(parent)
        self.widgets = [QLabel('&'+self.col_metadata['name']+':', w), w]
        l = QHBoxLayout()
        l.setContentsMargins(0, 0, 0, 0)
        w.setLayout(l)
        self.main_widget = main_widget_class(w)
        l.addWidget(self.main_widget)
        l.setStretchFactor(self.main_widget, 10)
        if add_tags_edit_button:
            self.edit_tags_button = QToolButton(parent)
            self.edit_tags_button.setToolTip(_('Open Item Editor'))
            self.edit_tags_button.setIcon(QIcon(I('chapters.png')))
            l.addWidget(self.edit_tags_button)
        self.a_c_checkbox = QCheckBox(_('Apply changes'), w)
        l.addWidget(self.a_c_checkbox)
        self.ignore_change_signals = True

        # connect to the various changed signals so we can auto-update the
        # apply changes checkbox
        if hasattr(self.main_widget, 'editTextChanged'):
            # editable combobox widgets
            self.main_widget.editTextChanged.connect(self.a_c_checkbox_changed)
        if hasattr(self.main_widget, 'textChanged'):
            # lineEdit widgets
            self.main_widget.textChanged.connect(self.a_c_checkbox_changed)
        if hasattr(self.main_widget, 'currentIndexChanged'):
            # combobox widgets
            self.main_widget.currentIndexChanged[int].connect(self.a_c_checkbox_changed)
        if hasattr(self.main_widget, 'valueChanged'):
            # spinbox widgets
            self.main_widget.valueChanged.connect(self.a_c_checkbox_changed)
        if hasattr(self.main_widget, 'dateTimeChanged'):
            # dateEdit widgets
            self.main_widget.dateTimeChanged.connect(self.a_c_checkbox_changed)

    def a_c_checkbox_changed(self):
        if not self.ignore_change_signals:
            self.a_c_checkbox.setChecked(True)
class ConfigWidget(QWidget, Logger):
    '''
    Config dialog for Marvin Manager
    '''

    WIZARD_PROFILES = {
        'Annotations': {
            'label': 'mm_annotations',
            'datatype': 'comments',
            'display': {},
            'is_multiple': False
        },
        'Collections': {
            'label': 'mm_collections',
            'datatype': 'text',
            'display': {u'is_names': False},
            'is_multiple': True
        },
        'Last read': {
            'label': 'mm_date_read',
            'datatype': 'datetime',
            'display': {},
            'is_multiple': False
        },
        'Locked': {
            'label': 'mm_locked',
            'datatype': 'bool',
            'display': {},
            'is_multiple': False
        },
        'Progress': {
            'label': 'mm_progress',
            'datatype': 'float',
            'display': {u'number_format': u'{0:.0f}%'},
            'is_multiple': False
        },
        'Read': {
            'label': 'mm_read',
            'datatype': 'bool',
            'display': {},
            'is_multiple': False
        },
        'Reading list': {
            'label': 'mm_reading_list',
            'datatype': 'bool',
            'display': {},
            'is_multiple': False
        },
        'Word count': {
            'label': 'mm_word_count',
            'datatype': 'int',
            'display': {u'number_format': u'{0:n}'},
            'is_multiple': False
        }
    }

    def __init__(self, plugin_action):
        QWidget.__init__(self)
        self.parent = plugin_action

        self.gui = get_gui()
        self.icon = plugin_action.icon
        self.opts = plugin_action.opts
        self.prefs = plugin_prefs
        self.resources_path = plugin_action.resources_path
        self.verbose = plugin_action.verbose

        self.restart_required = False

        self._log_location()

        self.l = QGridLayout()
        self.setLayout(self.l)
        self.column1_layout = QVBoxLayout()
        self.l.addLayout(self.column1_layout, 0, 0)
        self.column2_layout = QVBoxLayout()
        self.l.addLayout(self.column2_layout, 0, 1)

        # ----------------------------- Column 1 -----------------------------
        # ~~~~~~~~ Create the Custom fields options group box ~~~~~~~~
        self.cfg_custom_fields_gb = QGroupBox(self)
        self.cfg_custom_fields_gb.setTitle('Custom column assignments')
        self.column1_layout.addWidget(self.cfg_custom_fields_gb)

        self.cfg_custom_fields_qgl = QGridLayout(self.cfg_custom_fields_gb)
        current_row = 0

        # ++++++++ Labels + HLine ++++++++
        self.marvin_source_label = QLabel("Marvin source")
        self.cfg_custom_fields_qgl.addWidget(self.marvin_source_label, current_row, 0)
        self.calibre_destination_label = QLabel("calibre destination")
        self.cfg_custom_fields_qgl.addWidget(self.calibre_destination_label, current_row, 1)
        current_row += 1
        self.sd_hl = QFrame(self.cfg_custom_fields_gb)
        self.sd_hl.setFrameShape(QFrame.HLine)
        self.sd_hl.setFrameShadow(QFrame.Raised)
        self.cfg_custom_fields_qgl.addWidget(self.sd_hl, current_row, 0, 1, 3)
        current_row += 1

        # ++++++++ Annotations ++++++++
        self.cfg_annotations_label = QLabel('Annotations')
        self.cfg_annotations_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_annotations_label, current_row, 0)

        self.annotations_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.annotations_field_comboBox.setObjectName('annotations_field_comboBox')
        self.annotations_field_comboBox.setToolTip('Select a custom column to store Marvin annotations')
        self.cfg_custom_fields_qgl.addWidget(self.annotations_field_comboBox, current_row, 1)

        self.cfg_highlights_wizard = QToolButton()
        self.cfg_highlights_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_highlights_wizard.setToolTip("Create a custom column to store Marvin annotations")
        self.cfg_highlights_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Annotations'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_highlights_wizard, current_row, 2)
        current_row += 1

        # ++++++++ Collections ++++++++
        self.cfg_collections_label = QLabel('Collections')
        self.cfg_collections_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_collections_label, current_row, 0)

        self.collection_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.collection_field_comboBox.setObjectName('collection_field_comboBox')
        self.collection_field_comboBox.setToolTip('Select a custom column to store Marvin collection assignments')
        self.cfg_custom_fields_qgl.addWidget(self.collection_field_comboBox, current_row, 1)

        self.cfg_collections_wizard = QToolButton()
        self.cfg_collections_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_collections_wizard.setToolTip("Create a custom column for Marvin collection assignments")
        self.cfg_collections_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Collections'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_collections_wizard, current_row, 2)
        current_row += 1

        # ++++++++ Last read ++++++++
        self.cfg_date_read_label = QLabel("Last read")
        self.cfg_date_read_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_date_read_label, current_row, 0)

        self.date_read_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.date_read_field_comboBox.setObjectName('date_read_field_comboBox')
        self.date_read_field_comboBox.setToolTip('Select a custom column to store Last read date')
        self.cfg_custom_fields_qgl.addWidget(self.date_read_field_comboBox, current_row, 1)

        self.cfg_collections_wizard = QToolButton()
        self.cfg_collections_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_collections_wizard.setToolTip("Create a custom column to store Last read date")
        self.cfg_collections_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Last read'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_collections_wizard, current_row, 2)
        current_row += 1

        # ++++++++ Locked ++++++++
        self.cfg_locked_label = QLabel("Locked")
        self.cfg_locked_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_locked_label, current_row, 0)

        self.locked_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.locked_field_comboBox.setObjectName('locked_field_comboBox')
        self.locked_field_comboBox.setToolTip('Select a custom column to store Locked status')
        self.cfg_custom_fields_qgl.addWidget(self.locked_field_comboBox, current_row, 1)

        self.cfg_locked_wizard = QToolButton()
        self.cfg_locked_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_locked_wizard.setToolTip("Create a custom column to store Locked status")
        self.cfg_locked_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Locked'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_locked_wizard, current_row, 2)
        current_row += 1

        # ++++++++ Progress ++++++++
        self.cfg_progress_label = QLabel('Progress')
        self.cfg_progress_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_progress_label, current_row, 0)

        self.progress_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.progress_field_comboBox.setObjectName('progress_field_comboBox')
        self.progress_field_comboBox.setToolTip('Select a custom column to store Marvin reading progress')
        self.cfg_custom_fields_qgl.addWidget(self.progress_field_comboBox, current_row, 1)

        self.cfg_progress_wizard = QToolButton()
        self.cfg_progress_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_progress_wizard.setToolTip("Create a custom column to store Marvin reading progress")
        self.cfg_progress_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Progress'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_progress_wizard, current_row, 2)
        current_row += 1

        # ++++++++ Read flag ++++++++
        self.cfg_read_label = QLabel('Read')
        self.cfg_read_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_read_label, current_row, 0)

        self.read_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.read_field_comboBox.setObjectName('read_field_comboBox')
        self.read_field_comboBox.setToolTip('Select a custom column to store Marvin Read status')
        self.cfg_custom_fields_qgl.addWidget(self.read_field_comboBox, current_row, 1)

        self.cfg_read_wizard = QToolButton()
        self.cfg_read_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_read_wizard.setToolTip("Create a custom column to store Marvin Read status")
        self.cfg_read_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Read'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_read_wizard, current_row, 2)
        current_row += 1

        # ++++++++ Reading list flag ++++++++
        self.cfg_reading_list_label = QLabel('Reading list')
        self.cfg_reading_list_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_reading_list_label, current_row, 0)

        self.reading_list_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.reading_list_field_comboBox.setObjectName('reading_list_field_comboBox')
        self.reading_list_field_comboBox.setToolTip('Select a custom column to store Marvin Reading list status')
        self.cfg_custom_fields_qgl.addWidget(self.reading_list_field_comboBox, current_row, 1)

        self.cfg_reading_list_wizard = QToolButton()
        self.cfg_reading_list_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_reading_list_wizard.setToolTip("Create a custom column to store Marvin Reading list status")
        self.cfg_reading_list_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Reading list'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_reading_list_wizard, current_row, 2)
        current_row += 1

        # ++++++++ Word count ++++++++
        self.cfg_word_count_label = QLabel('Word count')
        self.cfg_word_count_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_word_count_label, current_row, 0)

        self.word_count_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.word_count_field_comboBox.setObjectName('word_count_field_comboBox')
        self.word_count_field_comboBox.setToolTip('Select a custom column to store Marvin word counts')
        self.cfg_custom_fields_qgl.addWidget(self.word_count_field_comboBox, current_row, 1)

        self.cfg_word_count_wizard = QToolButton()
        self.cfg_word_count_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_word_count_wizard.setToolTip("Create a custom column to store Marvin word counts")
        self.cfg_word_count_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Word count'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_word_count_wizard, current_row, 2)
        current_row += 1

        self.spacerItem1 = QSpacerItem(20, 20, QSizePolicy.Minimum, QSizePolicy.Expanding)
        self.column1_layout.addItem(self.spacerItem1)

        # ----------------------------- Column 2 -----------------------------
        # ~~~~~~~~ Create the CSS group box ~~~~~~~~
        self.cfg_css_options_gb = QGroupBox(self)
        self.cfg_css_options_gb.setTitle('CSS')
        self.column2_layout.addWidget(self.cfg_css_options_gb)
        self.cfg_css_options_qgl = QGridLayout(self.cfg_css_options_gb)

        current_row = 0

        # ++++++++ Annotations appearance ++++++++
        self.annotations_icon = QIcon(os.path.join(self.resources_path, 'icons', 'annotations_hiliter.png'))
        self.cfg_annotations_appearance_toolbutton = QToolButton()
        self.cfg_annotations_appearance_toolbutton.setIcon(self.annotations_icon)
        self.cfg_annotations_appearance_toolbutton.clicked.connect(self.configure_appearance)
        self.cfg_css_options_qgl.addWidget(self.cfg_annotations_appearance_toolbutton, current_row, 0)
        self.cfg_annotations_label = ClickableQLabel("Book notes, Bookmark notes and Annotations")
        self.cfg_annotations_label.clicked.connect(self.configure_appearance)
        self.cfg_css_options_qgl.addWidget(self.cfg_annotations_label, current_row, 1)
        current_row += 1

        # ++++++++ Injected CSS ++++++++
        self.css_editor_icon = QIcon(I('format-text-heading.png'))
        self.cfg_css_editor_toolbutton = QToolButton()
        self.cfg_css_editor_toolbutton.setIcon(self.css_editor_icon)
        self.cfg_css_editor_toolbutton.clicked.connect(self.edit_css)
        self.cfg_css_options_qgl.addWidget(self.cfg_css_editor_toolbutton, current_row, 0)
        self.cfg_css_editor_label = ClickableQLabel("Articles, Vocabulary")
        self.cfg_css_editor_label.clicked.connect(self.edit_css)
        self.cfg_css_options_qgl.addWidget(self.cfg_css_editor_label, current_row, 1)


        """
        # ~~~~~~~~ Create the Dropbox syncing group box ~~~~~~~~
        self.cfg_dropbox_syncing_gb = QGroupBox(self)
        self.cfg_dropbox_syncing_gb.setTitle('Dropbox')
        self.column2_layout.addWidget(self.cfg_dropbox_syncing_gb)
        self.cfg_dropbox_syncing_qgl = QGridLayout(self.cfg_dropbox_syncing_gb)
        current_row = 0

        # ++++++++ Syncing enabled checkbox ++++++++
        self.dropbox_syncing_checkbox = QCheckBox('Enable Dropbox updates')
        self.dropbox_syncing_checkbox.setObjectName('dropbox_syncing')
        self.dropbox_syncing_checkbox.setToolTip('Refresh custom column content from Marvin metadata')
        self.cfg_dropbox_syncing_qgl.addWidget(self.dropbox_syncing_checkbox,
            current_row, 0, 1, 3)
        current_row += 1

        # ++++++++ Dropbox folder picker ++++++++
        self.dropbox_folder_icon = QIcon(os.path.join(self.resources_path, 'icons', 'dropbox.png'))
        self.cfg_dropbox_folder_toolbutton = QToolButton()
        self.cfg_dropbox_folder_toolbutton.setIcon(self.dropbox_folder_icon)
        self.cfg_dropbox_folder_toolbutton.setToolTip("Specify Dropbox folder location on your computer")
        self.cfg_dropbox_folder_toolbutton.clicked.connect(self.select_dropbox_folder)
        self.cfg_dropbox_syncing_qgl.addWidget(self.cfg_dropbox_folder_toolbutton,
            current_row, 1)

        # ++++++++ Dropbox location lineedit ++++++++
        self.dropbox_location_lineedit = QLineEdit()
        self.dropbox_location_lineedit.setPlaceholderText("Dropbox folder location")
        self.cfg_dropbox_syncing_qgl.addWidget(self.dropbox_location_lineedit,
            current_row, 2)
        """

        # ~~~~~~~~ Create the General options group box ~~~~~~~~
        self.cfg_runtime_options_gb = QGroupBox(self)
        self.cfg_runtime_options_gb.setTitle('General options')
        self.column2_layout.addWidget(self.cfg_runtime_options_gb)
        self.cfg_runtime_options_qvl = QVBoxLayout(self.cfg_runtime_options_gb)

        # ++++++++ Temporary markers: Duplicates ++++++++
        self.duplicate_markers_checkbox = QCheckBox('Apply temporary markers to duplicate books')
        self.duplicate_markers_checkbox.setObjectName('apply_markers_to_duplicates')
        self.duplicate_markers_checkbox.setToolTip('Books with identical content will be flagged in the Library window')
        self.cfg_runtime_options_qvl.addWidget(self.duplicate_markers_checkbox)

        # ++++++++ Temporary markers: Updated ++++++++
        self.updated_markers_checkbox = QCheckBox('Apply temporary markers to books with updated content')
        self.updated_markers_checkbox.setObjectName('apply_markers_to_updated')
        self.updated_markers_checkbox.setToolTip('Books with updated content will be flagged in the Library window')
        self.cfg_runtime_options_qvl.addWidget(self.updated_markers_checkbox)

        # ++++++++ Auto refresh checkbox ++++++++
        self.auto_refresh_checkbox = QCheckBox('Automatically refresh custom column content')
        self.auto_refresh_checkbox.setObjectName('auto_refresh_at_startup')
        self.auto_refresh_checkbox.setToolTip('Update calibre custom column when Marvin XD is opened')
        self.cfg_runtime_options_qvl.addWidget(self.auto_refresh_checkbox)

        # ++++++++ Progress as percentage checkbox ++++++++
        self.reading_progress_checkbox = QCheckBox('Show reading progress as percentage')
        self.reading_progress_checkbox.setObjectName('show_progress_as_percentage')
        self.reading_progress_checkbox.setToolTip('Display percentage in Progress column')
        self.cfg_runtime_options_qvl.addWidget(self.reading_progress_checkbox)

        # ~~~~~~~~ Create the Debug options group box ~~~~~~~~
        self.cfg_debug_options_gb = QGroupBox(self)
        self.cfg_debug_options_gb.setTitle('Debug options')
        self.column2_layout.addWidget(self.cfg_debug_options_gb)
        self.cfg_debug_options_qvl = QVBoxLayout(self.cfg_debug_options_gb)

        # ++++++++ Debug logging checkboxes ++++++++
        self.debug_plugin_checkbox = QCheckBox('Enable debug logging for Marvin XD')
        self.debug_plugin_checkbox.setObjectName('debug_plugin_checkbox')
        self.debug_plugin_checkbox.setToolTip('Print plugin diagnostic messages to console')
        self.cfg_debug_options_qvl.addWidget(self.debug_plugin_checkbox)

        self.debug_libimobiledevice_checkbox = QCheckBox('Enable debug logging for libiMobileDevice')
        self.debug_libimobiledevice_checkbox.setObjectName('debug_libimobiledevice_checkbox')
        self.debug_libimobiledevice_checkbox.setToolTip('Print libiMobileDevice diagnostic messages to console')
        self.cfg_debug_options_qvl.addWidget(self.debug_libimobiledevice_checkbox)

        self.spacerItem2 = QSpacerItem(20, 20, QSizePolicy.Minimum, QSizePolicy.Expanding)
        self.column2_layout.addItem(self.spacerItem2)

        # ~~~~~~~~ End of construction zone ~~~~~~~~
        self.resize(self.sizeHint())

        # ~~~~~~~~ Populate/restore config options ~~~~~~~~
        #  Annotations comboBox
        self.populate_annotations()
        self.populate_collections()
        self.populate_date_read()
        self.populate_locked()
        self.populate_progress()
        self.populate_read()
        self.populate_reading_list()
        self.populate_word_count()

        """
        # Restore Dropbox settings, hook changes
        dropbox_syncing = self.prefs.get('dropbox_syncing', False)
        self.dropbox_syncing_checkbox.setChecked(dropbox_syncing)
        self.set_dropbox_syncing(dropbox_syncing)
        self.dropbox_syncing_checkbox.clicked.connect(partial(self.set_dropbox_syncing))
        self.dropbox_location_lineedit.setText(self.prefs.get('dropbox_folder', ''))
        """

        # Restore general settings
        self.duplicate_markers_checkbox.setChecked(self.prefs.get('apply_markers_to_duplicates', True))
        self.updated_markers_checkbox.setChecked(self.prefs.get('apply_markers_to_updated', True))
        self.auto_refresh_checkbox.setChecked(self.prefs.get('auto_refresh_at_startup', False))
        self.reading_progress_checkbox.setChecked(self.prefs.get('show_progress_as_percentage', False))

        # Restore debug settings, hook changes
        self.debug_plugin_checkbox.setChecked(self.prefs.get('debug_plugin', False))
        self.debug_plugin_checkbox.stateChanged.connect(self.set_restart_required)
        self.debug_libimobiledevice_checkbox.setChecked(self.prefs.get('debug_libimobiledevice', False))
        self.debug_libimobiledevice_checkbox.stateChanged.connect(self.set_restart_required)

        # Hook changes to Annotations comboBox
#         self.annotations_field_comboBox.currentIndexChanged.connect(
#             partial(self.save_combobox_setting, 'annotations_field_comboBox'))
#        self.connect(self.annotations_field_comboBox,
#                     SIGNAL('currentIndexChanged(const QString &)'),
#                     self.annotations_destination_changed)
        self.annotations_field_comboBox.currentIndexChanged.connect(self.annotations_destination_changed)
        # Launch the annotated_books_scanner
        field = get_cc_mapping('annotations', 'field', None)
        self.annotated_books_scanner = InventoryAnnotatedBooks(self.gui, field)
        self.annotated_books_scanner.signal.connect(self.inventory_complete)
        QTimer.singleShot(1, self.start_inventory)

    def annotations_destination_changed(self, qs_new_destination_name):
        '''
        If the destination field changes, move all existing annotations from old to new
        '''
        self._log_location(str(qs_new_destination_name))
        self._log("self.eligible_annotations_fields: %s" % self.eligible_annotations_fields)

        old_destination_field = get_cc_mapping('annotations', 'field', None)
        old_destination_name = get_cc_mapping('annotations', 'combobox', None)

        self._log("old_destination_field: %s" % old_destination_field)
        self._log("old_destination_name: %s" % old_destination_name)

#        new_destination_name = unicode(qs_new_destination_name)
        # Signnls available have changed. Now receivin an indec rather than a name. Can get the name
        # from the combobox
        new_destination_name = unicode(self.annotations_field_comboBox.currentText())
        self._log("new_destination_name: %s" % repr(new_destination_name))

        if old_destination_name == new_destination_name:
            self._log_location("old_destination_name = new_destination_name, no changes")
            return

        if new_destination_name == '':
            self._log_location("annotations storage disabled")
            set_cc_mapping('annotations', field=None, combobox=new_destination_name)
            return

        new_destination_field = self.eligible_annotations_fields[new_destination_name]

        if existing_annotations(self.parent, old_destination_field):
            command = self.launch_new_destination_dialog(old_destination_name, new_destination_name)

            if command == 'move':
                set_cc_mapping('annotations', field=new_destination_field, combobox=new_destination_name)

                if self.annotated_books_scanner.isRunning():
                    self.annotated_books_scanner.wait()
                move_annotations(self, self.annotated_books_scanner.annotation_map,
                    old_destination_field, new_destination_field)

            elif command == 'change':
                # Keep the updated destination field, but don't move annotations
                pass

            elif command == 'cancel':
                # Restore previous destination
                self.annotations_field_comboBox.blockSignals(True)
                old_index = self.annotations_field_comboBox.findText(old_destination_name)
                self.annotations_field_comboBox.setCurrentIndex(old_index)
                self.annotations_field_comboBox.blockSignals(False)

        else:
            # No existing annotations, just update prefs
            self._log("no existing annotations, updating destination to '{0}'".format(new_destination_name))
            set_cc_mapping('annotations', field=new_destination_field, combobox=new_destination_name)

    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 edit_css(self):
        '''
        '''
        self._log_location()
        from calibre_plugins.marvin_manager.book_status import dialog_resources_path
        klass = os.path.join(dialog_resources_path, 'css_editor.py')
        if os.path.exists(klass):
            sys.path.insert(0, dialog_resources_path)
            this_dc = importlib.import_module('css_editor')
            sys.path.remove(dialog_resources_path)
            dlg = this_dc.CSSEditorDialog(self, 'css_editor')
            dlg.initialize(self)
            dlg.exec_()

    def get_eligible_custom_fields(self, eligible_types=[], is_multiple=None):
        '''
        Discover qualifying custom fields for eligible_types[]
        '''
        #self._log_location(eligible_types)

        eligible_custom_fields = {}
        for cf in self.gui.current_db.custom_field_keys():
            cft = self.gui.current_db.metadata_for_field(cf)['datatype']
            cfn = self.gui.current_db.metadata_for_field(cf)['name']
            cfim = self.gui.current_db.metadata_for_field(cf)['is_multiple']
            #self._log("cf: %s  cft: %s  cfn: %s cfim: %s" % (cf, cft, cfn, cfim))
            if cft in eligible_types:
                if is_multiple is not None:
                    if bool(cfim) == is_multiple:
                        eligible_custom_fields[cfn] = cf
                else:
                    eligible_custom_fields[cfn] = cf
        return eligible_custom_fields

    def inventory_complete(self, msg):
        self._log_location(msg)

    def launch_cc_wizard(self, column_type):
        '''
        '''
        def _update_combo_box(comboBox, destination, previous):
            '''
            '''
            cb = getattr(self, comboBox)
            cb.blockSignals(True)
            all_items = [str(cb.itemText(i))
                         for i in range(cb.count())]
            if previous and previous in all_items:
                all_items.remove(previous)
            all_items.append(destination)

            cb.clear()
            cb.addItems(sorted(all_items, key=lambda s: s.lower()))

            # Select the new destination in the comboBox
            idx = cb.findText(destination)
            if idx > -1:
                cb.setCurrentIndex(idx)

            cb.blockSignals(False)

        from calibre_plugins.marvin_manager.book_status import dialog_resources_path

        klass = os.path.join(dialog_resources_path, 'cc_wizard.py')
        if os.path.exists(klass):
            #self._log("importing CC Wizard dialog from '%s'" % klass)
            sys.path.insert(0, dialog_resources_path)
            this_dc = importlib.import_module('cc_wizard')
            sys.path.remove(dialog_resources_path)
            dlg = this_dc.CustomColumnWizard(self,
                                             column_type,
                                             self.WIZARD_PROFILES[column_type],
                                             verbose=True)
            dlg.exec_()

            if dlg.modified_column:
                self._log("modified_column: %s" % dlg.modified_column)

                self.restart_required = True

                destination = dlg.modified_column['destination']
                label = dlg.modified_column['label']
                previous = dlg.modified_column['previous']
                source = dlg.modified_column['source']

                if source == "Annotations":
                    _update_combo_box("annotations_field_comboBox", destination, previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_annotations_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('annotations', combobox=destination, field=label)

                elif source == 'Collections':
                    _update_combo_box("collection_field_comboBox", destination, previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_collection_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('collections', combobox=destination, field=label)

                elif source == 'Last read':
                    _update_combo_box("date_read_field_comboBox", destination, previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_date_read_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('date_read', combobox=destination, field=label)

                elif source == 'Locked':
                    _update_combo_box("locked_field_comboBox", destination, previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_locked_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('locked', combobox=destination, field=label)

                elif source == "Progress":
                    _update_combo_box("progress_field_comboBox", destination, previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_progress_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('progress', combobox=destination, field=label)

                elif source == "Read":
                    _update_combo_box("read_field_comboBox", destination, previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_read_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('read', combobox=destination, field=label)

                elif source == "Reading list":
                    _update_combo_box("reading_list_field_comboBox", destination, previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_reading_list_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('reading_list', combobox=destination, field=label)

                elif source == "Word count":
                    _update_combo_box("word_count_field_comboBox", destination, previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_word_count_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('word_count', combobox=destination, field=label)
        else:
            self._log("ERROR: Can't import from '%s'" % klass)

    def launch_new_destination_dialog(self, old, new):
        '''
        Return 'move', 'change' or 'cancel'
        '''
        from calibre_plugins.marvin_manager.book_status import dialog_resources_path
        self._log_location()
        klass = os.path.join(dialog_resources_path, 'new_destination.py')
        if os.path.exists(klass):
            self._log("importing new destination dialog from '%s'" % klass)
            sys.path.insert(0, dialog_resources_path)
            this_dc = importlib.import_module('new_destination')
            sys.path.remove(dialog_resources_path)
            dlg = this_dc.NewDestinationDialog(self, old, new)
            dlg.exec_()
            return dlg.command

    def populate_annotations(self):
        datatype = self.WIZARD_PROFILES['Annotations']['datatype']
        self.eligible_annotations_fields = self.get_eligible_custom_fields([datatype])
        self.annotations_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_annotations_fields.keys(), key=lambda s: s.lower())
        self.annotations_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('annotations', 'combobox')
        if existing:
            ci = self.annotations_field_comboBox.findText(existing)
            self.annotations_field_comboBox.setCurrentIndex(ci)

    def populate_collections(self):
        datatype = self.WIZARD_PROFILES['Collections']['datatype']
        self.eligible_collection_fields = self.get_eligible_custom_fields([datatype],
                                                                          is_multiple=True)
        self.collection_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_collection_fields.keys(), key=lambda s: s.lower())
        self.collection_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('collections', 'combobox')
        if existing:
            ci = self.collection_field_comboBox.findText(existing)
            self.collection_field_comboBox.setCurrentIndex(ci)

    def populate_date_read(self):
        #self.eligible_date_read_fields = self.get_eligible_custom_fields(['datetime'])
        datatype = self.WIZARD_PROFILES['Last read']['datatype']
        self.eligible_date_read_fields = self.get_eligible_custom_fields([datatype])
        self.date_read_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_date_read_fields.keys(), key=lambda s: s.lower())
        self.date_read_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('date_read', 'combobox')
        if existing:
            ci = self.date_read_field_comboBox.findText(existing)
            self.date_read_field_comboBox.setCurrentIndex(ci)

    def populate_locked(self):
        datatype = self.WIZARD_PROFILES['Locked']['datatype']
        self.eligible_locked_fields = self.get_eligible_custom_fields([datatype])
        self.locked_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_locked_fields.keys(), key=lambda s: s.lower())
        self.locked_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('locked', 'combobox')
        if existing:
            ci = self.locked_field_comboBox.findText(existing)
            self.locked_field_comboBox.setCurrentIndex(ci)

    def populate_progress(self):
        #self.eligible_progress_fields = self.get_eligible_custom_fields(['float'])
        datatype = self.WIZARD_PROFILES['Progress']['datatype']
        self.eligible_progress_fields = self.get_eligible_custom_fields([datatype])
        self.progress_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_progress_fields.keys(), key=lambda s: s.lower())
        self.progress_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('progress', 'combobox')
        if existing:
            ci = self.progress_field_comboBox.findText(existing)
            self.progress_field_comboBox.setCurrentIndex(ci)

    def populate_read(self):
        datatype = self.WIZARD_PROFILES['Read']['datatype']
        self.eligible_read_fields = self.get_eligible_custom_fields([datatype])
        self.read_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_read_fields.keys(), key=lambda s: s.lower())
        self.read_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('read', 'combobox')
        if existing:
            ci = self.read_field_comboBox.findText(existing)
            self.read_field_comboBox.setCurrentIndex(ci)

    def populate_reading_list(self):
        datatype = self.WIZARD_PROFILES['Reading list']['datatype']
        self.eligible_reading_list_fields = self.get_eligible_custom_fields([datatype])
        self.reading_list_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_reading_list_fields.keys(), key=lambda s: s.lower())
        self.reading_list_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('reading_list', 'combobox')
        if existing:
            ci = self.reading_list_field_comboBox.findText(existing)
            self.reading_list_field_comboBox.setCurrentIndex(ci)

    def populate_word_count(self):
        #self.eligible_word_count_fields = self.get_eligible_custom_fields(['int'])
        datatype = self.WIZARD_PROFILES['Word count']['datatype']
        self.eligible_word_count_fields = self.get_eligible_custom_fields([datatype])
        self.word_count_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_word_count_fields.keys(), key=lambda s: s.lower())
        self.word_count_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('word_count', 'combobox')
        if existing:
            ci = self.word_count_field_comboBox.findText(existing)
            self.word_count_field_comboBox.setCurrentIndex(ci)

    """
    def select_dropbox_folder(self):
        '''
        '''
        self._log_location()
        dropbox_location = QFileDialog.getExistingDirectory(
            self,
            "Dropbox folder",
            os.path.expanduser("~"),
            QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks)
        self.dropbox_location_lineedit.setText(unicode(dropbox_location))

    def set_dropbox_syncing(self, state):
        '''
        Called when checkbox changes state, or when restoring state
        Set enabled state of Dropbox folder picker to match
        '''
        self.cfg_dropbox_folder_toolbutton.setEnabled(state)
        self.dropbox_location_lineedit.setEnabled(state)
    """

    def set_restart_required(self, state):
        '''
        Set restart_required flag to show show dialog when closing dialog
        '''
        self.restart_required = True

    """
    def save_combobox_setting(self, cb, index):
        '''
        Apply changes immediately
        '''
        cf = str(getattr(self, cb).currentText())
        self._log_location("%s => %s" % (cb, repr(cf)))

        if cb == 'annotations_field_comboBox':
            field = None
            if cf:
                field = self.eligible_annotations_fields[cf]
            set_cc_mapping('annotations', combobox=cf, field=field)
    """

    def save_settings(self):
        self._log_location()

        # Annotations field
        cf = unicode(self.annotations_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_annotations_fields[cf]
        set_cc_mapping('annotations', combobox=cf, field=field)

        # Collections field
        cf = unicode(self.collection_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_collection_fields[cf]
        set_cc_mapping('collections', combobox=cf, field=field)

        # Date read field
        cf = unicode(self.date_read_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_date_read_fields[cf]
        set_cc_mapping('date_read', combobox=cf, field=field)

        # Locked field
        cf = unicode(self.locked_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_locked_fields[cf]
        set_cc_mapping('locked', combobox=cf, field=field)

        # Progress field
        cf = unicode(self.progress_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_progress_fields[cf]
        set_cc_mapping('progress', combobox=cf, field=field)

        # Read field
        cf = unicode(self.read_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_read_fields[cf]
        set_cc_mapping('read', combobox=cf, field=field)

        # Reading list field
        cf = unicode(self.reading_list_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_reading_list_fields[cf]
        set_cc_mapping('reading_list', combobox=cf, field=field)

        # Word count field
        cf = unicode(self.word_count_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_word_count_fields[cf]
        set_cc_mapping('word_count', combobox=cf, field=field)

        '''
        # Save Dropbox settings
        self.prefs.set('dropbox_syncing', self.dropbox_syncing_checkbox.isChecked())
        self.prefs.set('dropbox_folder', unicode(self.dropbox_location_lineedit.text()))
        '''

        # Save general settings
        self.prefs.set('apply_markers_to_duplicates', self.duplicate_markers_checkbox.isChecked())
        self.prefs.set('apply_markers_to_updated', self.updated_markers_checkbox.isChecked())
        self.prefs.set('auto_refresh_at_startup', self.auto_refresh_checkbox.isChecked())
        self.prefs.set('show_progress_as_percentage', self.reading_progress_checkbox.isChecked())

        # Save debug settings
        self.prefs.set('debug_plugin', self.debug_plugin_checkbox.isChecked())
        self.prefs.set('debug_libimobiledevice', self.debug_libimobiledevice_checkbox.isChecked())

        # If restart needed, inform user
        if self.restart_required:
            do_restart = show_restart_warning('Restart calibre for the changes to be applied.',
                                              parent=self.gui)
            if do_restart:
                self.gui.quit(restart=True)

    def start_inventory(self):
        self._log_location()
        self.annotated_books_scanner.start()
예제 #48
0
class AddEmptyBookDialog(QDialog):

    def __init__(self, parent, db, author, series=None, title=None):
        QDialog.__init__(self, parent)
        self.db = db

        self.setWindowTitle(_('How many empty books?'))

        self._layout = QGridLayout(self)
        self.setLayout(self._layout)

        self.qty_label = QLabel(_('How many empty books should be added?'))
        self._layout.addWidget(self.qty_label, 0, 0, 1, 2)

        self.qty_spinbox = QSpinBox(self)
        self.qty_spinbox.setRange(1, 10000)
        self.qty_spinbox.setValue(1)
        self._layout.addWidget(self.qty_spinbox, 1, 0, 1, 2)

        self.author_label = QLabel(_('Set the author of the new books to:'))
        self._layout.addWidget(self.author_label, 2, 0, 1, 2)

        self.authors_combo = EditWithComplete(self)
        self.authors_combo.setSizeAdjustPolicy(
                self.authors_combo.AdjustToMinimumContentsLengthWithIcon)
        self.authors_combo.setEditable(True)
        self._layout.addWidget(self.authors_combo, 3, 0, 1, 1)
        self.initialize_authors(db, author)

        self.clear_button = QToolButton(self)
        self.clear_button.setIcon(QIcon(I('trash.png')))
        self.clear_button.setToolTip(_('Reset author to Unknown'))
        self.clear_button.clicked.connect(self.reset_author)
        self._layout.addWidget(self.clear_button, 3, 1, 1, 1)

        self.series_label = QLabel(_('Set the series of the new books to:'))
        self._layout.addWidget(self.series_label, 4, 0, 1, 2)

        self.series_combo = EditWithComplete(self)
        self.series_combo.setSizeAdjustPolicy(
                self.authors_combo.AdjustToMinimumContentsLengthWithIcon)
        self.series_combo.setEditable(True)
        self._layout.addWidget(self.series_combo, 5, 0, 1, 1)
        self.initialize_series(db, series)

        self.sclear_button = QToolButton(self)
        self.sclear_button.setIcon(QIcon(I('trash.png')))
        self.sclear_button.setToolTip(_('Reset series'))
        self.sclear_button.clicked.connect(self.reset_series)
        self._layout.addWidget(self.sclear_button, 5, 1, 1, 1)

        self.title_label = QLabel(_('Set the title of the new books to:'))
        self._layout.addWidget(self.title_label, 6, 0, 1, 2)

        self.title_edit = QLineEdit(self)
        self.title_edit.setText(title or '')
        self._layout.addWidget(self.title_edit, 7, 0, 1, 1)

        self.tclear_button = QToolButton(self)
        self.tclear_button.setIcon(QIcon(I('trash.png')))
        self.tclear_button.setToolTip(_('Reset title'))
        self.tclear_button.clicked.connect(self.title_edit.clear)
        self._layout.addWidget(self.tclear_button, 7, 1, 1, 1)

        self.format_label = QLabel(_('Also create an empty ebook in format:'))
        self._layout.addWidget(self.format_label, 8, 0, 1, 2)
        c = self.format_value = QComboBox(self)
        from calibre.ebooks.oeb.polish.create import valid_empty_formats
        possible_formats = [''] + sorted(x.upper() for x in valid_empty_formats)
        c.addItems(possible_formats)
        c.setToolTip(_('Also create an empty book format file that you can subsequently edit'))
        if gprefs.get('create_empty_epub_file', False):
            # Migration of the check box
            gprefs.set('create_empty_format_file', 'epub')
            del gprefs['create_empty_epub_file']
        use_format = gprefs.get('create_empty_format_file', '').upper()
        try:
            c.setCurrentIndex(possible_formats.index(use_format))
        except Exception:
            pass
        self._layout.addWidget(c, 9, 0, 1, 1)

        button_box = self.bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        button_box.accepted.connect(self.accept)
        button_box.rejected.connect(self.reject)
        self._layout.addWidget(button_box, 10, 0, 1, -1)
        self.resize(self.sizeHint())

    def accept(self):
        gprefs['create_empty_format_file'] = self.format_value.currentText().lower()
        return QDialog.accept(self)

    def reset_author(self, *args):
        self.authors_combo.setEditText(_('Unknown'))

    def reset_series(self):
        self.series_combo.setEditText('')

    def initialize_authors(self, db, author):
        au = author
        if not au:
            au = _('Unknown')
        self.authors_combo.show_initial_value(au.replace('|', ','))

        self.authors_combo.set_separator('&')
        self.authors_combo.set_space_before_sep(True)
        self.authors_combo.set_add_separator(tweaks['authors_completer_append_separator'])
        self.authors_combo.update_items_cache(db.all_author_names())

    def initialize_series(self, db, series):
        self.series_combo.show_initial_value(series or '')
        self.series_combo.update_items_cache(db.all_series_names())
        self.series_combo.set_separator(None)

    @property
    def qty_to_add(self):
        return self.qty_spinbox.value()

    @property
    def selected_authors(self):
        return string_to_authors(unicode(self.authors_combo.text()))

    @property
    def selected_series(self):
        return unicode(self.series_combo.text())

    @property
    def selected_title(self):
        return self.title_edit.text().strip()
예제 #49
0
class AnnotationElementsTable(QTableWidget):
    '''
    QTableWidget managing CSS rules
    '''
    DEBUG = True
    #MAXIMUM_TABLE_HEIGHT = 113
    ELEMENT_FIELD_WIDTH = 250
    if isosx:
        FONT = QFont('Monaco', 11)
    elif iswindows:
        FONT = QFont('Lucida Console', 9)
    elif islinux:
        FONT = QFont('Monospace', 9)
        FONT.setStyleHint(QFont.TypeWriter)

    COLUMNS = {
                'ELEMENT':   {'ordinal': 0, 'name': 'Element'},
                'CSS':  {'ordinal': 1, 'name': 'CSS'},
                }

    sample_ann_1 = {
        'text': ['Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean placerat condimentum semper. Aliquam hendrerit nisl mauris, nec laoreet orci. Donec rutrum consequat ultricies.',
                 'Curabitur sollicitudin euismod felis, vitae mollis magna vestibulum id.'],
        'note': ['This is a note appended to the highlight.',
                 'And additional comments after a linebreak.'],
        'highlightcolor': 'Yellow',
        'timestamp': time.mktime(time.localtime()),
        'location': 'Chapter 4',
        'location_sort': 0
        }
    sample_ann_2 = {
        'text': ['Phasellus sit amet ipsum id velit commodo convallis. In dictum felis non tellus volutpat in tincidunt neque varius. Sed at mauris augue. Vestibulum ligula nunc, ullamcorper id suscipit sed, auctor quis erat. In hac habitasse platea dictumst. Aliquam sit amet nulla dolor, ut tempus libero. In hac habitasse platea dictumst. Etiam consectetur orci vel massa eleifend in vestibulum odio auctor. Praesent orci turpis, aliquet non eleifend sit amet, sollicitudin sit amet augue.'],
        'highlightcolor': 'Green',
        'timestamp': time.mktime(time.localtime()),
        'location': 'Chapter 12',
        'location_sort': 1
        }
    sample_ann_3 = {
        'text': ['Morbi massa tellus, laoreet id pretium sed, volutpat in felis.',
                 'Donec massa nulla, malesuada vitae volutpat quis, accumsan ut tellus.'],
        'note': ['This is a note appended to the highlight.',
                 'And additional comments after a linebreak.'],
        'highlightcolor': 'Purple',
        'timestamp': time.mktime(time.localtime()),
        'location': 'Chapter 53',
        'location_sort': 2
        }


    def __init__(self, parent, object_name):
        self.parent = parent
        self.prefs = parent.prefs
        self.elements = self.prefs.get('appearance_css', None)
        if not self.elements:
            self.elements = default_elements

        QTableWidget.__init__(self)
        self.setObjectName(object_name)
        self.layout = parent.elements_hl.layout()

        # Add ourselves to the layout
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
        sizePolicy.setHorizontalStretch(0)
        #sizePolicy.setVerticalStretch(0)
        #sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth())
        self.setSizePolicy(sizePolicy)
        #self.setMaximumSize(QSize(16777215, self.MAXIMUM_TABLE_HEIGHT))

        self.setColumnCount(0)
        self.setRowCount(0)
        self.layout.addWidget(self)

    def _init_controls(self):
        # Add the control set
        vbl = QVBoxLayout()
        self.move_element_up_tb = QToolButton()
        self.move_element_up_tb.setObjectName("move_element_up_tb")
        self.move_element_up_tb.setToolTip('Move element up')
        self.move_element_up_tb.setIcon(QIcon(I('arrow-up.png')))
        self.move_element_up_tb.clicked.connect(self.move_row_up)
        vbl.addWidget(self.move_element_up_tb)

        self.undo_css_tb = QToolButton()
        self.undo_css_tb.setObjectName("undo_css_tb")
        self.undo_css_tb.setToolTip('Restore CSS to last saved')
        self.undo_css_tb.setIcon(QIcon(I('edit-undo.png')))
        self.undo_css_tb.clicked.connect(partial(self.undo_reset_button_clicked, 'undo'))
        vbl.addWidget(self.undo_css_tb)

        self.reset_css_tb = QToolButton()
        self.reset_css_tb.setObjectName("reset_css_tb")
        self.reset_css_tb.setToolTip('Reset CSS to default')
        self.reset_css_tb.setIcon(QIcon(I('trash.png')))
        self.reset_css_tb.clicked.connect(partial(self.undo_reset_button_clicked, 'reset'))
        vbl.addWidget(self.reset_css_tb)

        self.move_element_down_tb = QToolButton()
        self.move_element_down_tb.setObjectName("move_element_down_tb")
        self.move_element_down_tb.setToolTip('Move element down')
        self.move_element_down_tb.setIcon(QIcon(I('arrow-down.png')))
        self.move_element_down_tb.clicked.connect(self.move_row_down)
        vbl.addWidget(self.move_element_down_tb)

        self.layout.addLayout(vbl)

    def _init_table_widget(self):
        header_labels = [self.COLUMNS[index]['name'] \
            for index in sorted(self.COLUMNS.keys(), key=lambda c: self.COLUMNS[c]['ordinal'])]
        self.setColumnCount(len(header_labels))
        self.setHorizontalHeaderLabels(header_labels)
        self.verticalHeader().setVisible(False)

        self.setSortingEnabled(False)

        # Select single rows
        self.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.setSelectionMode(QAbstractItemView.SingleSelection)

    def convert_row_to_data(self, row):
        data = {}
        data['ordinal'] = row
        data['name'] = unicode(self.cellWidget(row, self.COLUMNS['ELEMENT']['ordinal']).text()).strip()
        data['css'] = unicode(self.cellWidget(row, self.COLUMNS['CSS']['ordinal']).toPlainText()).strip()
        return data

    def css_edited(self, row):
        self.select_and_scroll_to_row(row)
        col = self.COLUMNS['CSS']['ordinal']
        widget = self.cellWidget(row, col)
        css = unicode(widget.toPlainText())
        lines = []
        for line in css.split('\n'):
            lines.append(re.sub('^\s*', '', line))
        self.resize_row_height(lines, row)
        self.prefs.set('appearance_css', self.get_data())
        widget.setFocus()
        self.preview_css()

    def get_data(self):
        data_items = []
        for row in range(self.rowCount()):
            data = self.convert_row_to_data(row)
            data_items.append(
                               {'ordinal': data['ordinal'],
                                'name': data['name'],
                                'css': data['css'],
                                })
        return data_items

    def initialize(self):
        self._init_table_widget()
        self._init_controls()
        self.populate_table()
        self.resizeColumnsToContents()
        self.horizontalHeader().setStretchLastSection(True)

        # Update preview window, select first row
        self.css_edited(0)

    def move_row(self, source, dest):

        self.blockSignals(True)
        # Save the contents of the destination row
        saved_data = self.convert_row_to_data(dest)

        # Remove the destination row
        self.removeRow(dest)

        # Insert a new row at the original location
        self.insertRow(source)

        # Populate it with the saved data
        self.populate_table_row(source, saved_data)

        self.select_and_scroll_to_row(dest)
        self.blockSignals(False)

        self.css_edited(dest)

    def move_row_down(self):
        src_row = self.currentRow()
        dest_row = src_row + 1
        if dest_row == self.rowCount():
            return
        self.move_row(src_row, dest_row)

    def move_row_up(self):
        src_row = self.currentRow()
        dest_row = src_row - 1
        if dest_row < 0:
            return
        self.move_row(src_row, dest_row)

    def populate_table(self):
        # Format of rules list is different if default values vs retrieved JSON
        # Hack to normalize list style
        elements = self.elements
        if elements and type(elements[0]) is list:
            elements = elements[0]
        self.setFocus()
        elements = sorted(elements, key=lambda k: k['ordinal'])
        for row, element in enumerate(elements):
            self.insertRow(row)
            self.select_and_scroll_to_row(row)
            self.populate_table_row(row, element)
        self.selectRow(0)

    def populate_table_row(self, row, data):
        self.blockSignals(True)
        self.set_element_name_in_row(row, self.COLUMNS['ELEMENT']['ordinal'], data['name'])
        self.set_css_in_row(row, self.COLUMNS['CSS']['ordinal'], data['css'])
        self.blockSignals(False)

    def preview_css(self):
        '''
        Construct a dummy annotation for preview purposes
        '''
        from calibre_plugins.annotations.annotations import Annotation, Annotations

        pas = Annotations(None, title="Preview")
        pas.annotations.append(Annotation(self.sample_ann_1))
        pas.annotations.append(Annotation(self.sample_ann_2))
        pas.annotations.append(Annotation(self.sample_ann_3))
        self.parent.wv.setHtml(pas.to_HTML())

    def resize_row_height(self, lines, row):
        point_size = self.FONT.pointSize()
        if isosx:
            height = 30 + (len(lines) - 1) * (point_size + 4)
        elif iswindows:
            height = 26 + (len(lines) - 1) * (point_size + 3)
        elif islinux:
            height = 30 + (len(lines) - 1) * (point_size + 6)

        self.verticalHeader().resizeSection(row, height)

    def select_and_scroll_to_row(self, row):
        self.setFocus()
        self.selectRow(row)
        self.scrollToItem(self.currentItem())

    def set_element_name_in_row(self, row, col, name):
        rule_name = QLabel(" %s " % name)
        rule_name.setFont(self.FONT)
        self.setCellWidget(row, col, rule_name)

    def set_css_in_row(self, row, col, css):
        # Clean up multi-line css formatting
        # A single line is 30px tall, subsequent lines add 16px

        lines = []
        for line in css.split('\n'):
            lines.append(re.sub('^\s*', '', line))
        css_content = QPlainTextEdit('\n'.join(lines))
        css_content.setFont(self.FONT)
        css_content.textChanged.connect(partial(self.css_edited, row))
        self.setCellWidget(row, col, css_content)
        self.resize_row_height(lines, row)

    def undo_reset_button_clicked(self, mode):
        """
        Figure out which element is being reset
        Reset to last save or default
        """
        row = self.currentRow()
        data = self.convert_row_to_data(row)

        # Get default
        default_css = None
        for de in default_elements:
            if de['name'] == data['name']:
                default_css = de
                break

        # Get last saved
        last_saved_css = None
        saved_elements = self.prefs.get('appearance_css', None)
        last_saved_css = default_css
        if saved_elements:
            for se in saved_elements:
                if se['name'] == data['name']:
                    last_saved_css = se
                    break

        # Restore css
        if mode == 'reset':
            self.populate_table_row(row, default_css)
        elif mode == 'undo':
            self.populate_table_row(row, last_saved_css)

        # Refresh the stored data
        #self.prefs.set('appearance_css', self.get_data())
        self.css_edited(row)