def __init__(self, names, txt, parent=None): QDialog.__init__(self, parent) self.l = l = QVBoxLayout(self) self.setLayout(l) self.la = la = QLabel(_('Create a Virtual library based on %s') % txt) l.addWidget(la) self._names = QListWidget(self) self._names.addItems(sorted(names, key=sort_key)) self._names.setSelectionMode(QAbstractItemView.SelectionMode.MultiSelection) l.addWidget(self._names) self._or = QRadioButton(_('Match any of the selected %s')%txt) self._and = QRadioButton(_('Match all of the selected %s')%txt) self._or.setChecked(True) l.addWidget(self._or) l.addWidget(self._and) self.bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) self.bb.accepted.connect(self.accept) self.bb.rejected.connect(self.reject) l.addWidget(self.bb) self.resize(self.sizeHint())
class SelectNames(QDialog): # {{{ def __init__(self, names, txt, parent=None): QDialog.__init__(self, parent) self.l = l = QVBoxLayout(self) self.setLayout(l) self.la = la = QLabel(_('Create a Virtual library based on %s') % txt) l.addWidget(la) self.filter = f = QLineEdit(self) f.setPlaceholderText(_('Filter {}').format(txt)) f.setClearButtonEnabled(True) l.addWidget(f) self.model = QStringListModel(sorted(names, key=sort_key)) self.pmodel = QSortFilterProxyModel(self) self.pmodel.setFilterCaseSensitivity( Qt.CaseSensitivity.CaseInsensitive) f.textChanged.connect(self.pmodel.setFilterFixedString) self.pmodel.setSourceModel(self.model) self._names = QListView(self) self._names.setModel(self.pmodel) self._names.setSelectionMode( QAbstractItemView.SelectionMode.MultiSelection) l.addWidget(self._names) self._or = QRadioButton(_('Match any of the selected %s') % txt) self._and = QRadioButton(_('Match all of the selected %s') % txt) self._or.setChecked(True) l.addWidget(self._or) l.addWidget(self._and) self.bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) self.bb.accepted.connect(self.accept) self.bb.rejected.connect(self.reject) l.addWidget(self.bb) self.resize(self.sizeHint()) @property def names(self): for index in self._names.selectedIndexes(): yield index.data(Qt.ItemDataRole.DisplayRole) or '' @property def match_type(self): return ' and ' if self._and.isChecked() else ' or '
def __init__(self, names, parent=None): QDialog.__init__(self, parent) self.names = names self.setWindowTitle(_('Choose master file')) self.l = l = QVBoxLayout() self.setLayout(l) self.la = la = QLabel(_('Choose the master file. All selected files will be merged into the master file:')) la.setWordWrap(True) l.addWidget(la) self.sa = sa = QScrollArea(self) l.addWidget(sa) self.bb = bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) l.addWidget(bb) bb.accepted.connect(self.accept) bb.rejected.connect(self.reject) self.w = w = QWidget(self) w.l = QVBoxLayout() w.setLayout(w.l) buttons = self.buttons = [QRadioButton(n) for n in names] buttons[0].setChecked(True) for i in buttons: w.l.addWidget(i) sa.setWidget(w) self.resize(self.sizeHint() + QSize(150, 20))
def __init__(self, parent=None): QFrame.__init__(self, parent) self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) self.setAutoFillBackground(True) self.capture = 0 self.setFrameShape(QFrame.Shape.StyledPanel) self.setFrameShadow(QFrame.Shadow.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)
def __init__(self, index, dup_check, parent=None): QFrame.__init__(self, parent) self.setFrameShape(QFrame.Shape.StyledPanel) self.setFrameShadow(QFrame.Shadow.Raised) self.setFocusPolicy(Qt.FocusPolicy.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)
def b(name, text, tt): ans = QRadioButton(text, w) l.addWidget(ans) ans.setToolTip(tt) setattr(w, name, ans) ans.setObjectName(name) return ans
class SelectNames(QDialog): # {{{ def __init__(self, names, txt, parent=None): QDialog.__init__(self, parent) self.l = l = QVBoxLayout(self) self.setLayout(l) self.la = la = QLabel(_('Create a Virtual library based on %s') % txt) l.addWidget(la) self._names = QListWidget(self) self._names.addItems(sorted(names, key=sort_key)) self._names.setSelectionMode( QAbstractItemView.SelectionMode.MultiSelection) l.addWidget(self._names) self._or = QRadioButton(_('Match any of the selected %s') % txt) self._and = QRadioButton(_('Match all of the selected %s') % txt) self._or.setChecked(True) l.addWidget(self._or) l.addWidget(self._and) self.bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) self.bb.accepted.connect(self.accept) self.bb.rejected.connect(self.reject) l.addWidget(self.bb) self.resize(self.sizeHint()) @property def names(self): for item in self._names.selectedItems(): yield unicode_type(item.data(Qt.ItemDataRole.DisplayRole) or '') @property def match_type(self): return ' and ' if self._and.isChecked() else ' or '
def setup_ui(self): # {{{ self._g = g = QHBoxLayout(self) self.setLayout(g) self._l = l = QVBoxLayout() g.addLayout(l) fmts = sorted(x.upper() for x in self.fmts) self.fmt_choice_box = QGroupBox(_('Choose the format to unpack:'), self) self._fl = fl = QHBoxLayout() self.fmt_choice_box.setLayout(self._fl) self.fmt_choice_buttons = [QRadioButton(y, self) for y in fmts] for x in self.fmt_choice_buttons: fl.addWidget(x, stretch=10 if x is self.fmt_choice_buttons[-1] else 0) l.addWidget(self.fmt_choice_box) self.fmt_choice_box.setVisible(len(fmts) > 1) self.help_label = QLabel(_('''\ <h2>About Unpack book</h2> <p>Unpack book allows you to fine tune the appearance of an e-book by making small changes to its internals. In order to use Unpack book, you need to know a little bit about HTML and CSS, technologies that are used in e-books. Follow the steps:</p> <br> <ol> <li>Click "Explode book": This will "explode" the book into its individual internal components.<br></li> <li>Right click on any individual file and select "Open with..." to edit it in your favorite text editor.<br></li> <li>When you are done: <b>close the file browser window and the editor windows you used to make your tweaks</b>. Then click the "Rebuild book" button, to update the book in your calibre library.</li> </ol>''')) self.help_label.setWordWrap(True) self._fr = QFrame() self._fr.setFrameShape(QFrame.Shape.VLine) g.addWidget(self._fr) g.addWidget(self.help_label) self._b = b = QGridLayout() left, top, right, bottom = b.getContentsMargins() top += top b.setContentsMargins(left, top, right, bottom) l.addLayout(b, stretch=10) self.explode_button = QPushButton(QIcon(I('wizard.png')), _('&Explode book')) self.preview_button = QPushButton(QIcon(I('view.png')), _('&Preview book')) self.cancel_button = QPushButton(QIcon(I('window-close.png')), _('&Cancel')) self.rebuild_button = QPushButton(QIcon(I('exec.png')), _('&Rebuild book')) self.explode_button.setToolTip( _('Explode the book to edit its components')) self.preview_button.setToolTip( _('Preview the result of your changes')) self.cancel_button.setToolTip( _('Abort without saving any changes')) self.rebuild_button.setToolTip( _('Save your changes and update the book in the calibre library')) a = b.addWidget a(self.explode_button, 0, 0, 1, 1) a(self.preview_button, 0, 1, 1, 1) a(self.cancel_button, 1, 0, 1, 1) a(self.rebuild_button, 1, 1, 1, 1) for x in ('explode', 'preview', 'cancel', 'rebuild'): getattr(self, x+'_button').clicked.connect(getattr(self, x)) self.msg = QLabel('dummy', self) self.msg.setVisible(False) self.msg.setStyleSheet(''' QLabel { text-align: center; background-color: white; color: black; border-width: 1px; border-style: solid; border-radius: 20px; font-size: x-large; font-weight: bold; } ''') self.resize(self.sizeHint() + QSize(40, 10))
def setup_ui(self): self.setWindowIcon(QIcon(I('diff.png'))) self.stacks = st = QStackedLayout(self) self.busy = BusyWidget(self) self.w = QWidget(self) st.addWidget(self.busy), st.addWidget(self.w) self.setLayout(st) self.l = l = QGridLayout() self.w.setLayout(l) self.view = v = DiffView(self, show_open_in_editor=self.show_open_in_editor) l.addWidget(v, l.rowCount(), 0, 1, -1) r = l.rowCount() self.bp = b = QToolButton(self) b.setIcon(QIcon(I('back.png'))) connect_lambda(b.clicked, self, lambda self: self.view.next_change(-1)) b.setToolTip(_('Go to previous change') + ' [p]') b.setText(_('&Previous change')), b.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon) l.addWidget(b, r, 0) self.bn = b = QToolButton(self) b.setIcon(QIcon(I('forward.png'))) connect_lambda(b.clicked, self, lambda self: self.view.next_change(1)) b.setToolTip(_('Go to next change') + ' [n]') b.setText(_('&Next change')), b.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon) l.addWidget(b, r, 1) self.search = s = HistoryLineEdit2(self) s.initialize('diff_search_history') l.addWidget(s, r, 2) s.setPlaceholderText(_('Search for text')) connect_lambda(s.returnPressed, self, lambda self: self.do_search(False)) self.sbn = b = QToolButton(self) b.setIcon(QIcon(I('arrow-down.png'))) connect_lambda(b.clicked, self, lambda self: self.do_search(False)) b.setToolTip(_('Find next match')) b.setText(_('Next &match')), b.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon) l.addWidget(b, r, 3) self.sbp = b = QToolButton(self) b.setIcon(QIcon(I('arrow-up.png'))) connect_lambda(b.clicked, self, lambda self: self.do_search(True)) b.setToolTip(_('Find previous match')) b.setText(_('P&revious match')), b.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon) l.addWidget(b, r, 4) self.lb = b = QRadioButton(_('Left panel'), self) b.setToolTip(_('Perform search in the left panel')) l.addWidget(b, r, 5) self.rb = b = QRadioButton(_('Right panel'), self) b.setToolTip(_('Perform search in the right panel')) l.addWidget(b, r, 6) b.setChecked(True) self.pb = b = QToolButton(self) b.setIcon(QIcon(I('config.png'))) b.setText(_('&Options')), b.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon) b.setToolTip(_('Change how the differences are displayed')) b.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup) m = QMenu(b) b.setMenu(m) cm = self.cm = QMenu(_('Lines of context around each change')) for i in (3, 5, 10, 50): cm.addAction(_('Show %d lines of context') % i, partial(self.change_context, i)) cm.addAction(_('Show all text'), partial(self.change_context, None)) self.beautify_action = m.addAction('', self.toggle_beautify) self.set_beautify_action_text() m.addMenu(cm) l.addWidget(b, r, 7) self.hl = QHBoxLayout() l.addLayout(self.hl, l.rowCount(), 0, 1, -1) self.names = QLabel('') self.hl.addWidget(self.names, stretch=100) if self.show_open_in_editor: self.edit_msg = QLabel(_('Double click right side to edit')) self.edit_msg.setToolTip(textwrap.fill(_( 'Double click on any change in the right panel to edit that location in the editor'))) self.hl.addWidget(self.edit_msg) self.bb.setStandardButtons(QDialogButtonBox.StandardButton.Close) if self.revert_button_msg is not None: self.rvb = b = self.bb.addButton(self.revert_button_msg, QDialogButtonBox.ButtonRole.ActionRole) b.setIcon(QIcon(I('edit-undo.png'))), b.setAutoDefault(False) b.clicked.connect(self.revert_requested) b.clicked.connect(self.reject) self.bb.button(QDialogButtonBox.StandardButton.Close).setDefault(True) self.hl.addWidget(self.bb) self.view.setFocus(Qt.FocusReason.OtherFocusReason)
def __init__(self, recipe_model, parent=None): QDialog.__init__(self, parent) self.commit_on_change = True self.previous_urn = None self.setWindowIcon(QIcon(I('scheduler.png'))) self.l = l = QGridLayout(self) # Left panel self.h = h = QHBoxLayout() l.addLayout(h, 0, 0, 1, 1) self.search = s = SearchBox2(self) self.search.initialize('scheduler_search_history') self.search.setMinimumContentsLength(15) self.go_button = b = QToolButton(self) b.setText(_("Go")) b.clicked.connect(self.search.do_search) h.addWidget(s), h.addWidget(b) self.recipes = RecipesView(self) l.addWidget(self.recipes, 1, 0, 2, 1) self.recipe_model = recipe_model self.recipe_model.do_refresh() self.recipes.setModel(self.recipe_model) self.recipes.setFocus(Qt.FocusReason.OtherFocusReason) self.recipes.item_activated.connect(self.download_clicked) self.setWindowTitle( _("Schedule news download [{} sources]").format( self.recipe_model.showing_count)) self.search.search.connect(self.recipe_model.search) self.recipe_model.searched.connect( self.search.search_done, type=Qt.ConnectionType.QueuedConnection) self.recipe_model.searched.connect(self.search_done) # Right Panel self.scroll_area_contents = sac = QWidget(self) self.l.addWidget(sac, 0, 1, 2, 1) sac.v = v = QVBoxLayout(sac) v.setContentsMargins(0, 0, 0, 0) self.detail_box = QTabWidget(self) self.detail_box.setVisible(False) self.detail_box.setCurrentIndex(0) v.addWidget(self.detail_box) v.addItem( QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)) # First Tab (scheduling) self.tab = QWidget() self.detail_box.addTab(self.tab, _("&Schedule")) self.tab.v = vt = QVBoxLayout(self.tab) vt.setContentsMargins(0, 0, 0, 0) self.blurb = la = QLabel('blurb') la.setWordWrap(True), la.setOpenExternalLinks(True) vt.addWidget(la) self.frame = f = QFrame(self.tab) vt.addWidget(f) f.setFrameShape(QFrame.Shape.StyledPanel) f.setFrameShadow(QFrame.Shadow.Raised) f.v = vf = QVBoxLayout(f) self.schedule = s = QCheckBox(_("&Schedule for download:"), f) self.schedule.stateChanged[int].connect(self.toggle_schedule_info) vf.addWidget(s) f.h = h = QHBoxLayout() vf.addLayout(h) self.days_of_week = QRadioButton(_("&Days of week"), f) self.days_of_month = QRadioButton(_("Da&ys of month"), f) self.every_x_days = QRadioButton(_("Every &x days"), f) self.days_of_week.setChecked(True) h.addWidget(self.days_of_week), h.addWidget( self.days_of_month), h.addWidget(self.every_x_days) self.schedule_stack = ss = QStackedWidget(f) self.schedule_widgets = [] for key in reversed(self.SCHEDULE_TYPES): self.schedule_widgets.insert(0, self.SCHEDULE_TYPES[key](self)) self.schedule_stack.insertWidget(0, self.schedule_widgets[0]) vf.addWidget(ss) self.last_downloaded = la = QLabel(f) la.setWordWrap(True) vf.addWidget(la) self.account = acc = QGroupBox(self.tab) acc.setTitle(_("&Account")) vt.addWidget(acc) acc.g = g = QGridLayout(acc) acc.unla = la = QLabel(_("&Username:"******"&Password:"******"&Show password"), self.account) spw.stateChanged[int].connect(self.set_pw_echo_mode) g.addWidget(spw, 2, 0, 1, 2) self.rla = la = QLabel( _("For the scheduling to work, you must leave calibre running.")) vt.addWidget(la) for b, c in iteritems(self.SCHEDULE_TYPES): b = getattr(self, b) b.toggled.connect(self.schedule_type_selected) b.setToolTip(textwrap.dedent(c.HELP)) # Second tab (advanced settings) self.tab2 = t2 = QWidget() self.detail_box.addTab(self.tab2, _("&Advanced")) self.tab2.g = g = QGridLayout(t2) g.setContentsMargins(0, 0, 0, 0) self.add_title_tag = tt = QCheckBox(_("Add &title as tag"), t2) g.addWidget(tt, 0, 0, 1, 2) t2.la = la = QLabel(_("&Extra tags:")) self.custom_tags = ct = QLineEdit(self) la.setBuddy(ct) g.addWidget(la), g.addWidget(ct, 1, 1) t2.la2 = la = QLabel(_("&Keep at most:")) la.setToolTip( _("Maximum number of copies (issues) of this recipe to keep. Set to 0 to keep all (disable)." )) self.keep_issues = ki = QSpinBox(t2) tt.toggled['bool'].connect(self.keep_issues.setEnabled) ki.setMaximum(100000), la.setBuddy(ki) ki.setToolTip( _("<p>When set, this option will cause calibre to keep, at most, the specified number of issues" " of this periodical. Every time a new issue is downloaded, the oldest one is deleted, if the" " total is larger than this number.\n<p>Note that this feature only works if you have the" " option to add the title as tag checked, above.\n<p>Also, the setting for deleting periodicals" " older than a number of days, below, takes priority over this setting." )) ki.setSpecialValueText(_("all issues")), ki.setSuffix(_(" issues")) g.addWidget(la), g.addWidget(ki, 2, 1) si = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding) g.addItem(si, 3, 1, 1, 1) # Bottom area self.hb = h = QHBoxLayout() self.l.addLayout(h, 2, 1, 1, 1) self.labt = la = QLabel(_("Delete downloaded &news older than:")) self.old_news = on = QSpinBox(self) on.setToolTip( _("<p>Delete downloaded news older than the specified number of days. Set to zero to disable.\n" "<p>You can also control the maximum number of issues of a specific periodical that are kept" " by clicking the Advanced tab for that periodical above.")) on.setSpecialValueText(_("never delete")), on.setSuffix(_(" days")) on.setMaximum(1000), la.setBuddy(on) on.setValue(gconf['oldest_news']) h.addWidget(la), h.addWidget(on) self.download_all_button = b = QPushButton( QIcon(I('news.png')), _("Download &all scheduled"), self) b.setToolTip(_("Download all scheduled news sources at once")) b.clicked.connect(self.download_all_clicked) self.l.addWidget(b, 3, 0, 1, 1) self.bb = bb = QDialogButtonBox( QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel, self) bb.accepted.connect(self.accept), bb.rejected.connect(self.reject) self.download_button = b = bb.addButton( _('&Download now'), QDialogButtonBox.ButtonRole.ActionRole) b.setIcon(QIcon(I('arrow-down.png'))), b.setVisible(False) b.clicked.connect(self.download_clicked) self.l.addWidget(bb, 3, 1, 1, 1) geom = gprefs.get('scheduler_dialog_geometry') if geom is not None: QApplication.instance().safe_restore_geometry(self, geom)
class SchedulerDialog(QDialog): SCHEDULE_TYPES = OrderedDict([ ('days_of_week', DaysOfWeek), ('days_of_month', DaysOfMonth), ('every_x_days', EveryXDays), ]) download = pyqtSignal(object) def __init__(self, recipe_model, parent=None): QDialog.__init__(self, parent) self.commit_on_change = True self.previous_urn = None self.setWindowIcon(QIcon(I('scheduler.png'))) self.l = l = QGridLayout(self) # Left panel self.h = h = QHBoxLayout() l.addLayout(h, 0, 0, 1, 1) self.search = s = SearchBox2(self) self.search.initialize('scheduler_search_history') self.search.setMinimumContentsLength(15) self.go_button = b = QToolButton(self) b.setText(_("Go")) b.clicked.connect(self.search.do_search) h.addWidget(s), h.addWidget(b) self.recipes = RecipesView(self) l.addWidget(self.recipes, 1, 0, 2, 1) self.recipe_model = recipe_model self.recipe_model.do_refresh() self.recipes.setModel(self.recipe_model) self.recipes.setFocus(Qt.FocusReason.OtherFocusReason) self.recipes.item_activated.connect(self.download_clicked) self.setWindowTitle( _("Schedule news download [{} sources]").format( self.recipe_model.showing_count)) self.search.search.connect(self.recipe_model.search) self.recipe_model.searched.connect( self.search.search_done, type=Qt.ConnectionType.QueuedConnection) self.recipe_model.searched.connect(self.search_done) # Right Panel self.scroll_area_contents = sac = QWidget(self) self.l.addWidget(sac, 0, 1, 2, 1) sac.v = v = QVBoxLayout(sac) v.setContentsMargins(0, 0, 0, 0) self.detail_box = QTabWidget(self) self.detail_box.setVisible(False) self.detail_box.setCurrentIndex(0) v.addWidget(self.detail_box) v.addItem( QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)) # First Tab (scheduling) self.tab = QWidget() self.detail_box.addTab(self.tab, _("&Schedule")) self.tab.v = vt = QVBoxLayout(self.tab) vt.setContentsMargins(0, 0, 0, 0) self.blurb = la = QLabel('blurb') la.setWordWrap(True), la.setOpenExternalLinks(True) vt.addWidget(la) self.frame = f = QFrame(self.tab) vt.addWidget(f) f.setFrameShape(QFrame.Shape.StyledPanel) f.setFrameShadow(QFrame.Shadow.Raised) f.v = vf = QVBoxLayout(f) self.schedule = s = QCheckBox(_("&Schedule for download:"), f) self.schedule.stateChanged[int].connect(self.toggle_schedule_info) vf.addWidget(s) f.h = h = QHBoxLayout() vf.addLayout(h) self.days_of_week = QRadioButton(_("&Days of week"), f) self.days_of_month = QRadioButton(_("Da&ys of month"), f) self.every_x_days = QRadioButton(_("Every &x days"), f) self.days_of_week.setChecked(True) h.addWidget(self.days_of_week), h.addWidget( self.days_of_month), h.addWidget(self.every_x_days) self.schedule_stack = ss = QStackedWidget(f) self.schedule_widgets = [] for key in reversed(self.SCHEDULE_TYPES): self.schedule_widgets.insert(0, self.SCHEDULE_TYPES[key](self)) self.schedule_stack.insertWidget(0, self.schedule_widgets[0]) vf.addWidget(ss) self.last_downloaded = la = QLabel(f) la.setWordWrap(True) vf.addWidget(la) self.account = acc = QGroupBox(self.tab) acc.setTitle(_("&Account")) vt.addWidget(acc) acc.g = g = QGridLayout(acc) acc.unla = la = QLabel(_("&Username:"******"&Password:"******"&Show password"), self.account) spw.stateChanged[int].connect(self.set_pw_echo_mode) g.addWidget(spw, 2, 0, 1, 2) self.rla = la = QLabel( _("For the scheduling to work, you must leave calibre running.")) vt.addWidget(la) for b, c in iteritems(self.SCHEDULE_TYPES): b = getattr(self, b) b.toggled.connect(self.schedule_type_selected) b.setToolTip(textwrap.dedent(c.HELP)) # Second tab (advanced settings) self.tab2 = t2 = QWidget() self.detail_box.addTab(self.tab2, _("&Advanced")) self.tab2.g = g = QGridLayout(t2) g.setContentsMargins(0, 0, 0, 0) self.add_title_tag = tt = QCheckBox(_("Add &title as tag"), t2) g.addWidget(tt, 0, 0, 1, 2) t2.la = la = QLabel(_("&Extra tags:")) self.custom_tags = ct = QLineEdit(self) la.setBuddy(ct) g.addWidget(la), g.addWidget(ct, 1, 1) t2.la2 = la = QLabel(_("&Keep at most:")) la.setToolTip( _("Maximum number of copies (issues) of this recipe to keep. Set to 0 to keep all (disable)." )) self.keep_issues = ki = QSpinBox(t2) tt.toggled['bool'].connect(self.keep_issues.setEnabled) ki.setMaximum(100000), la.setBuddy(ki) ki.setToolTip( _("<p>When set, this option will cause calibre to keep, at most, the specified number of issues" " of this periodical. Every time a new issue is downloaded, the oldest one is deleted, if the" " total is larger than this number.\n<p>Note that this feature only works if you have the" " option to add the title as tag checked, above.\n<p>Also, the setting for deleting periodicals" " older than a number of days, below, takes priority over this setting." )) ki.setSpecialValueText(_("all issues")), ki.setSuffix(_(" issues")) g.addWidget(la), g.addWidget(ki, 2, 1) si = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding) g.addItem(si, 3, 1, 1, 1) # Bottom area self.hb = h = QHBoxLayout() self.l.addLayout(h, 2, 1, 1, 1) self.labt = la = QLabel(_("Delete downloaded &news older than:")) self.old_news = on = QSpinBox(self) on.setToolTip( _("<p>Delete downloaded news older than the specified number of days. Set to zero to disable.\n" "<p>You can also control the maximum number of issues of a specific periodical that are kept" " by clicking the Advanced tab for that periodical above.")) on.setSpecialValueText(_("never delete")), on.setSuffix(_(" days")) on.setMaximum(1000), la.setBuddy(on) on.setValue(gconf['oldest_news']) h.addWidget(la), h.addWidget(on) self.download_all_button = b = QPushButton( QIcon(I('news.png')), _("Download &all scheduled"), self) b.setToolTip(_("Download all scheduled news sources at once")) b.clicked.connect(self.download_all_clicked) self.l.addWidget(b, 3, 0, 1, 1) self.bb = bb = QDialogButtonBox( QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel, self) bb.accepted.connect(self.accept), bb.rejected.connect(self.reject) self.download_button = b = bb.addButton( _('&Download now'), QDialogButtonBox.ButtonRole.ActionRole) b.setIcon(QIcon(I('arrow-down.png'))), b.setVisible(False) b.clicked.connect(self.download_clicked) self.l.addWidget(bb, 3, 1, 1, 1) geom = gprefs.get('scheduler_dialog_geometry') if geom is not None: QApplication.instance().safe_restore_geometry(self, geom) def sizeHint(self): return QSize(800, 600) def set_pw_echo_mode(self, state): self.password.setEchoMode( QLineEdit.EchoMode.Normal if state == Qt.CheckState.Checked else QLineEdit.EchoMode.Password) def schedule_type_selected(self, *args): for i, st in enumerate(self.SCHEDULE_TYPES): if getattr(self, st).isChecked(): self.schedule_stack.setCurrentIndex(i) break def keyPressEvent(self, ev): if ev.key() not in (Qt.Key.Key_Enter, Qt.Key.Key_Return): return QDialog.keyPressEvent(self, ev) def break_cycles(self): try: self.recipe_model.searched.disconnect(self.search_done) self.recipe_model.searched.disconnect(self.search.search_done) self.search.search.disconnect() self.download.disconnect() except: pass self.recipe_model = None def search_done(self, *args): if self.recipe_model.showing_count < 20: self.recipes.expandAll() def toggle_schedule_info(self, *args): enabled = self.schedule.isChecked() for x in self.SCHEDULE_TYPES: getattr(self, x).setEnabled(enabled) self.schedule_stack.setEnabled(enabled) self.last_downloaded.setVisible(enabled) def current_changed(self, current, previous): if self.previous_urn is not None: self.commit(urn=self.previous_urn) self.previous_urn = None urn = self.current_urn if urn is not None: self.initialize_detail_box(urn) self.recipes.scrollTo(current) def accept(self): if not self.commit(): return False self.save_geometry() return QDialog.accept(self) def reject(self): self.save_geometry() return QDialog.reject(self) def save_geometry(self): gprefs.set('scheduler_dialog_geometry', bytearray(self.saveGeometry())) def download_clicked(self, *args): self.commit() if self.commit() and self.current_urn: self.download.emit(self.current_urn) def download_all_clicked(self, *args): if self.commit() and self.commit(): self.download.emit(None) @property def current_urn(self): current = self.recipes.currentIndex() if current.isValid(): return getattr(current.internalPointer(), 'urn', None) def commit(self, urn=None): urn = self.current_urn if urn is None else urn if not self.detail_box.isVisible() or urn is None: return True if self.account.isVisible(): un, pw = map(unicode_type, (self.username.text(), self.password.text())) un, pw = un.strip(), pw.strip() if not un and not pw and self.schedule.isChecked(): if not getattr(self, 'subscription_optional', False): error_dialog( self, _('Need username and password'), _('You must provide a username and/or password to ' 'use this news source.'), show=True) return False if un or pw: self.recipe_model.set_account_info(urn, un, pw) else: self.recipe_model.clear_account_info(urn) if self.schedule.isChecked(): schedule_type, schedule = \ self.schedule_stack.currentWidget().schedule self.recipe_model.schedule_recipe(urn, schedule_type, schedule) else: self.recipe_model.un_schedule_recipe(urn) add_title_tag = self.add_title_tag.isChecked() keep_issues = '0' if self.keep_issues.isEnabled(): keep_issues = unicode_type(self.keep_issues.value()) custom_tags = unicode_type(self.custom_tags.text()).strip() custom_tags = [x.strip() for x in custom_tags.split(',')] self.recipe_model.customize_recipe(urn, add_title_tag, custom_tags, keep_issues) return True def initialize_detail_box(self, urn): self.previous_urn = urn self.detail_box.setVisible(True) self.download_button.setVisible(True) self.detail_box.setCurrentIndex(0) recipe = self.recipe_model.recipe_from_urn(urn) try: schedule_info = self.recipe_model.schedule_info_from_urn(urn) except: # Happens if user does something stupid like unchecking all the # days of the week schedule_info = None account_info = self.recipe_model.account_info_from_urn(urn) customize_info = self.recipe_model.get_customize_info(urn) ns = recipe.get('needs_subscription', '') self.account.setVisible(ns in ('yes', 'optional')) self.subscription_optional = ns == 'optional' act = _('Account') act2 = _('(optional)') if self.subscription_optional else \ _('(required)') self.account.setTitle(act + ' ' + act2) un = pw = '' if account_info is not None: un, pw = account_info[:2] if not un: un = '' if not pw: pw = '' self.username.setText(un) self.password.setText(pw) self.show_password.setChecked(False) self.blurb.setText(''' <p> <b>%(title)s</b><br> %(cb)s %(author)s<br/> %(description)s </p> ''' % dict(title=recipe.get('title'), cb=_('Created by: '), author=recipe.get('author', _('Unknown')), description=recipe.get('description', ''))) self.download_button.setToolTip( _('Download %s now') % recipe.get('title')) scheduled = schedule_info is not None self.schedule.setChecked(scheduled) self.toggle_schedule_info() self.last_downloaded.setText(_('Last downloaded: never')) ld_text = _('never') if scheduled: typ, sch, last_downloaded = schedule_info d = utcnow() - last_downloaded def hm(x): return (x - x % 3600) // 3600, (x % 3600 - (x % 3600) % 60) // 60 hours, minutes = hm(d.seconds) tm = _('%(days)d days, %(hours)d hours' ' and %(mins)d minutes ago') % dict( days=d.days, hours=hours, mins=minutes) if d < timedelta(days=366): ld_text = tm else: typ, sch = 'day/time', (-1, 6, 0) sch_widget = { 'day/time': 0, 'days_of_week': 0, 'days_of_month': 1, 'interval': 2 }[typ] rb = getattr(self, list(self.SCHEDULE_TYPES)[sch_widget]) rb.setChecked(True) self.schedule_stack.setCurrentIndex(sch_widget) self.schedule_stack.currentWidget().initialize(typ, sch) add_title_tag, custom_tags, keep_issues = customize_info self.add_title_tag.setChecked(add_title_tag) self.custom_tags.setText(', '.join(custom_tags)) self.last_downloaded.setText(_('Last downloaded:') + ' ' + ld_text) try: keep_issues = int(keep_issues) except: keep_issues = 0 self.keep_issues.setValue(keep_issues) self.keep_issues.setEnabled(self.add_title_tag.isChecked())
class Editor(QFrame): # {{{ editing_done = pyqtSignal(object) def __init__(self, parent=None): QFrame.__init__(self, parent) self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) self.setAutoFillBackground(True) self.capture = 0 self.setFrameShape(QFrame.Shape.StyledPanel) self.setFrameShadow(QFrame.Shadow.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) def initialize(self, shortcut, all_shortcuts): self.header.setText('<b>%s: %s</b>' % (_('Customize'), shortcut['name'])) self.all_shortcuts = all_shortcuts self.shortcut = shortcut self.default_keys = [ QKeySequence(k, QKeySequence.SequenceFormat.PortableText) for k in shortcut['default_keys'] ] self.current_keys = list(shortcut['keys']) default = ', '.join([ unicode_type(k.toString(QKeySequence.SequenceFormat.NativeText)) for k in self.default_keys ]) if not default: default = _('None') current = ', '.join([ unicode_type(k.toString(QKeySequence.SequenceFormat.NativeText)) for k in self.current_keys ]) if not current: current = _('None') self.use_default.setText( _('&Default: %(deflt)s [Currently not conflicting: %(curr)s]') % dict(deflt=default, curr=current)) if shortcut['set_to_default']: self.use_default.setChecked(True) else: self.use_custom.setChecked(True) for key, which in zip(self.current_keys, [1, 2]): button = getattr(self, 'button%d' % which) button.setText( key.toString(QKeySequence.SequenceFormat.NativeText)) def custom_toggled(self, checked): for w in ('1', '2'): for o in ('label', 'button', 'clear'): getattr(self, o + w).setEnabled(checked) def capture_clicked(self, which=1): self.capture = which button = getattr(self, 'button%d' % which) button.setText(_('Press a key...')) button.setFocus(Qt.FocusReason.OtherFocusReason) button.setStyleSheet('QPushButton { font-weight: bold}') def clear_clicked(self, which=0): button = getattr(self, 'button%d' % which) button.setText(_('None')) def eventFilter(self, obj, event): if self.capture and obj in (self.button1, self.button2): t = event.type() if t == QEvent.Type.ShortcutOverride: event.accept() return True if t == QEvent.Type.KeyPress: self.key_press_event(event, 1 if obj is self.button1 else 2) return True return QFrame.eventFilter(self, obj, event) def key_press_event(self, ev, which=0): if self.capture == 0: return QWidget.keyPressEvent(self, ev) sequence = keysequence_from_event(ev) if sequence is None: return QWidget.keyPressEvent(self, ev) ev.accept() button = getattr(self, 'button%d' % which) button.setStyleSheet('QPushButton { font-weight: normal}') button.setText( sequence.toString(QKeySequence.SequenceFormat.NativeText)) self.capture = 0 dup_desc = self.dup_check(sequence) if dup_desc is not None: error_dialog(self, _('Already assigned'), unicode_type( sequence.toString( QKeySequence.SequenceFormat.NativeText)) + ' ' + _('already assigned to') + ' ' + dup_desc, show=True) self.clear_clicked(which=which) def dup_check(self, sequence): for sc in self.all_shortcuts: if sc is self.shortcut: continue for k in sc['keys']: if k == sequence: return sc['name'] @property def custom_keys(self): if self.use_default.isChecked(): return None ans = [] for which in (1, 2): button = getattr(self, 'button%d' % which) t = unicode_type(button.text()) if t == _('None'): continue ks = QKeySequence(t, QKeySequence.SequenceFormat.NativeText) if not ks.isEmpty(): ans.append(ks) return tuple(ans)
def add_bool_radio_button(txt): b = QRadioButton(txt) b.clicked.connect(partial(self.bool_radio_button_clicked, b)) h1.addWidget(b) return b