def test(scale=0.5): from PyQt5.Qt import QLabel, QApplication, QPixmap, QMainWindow, QWidget, QScrollArea, QGridLayout app = QApplication([]) mi = Metadata('xxx', ['Kovid Goyal', 'John Q. Doe', 'Author']) mi.series = 'A series of styles' m = QMainWindow() sa = QScrollArea(m) w = QWidget(m) sa.setWidget(w) l = QGridLayout(w) w.setLayout(l), l.setSpacing(30) labels = [] for r, color in enumerate(sorted(default_color_themes)): for c, style in enumerate(sorted(all_styles())): mi.series_index = c + 1 mi.title = 'An algorithmic cover [%s]' % color prefs = override_prefs(cprefs, override_color_theme=color, override_style=style) for x in ('cover_width', 'cover_height', 'title_font_size', 'subtitle_font_size', 'footer_font_size'): prefs[x] = int(scale * prefs[x]) img = generate_cover(mi, prefs=prefs, as_qimage=True) la = QLabel() la.setPixmap(QPixmap.fromImage(img)) l.addWidget(la, r, c) labels.append(la) m.setCentralWidget(sa) w.resize(w.sizeHint()) m.show() app.exec_()
def test(scale=0.25): from PyQt5.Qt import QLabel, QApplication, QPixmap, QMainWindow, QWidget, QScrollArea, QGridLayout app = QApplication([]) mi = Metadata('xxx', ['Kovid Goyal', 'John & Doe', 'Author']) mi.series = 'A series of styles' m = QMainWindow() sa = QScrollArea(m) w = QWidget(m) sa.setWidget(w) l = QGridLayout(w) w.setLayout(l), l.setSpacing(30) labels = [] for r, color in enumerate(sorted(default_color_themes)): for c, style in enumerate(sorted(all_styles())): mi.series_index = c + 1 mi.title = 'An algorithmic cover [%s]' % color prefs = override_prefs(cprefs, override_color_theme=color, override_style=style) scale_cover(prefs, scale) img = generate_cover(mi, prefs=prefs, as_qimage=True) la = QLabel() la.setPixmap(QPixmap.fromImage(img)) l.addWidget(la, r, c) labels.append(la) m.setCentralWidget(sa) w.resize(w.sizeHint()) m.show() app.exec_()
def test(scale=0.25): from PyQt5.Qt import QLabel, QPixmap, QMainWindow, QWidget, QScrollArea, QGridLayout from calibre.gui2 import Application app = Application([]) mi = Metadata('Unknown', ['Kovid Goyal', 'John & Doe', 'Author']) mi.series = 'A series & styles' m = QMainWindow() sa = QScrollArea(m) w = QWidget(m) sa.setWidget(w) l = QGridLayout(w) w.setLayout(l), l.setSpacing(30) scale *= w.devicePixelRatioF() labels = [] for r, color in enumerate(sorted(default_color_themes)): for c, style in enumerate(sorted(all_styles())): mi.series_index = c + 1 mi.title = 'An algorithmic cover [%s]' % color prefs = override_prefs(cprefs, override_color_theme=color, override_style=style) scale_cover(prefs, scale) img = generate_cover(mi, prefs=prefs, as_qimage=True) img.setDevicePixelRatio(w.devicePixelRatioF()) la = QLabel() la.setPixmap(QPixmap.fromImage(img)) l.addWidget(la, r, c) labels.append(la) m.setCentralWidget(sa) w.resize(w.sizeHint()) m.show() app.exec_()
def __init__(self, parent): QWidget.__init__(self, parent) self.sa = QScrollArea(self) self.lw = QWidget(self) self.l = QVBoxLayout(self.lw) self.sa.setWidget(self.lw), self.sa.setWidgetResizable(True) self.gl = gl = QVBoxLayout(self) self.la = QLabel(_( 'Add new locations to search for books or authors using the "Search the internet" feature' ' of the Content server. The URLs should contain {author} which will be' ' replaced by the author name and, for book URLs, {title} which will' ' be replaced by the book title.')) self.la.setWordWrap(True) gl.addWidget(self.la) self.h = QHBoxLayout() gl.addLayout(self.h) self.add_url_button = b = QPushButton(QIcon(I('plus.png')), _('&Add URL')) b.clicked.connect(self.add_url) self.h.addWidget(b) self.export_button = b = QPushButton(_('Export URLs')) b.clicked.connect(self.export_urls) self.h.addWidget(b) self.import_button = b = QPushButton(_('Import URLs')) b.clicked.connect(self.import_urls) self.h.addWidget(b) self.clear_button = b = QPushButton(_('Clear')) b.clicked.connect(self.clear) self.h.addWidget(b) self.h.addStretch(10) gl.addWidget(self.sa, stretch=10) self.items = []
def __init__(self, *args, **kw): ConfigWidgetBase.__init__(self, *args, **kw) self.l = l = QVBoxLayout(self) l.setContentsMargins(0, 0, 0, 0) self.tabs_widget = t = QTabWidget(self) l.addWidget(t) self.main_tab = m = MainTab(self) t.addTab(m, _('&Main')) m.start_server.connect(self.start_server) m.stop_server.connect(self.stop_server) m.test_server.connect(self.test_server) m.show_logs.connect(self.view_server_logs) self.opt_autolaunch_server = m.opt_autolaunch_server self.users_tab = ua = Users(self) t.addTab(ua, _('&User accounts')) self.advanced_tab = a = AdvancedTab(self) sa = QScrollArea(self) sa.setWidget(a), sa.setWidgetResizable(True) t.addTab(sa, _('&Advanced')) self.custom_list_tab = clt = CustomList(self) sa = QScrollArea(self) sa.setWidget(clt), sa.setWidgetResizable(True) t.addTab(sa, _('Book &list template')) self.search_net_tab = SearchTheInternet(self) t.addTab(self.search_net_tab, _('&Search the internet')) for tab in self.tabs: if hasattr(tab, 'changed_signal'): tab.changed_signal.connect(self.changed_signal.emit)
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.do_layout() geom = gprefs.get('metasingle_window_geometry3', None) if geom is not None: self.restoreGeometry(bytes(geom))
def wrap_widget(self, page): sw = QScrollArea(self) name = 'STW{}'.format(abs(id(self))) sw.setObjectName(name) sw.setWidget(page) sw.setWidgetResizable(True) page.setAutoFillBackground(False) sw.setStyleSheet('#%s { background: transparent }' % name) return sw
def __init__(self, parent_dialog, plugin_action): self.parent_dialog = parent_dialog self.plugin_action = plugin_action QWidget.__init__(self) self.l = QVBoxLayout() self.setLayout(self.l) label = QLabel(_("If you have custom columns defined, they will be listed below. Choose how you would like these columns handled.")) label.setWordWrap(True) self.l.addWidget(label) self.l.addSpacing(5) scrollable = QScrollArea() scrollcontent = QWidget() scrollable.setWidget(scrollcontent) scrollable.setWidgetResizable(True) self.l.addWidget(scrollable) self.sl = QVBoxLayout() scrollcontent.setLayout(self.sl) self.custcol_dropdowns = {} custom_columns = self.plugin_action.gui.library_view.model().custom_columns grid = QGridLayout() self.sl.addLayout(grid) row=0 for key, column in custom_columns.iteritems(): if column['datatype'] in permitted_values: # print("\n============== %s ===========\n"%key) # for (k,v) in column.iteritems(): # print("column['%s'] => %s"%(k,v)) label = QLabel('%s(%s)'%(column['name'],key)) label.setToolTip(_("Set this %s column on new merged books...")%column['datatype']) grid.addWidget(label,row,0) dropdown = QComboBox(self) dropdown.addItem('','none') for md in permitted_values[column['datatype']]: # tags-like column also 'text' if md == 'union' and not column['is_multiple']: continue if md == 'concat' and column['is_multiple']: continue dropdown.addItem(titleLabels[md],md) self.custcol_dropdowns[key] = dropdown if key in prefs['custom_cols']: dropdown.setCurrentIndex(dropdown.findData(prefs['custom_cols'][key])) dropdown.setToolTip(_("How this column will be populated by default.")) grid.addWidget(dropdown,row,1) row+=1 self.sl.insertStretch(-1)
def __init__(self, ia): QWidget.__init__(self) self.ia = ia self.layout = QVBoxLayout() self.setLayout(self.layout) # make the config menu as a widget self.config_menu = self.make_menu() # make a scroll area to hold the menu and add it to the layout self.scroll = QScrollArea() self.scroll.setWidget(self.config_menu) self.layout.addWidget(self.scroll)
def __init__(self, parent_dialog, plugin_action): QWidget.__init__(self) self.parent_dialog = parent_dialog self.plugin_action = plugin_action self.l = QVBoxLayout() self.setLayout(self.l) label = QLabel(_('Searches to use for:')) label.setWordWrap(True) self.l.addWidget(label) #self.l.addSpacing(5) scrollable = QScrollArea() scrollcontent = QWidget() scrollable.setWidget(scrollcontent) scrollable.setWidgetResizable(True) self.l.addWidget(scrollable) self.sl = QVBoxLayout() scrollcontent.setLayout(self.sl) self.sl.addWidget(QLabel(_("Search for Duplicated Books:"))) self.checkdups_search = QLineEdit(self) self.sl.addWidget(self.checkdups_search) self.checkdups_search.setText(prefs['checkdups_search']) self.checkdups_search.setToolTip(_('Default is %s')%default_prefs['checkdups_search']) self.sl.addSpacing(5) self.sl.addWidget(QLabel(_("Deleted Books (not in Library):"))) self.checknotinlibrary_search = QLineEdit(self) self.sl.addWidget(self.checknotinlibrary_search) self.checknotinlibrary_search.setText(prefs['checknotinlibrary_search']) self.checknotinlibrary_search.setToolTip(_('Default is %s')%default_prefs['checknotinlibrary_search']) self.sl.addSpacing(5) self.sl.addWidget(QLabel(_("Added Books (not on Device):"))) self.checknotondevice_search = QLineEdit(self) self.sl.addWidget(self.checknotondevice_search) self.checknotondevice_search.setText(prefs['checknotondevice_search']) self.checknotondevice_search.setToolTip(_('Default is %s')%default_prefs['checknotondevice_search']) self.sl.insertStretch(-1) self.l.addSpacing(15) restore_defaults_button = QPushButton(_('Restore Defaults'), self) restore_defaults_button.setToolTip(_('Revert all searches to the defaults.')) restore_defaults_button.clicked.connect(self.restore_defaults_button) self.l.addWidget(restore_defaults_button)
def __init__(self, *args, **kw): ConfigWidgetBase.__init__(self, *args, **kw) self.l = l = QVBoxLayout(self) l.setContentsMargins(0, 0, 0, 0) self.tabs_widget = t = QTabWidget(self) l.addWidget(t) self.main_tab = m = MainTab(self) t.addTab(m, _('&Main')) m.start_server.connect(self.start_server) m.stop_server.connect(self.stop_server) m.test_server.connect(self.test_server) m.show_logs.connect(self.view_server_logs) self.opt_autolaunch_server = m.opt_autolaunch_server self.users_tab = ua = Users(self) t.addTab(ua, _('&User accounts')) self.advanced_tab = a = AdvancedTab(self) sa = QScrollArea(self) sa.setWidget(a), sa.setWidgetResizable(True) t.addTab(sa, _('&Advanced')) self.custom_list_tab = clt = CustomList(self) sa = QScrollArea(self) sa.setWidget(clt), sa.setWidgetResizable(True) t.addTab(sa, _('Book &list template')) for tab in self.tabs: if hasattr(tab, 'changed_signal'): tab.changed_signal.connect(self.changed_signal.emit)
def setup_import_panel(self): self.import_panel = w = QWidget(self) self.stack.addWidget(w) w.stack = s = QStackedLayout(w) self.ig = w = QWidget() s.addWidget(w) w.l = l = QVBoxLayout(w) w.la = la = QLabel( _('Specify the folder containing the previously exported calibre data that you' ' wish to import.')) la.setWordWrap(True) l.addWidget(la) self.export_dir_button = b = QPushButton(QIcon(I('document_open.png')), _('Choose &folder'), self) b.clicked.connect(self.select_import_folder) l.addWidget(b), l.addStretch() self.select_libraries_panel = w = QScrollArea(self) w.setWidgetResizable(True) s.addWidget(w) self.slp = w = QWidget(self) self.select_libraries_panel.setWidget(w) w.l = l = QVBoxLayout(w) w.la = la = QLabel( _('Specify locations for the libraries you want to import. A location must be an empty folder' ' on your computer. If you leave any blank, those libraries will not be imported.' )) la.setWordWrap(True) l.addWidget(la)
def wrap_widget(self, page): sw = QScrollArea(self) pl = page.layout() if pl is not None: cm = pl.contentsMargins() # For some reasons designer insists on setting zero margins for # widgets added to a tab widget, which looks horrible. if (cm.left(), cm.top(), cm.right(), cm.bottom()) == (0, 0, 0, 0): pl.setContentsMargins(9, 9, 9, 9) name = 'STW{}'.format(abs(id(self))) sw.setObjectName(name) sw.setWidget(page) sw.setWidgetResizable(True) page.setAutoFillBackground(False) sw.setStyleSheet('#%s { background: transparent }' % name) return sw
def __init__(self, plugin, parent): QWidget.__init__(self, parent) self.plugin = plugin self.l = l = QVBoxLayout() self.setLayout(l) self.c = c = QLabel( _('<b>Configure %(name)s</b><br>%(desc)s') % dict(name=plugin.name, desc=plugin.description)) c.setAlignment(Qt.AlignmentFlag.AlignHCenter) l.addWidget(c) self.config_widget = plugin.config_widget() self.sa = sa = QScrollArea(self) sa.setWidgetResizable(True) sa.setWidget(self.config_widget) l.addWidget(sa) self.bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Save | QDialogButtonBox.StandardButton.Cancel, parent=self) self.bb.accepted.connect(self.finished) self.bb.rejected.connect(self.finished) self.bb.accepted.connect(self.commit) l.addWidget(self.bb) self.f = QFrame(self) self.f.setFrameShape(QFrame.Shape.HLine) l.addWidget(self.f)
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.Ok | QDialogButtonBox.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) map(w.l.addWidget, buttons) sa.setWidget(w) self.resize(self.sizeHint() + QSize(150, 20))
def __init__(self, preview, parent=None): QWidget.__init__(self, parent) self.preview = preview self.preview_is_refreshing = False self.refresh_needed = False preview.refresh_starting.connect(self.preview_refresh_starting) preview.refreshed.connect(self.preview_refreshed) self.apply_theme() self.setAutoFillBackground(True) self.update_timer = QTimer(self) self.update_timer.timeout.connect(self.update_data) self.update_timer.setSingleShot(True) self.update_timer.setInterval(500) self.now_showing = (None, None, None) self.stack = s = QStackedLayout(self) self.setLayout(s) self.clear_label = la = QLabel( '<h3>' + _('No style information found') + '</h3><p>' + _('Move the cursor inside a HTML tag to see what styles' ' apply to that tag.')) la.setWordWrap(True) la.setAlignment(Qt.AlignTop | Qt.AlignLeft) s.addWidget(la) self.box = box = Box(self) box.hyperlink_activated.connect(self.goto_declaration, type=Qt.QueuedConnection) self.scroll = sc = QScrollArea(self) sc.setWidget(box) sc.setWidgetResizable(True) s.addWidget(sc)
def __init__(self, device, rules): QGroupBox.__init__(self, _('Format specific sending')) self._device = weakref.ref(device) self.l = l = QVBoxLayout() self.setLayout(l) self.la = la = QLabel( '<p>' + _('''You can create rules that control where e-books of a specific format are sent to on the device. These will take precedence over the folders specified above.''')) la.setWordWrap(True) l.addWidget(la) self.sa = sa = QScrollArea(self) sa.setWidgetResizable(True) self.w = w = QWidget(self) w.l = QVBoxLayout() w.setLayout(w.l) sa.setWidget(w) l.addWidget(sa) self.widgets = [] for rule in rules: r = Rule(device, rule) self.widgets.append(r) w.l.addWidget(r) r.remove.connect(self.remove_rule) if not self.widgets: self.add_rule() self.b = b = QPushButton(QIcon(I('plus.png')), _('Add a &new rule')) l.addWidget(b) b.clicked.connect(self.add_rule) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Ignored)
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 __init__(self): super().__init__(argv) self.w = QMainWindow() self.w.setMinimumWidth(300) self.w.setMinimumHeight(300) # self.w.setWindowTitle(tr("Отправка запросов на ВС АУГО")) self.cw = QScrollArea() # self.__create_ui() self.__showed = False self.__wgts = {} self.cb = QComboBox() self.individuals = [] self.entities = [] self.documents = [] self.doc_files = [] self.__setupUi(self.w) self.w.showMaximized()
def __init__(self, parent, current_img, current_url, geom_name='viewer_image_popup_geometry'): QDialog.__init__(self) self.setWindowFlag(Qt.WindowMinimizeButtonHint) self.setWindowFlag(Qt.WindowMaximizeButtonHint) dw = QApplication.instance().desktop() self.avail_geom = dw.availableGeometry( parent if parent is not None else self) self.current_img = current_img self.current_url = current_url self.factor = 1.0 self.geom_name = geom_name self.label = l = QLabel(self) l.setBackgroundRole(QPalette.Text if QApplication.instance(). is_dark_theme else QPalette.Base) l.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) l.setScaledContents(True) self.scrollarea = sa = QScrollArea() sa.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) sa.setBackgroundRole(QPalette.Dark) sa.setWidget(l) self.bb = bb = QDialogButtonBox(QDialogButtonBox.Close) bb.accepted.connect(self.accept) bb.rejected.connect(self.reject) self.zi_button = zi = bb.addButton(_('Zoom &in'), bb.ActionRole) self.zo_button = zo = bb.addButton(_('Zoom &out'), bb.ActionRole) self.save_button = so = bb.addButton(_('&Save as'), bb.ActionRole) self.rotate_button = ro = bb.addButton(_('&Rotate'), bb.ActionRole) zi.setIcon(QIcon(I('plus.png'))) zo.setIcon(QIcon(I('minus.png'))) so.setIcon(QIcon(I('save.png'))) ro.setIcon(QIcon(I('rotate-right.png'))) zi.clicked.connect(self.zoom_in) zo.clicked.connect(self.zoom_out) so.clicked.connect(self.save_image) ro.clicked.connect(self.rotate_image) self.l = l = QVBoxLayout(self) l.addWidget(sa) self.h = h = QHBoxLayout() h.setContentsMargins(0, 0, 0, 0) l.addLayout(h) self.fit_image = i = QCheckBox(_('&Fit image')) i.setToolTip(_('Fit image inside the available space')) i.setChecked(bool(gprefs.get('image_popup_fit_image'))) i.stateChanged.connect(self.fit_changed) h.addWidget(i), h.addStretch(), h.addWidget(bb) if self.fit_image.isChecked(): self.set_to_viewport_size() geom = gprefs.get(self.geom_name) if geom is not None: self.restoreGeometry(geom)
def _setup_ui(self): self.setStyleSheet(self._default) self.setFixedWidth(300) scroll = QScrollArea() scroll.setWidgetResizable(True) scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) scroll.setWidget(ShotList()) self.addWidget(scroll)
def setup_ui(self): self.block_show = False self.properties = [] self.l = l = QVBoxLayout(self) self.setLayout(l) h = QHBoxLayout() l.addLayout(h) self.la = la = QLabel(_('&Edit theme:')) h.addWidget(la) self.theme = t = QComboBox(self) la.setBuddy(t) t.addItems(sorted(custom_theme_names())) t.setMinimumWidth(200) if t.count() > 0: t.setCurrentIndex(0) t.currentIndexChanged[int].connect(self.show_theme) h.addWidget(t) self.add_button = b = QPushButton(QIcon(I('plus.png')), _('Add &new theme'), self) b.clicked.connect(self.create_new_theme) h.addWidget(b) self.remove_button = b = QPushButton(QIcon(I('minus.png')), _('&Remove theme'), self) b.clicked.connect(self.remove_theme) h.addWidget(b) h.addStretch(1) self.scroll = s = QScrollArea(self) self.w = w = QWidget(self) s.setWidget(w), s.setWidgetResizable(True) self.cl = cl = QVBoxLayout() w.setLayout(cl) from calibre.gui2.tweak_book.editor.text import TextEdit self.preview = p = TextEdit(self, expected_geometry=(73, 50)) p.load_text( HELP_TEXT.format(*[ '<b>%s</b>' % x for x in ('Normal', 'Visual', 'CursorLine', 'LineNr', 'MatchParen', 'Function', 'Type', 'Statement', 'Constant', 'SpecialCharacter', 'Error', 'SpellError', 'Comment') ])) p.setMaximumWidth(p.size_hint.width() + 5) s.setMinimumWidth(600) self.splitter = sp = QSplitter(self) l.addWidget(sp) sp.addWidget(s), sp.addWidget(p) self.bb.clear() self.bb.addButton(self.bb.Close) l.addWidget(self.bb) if self.theme.count() > 0: self.show_theme()
def __setupUi(self, mainWindow): mainWindow.setObjectName("MainWindow") self.centralwidget = QWidget(mainWindow) self.centralwidget.setObjectName("centralwidget") self.horizontalLayout = QHBoxLayout(self.centralwidget) self.horizontalLayout.setObjectName("horizontalLayout") self.scrollArea = QScrollArea(self.centralwidget) self.scrollArea.setWidgetResizable(True) self.scrollArea.setObjectName("scrollArea") self.scrollAreaWidgetContents = QWidget() self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents") self.gridLayout = QGridLayout(self.scrollAreaWidgetContents) self.gridLayout.setObjectName("gridLayout") self.__show_form() self.scrollArea.setWidget(self.scrollAreaWidgetContents) self.horizontalLayout.addWidget(self.scrollArea) mainWindow.setCentralWidget(self.centralwidget) self.menubar = QMenuBar(mainWindow) self.menubar.setObjectName("menubar") self.menu = QMenu(self.menubar) self.menu.setObjectName("menu") mainWindow.setMenuBar(self.menubar) self.statusbar = QStatusBar(mainWindow) self.statusbar.setObjectName("statusbar") mainWindow.setStatusBar(self.statusbar) self.action_1 = QAction(mainWindow) self.action_1.setObjectName("action") self.action_1.triggered.connect(self.send) self.action = QAction(mainWindow) self.action.setObjectName("action") self.action.triggered.connect(self.on_action_triggered) self.action_2 = QAction(mainWindow) self.action_2.setObjectName("action_2") self.action_3 = QAction(mainWindow) self.action_3.setObjectName("action_3") self.action_3.triggered.connect(self.get_response) self.menu.addAction(self.action) self.menubar.addAction(self.action_1) self.menubar.addAction(self.action_2) self.menubar.addAction(self.action_3) self.menubar.addAction(self.menu.menuAction()) self.__retranslateUi(mainWindow) QMetaObject.connectSlotsByName(mainWindow)
def __init__(self, parent=None): QScrollArea.__init__(self, parent) self.setWidgetResizable(True) category_map, category_names = {}, {} for plugin in preferences_plugins(): if plugin.category not in category_map: category_map[plugin.category] = plugin.category_order if category_map[plugin.category] < plugin.category_order: category_map[plugin.category] = plugin.category_order if plugin.category not in category_names: category_names[plugin.category] = (plugin.gui_category if plugin.gui_category else plugin.category) self.category_names = category_names categories = list(category_map.keys()) categories.sort(cmp=lambda x, y: cmp(category_map[x], category_map[y])) self.category_map = OrderedDict() for c in categories: self.category_map[c] = [] for plugin in preferences_plugins(): self.category_map[plugin.category].append(plugin) for plugins in self.category_map.values(): plugins.sort(cmp=lambda x, y: cmp(x.name_order, y.name_order)) self.widgets = [] self._layout = QVBoxLayout() self.container = QWidget(self) self.container.setLayout(self._layout) self.setWidget(self.container) for name, plugins in self.category_map.items(): w = Category(name, plugins, self.category_names[name], parent=self) self.widgets.append(w) self._layout.addWidget(w) w.plugin_activated.connect(self.show_plugin.emit) self._layout.addStretch(1)
def __init__(self, parent, book_settings): QDialog.__init__(self, parent) self.resize(500, 500) self.setWindowTitle('title - author') self._index = 0 self._book_settings = book_settings v_layout = QVBoxLayout(self) # add ASIN and Goodreads url text boxes and update buttons asin_browser_button, goodreads_browser_button = self._initialize_general( v_layout) # add scrollable area for aliases v_layout.addWidget(QLabel('Aliases:')) self._scroll_area = QScrollArea() v_layout.addWidget(self._scroll_area) # add status box self._status = QLabel('') v_layout.addWidget(self._status) previous_button = next_button = None if len(self._book_settings) > 1: previous_button = QPushButton('Previous') previous_button.setEnabled(False) previous_button.setFixedWidth(100) next_button = QPushButton('Next') next_button.setFixedWidth(100) previous_button.clicked.connect(lambda: self.previous_clicked( previous_button, next_button, asin_browser_button, goodreads_browser_button)) next_button.clicked.connect(lambda: self.next_clicked( previous_button, next_button, asin_browser_button, goodreads_browser_button)) self._initialize_navigation_buttons(v_layout, previous_button, next_button) self.setLayout(v_layout) self.show_book_prefs(asin_browser_button, goodreads_browser_button) self.show()
def __init__(self, parent=None): QScrollArea.__init__(self, parent) self.setWidgetResizable(True) category_map, category_names = {}, {} for plugin in preferences_plugins(): if plugin.category not in category_map: category_map[plugin.category] = plugin.category_order if category_map[plugin.category] < plugin.category_order: category_map[plugin.category] = plugin.category_order if plugin.category not in category_names: category_names[plugin.category] = (plugin.gui_category if plugin.gui_category else plugin.category) self.category_names = category_names categories = list(category_map.keys()) categories.sort(key=lambda x: category_map[x]) self.category_map = OrderedDict() for c in categories: self.category_map[c] = [] for plugin in preferences_plugins(): self.category_map[plugin.category].append(plugin) for plugins in self.category_map.values(): plugins.sort(key=lambda x: x.name_order) self.widgets = [] self._layout = QVBoxLayout() self.container = QWidget(self) self.container.setLayout(self._layout) self.setWidget(self.container) for name, plugins in self.category_map.items(): w = Category(name, plugins, self.category_names[name], parent=self) self.widgets.append(w) self._layout.addWidget(w) w.plugin_activated.connect(self.show_plugin.emit) self._layout.addStretch(1)
def __init__(self, gui, icon, do_user_config): QDialog.__init__(self, gui) self.gui = gui self.do_user_config = do_user_config self.db = gui.current_db self.l = QVBoxLayout() self.setLayout(self.l) self.setWindowTitle('Wiki Reader') self.setWindowIcon(icon) self.helpl = QLabel( 'Enter the URL of a wikipedia article below. ' 'It will be downloaded and converted into an ebook.') self.helpl.setWordWrap(True) self.l.addWidget(self.helpl) self.w = QWidget(self) self.sa = QScrollArea(self) self.l.addWidget(self.sa) self.w.l = QVBoxLayout() self.w.setLayout(self.w.l) self.sa.setWidget(self.w) self.sa.setWidgetResizable(True) self.title = Title(self) self.w.l.addWidget(self.title) self.urls = [URL(self)] self.w.l.addWidget(self.urls[0]) self.add_more_button = QPushButton( QIcon(I('plus.png')), 'Add another URL') self.l.addWidget(self.add_more_button) self.bb = QDialogButtonBox(self) self.bb.setStandardButtons(self.bb.Ok | self.bb.Cancel) self.bb.accepted.connect(self.accept) self.bb.rejected.connect(self.reject) self.l.addWidget(self.bb) self.book_button = b = self.bb.addButton( 'Convert a Wiki&Book', self.bb.ActionRole) b.clicked.connect(self.wiki_book) self.add_more_button.clicked.connect(self.add_more) self.finished.connect(self.download) self.setMinimumWidth(500) self.resize(self.sizeHint()) self.single_url = None
def __init__(self, parent_dialog, plugin_action): self.parent_dialog = parent_dialog self.plugin_action = plugin_action QWidget.__init__(self) self.l = QVBoxLayout() self.setLayout(self.l) label = QLabel(_("If you have custom columns defined, they will be listed below. Choose how you would like these columns handled.")) label.setWordWrap(True) self.l.addWidget(label) self.l.addSpacing(5) scrollable = QScrollArea() scrollcontent = QWidget() scrollable.setWidget(scrollcontent) scrollable.setWidgetResizable(True) self.l.addWidget(scrollable) self.sl = QVBoxLayout() scrollcontent.setLayout(self.sl) self.custcol_dropdowns = {} custom_columns = self.plugin_action.gui.library_view.model().custom_columns grid = QGridLayout() self.sl.addLayout(grid) row=0 for key, column in custom_columns.iteritems(): if column['datatype'] in permitted_values: # logger.debug("\n============== %s ===========\n"%key) # for (k,v) in column.iteritems(): # logger.debug("column['%s'] => %s"%(k,v)) label = QLabel('%s(%s)'%(column['name'],key)) label.setToolTip(_("Set this %s column on new merged books...")%column['datatype']) grid.addWidget(label,row,0) dropdown = QComboBox(self) dropdown.addItem('','none') for md in permitted_values[column['datatype']]: # tags-like column also 'text' if md == 'union' and not column['is_multiple']: continue if md == 'concat' and column['is_multiple']: continue dropdown.addItem(titleLabels[md],md) self.custcol_dropdowns[key] = dropdown if key in prefs['custom_cols']: dropdown.setCurrentIndex(dropdown.findData(prefs['custom_cols'][key])) dropdown.setToolTip(_("How this column will be populated by default.")) grid.addWidget(dropdown,row,1) row+=1 self.sl.insertStretch(-1)
def get_notes_mail_widget(self): """ Return QWidget with notes and email areas and edition buttons :return: notes and email QWidget :rtype: QWidget """ notes_widget = QWidget() notes_layout = QGridLayout(notes_widget) # Notes title and button notes_label = QLabel(_('Notes:')) notes_label.setObjectName('subtitle') notes_layout.addWidget(notes_label, 0, 0, 1, 1) notes_btn = QPushButton() notes_btn.setIcon(QIcon(settings.get_image('edit'))) notes_btn.setToolTip(_("Edit your notes.")) notes_btn.setFixedSize(32, 32) notes_btn.clicked.connect(lambda: self.patch_data('notes')) notes_layout.addWidget(notes_btn, 0, 2, 1, 1) # Notes scroll area self.labels['notes'].setText(data_manager.database['user'].data['notes']) self.labels['notes'].setWordWrap(True) self.labels['notes'].setTextInteractionFlags(Qt.TextSelectableByMouse) notes_scrollarea = QScrollArea() notes_scrollarea.setWidget(self.labels['notes']) notes_scrollarea.setWidgetResizable(True) notes_scrollarea.setObjectName('notes') notes_layout.addWidget(notes_scrollarea, 1, 0, 1, 3) # Mail mail_label = QLabel(_('Email:')) mail_label.setObjectName('subtitle') notes_layout.addWidget(mail_label, 2, 0, 1, 1) self.labels['email'].setObjectName('edit') notes_layout.addWidget(self.labels['email'], 2, 1, 1, 1) mail_btn = QPushButton() mail_btn.setIcon(QIcon(settings.get_image('edit'))) mail_btn.setFixedSize(32, 32) mail_btn.clicked.connect(lambda: self.patch_data('email')) notes_layout.addWidget(mail_btn, 2, 2, 1, 1) notes_layout.setAlignment(Qt.AlignTop) return notes_widget
def show_plugin_tab(self, idx): cf = unicode(self.format.currentText()).lower() while self.tabs.count() > 1: self.tabs.removeTab(1) for pw in self.widgets: if cf in pw.formats: if getattr(pw, 'handles_scrolling', False): self.tabs.addTab(pw, pw.TITLE) else: self.sw__mem = s = QScrollArea(self) s.setWidget(pw), s.setWidgetResizable(True) self.tabs.addTab(s, pw.TITLE) break if hasattr(self.options_widget, 'show_help'): self.buttonBox.button(self.buttonBox.Help).setVisible(True) else: self.buttonBox.button(self.buttonBox.Help).setVisible(False)
def __init__(self, username, restriction, parent=None): QDialog.__init__(self, parent) self.setWindowTitle( _('Change library access permissions for {}').format(username)) self.username = username self._items = [] self.l = l = QFormLayout(self) l.setFieldGrowthPolicy(l.AllNonFixedFieldsGrow) self.libraries = t = QWidget(self) t.setObjectName('libraries') t.l = QVBoxLayout(self.libraries) self.atype = a = QComboBox(self) a.addItems([ _('All libraries'), _('Only the specified libraries'), _('All except the specified libraries') ]) self.library_restrictions = restriction['library_restrictions'].copy() if restriction['allowed_library_names']: a.setCurrentIndex(1) self.items = restriction['allowed_library_names'] elif restriction['blocked_library_names']: a.setCurrentIndex(2) self.items = restriction['blocked_library_names'] else: a.setCurrentIndex(0) a.currentIndexChanged.connect(self.atype_changed) l.addRow(_('Allow access to:'), a) self.msg = la = QLabel(self) la.setWordWrap(True) l.addRow(la) self.la = la = QLabel(_('Specify the libraries below:')) la.setWordWrap(True) self.sa = sa = QScrollArea(self) sa.setWidget(t), sa.setWidgetResizable(True) l.addRow(la), l.addRow(sa) self.atype_changed() self.bb = bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) bb.accepted.connect(self.accept), bb.rejected.connect(self.reject) l.addWidget(bb) self.items = self.items
def __init__(self, parent, current_img, current_url, geom_name='viewer_image_popup_geometry'): QDialog.__init__(self) dw = QApplication.instance().desktop() self.avail_geom = dw.availableGeometry( parent if parent is not None else self) self.current_img = current_img self.current_url = current_url self.factor = 1.0 self.geom_name = geom_name self.label = l = QLabel() l.setBackgroundRole(QPalette.Base) l.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) l.setScaledContents(True) self.scrollarea = sa = QScrollArea() sa.setBackgroundRole(QPalette.Dark) sa.setWidget(l) self.bb = bb = QDialogButtonBox(QDialogButtonBox.Close) bb.accepted.connect(self.accept) bb.rejected.connect(self.reject) self.zi_button = zi = bb.addButton(_('Zoom &in'), bb.ActionRole) self.zo_button = zo = bb.addButton(_('Zoom &out'), bb.ActionRole) self.save_button = so = bb.addButton(_('&Save as'), bb.ActionRole) self.rotate_button = ro = bb.addButton(_('&Rotate'), bb.ActionRole) zi.setIcon(QIcon(I('plus.png'))) zo.setIcon(QIcon(I('minus.png'))) so.setIcon(QIcon(I('save.png'))) ro.setIcon(QIcon(I('rotate-right.png'))) zi.clicked.connect(self.zoom_in) zo.clicked.connect(self.zoom_out) so.clicked.connect(self.save_image) ro.clicked.connect(self.rotate_image) self.l = l = QVBoxLayout() self.setLayout(l) l.addWidget(sa) l.addWidget(bb)
def get_last_check_widget(self): """ Return QWidget with last check data :return: widget with last check data :rtype: QWidget """ widget = QWidget() layout = QGridLayout() widget.setLayout(layout) # Title check_title = QLabel(_('My last check')) check_title.setObjectName('itemtitle') check_title.setFixedHeight(30) layout.addWidget(check_title, 0, 0, 1, 2) # When last check when_title = QLabel(_("When:")) when_title.setObjectName('title') layout.addWidget(when_title, 2, 0, 1, 1) layout.addWidget(self.labels['ls_last_check'], 2, 1, 1, 1) # Output output_title = QLabel(_("Output")) output_title.setObjectName('title') layout.addWidget(output_title, 3, 0, 1, 1) self.labels['ls_output'].setWordWrap(True) self.labels['ls_output'].setTextInteractionFlags( Qt.TextSelectableByMouse) output_scrollarea = QScrollArea() output_scrollarea.setWidget(self.labels['ls_output']) output_scrollarea.setWidgetResizable(True) output_scrollarea.setObjectName('output') layout.addWidget(output_scrollarea, 3, 1, 1, 1) return widget
def __init__(self, parent, book_settings): QDialog.__init__(self, parent) self.resize(500, 500) self.setWindowTitle('title - author') self._index = 0 self._book_settings = book_settings v_layout = QVBoxLayout(self) # add ASIN and Goodreads url text boxes and update buttons asin_browser_button, goodreads_browser_button = self._initialize_general(v_layout) # add scrollable area for aliases v_layout.addWidget(QLabel('Aliases:')) self._scroll_area = QScrollArea() v_layout.addWidget(self._scroll_area) # add status box self._status = QLabel('') v_layout.addWidget(self._status) previous_button = next_button = None if len(self._book_settings) > 1: previous_button = QPushButton('Previous') previous_button.setEnabled(False) previous_button.setFixedWidth(100) next_button = QPushButton('Next') next_button.setFixedWidth(100) previous_button.clicked.connect(lambda: self.previous_clicked(previous_button, next_button, asin_browser_button, goodreads_browser_button)) next_button.clicked.connect(lambda: self.next_clicked(previous_button, next_button, asin_browser_button, goodreads_browser_button)) self._initialize_navigation_buttons(v_layout, previous_button, next_button) self.setLayout(v_layout) self.show_book_prefs(asin_browser_button, goodreads_browser_button) self.show()
def genesis(self, gui): log = Log() log.outputs = [] self.plumber = Plumber('dummy.epub', 'dummy.epub', log, dummy=True, merge_plugin_recs=False) def widget_factory(cls): plugin = getattr(cls, 'conv_plugin', None) if plugin is None: hfunc = self.plumber.get_option_help else: options = plugin.options.union(plugin.common_options) def hfunc(name): for rec in options: if rec.option == name: ans = getattr(rec, 'help', None) if ans is not None: return ans.replace( '%default', unicode_type(rec.recommended_value)) return cls(self, self.plumber.get_option_by_name, hfunc, None, None) self.load_conversion_widgets() widgets = list(map(widget_factory, self.conversion_widgets)) self.model = Model(widgets) self.list.setModel(self.model) for w in widgets: w.changed_signal.connect(self.changed_signal) sa = QScrollArea(self) sa.setWidget(w) sa.setWidgetResizable(True) self.stack.addWidget(sa) if isinstance(w, TOCWidget): w.manually_fine_tune_toc.hide() self.list.current_changed.connect(self.category_current_changed) self.list.setCurrentIndex(self.model.index(0))
def get_last_check_widget(self): """ Return QWidget with last check data :return: widget with last check data :rtype: QWidget """ widget = QWidget() layout = QGridLayout() widget.setLayout(layout) # Title check_title = QLabel(_('My last check')) check_title.setObjectName('itemtitle') check_title.setFixedHeight(30) layout.addWidget(check_title, 0, 0, 1, 2) # When last check when_title = QLabel(_("When:")) when_title.setObjectName('title') layout.addWidget(when_title, 2, 0, 1, 1) layout.addWidget(self.labels['ls_last_check'], 2, 1, 1, 1) # Output output_title = QLabel(_("Output")) output_title.setObjectName('title') layout.addWidget(output_title, 3, 0, 1, 1) self.labels['ls_output'].setWordWrap(True) self.labels['ls_output'].setTextInteractionFlags(Qt.TextSelectableByMouse) output_scrollarea = QScrollArea() output_scrollarea.setWidget(self.labels['ls_output']) output_scrollarea.setWidgetResizable(True) output_scrollarea.setObjectName('output') layout.addWidget(output_scrollarea, 3, 1, 1, 1) return widget
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()
def __init__(self, parent_dialog, plugin_action): self.parent_dialog = parent_dialog self.plugin_action = plugin_action QWidget.__init__(self) custom_columns = self.plugin_action.gui.library_view.model().custom_columns self.l = QVBoxLayout() self.setLayout(self.l) label = QLabel(_("Save Source column:")) label.setToolTip(_("If set, the column below will be populated with the template below to record the source of the split file.")) label.setWordWrap(True) self.l.addWidget(label) horz = QHBoxLayout() self.sourcecol = QComboBox(self) self.sourcecol.setToolTip(_("Choose a column to populate with template on split.")) self.sourcecol.addItem('','none') for key, column in custom_columns.iteritems(): if column['datatype'] in ('text','comments','series'): self.sourcecol.addItem(column['name'],key) self.sourcecol.setCurrentIndex(self.sourcecol.findData(prefs['sourcecol'])) horz.addWidget(self.sourcecol) self.sourcetemplate = QLineEdit(self) self.sourcetemplate.setToolTip(_("Template from source book. Example: {title} by {authors}")) # if 'sourcetemplate' in prefs: self.sourcetemplate.setText(prefs['sourcetemplate']) # else: # self.sourcetemplate.setText("{title} by {authors}") horz.addWidget(self.sourcetemplate) self.l.addLayout(horz) self.l.addSpacing(5) label = QLabel(_("If you have custom columns defined, they will be listed below. Choose if you would like these columns copied to new split books.")) label.setWordWrap(True) self.l.addWidget(label) self.l.addSpacing(5) scrollable = QScrollArea() scrollcontent = QWidget() scrollable.setWidget(scrollcontent) scrollable.setWidgetResizable(True) self.l.addWidget(scrollable) self.sl = QVBoxLayout() scrollcontent.setLayout(self.sl) self.custcol_checkboxes = {} for key, column in custom_columns.iteritems(): # print("\n============== %s ===========\n"%key) # for (k,v) in column.iteritems(): # print("column['%s'] => %s"%(k,v)) checkbox = QCheckBox('%s(%s)'%(column['name'],key)) checkbox.setToolTip(_("Copy this %s column to new split books...")%column['datatype']) checkbox.setChecked(key in prefs['custom_cols'] and prefs['custom_cols'][key]) self.custcol_checkboxes[key] = checkbox self.sl.addWidget(checkbox) self.sl.insertStretch(-1)
def __init__(self, parent=None): QScrollArea.__init__(self, parent) self.setWidgetResizable(True) self.w = QWidget(self) self.l = QGridLayout(self.w) self.setWidget(self.w)
class BookConfigWidget(QDialog): '''Creates book specific preferences dialog''' # title case given words except for articles in the middle # i.e the lord ruler would become The Lord Ruler but john the great would become John the Great ARTICLES = ['The', 'For', 'De', 'And', 'Or', 'Of', 'La'] TITLE_CASE = lambda self, words: ' '.join([word.lower() if word in self.ARTICLES and index != 0 else word for index, word in enumerate(words.title().split())]) def __init__(self, parent, book_settings): QDialog.__init__(self, parent) self.resize(500, 500) self.setWindowTitle('title - author') self._index = 0 self._book_settings = book_settings v_layout = QVBoxLayout(self) # add ASIN and Goodreads url text boxes and update buttons asin_browser_button, goodreads_browser_button = self._initialize_general(v_layout) # add scrollable area for aliases v_layout.addWidget(QLabel('Aliases:')) self._scroll_area = QScrollArea() v_layout.addWidget(self._scroll_area) # add status box self._status = QLabel('') v_layout.addWidget(self._status) previous_button = next_button = None if len(self._book_settings) > 1: previous_button = QPushButton('Previous') previous_button.setEnabled(False) previous_button.setFixedWidth(100) next_button = QPushButton('Next') next_button.setFixedWidth(100) previous_button.clicked.connect(lambda: self.previous_clicked(previous_button, next_button, asin_browser_button, goodreads_browser_button)) next_button.clicked.connect(lambda: self.next_clicked(previous_button, next_button, asin_browser_button, goodreads_browser_button)) self._initialize_navigation_buttons(v_layout, previous_button, next_button) self.setLayout(v_layout) self.show_book_prefs(asin_browser_button, goodreads_browser_button) self.show() def _initialize_general(self, v_layout): '''Initialize asin/goodreads sections''' # Add the ASIN label, line edit, and button to dialog self._asin_edit = QLineEdit('') asin_layout = QHBoxLayout(None) asin_label = QLabel('ASIN:') asin_label.setFixedWidth(100) asin_browser_button = QPushButton('Open..') asin_browser_button.clicked.connect(self.browse_amazon_url) asin_browser_button.setToolTip('Open Amazon page for the specified ASIN') self._asin_edit.textEdited.connect(lambda: self.edit_asin(self._asin_edit.text(), asin_browser_button)) asin_layout.addWidget(asin_label) asin_layout.addWidget(self._asin_edit) asin_layout.addWidget(asin_browser_button) v_layout.addLayout(asin_layout) # Add the Goodreads URL label, line edit, and button to dialog self._goodreads_url_edit = QLineEdit('') self._goodreads_url_edit.textEdited.connect(lambda: self.edit_goodreads_url(self._goodreads_url_edit.text(), goodreads_browser_button)) goodreads_layout = QHBoxLayout(None) goodreads_url_label = QLabel('Goodreads URL:') goodreads_url_label.setFixedWidth(100) goodreads_browser_button = QPushButton('Open..') goodreads_browser_button.clicked.connect(self.browse_goodreads_url) goodreads_browser_button.setToolTip('Open Goodreads page at the specified URL') goodreads_layout.addWidget(goodreads_url_label) goodreads_layout.addWidget(self._goodreads_url_edit) goodreads_layout.addWidget(goodreads_browser_button) v_layout.addLayout(goodreads_layout) # Add the sample xray label, line edit, and button to dialog self._sample_xray_edit = QLineEdit('') self._sample_xray_edit.textEdited.connect(lambda: self.edit_sample_xray(self._sample_xray_edit.text())) sample_xray_layout = QHBoxLayout(None) sample_xray_label = QLabel('X-Ray or JSON:') sample_xray_label.setFixedWidth(100) sample_xray_button = QPushButton('Browse...') sample_xray_button.clicked.connect(self.browse_sample_xray) sample_xray_button.setToolTip('Browse for a sample x-ray file or JSON to be used') sample_xray_layout.addWidget(sample_xray_label) sample_xray_layout.addWidget(self._sample_xray_edit) sample_xray_layout.addWidget(sample_xray_button) v_layout.addLayout(sample_xray_layout) # Add the update buttons to dialog search_buttons_layout = QHBoxLayout(None) search_asin_button = QPushButton('Search for ASIN') search_asin_button.setFixedWidth(225) search_asin_button.clicked.connect(lambda: self.search_for_asin_clicked(asin_browser_button)) search_buttons_layout.addWidget(search_asin_button) search_goodreads_url_button = QPushButton('Search for Goodreads URL') search_goodreads_url_button.setFixedWidth(225) search_goodreads_url_button.clicked.connect(lambda: self.search_for_goodreads_url(goodreads_browser_button)) search_buttons_layout.addWidget(search_goodreads_url_button) v_layout.addLayout(search_buttons_layout) update_buttons_layout = QHBoxLayout(None) self._update_from_url_button = QPushButton('Update Aliases from URL') self._update_from_url_button.setFixedWidth(225) self._update_from_url_button.clicked.connect(self.update_aliases_from_url) update_buttons_layout.addWidget(self._update_from_url_button) self._update_from_file_button = QPushButton('Update Aliases from Input File') self._update_from_file_button.setFixedWidth(225) self._update_from_file_button.clicked.connect(self.update_aliases_from_file) update_buttons_layout.addWidget(self._update_from_file_button) v_layout.addLayout(update_buttons_layout) return asin_browser_button, goodreads_browser_button def _initialize_navigation_buttons(self, v_layout, previous_button, next_button): '''Add previous, ok, cancel, and next buttons''' buttons_layout = QHBoxLayout(None) buttons_layout.setAlignment(Qt.AlignRight) if len(self._book_settings) > 1: buttons_layout.addWidget(previous_button) ok_button = QPushButton('OK') ok_button.setFixedWidth(100) ok_button.clicked.connect(self.ok_clicked) buttons_layout.addWidget(ok_button) cancel_button = QPushButton('Cancel') cancel_button.setFixedWidth(100) cancel_button.clicked.connect(self.cancel_clicked) buttons_layout.addWidget(cancel_button) if len(self._book_settings) > 1: buttons_layout.addWidget(next_button) v_layout.addLayout(buttons_layout) @property def book(self): return self._book_settings[self._index] def set_status_and_repaint(self, message): '''Sets the status text and redraws the status text box''' self._status.setText(message) self._status.repaint() def edit_asin(self, val, asin_browser_button): '''Set asin edit to specified value; update asin browser button accordingly''' self.book.asin = val if val == '': asin_browser_button.setEnabled(False) else: asin_browser_button.setEnabled(True) def edit_goodreads_url(self, val, goodreads_browser_button): '''Sets book's goodreads_url to val and warns if the url is invalid; update goodreads browser button accordingly''' self.book.goodreads_url = val if val == '': goodreads_browser_button.setEnabled(False) if self._status.text() == 'Warning: Invalid Goodreads URL. URL must have goodreads as the domain.': self._status.setText('') else: goodreads_browser_button.setEnabled(True) if 'goodreads.com' not in val: self._status.setText('Warning: Invalid Goodreads URL. URL must have goodreads as the domain.') def edit_sample_xray(self, val): '''Sets book's sample x-ray to val and warns if the file path is invalid''' self.book.sample_xray = val if os.path.isfile(val): if not val.lower().endwith('.json') and not val.lower().endswith('.asc'): return self.update_aliases_from_file() def search_for_asin_clicked(self, asin_browser_button): '''Searches for current book's ASIN on amazon''' asin = None self.set_status_and_repaint('Searching for ASIN...') if self.book.title != 'Unknown' and self.book.author != 'Unknown': asin = self.book.search_for_asin_on_amazon(self.book.title_and_author) if asin: self._status.setText('ASIN found.') asin_browser_button.setEnabled(True) self.book.asin = asin self._asin_edit.setText(asin) else: self._status.setText('ASIN not found.') asin_browser_button.setEnabled(False) self._asin_edit.setText('') def browse_amazon_url(self): '''Opens Amazon page for current book's ASIN using user's local store''' # Try to use the nearest Amazon store to the user. # If this fails we'll default to .com, the user will have to manually # edit the preferences file to fix it (it is a simple text file). if not prefs['tld']: import json from collections import defaultdict from urllib2 import urlopen, URLError try: country = json.loads(urlopen('http://ipinfo.io/json').read())['country'] except (URLError, KeyError): country = 'unknown' country_tld = defaultdict(lambda: 'com', {'AU': 'com.au', 'BR': 'com.br', 'CA': 'ca', 'CN': 'cn', 'FR': 'fr', 'DE': 'de', 'IN': 'in', 'IT': 'it', 'JP': 'co.jp', 'MX': 'com.mx', 'NL': 'nl', 'ES': 'es', 'GB': 'co.uk', 'US': 'com'}) prefs['tld'] = country_tld[country] webbrowser.open('https://www.amazon.{0}/gp/product/{1}/'.format(prefs['tld'], self._asin_edit.text())) def browse_goodreads_url(self): '''Opens url for current book's goodreads url''' webbrowser.open(self._goodreads_url_edit.text()) def browse_sample_xray(self): """Browse for a sample xray file to use during x-ray creation""" file_dialog = QFileDialog(self) sample_file = file_dialog.getOpenFileName(caption='Choose sample x-ray to use:', filter='X-Ray or JSON (*.asc *.json)')[0] self.book.sample_xray = sample_file self._sample_xray_edit.setText(sample_file) if sample_file: self.update_aliases_from_file() def search_for_goodreads_url(self, goodreads_browser_button): '''Searches for goodreads url using asin first then title and author if asin doesn't exist''' url = None self.set_status_and_repaint('Searching for Goodreads url...') if self.book.asin: url = self.book.search_for_goodreads_url(self.book.asin) if not url and self.book.title != 'Unknown' and self.book.author != 'Unknown': url = self.book.search_for_goodreads_url(self.book.title_and_author) if url: self._status.setText('Goodreads url found.') self._update_from_url_button.setEnabled(True) goodreads_browser_button.setEnabled(True) self.book.goodreads_url = url self._goodreads_url_edit.setText(url) else: self._status.setText('Goodreads url not found.') self._update_from_url_button.setEnabled(False) goodreads_browser_button.setEnabled(False) self._goodreads_url_edit.setText('') def update_aliases_from_url(self): '''Update aliases using goodreads''' if 'goodreads.com' not in self._goodreads_url_edit.text(): self._status.setText('Error: Invalid Goodreads URL. URL must have goodreads as the domain.') return self.update_aliases_from_goodreads() def update_aliases_from_file(self): '''Update aliases on the preferences dailog using the information in the specified file''' self.set_status_and_repaint('Updating aliases...') if os.path.exists(self.book.sample_xray): self.book.update_aliases(self.book.sample_xray, source_type=os.path.splitext(self.book.sample_xray)[1][1:]) self.update_aliases_on_gui() self._status.setText('Aliases updated.') else: self._status.setText('Error: Input file not found.') def update_aliases_from_goodreads(self): '''Updates aliases on the preferences dialog using the information on the current goodreads url''' try: self.set_status_and_repaint('Updating aliases...') self.book.update_aliases(self._goodreads_url_edit.text()) self.update_aliases_on_gui() self._status.setText('Aliases updated.') except PageDoesNotExist: self._status.setText('Invalid Goodreads url.') def edit_aliases(self, term, val): '''Sets book's aliases to tuple (term, val)''' self.book.set_aliases(term, val) def previous_clicked(self, previous_button, next_button, asin_browser_button, goodreads_browser_button): '''Goes to previous book''' self._status.setText('') self._index -= 1 next_button.setEnabled(True) if self._index == 0: previous_button.setEnabled(False) self.show_book_prefs(asin_browser_button, goodreads_browser_button) def ok_clicked(self): '''Saves book's settings using current settings''' for book in self._book_settings: book.save() self.close() def cancel_clicked(self): '''Closes dialog without saving settings''' self.close() def next_clicked(self, previous_button, next_button, asin_browser_button, goodreads_browser_button): '''Goes to next book''' self._status.setText('') self._index += 1 previous_button.setEnabled(True) if self._index == len(self._book_settings) - 1: next_button.setEnabled(False) self.show_book_prefs(asin_browser_button, goodreads_browser_button) def show_book_prefs(self, asin_browser_button, goodreads_browser_button): '''Shows current book's preferences''' self.setWindowTitle(self.book.title_and_author) self._asin_edit.setText(self.book.asin) if self._asin_edit.text() == '': asin_browser_button.setEnabled(False) else: asin_browser_button.setEnabled(True) self._goodreads_url_edit.setText(self.book.goodreads_url) if self._goodreads_url_edit.text() == '': goodreads_browser_button.setEnabled(False) else: goodreads_browser_button.setEnabled(True) self._sample_xray_edit.setText(self.book.sample_xray) self.update_aliases_on_gui() def update_aliases_on_gui(self): '''Updates aliases on the dialog using the info in the book's aliases dict''' aliases_widget = QWidget() aliases_layout = QGridLayout(aliases_widget) aliases_layout.setAlignment(Qt.AlignTop) # add aliases for current book for index, (character, aliases) in enumerate(sorted(self.book.aliases.items())): label = QLabel(character + ':') label.setFixedWidth(150) aliases_layout.addWidget(label, index, 0) line_edit = QLineEdit(', '.join([self.TITLE_CASE(alias) for alias in aliases])) line_edit.setFixedWidth(350) line_edit.textEdited.connect(functools.partial(self.edit_aliases, character)) aliases_layout.addWidget(line_edit, index, 1) self._scroll_area.setWidget(aliases_widget)
def __init__(self, ids, get_metadata, field_metadata, parent=None, window_title=None, reject_button_tooltip=None, accept_all_tooltip=None, reject_all_tooltip=None, revert_tooltip=None, intro_msg=None, action_button=None, **kwargs): QDialog.__init__(self, parent) self.l = l = QVBoxLayout() self.setLayout(l) self.setWindowIcon(QIcon(I('auto_author_sort.png'))) self.get_metadata = get_metadata self.ids = list(ids) self.total = len(self.ids) self.accepted = OrderedDict() self.rejected_ids = set() self.window_title = window_title or _('Compare metadata') if intro_msg: self.la = la = QLabel(intro_msg) la.setWordWrap(True) l.addWidget(la) self.compare_widget = CompareSingle(field_metadata, parent=parent, revert_tooltip=revert_tooltip, **kwargs) self.sa = sa = QScrollArea() l.addWidget(sa) sa.setWidget(self.compare_widget) sa.setWidgetResizable(True) self.bb = bb = QDialogButtonBox(QDialogButtonBox.Cancel) bb.button(bb.Cancel).setAutoDefault(False) bb.rejected.connect(self.reject) if self.total > 1: self.aarb = b = bb.addButton(_('&Accept all remaining'), bb.YesRole) b.setIcon(QIcon(I('ok.png'))), b.setAutoDefault(False) if accept_all_tooltip: b.setToolTip(accept_all_tooltip) b.clicked.connect(self.accept_all_remaining) self.rarb = b = bb.addButton(_('Re&ject all remaining'), bb.NoRole) b.setIcon(QIcon(I('minus.png'))), b.setAutoDefault(False) if reject_all_tooltip: b.setToolTip(reject_all_tooltip) b.clicked.connect(self.reject_all_remaining) self.sb = b = bb.addButton(_('&Reject'), bb.ActionRole) b.clicked.connect(partial(self.next_item, False)) b.setIcon(QIcon(I('minus.png'))), b.setAutoDefault(False) if reject_button_tooltip: b.setToolTip(reject_button_tooltip) self.next_action = ac = QAction(self) ac.setShortcut(QKeySequence(Qt.ALT | Qt.Key_Right)) self.addAction(ac) if action_button is not None: self.acb = b = bb.addButton(action_button[0], bb.ActionRole) b.setIcon(QIcon(action_button[1])) self.action_button_action = action_button[2] b.clicked.connect(self.action_button_clicked) self.nb = b = bb.addButton( _('&Next') if self.total > 1 else _('&OK'), bb.ActionRole) if self.total > 1: b.setToolTip( _('Move to next [%s]') % self.next_action.shortcut().toString(QKeySequence.NativeText)) self.next_action.triggered.connect(b.click) b.setIcon(QIcon(I('forward.png' if self.total > 1 else 'ok.png'))) b.clicked.connect(partial(self.next_item, True)) b.setDefault(True), b.setAutoDefault(True) self.bbh = h = QHBoxLayout() h.setContentsMargins(0, 0, 0, 0) l.addLayout(h) self.markq = m = QCheckBox(_('&Mark rejected books')) m.setChecked(gprefs['metadata_diff_mark_rejected']) m.stateChanged[int].connect( lambda: gprefs.set('metadata_diff_mark_rejected', m.isChecked())) m.setToolTip( _('Mark rejected books in the book list after this dialog is closed' )) h.addWidget(m), h.addWidget(bb) self.next_item(True) desktop = QApplication.instance().desktop() geom = desktop.availableGeometry(parent or self) width = max(700, min(950, geom.width() - 50)) height = max(650, min(1000, geom.height() - 100)) self.resize(QSize(width, height)) geom = gprefs.get('diff_dialog_geom', None) if geom is not None: self.restoreGeometry(geom) b.setFocus(Qt.OtherFocusReason)
def do_layout(self): self.central_widget.clear() self.labels = [] sto = QWidget.setTabOrder self.central_widget.tabBar().setVisible(False) tab0 = QWidget(self) self.central_widget.addTab(tab0, _("&Metadata")) l = QGridLayout() tab0.setLayout(l) # Basic metadata in col 0 tl = QGridLayout() gb = QGroupBox(_('Basic metadata'), tab0) l.addWidget(gb, 0, 0, 1, 1) gb.setLayout(tl) self.button_box_layout.insertWidget(1, self.fetch_metadata_button) self.button_box_layout.insertWidget(2, self.config_metadata_button) sto(self.button_box, self.fetch_metadata_button) sto(self.fetch_metadata_button, self.config_metadata_button) sto(self.config_metadata_button, self.title) def create_row(row, widget, tab_to, button=None, icon=None, span=1): ql = BuddyLabel(widget) tl.addWidget(ql, row, 1, 1, 1) tl.addWidget(widget, row, 2, 1, 1) if button is not None: tl.addWidget(button, row, 3, span, 1) if icon is not None: button.setIcon(QIcon(I(icon))) if tab_to is not None: if button is not None: sto(widget, button) sto(button, tab_to) else: sto(widget, tab_to) tl.addWidget(self.swap_title_author_button, 0, 0, 2, 1) tl.addWidget(self.manage_authors_button, 2, 0, 2, 1) tl.addWidget(self.paste_isbn_button, 12, 0, 1, 1) tl.addWidget(self.tags_editor_button, 6, 0, 1, 1) create_row(0, self.title, self.title_sort, button=self.deduce_title_sort_button, span=2, icon='auto_author_sort.png') create_row(1, self.title_sort, self.authors) create_row(2, self.authors, self.author_sort, button=self.deduce_author_sort_button, span=2, icon='auto_author_sort.png') create_row(3, self.author_sort, self.series) create_row(4, self.series, self.series_index, button=self.clear_series_button, icon='trash.png') create_row(5, self.series_index, self.tags) create_row(6, self.tags, self.rating, button=self.clear_tags_button) create_row(7, self.rating, self.pubdate, button=self.clear_ratings_button) create_row(8, self.pubdate, self.publisher, button=self.pubdate.clear_button, icon='trash.png') create_row(9, self.publisher, self.languages, button=self.publisher.clear_button, icon='trash.png') create_row(10, self.languages, self.timestamp) create_row(11, self.timestamp, self.identifiers, button=self.timestamp.clear_button, icon='trash.png') create_row(12, self.identifiers, self.comments, button=self.clear_identifiers_button, icon='trash.png') sto(self.clear_identifiers_button, self.swap_title_author_button) sto(self.swap_title_author_button, self.manage_authors_button) sto(self.manage_authors_button, self.tags_editor_button) sto(self.tags_editor_button, self.paste_isbn_button) tl.addItem(QSpacerItem(1, 1, QSizePolicy.Fixed, QSizePolicy.Expanding), 13, 1, 1 ,1) # Custom metadata in col 1 w = getattr(self, 'custom_metadata_widgets_parent', None) if w is not None: gb = QGroupBox(_('Custom metadata'), tab0) gbl = QVBoxLayout() gb.setLayout(gbl) sr = QScrollArea(gb) sr.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) sr.setWidgetResizable(True) sr.setFrameStyle(QFrame.NoFrame) sr.setWidget(w) gbl.addWidget(sr) l.addWidget(gb, 0, 1, 1, 1) sp = QSizePolicy() sp.setVerticalStretch(10) sp.setHorizontalPolicy(QSizePolicy.Minimum) sp.setVerticalPolicy(QSizePolicy.Expanding) gb.setSizePolicy(sp) self.set_custom_metadata_tab_order() # comments span col 0 & 1 w = QGroupBox(_('Comments'), tab0) sp = QSizePolicy() sp.setVerticalStretch(10) sp.setHorizontalPolicy(QSizePolicy.Expanding) sp.setVerticalPolicy(QSizePolicy.Expanding) w.setSizePolicy(sp) lb = QHBoxLayout() w.setLayout(lb) lb.addWidget(self.comments) l.addWidget(w, 1, 0, 1, 2) # Cover & formats in col 3 gb = QGroupBox(_('Cover'), tab0) lb = QGridLayout() gb.setLayout(lb) lb.addWidget(self.cover, 0, 0, 1, 3, alignment=Qt.AlignCenter) sto(self.manage_authors_button, self.cover.buttons[0]) for i, b in enumerate(self.cover.buttons[:3]): lb.addWidget(b, 1, i, 1, 1) sto(b, self.cover.buttons[i+1]) hl = QHBoxLayout() for b in self.cover.buttons[3:]: hl.addWidget(b) sto(self.cover.buttons[-2], self.cover.buttons[-1]) lb.addLayout(hl, 2, 0, 1, 3) l.addWidget(gb, 0, 2, 1, 1) l.addWidget(self.formats_manager, 1, 2, 1, 1) sto(self.cover.buttons[-1], self.formats_manager) self.formats_manager.formats.setMaximumWidth(10000) self.formats_manager.formats.setIconSize(QSize(32, 32))
def __init__(self, gui, initial_plugin=None, close_after_initial=False): QMainWindow.__init__(self, gui) self.gui = gui self.must_restart = False self.committed = False self.close_after_initial = close_after_initial self.resize(930, 720) nh, nw = min_available_height()-25, available_width()-10 if nh < 0: nh = 800 if nw < 0: nw = 600 nh = min(self.height(), nh) nw = min(self.width(), nw) self.resize(nw, nh) self.esc_action = QAction(self) self.addAction(self.esc_action) self.esc_action.setShortcut(QKeySequence(Qt.Key_Escape)) self.esc_action.triggered.connect(self.esc) geom = gprefs.get('preferences_window_geometry', None) if geom is not None: self.restoreGeometry(geom) # Center if islinux: self.move(gui.rect().center() - self.rect().center()) self.setWindowModality(Qt.WindowModal) self.setWindowTitle(__appname__ + ' - ' + _('Preferences')) self.setWindowIcon(QIcon(I('config.png'))) self.status_bar = StatusBar(self) self.setStatusBar(self.status_bar) self.stack = QStackedWidget(self) self.cw = QWidget(self) self.cw.setLayout(QVBoxLayout()) self.cw.layout().addWidget(self.stack) self.bb = QDialogButtonBox(QDialogButtonBox.Close) self.wizard_button = self.bb.addButton(_('Run welcome wizard'), self.bb.ActionRole) self.wizard_button.setIcon(QIcon(I('wizard.png'))) self.wizard_button.clicked.connect(self.run_wizard, type=Qt.QueuedConnection) self.cw.layout().addWidget(self.bb) self.bb.button(self.bb.Close).setDefault(True) self.bb.rejected.connect(self.close, type=Qt.QueuedConnection) self.setCentralWidget(self.cw) self.browser = Browser(self) self.browser.show_plugin.connect(self.show_plugin) self.stack.addWidget(self.browser) self.scroll_area = QScrollArea(self) self.stack.addWidget(self.scroll_area) self.scroll_area.setWidgetResizable(True) self.setContextMenuPolicy(Qt.NoContextMenu) self.bar = QToolBar(self) self.addToolBar(self.bar) self.bar.setVisible(False) self.bar.setIconSize(QSize(ICON_SIZE, ICON_SIZE)) self.bar.setMovable(False) self.bar.setFloatable(False) self.bar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.apply_action = self.bar.addAction(QIcon(I('ok.png')), _('&Apply'), self.commit) self.cancel_action = self.bar.addAction(QIcon(I('window-close.png')), _('&Cancel'), self.cancel) self.bar_title = BarTitle(self.bar) self.bar.addWidget(self.bar_title) self.restore_action = self.bar.addAction(QIcon(I('clear_left.png')), _('Restore &defaults'), self.restore_defaults) for ac, tt in [('apply', _('Save changes')), ('cancel', _('Cancel and return to overview'))]: ac = getattr(self, ac+'_action') ac.setToolTip(tt) ac.setWhatsThis(tt) ac.setStatusTip(tt) for ch in self.bar.children(): if isinstance(ch, QToolButton): ch.setCursor(Qt.PointingHandCursor) ch.setAutoRaise(True) self.stack.setCurrentIndex(0) if initial_plugin is not None: category, name = initial_plugin plugin = get_plugin(category, name) if plugin is not None: self.show_plugin(plugin)
class Preferences(QDialog): run_wizard_requested = pyqtSignal() def __init__(self, gui, initial_plugin=None, close_after_initial=False): QDialog.__init__(self, gui) self.gui = gui self.must_restart = False self.do_restart = False self.committed = False self.close_after_initial = close_after_initial self.resize(930, 720) nh, nw = min_available_height()-25, available_width()-10 if nh < 0: nh = 800 if nw < 0: nw = 600 nh = min(self.height(), nh) nw = min(self.width(), nw) self.resize(nw, nh) geom = gprefs.get('preferences dialog geometry', None) if geom is not None: self.restoreGeometry(geom) # Center if islinux: self.move(gui.rect().center() - self.rect().center()) self.setWindowModality(Qt.ApplicationModal) self.setWindowTitle(__appname__ + ' - ' + _('Preferences')) self.setWindowIcon(QIcon(I('config.png'))) self.l = l = QVBoxLayout(self) self.stack = QStackedWidget(self) self.bb = QDialogButtonBox(QDialogButtonBox.Close | QDialogButtonBox.Apply | QDialogButtonBox.Discard | QDialogButtonBox.RestoreDefaults) self.bb.button(self.bb.Apply).clicked.connect(self.accept) self.bb.button(self.bb.Discard).clicked.connect(self.reject) self.bb.button(self.bb.RestoreDefaults).setIcon(QIcon(I('clear_left.png'))) self.bb.button(self.bb.RestoreDefaults).clicked.connect(self.restore_defaults) self.wizard_button = self.bb.addButton(_('Run Welcome &wizard'), self.bb.ActionRole) self.wizard_button.setIcon(QIcon(I('wizard.png'))) self.wizard_button.clicked.connect(self.run_wizard, type=Qt.QueuedConnection) self.wizard_button.setAutoDefault(False) self.bb.rejected.connect(self.reject) self.browser = Browser(self) self.browser.show_plugin.connect(self.show_plugin) self.stack.addWidget(self.browser) self.scroll_area = QScrollArea(self) self.stack.addWidget(self.scroll_area) self.scroll_area.setWidgetResizable(True) self.setContextMenuPolicy(Qt.NoContextMenu) self.title_bar = TitleBar(self) for ac, tt in [(self.bb.Apply, _('Save changes')), (self.bb.Discard, _('Cancel and return to overview'))]: self.bb.button(ac).setToolTip(tt) l.addWidget(self.title_bar), l.addWidget(self.stack), l.addWidget(self.bb) if initial_plugin is not None: category, name = initial_plugin[:2] plugin = get_plugin(category, name) if plugin is not None: self.show_plugin(plugin) if len(initial_plugin) > 2: w = self.findChild(QWidget, initial_plugin[2]) if w is not None: for c in self.showing_widget.children(): if isinstance(c, QTabWidget): idx = c.indexOf(w) if idx > -1: c.setCurrentIndex(idx) break else: self.hide_plugin() def event(self, ev): if isinstance(ev, QStatusTipEvent): msg = re.sub(r'</?[a-z1-6]+>', ' ', ev.tip()) self.title_bar.show_msg(msg) return QDialog.event(self, ev) def run_wizard(self): self.run_wizard_requested.emit() self.accept() def set_tooltips_for_labels(self): def process_child(child): for g in child.children(): if isinstance(g, QLabel): buddy = g.buddy() if buddy is not None and hasattr(buddy, 'toolTip'): htext = unicode_type(buddy.toolTip()).strip() etext = unicode_type(g.toolTip()).strip() if htext and not etext: g.setToolTip(htext) g.setWhatsThis(htext) else: process_child(g) process_child(self.showing_widget) def show_plugin(self, plugin): self.showing_widget = plugin.create_widget(self.scroll_area) self.showing_widget.genesis(self.gui) self.showing_widget.initialize() self.set_tooltips_for_labels() self.scroll_area.setWidget(self.showing_widget) self.stack.setCurrentIndex(1) self.showing_widget.show() self.setWindowTitle(__appname__ + ' - ' + _('Preferences') + ' - ' + plugin.gui_name) self.showing_widget.restart_now.connect(self.restart_now) self.title_bar.show_plugin(plugin) self.setWindowIcon(QIcon(plugin.icon)) self.bb.button(self.bb.Close).setVisible(False) self.wizard_button.setVisible(False) for button in (self.bb.Apply, self.bb.RestoreDefaults, self.bb.Discard): button = self.bb.button(button) button.setVisible(True) self.bb.button(self.bb.Apply).setEnabled(False) self.bb.button(self.bb.Apply).setDefault(False), self.bb.button(self.bb.Apply).setDefault(True) self.bb.button(self.bb.RestoreDefaults).setEnabled(self.showing_widget.supports_restoring_to_defaults) self.bb.button(self.bb.RestoreDefaults).setToolTip( self.showing_widget.restore_defaults_desc if self.showing_widget.supports_restoring_to_defaults else (_('Restoring to defaults not supported for') + ' ' + plugin.gui_name)) self.bb.button(self.bb.RestoreDefaults).setText(_('Restore &defaults')) self.showing_widget.changed_signal.connect(self.changed_signal) def changed_signal(self): b = self.bb.button(self.bb.Apply) b.setEnabled(True) def hide_plugin(self): for sig in 'changed_signal restart_now'.split(): try: getattr(self.showing_widget, sig).disconnect(getattr(self, sig)) except Exception: pass self.showing_widget = QWidget(self.scroll_area) self.scroll_area.setWidget(self.showing_widget) self.setWindowTitle(__appname__ + ' - ' + _('Preferences')) self.stack.setCurrentIndex(0) self.title_bar.show_plugin() self.setWindowIcon(QIcon(I('config.png'))) for button in (self.bb.Apply, self.bb.RestoreDefaults, self.bb.Discard): button = self.bb.button(button) button.setVisible(False) self.bb.button(self.bb.Close).setVisible(True) self.bb.button(self.bb.Close).setDefault(False), self.bb.button(self.bb.Close).setDefault(True) self.wizard_button.setVisible(True) def restart_now(self): try: self.showing_widget.commit() except AbortCommit: return self.do_restart = True self.hide_plugin() self.accept() def commit(self, *args): must_restart = self.showing_widget.commit() rc = self.showing_widget.restart_critical self.committed = True do_restart = False if must_restart: self.must_restart = True msg = _('Some of the changes you made require a restart.' ' Please restart calibre as soon as possible.') if rc: msg = _('The changes you have made require calibre be ' 'restarted immediately. You will not be allowed to ' 'set any more preferences, until you restart.') do_restart = show_restart_warning(msg, parent=self) self.showing_widget.refresh_gui(self.gui) if do_restart: self.do_restart = True return self.close_after_initial or (must_restart and rc) or do_restart def restore_defaults(self, *args): self.showing_widget.restore_defaults() def on_shutdown(self): gprefs.set('preferences dialog geometry', bytearray(self.saveGeometry())) if self.committed: self.gui.must_restart_before_config = self.must_restart self.gui.tags_view.recount() self.gui.create_device_menu() self.gui.set_device_menu_items_state(bool(self.gui.device_connected)) self.gui.bars_manager.apply_settings() self.gui.bars_manager.update_bars() self.gui.build_context_menus() def accept(self): if self.stack.currentIndex() == 0: self.on_shutdown() return QDialog.accept(self) try: close = self.commit() except AbortCommit: return if close: self.on_shutdown() return QDialog.accept(self) self.hide_plugin() def reject(self): if self.stack.currentIndex() == 0 or self.close_after_initial: self.on_shutdown() return QDialog.reject(self) self.hide_plugin()
def __init__(self, gui, initial_plugin=None, close_after_initial=False): QDialog.__init__(self, gui) self.gui = gui self.must_restart = False self.do_restart = False self.committed = False self.close_after_initial = close_after_initial self.resize(930, 720) nh, nw = min_available_height()-25, available_width()-10 if nh < 0: nh = 800 if nw < 0: nw = 600 nh = min(self.height(), nh) nw = min(self.width(), nw) self.resize(nw, nh) geom = gprefs.get('preferences dialog geometry', None) if geom is not None: self.restoreGeometry(geom) # Center if islinux: self.move(gui.rect().center() - self.rect().center()) self.setWindowModality(Qt.ApplicationModal) self.setWindowTitle(__appname__ + ' - ' + _('Preferences')) self.setWindowIcon(QIcon(I('config.png'))) self.l = l = QVBoxLayout(self) self.stack = QStackedWidget(self) self.bb = QDialogButtonBox(QDialogButtonBox.Close | QDialogButtonBox.Apply | QDialogButtonBox.Discard | QDialogButtonBox.RestoreDefaults) self.bb.button(self.bb.Apply).clicked.connect(self.accept) self.bb.button(self.bb.Discard).clicked.connect(self.reject) self.bb.button(self.bb.RestoreDefaults).setIcon(QIcon(I('clear_left.png'))) self.bb.button(self.bb.RestoreDefaults).clicked.connect(self.restore_defaults) self.wizard_button = self.bb.addButton(_('Run Welcome &wizard'), self.bb.ActionRole) self.wizard_button.setIcon(QIcon(I('wizard.png'))) self.wizard_button.clicked.connect(self.run_wizard, type=Qt.QueuedConnection) self.wizard_button.setAutoDefault(False) self.bb.rejected.connect(self.reject) self.browser = Browser(self) self.browser.show_plugin.connect(self.show_plugin) self.stack.addWidget(self.browser) self.scroll_area = QScrollArea(self) self.stack.addWidget(self.scroll_area) self.scroll_area.setWidgetResizable(True) self.setContextMenuPolicy(Qt.NoContextMenu) self.title_bar = TitleBar(self) for ac, tt in [(self.bb.Apply, _('Save changes')), (self.bb.Discard, _('Cancel and return to overview'))]: self.bb.button(ac).setToolTip(tt) l.addWidget(self.title_bar), l.addWidget(self.stack), l.addWidget(self.bb) if initial_plugin is not None: category, name = initial_plugin[:2] plugin = get_plugin(category, name) if plugin is not None: self.show_plugin(plugin) if len(initial_plugin) > 2: w = self.findChild(QWidget, initial_plugin[2]) if w is not None: for c in self.showing_widget.children(): if isinstance(c, QTabWidget): idx = c.indexOf(w) if idx > -1: c.setCurrentIndex(idx) break else: self.hide_plugin()
class DemoDialog(QDialog): def __init__(self, gui, icon, do_user_config): QDialog.__init__(self, gui) self.gui = gui self.do_user_config = do_user_config self.db = gui.current_db self.l = QVBoxLayout() self.setLayout(self.l) self.setWindowTitle('Wiki Reader') self.setWindowIcon(icon) self.helpl = QLabel( 'Enter the URL of a wikipedia article below. ' 'It will be downloaded and converted into an ebook.') self.helpl.setWordWrap(True) self.l.addWidget(self.helpl) self.w = QWidget(self) self.sa = QScrollArea(self) self.l.addWidget(self.sa) self.w.l = QVBoxLayout() self.w.setLayout(self.w.l) self.sa.setWidget(self.w) self.sa.setWidgetResizable(True) self.title = Title(self) self.w.l.addWidget(self.title) self.urls = [URL(self)] self.w.l.addWidget(self.urls[0]) self.add_more_button = QPushButton( QIcon(I('plus.png')), 'Add another URL') self.l.addWidget(self.add_more_button) self.bb = QDialogButtonBox(self) self.bb.setStandardButtons(self.bb.Ok | self.bb.Cancel) self.bb.accepted.connect(self.accept) self.bb.rejected.connect(self.reject) self.l.addWidget(self.bb) self.book_button = b = self.bb.addButton( 'Convert a Wiki&Book', self.bb.ActionRole) b.clicked.connect(self.wiki_book) self.add_more_button.clicked.connect(self.add_more) self.finished.connect(self.download) self.setMinimumWidth(500) self.resize(self.sizeHint()) self.single_url = None def wiki_book(self): d = QDialog(self) d.l = l = QFormLayout(d) l.setFieldGrowthPolicy(l.ExpandingFieldsGrow) d.setWindowTitle('Enter WikiBook URL') d.la = la = QLabel( '<p>You can convert a pre-made WikiBook into a book here. ' 'Simply enter the URL to the WikiBook page. For a list of ' 'WikiBooks, see ' '<a href="https://en.wikipedia.org/wiki/Special:PrefixIndex/Book:' '">here</a>.' ) la.setMinimumWidth(400) la.setWordWrap(True) la.setOpenExternalLinks(True) l.addRow(la) d.le = le = QLineEdit(self) l.addRow('WikiBook &URL:', le) le.setText('https://') le.selectAll() d.bb = bb = QDialogButtonBox( QDialogButtonBox.Ok | QDialogButtonBox.Cancel, d) l.addRow(bb) bb.accepted.connect(d.accept), bb.rejected.connect(d.reject) if d.exec_() == d.Accepted: self.single_url = le.text() self.accept() def do_resize(self): a = self.sizeHint() b = self.w.sizeHint() h = min(400, b.height()) self.resize(QSize(a.width(), h + 200)) def scroll_to_bottom(self): v = self.sa.verticalScrollBar() v.setValue(v.maximum()) def add_more(self): url = URL(self) self.urls.append(url) self.w.l.addWidget(url) QTimer.singleShot(0, self.do_resize) QTimer.singleShot(10, self.scroll_to_bottom) def about(self): # Get the about text from a file inside the plugin zip file # The get_resources function is a builtin function defined for all your # plugin code. It loads files from the plugin zip file. It returns # the bytes from the specified file. # # Note that if you are loading more than one file, for performance, you # should pass a list of names to get_resources. In this case, # get_resources will return a dictionary mapping names to bytes. Names # that are not found in the zip file will not be in the returned # dictionary. text = get_resources('about.txt') QMessageBox.about(self, 'About the Wiki Reader', text.decode('utf-8')) # def config(self): # self.do_user_config(parent=self) # Apply the changes # self.label.setText(prefs['hello_world_msg']) def download(self, retcode): if retcode != self.Accepted: return if self.single_url is None: urls = [x.url for x in self.urls] urls = [x.strip() for x in urls if x.strip()] urls = [ ('http://' + x) if not urlparse(x).scheme else x for x in urls] else: urls = self.single_url self.single_url = None args, fmt, temp_files = get_recipe(urls, self.title.title) job = self.gui.job_manager.run_job( Dispatcher(self.fetched), 'gui_convert', args=args, description='Fetch article from Wikipedia') job.extra_conversion_args = (temp_files, fmt) # don't prompt if all OK # if isinstance(urls, list): # info_dialog( # self, 'Downloading', # 'Downloading %d article(s) from Wikipedia. When the download' # ' completes the book will be added to your calibre library.' # % len(urls), show=True, show_copy_button=False) # else: # info_dialog( # self, 'Downloading', # 'Downloading book from Wikipedia. When the download' # ' completes the book will be added to your calibre library.', # show=True, show_copy_button=False) def fetched(self, job): if job.failed: return self.gui.job_exception(job) temp_files, fmt = job.extra_conversion_args fname = temp_files[0].name self.gui.iactions['Add Books']._add_books([fname], False) for f in temp_files[1:]: try: os.remove(f.name) except: pass
def __init__(self, parent_dialog, plugin_action): QWidget.__init__(self) self.parent_dialog = parent_dialog self.plugin_action = plugin_action self.l = QVBoxLayout() self.setLayout(self.l) label = QLabel(_('When Ejecting a Device, Check for:')) label.setWordWrap(True) self.l.addWidget(label) #self.l.addSpacing(5) scrollable = QScrollArea() scrollcontent = QWidget() scrollable.setWidget(scrollcontent) scrollable.setWidgetResizable(True) self.l.addWidget(scrollable) self.sl = QVBoxLayout() scrollcontent.setLayout(self.sl) self.checkreadinglistsync = QCheckBox(_('Reading List books to Sync'),self) self.checkreadinglistsync.setToolTip(_('Check Reading List plugin for books ready to Sync to the current device.')) self.checkreadinglistsync.setChecked(prefs['checkreadinglistsync']) self.sl.addWidget(self.checkreadinglistsync) if 'Reading List' not in plugin_action.gui.iactions: self.checkreadinglistsync.setEnabled(False) self.checkdups = QCheckBox(_('Duplicated Books'),self) self.checkdups.setToolTip(_('Check for books that are on the device more than once.')) self.checkdups.setChecked(prefs['checkdups']) self.sl.addWidget(self.checkdups) self.checknotinlibrary = QCheckBox(_('Deleted Books (not in Library)'),self) self.checknotinlibrary.setToolTip(_('Check for books on the device that are not in the current library.')) self.checknotinlibrary.setChecked(prefs['checknotinlibrary']) self.sl.addWidget(self.checknotinlibrary) self.checknotondevice = QCheckBox(_('Added Books (not on Device)'),self) self.checknotondevice.setToolTip(_('Check for books in the current library that are not on the device.')) self.checknotondevice.setChecked(prefs['checknotondevice']) self.sl.addWidget(self.checknotondevice) self.sl.insertStretch(-1) self.l.addSpacing(15) label = QLabel(_("These controls aren't plugin settings as such, but convenience buttons for setting Keyboard shortcuts and viewing all plugins settings.")) label.setWordWrap(True) self.l.addWidget(label) self.l.addSpacing(5) keyboard_shortcuts_button = QPushButton(_('Keyboard shortcuts...'), self) keyboard_shortcuts_button.setToolTip(_('Edit the keyboard shortcuts associated with this plugin')) keyboard_shortcuts_button.clicked.connect(parent_dialog.edit_shortcuts) self.l.addWidget(keyboard_shortcuts_button) view_prefs_button = QPushButton(_('&View library preferences...'), self) view_prefs_button.setToolTip(_('View data stored in the library database for this plugin')) view_prefs_button.clicked.connect(self.view_prefs) self.l.addWidget(view_prefs_button)
def get_notes_output_widget(self): """ Return QWidget with output and notes data :return: widget with host output and notes :rtype: QWidget """ widget = QWidget() layout = QGridLayout() widget.setLayout(layout) # Output output_title = QLabel(_("Output")) output_title.setObjectName('title') layout.addWidget(output_title, 0, 0, 1, 1) self.labels['ls_output'].setWordWrap(True) self.labels['ls_output'].setTextInteractionFlags(Qt.TextSelectableByMouse) output_scrollarea = QScrollArea() output_scrollarea.setWidget(self.labels['ls_output']) output_scrollarea.setWidgetResizable(True) output_scrollarea.setObjectName('output') layout.addWidget(output_scrollarea, 1, 0, 1, 2) # Notes notes_title = QLabel(_("Notes:")) notes_title.setObjectName('title') layout.addWidget(notes_title, 0, 2, 1, 1) notes_btn = QPushButton() notes_btn.setIcon(QIcon(settings.get_image('edit'))) notes_btn.setToolTip(_("Edit host notes.")) notes_btn.setFixedSize(32, 32) notes_btn.clicked.connect(self.patch_data) layout.addWidget(notes_btn, 0, 3, 1, 1) self.labels['notes'].setWordWrap(True) self.labels['notes'].setTextInteractionFlags(Qt.TextSelectableByMouse) notes_scrollarea = QScrollArea() notes_scrollarea.setWidget(self.labels['notes']) notes_scrollarea.setWidgetResizable(True) notes_scrollarea.setObjectName('notes') layout.addWidget(notes_scrollarea, 1, 2, 1, 2) return widget
def __init__(self, parent_dialog, plugin_action): QWidget.__init__(self) self.parent_dialog = parent_dialog self.plugin_action = plugin_action self.l = QVBoxLayout() self.setLayout(self.l) self.editmetadata = QCheckBox(_('Edit Metadata for New Book(s)'),self) self.editmetadata.setToolTip(_('Show Edit Metadata Dialog after creating each new book entry, but <i>before</i> EPUB is created.<br>Allows for downloading metadata and ensures EPUB has updated metadata.')) self.editmetadata.setChecked(prefs['editmetadata']) self.l.addWidget(self.editmetadata) self.show_checkedalways = QCheckBox(_("Show 'Always Include' Checkboxes"),self) self.show_checkedalways.setToolTip(_('If enabled, a checkbox will appear for each section.')+' '+ _('Checked sections will be included in <i>all</i> split books.<br>Default title will still be taken from the first <i>selected</i> section, and section order will remain as shown.')) self.show_checkedalways.setChecked(prefs['show_checkedalways']) self.l.addWidget(self.show_checkedalways) self.l.addSpacing(5) label = QLabel(_('When making a new Epub, the metadata from the source book will be copied or not as you choose below.')) label.setWordWrap(True) self.l.addWidget(label) scrollable = QScrollArea() scrollcontent = QWidget() scrollable.setWidget(scrollcontent) scrollable.setWidgetResizable(True) self.l.addWidget(scrollable) self.sl = QVBoxLayout() scrollcontent.setLayout(self.sl) self.copytoctitle = QCheckBox(_('Title from First Included TOC'),self) self.copytoctitle.setToolTip(_('Copy Title from the the first Table of Contents entry included in the Split Epub.\nSupersedes Copy Title below.')) self.copytoctitle.setChecked(prefs['copytoctitle']) self.sl.addWidget(self.copytoctitle) self.copytitle = QCheckBox(_('Copy Title'),self) self.copytitle.setToolTip(_('Copy Title from the source Epub to the Split Epub. Adds "Split" to the title.')) self.copytitle.setChecked(prefs['copytitle']) self.sl.addWidget(self.copytitle) self.copyauthors = QCheckBox(_('Copy Authors'),self) self.copyauthors.setToolTip(_('Copy Authors from the source Epub to the Split Epub.')) self.copyauthors.setChecked(prefs['copyauthors']) self.sl.addWidget(self.copyauthors) self.copyseries = QCheckBox(_('Copy Series'),self) self.copyseries.setToolTip(_('Copy Series from the source Epub to the Split Epub.')) self.copyseries.setChecked(prefs['copyseries']) self.sl.addWidget(self.copyseries) self.copycover = QCheckBox(_('Copy Cover'),self) self.copycover.setToolTip(_('Copy Cover from the source Epub to the Split Epub.')) self.copycover.setChecked(prefs['copycover']) self.sl.addWidget(self.copycover) self.copyrating = QCheckBox(_('Copy Rating'),self) self.copyrating.setToolTip(_('Copy Rating from the source Epub to the Split Epub.')) self.copyrating.setChecked(prefs['copyrating']) self.sl.addWidget(self.copyrating) self.copytags = QCheckBox(_('Copy Tags'),self) self.copytags.setToolTip(_('Copy Tags from the source Epub to the Split Epub.')) self.copytags.setChecked(prefs['copytags']) self.sl.addWidget(self.copytags) self.copyidentifiers = QCheckBox(_('Copy Identifiers'),self) self.copyidentifiers.setToolTip(_('Copy Identifiers from the source Epub to the Split Epub.')) self.copyidentifiers.setChecked(prefs['copyidentifiers']) self.sl.addWidget(self.copyidentifiers) self.copydate = QCheckBox(_('Copy Date'),self) self.copydate.setToolTip(_('Copy Date from the source Epub to the Split Epub.')) self.copydate.setChecked(prefs['copydate']) self.sl.addWidget(self.copydate) self.copypubdate = QCheckBox(_('Copy Published Date'),self) self.copypubdate.setToolTip(_('Copy Published Date from the source Epub to the Split Epub.')) self.copypubdate.setChecked(prefs['copypubdate']) self.sl.addWidget(self.copypubdate) self.copypublisher = QCheckBox(_('Copy Publisher'),self) self.copypublisher.setToolTip(_('Copy Publisher from the source Epub to the Split Epub.')) self.copypublisher.setChecked(prefs['copypublisher']) self.sl.addWidget(self.copypublisher) self.copylanguages = QCheckBox(_('Copy Languages'),self) self.copylanguages.setToolTip(_('Copy Languages from the source Epub to the Split Epub.')) self.copylanguages.setChecked(prefs['copylanguages']) self.sl.addWidget(self.copylanguages) self.copycomments = QCheckBox(_('Copy Comments'),self) self.copycomments.setToolTip(_('Copy Comments from the source Epub to the Split Epub. Adds "Split from:" to the comments.')) self.copycomments.setChecked(prefs['copycomments']) self.sl.addWidget(self.copycomments) self.sl.insertStretch(-1) self.l.addSpacing(15) label = QLabel(_("These controls aren't plugin settings as such, but convenience buttons for setting Keyboard shortcuts and getting all the EpubSplit confirmation dialogs back again.")) label.setWordWrap(True) self.l.addWidget(label) self.l.addSpacing(5) keyboard_shortcuts_button = QPushButton(_('Keyboard shortcuts...'), self) keyboard_shortcuts_button.setToolTip(_('Edit the keyboard shortcuts associated with this plugin')) keyboard_shortcuts_button.clicked.connect(parent_dialog.edit_shortcuts) self.l.addWidget(keyboard_shortcuts_button) reset_confirmation_button = QPushButton(_('Reset disabled &confirmation dialogs'), self) reset_confirmation_button.setToolTip(_('Reset all show me again dialogs for the EpubSplit plugin')) reset_confirmation_button.clicked.connect(self.reset_dialogs) self.l.addWidget(reset_confirmation_button) view_prefs_button = QPushButton(_('View library preferences...'), self) view_prefs_button.setToolTip(_('View data stored in the library database for this plugin')) view_prefs_button.clicked.connect(self.view_prefs) self.l.addWidget(view_prefs_button)
def do_layout(self): self.central_widget.clear() self.tabs = [] self.labels = [] sto = QWidget.setTabOrder self.on_drag_enter.connect(self.handle_drag_enter) self.tabs.append(DragTrackingWidget(self, self.on_drag_enter)) self.central_widget.addTab(self.tabs[0], _("&Metadata")) self.tabs[0].l = QGridLayout() self.tabs[0].setLayout(self.tabs[0].l) self.tabs.append(QWidget(self)) self.central_widget.addTab(self.tabs[1], _("&Cover and formats")) self.tabs[1].l = QGridLayout() self.tabs[1].setLayout(self.tabs[1].l) # accept drop events so we can automatically switch to the second tab to # drop covers and formats self.tabs[0].setAcceptDrops(True) # Tab 0 tab0 = self.tabs[0] tl = QGridLayout() gb = QGroupBox(_('&Basic metadata'), self.tabs[0]) self.tabs[0].l.addWidget(gb, 0, 0, 1, 1) gb.setLayout(tl) self.button_box_layout.insertWidget(1, self.fetch_metadata_button) self.button_box_layout.insertWidget(2, self.config_metadata_button) sto(self.button_box, self.fetch_metadata_button) sto(self.fetch_metadata_button, self.config_metadata_button) sto(self.config_metadata_button, self.title) def create_row(row, widget, tab_to, button=None, icon=None, span=1): ql = BuddyLabel(widget) tl.addWidget(ql, row, 1, 1, 1) tl.addWidget(widget, row, 2, 1, 1) if button is not None: tl.addWidget(button, row, 3, span, 1) if icon is not None: button.setIcon(QIcon(I(icon))) if tab_to is not None: if button is not None: sto(widget, button) sto(button, tab_to) else: sto(widget, tab_to) tl.addWidget(self.swap_title_author_button, 0, 0, 2, 1) tl.addWidget(self.manage_authors_button, 2, 0, 1, 1) tl.addWidget(self.paste_isbn_button, 12, 0, 1, 1) tl.addWidget(self.tags_editor_button, 6, 0, 1, 1) create_row(0, self.title, self.title_sort, button=self.deduce_title_sort_button, span=2, icon='auto_author_sort.png') create_row(1, self.title_sort, self.authors) create_row(2, self.authors, self.author_sort, button=self.deduce_author_sort_button, span=2, icon='auto_author_sort.png') create_row(3, self.author_sort, self.series) create_row(4, self.series, self.series_index, button=self.clear_series_button, icon='trash.png') create_row(5, self.series_index, self.tags) create_row(6, self.tags, self.rating, button=self.clear_tags_button) create_row(7, self.rating, self.pubdate, button=self.clear_ratings_button) create_row(8, self.pubdate, self.publisher, button=self.pubdate.clear_button, icon='trash.png') create_row(9, self.publisher, self.languages, button=self.publisher.clear_button, icon='trash.png') create_row(10, self.languages, self.timestamp) create_row(11, self.timestamp, self.identifiers, button=self.timestamp.clear_button, icon='trash.png') create_row(12, self.identifiers, self.comments, button=self.clear_identifiers_button, icon='trash.png') sto(self.clear_identifiers_button, self.swap_title_author_button) sto(self.swap_title_author_button, self.manage_authors_button) sto(self.manage_authors_button, self.tags_editor_button) sto(self.tags_editor_button, self.paste_isbn_button) tl.addItem(QSpacerItem(1, 1, QSizePolicy.Fixed, QSizePolicy.Expanding), 13, 1, 1 ,1) w = getattr(self, 'custom_metadata_widgets_parent', None) if w is not None: gb = QGroupBox(_('C&ustom metadata'), tab0) gbl = QVBoxLayout() gb.setLayout(gbl) sr = QScrollArea(tab0) sr.setWidgetResizable(True) sr.setFrameStyle(QFrame.NoFrame) sr.setWidget(w) gbl.addWidget(sr) self.tabs[0].l.addWidget(gb, 0, 1, 1, 1) sto(self.identifiers, gb) w = QGroupBox(_('&Comments'), tab0) sp = QSizePolicy() sp.setVerticalStretch(10) sp.setHorizontalPolicy(QSizePolicy.Expanding) sp.setVerticalPolicy(QSizePolicy.Expanding) w.setSizePolicy(sp) l = QHBoxLayout() w.setLayout(l) l.addWidget(self.comments) tab0.l.addWidget(w, 1, 0, 1, 2) # Tab 1 tab1 = self.tabs[1] wsp = QWidget(tab1) wgl = QVBoxLayout() wsp.setLayout(wgl) # right-hand side of splitter gb = QGroupBox(_('Change cover'), tab1) l = QGridLayout() gb.setLayout(l) for i, b in enumerate(self.cover.buttons[:3]): l.addWidget(b, 0, i, 1, 1) sto(b, self.cover.buttons[i+1]) hl = QHBoxLayout() for b in self.cover.buttons[3:]: hl.addWidget(b) sto(self.cover.buttons[-2], self.cover.buttons[-1]) l.addLayout(hl, 1, 0, 1, 3) wgl.addWidget(gb) wgl.addItem(QSpacerItem(10, 10, QSizePolicy.Expanding, QSizePolicy.Expanding)) wgl.addItem(QSpacerItem(10, 10, QSizePolicy.Expanding, QSizePolicy.Expanding)) wgl.addWidget(self.formats_manager) self.splitter = QSplitter(Qt.Horizontal, tab1) tab1.l.addWidget(self.splitter) self.splitter.addWidget(self.cover) self.splitter.addWidget(wsp) self.formats_manager.formats.setMaximumWidth(10000) self.formats_manager.formats.setIconSize(QSize(64, 64))
class BookConfigWidget(QDialog): '''Creates book specific preferences dialog''' # title case given words except for articles in the middle # i.e the lord ruler would become The Lord Ruler but john the great would become John the Great ARTICLES = ['The', 'For', 'De', 'And', 'Or', 'Of', 'La'] TITLE_CASE = lambda self, words: ' '.join([ word.lower() if word in self.ARTICLES and index != 0 else word for index, word in enumerate(words.title().split()) ]) def __init__(self, parent, book_settings): QDialog.__init__(self, parent) self.resize(500, 500) self.setWindowTitle('title - author') self._index = 0 self._book_settings = book_settings v_layout = QVBoxLayout(self) # add ASIN and Goodreads url text boxes and update buttons asin_browser_button, goodreads_browser_button = self._initialize_general( v_layout) # add scrollable area for aliases v_layout.addWidget(QLabel('Aliases:')) self._scroll_area = QScrollArea() v_layout.addWidget(self._scroll_area) # add status box self._status = QLabel('') v_layout.addWidget(self._status) previous_button = next_button = None if len(self._book_settings) > 1: previous_button = QPushButton('Previous') previous_button.setEnabled(False) previous_button.setFixedWidth(100) next_button = QPushButton('Next') next_button.setFixedWidth(100) previous_button.clicked.connect(lambda: self.previous_clicked( previous_button, next_button, asin_browser_button, goodreads_browser_button)) next_button.clicked.connect(lambda: self.next_clicked( previous_button, next_button, asin_browser_button, goodreads_browser_button)) self._initialize_navigation_buttons(v_layout, previous_button, next_button) self.setLayout(v_layout) self.show_book_prefs(asin_browser_button, goodreads_browser_button) self.show() def _initialize_general(self, v_layout): '''Initialize asin/goodreads sections''' # Add the ASIN label, line edit, and button to dialog self._asin_edit = QLineEdit('') asin_layout = QHBoxLayout(None) asin_label = QLabel('ASIN:') asin_label.setFixedWidth(100) asin_browser_button = QPushButton('Open..') asin_browser_button.clicked.connect(self.browse_amazon_url) asin_browser_button.setToolTip( 'Open Amazon page for the specified ASIN') self._asin_edit.textEdited.connect(lambda: self.edit_asin( self._asin_edit.text(), asin_browser_button)) asin_layout.addWidget(asin_label) asin_layout.addWidget(self._asin_edit) asin_layout.addWidget(asin_browser_button) v_layout.addLayout(asin_layout) # Add the Goodreads URL label, line edit, and button to dialog self._goodreads_url_edit = QLineEdit('') self._goodreads_url_edit.textEdited.connect( lambda: self.edit_goodreads_url(self._goodreads_url_edit.text(), goodreads_browser_button)) goodreads_layout = QHBoxLayout(None) goodreads_url_label = QLabel('Goodreads URL:') goodreads_url_label.setFixedWidth(100) goodreads_browser_button = QPushButton('Open..') goodreads_browser_button.clicked.connect(self.browse_goodreads_url) goodreads_browser_button.setToolTip( 'Open Goodreads page at the specified URL') goodreads_layout.addWidget(goodreads_url_label) goodreads_layout.addWidget(self._goodreads_url_edit) goodreads_layout.addWidget(goodreads_browser_button) v_layout.addLayout(goodreads_layout) # Add the sample xray label, line edit, and button to dialog self._sample_xray_edit = QLineEdit('') self._sample_xray_edit.textEdited.connect( lambda: self.edit_sample_xray(self._sample_xray_edit.text())) sample_xray_layout = QHBoxLayout(None) sample_xray_label = QLabel('X-Ray or JSON:') sample_xray_label.setFixedWidth(100) sample_xray_button = QPushButton('Browse...') sample_xray_button.clicked.connect(self.browse_sample_xray) sample_xray_button.setToolTip( 'Browse for a sample x-ray file or JSON to be used') sample_xray_layout.addWidget(sample_xray_label) sample_xray_layout.addWidget(self._sample_xray_edit) sample_xray_layout.addWidget(sample_xray_button) v_layout.addLayout(sample_xray_layout) # Add the update buttons to dialog search_buttons_layout = QHBoxLayout(None) search_asin_button = QPushButton('Search for ASIN') search_asin_button.setFixedWidth(225) search_asin_button.clicked.connect( lambda: self.search_for_asin_clicked(asin_browser_button)) search_buttons_layout.addWidget(search_asin_button) search_goodreads_url_button = QPushButton('Search for Goodreads URL') search_goodreads_url_button.setFixedWidth(225) search_goodreads_url_button.clicked.connect( lambda: self.search_for_goodreads_url(goodreads_browser_button)) search_buttons_layout.addWidget(search_goodreads_url_button) v_layout.addLayout(search_buttons_layout) update_buttons_layout = QHBoxLayout(None) self._update_from_url_button = QPushButton('Update Aliases from URL') self._update_from_url_button.setFixedWidth(225) self._update_from_url_button.clicked.connect( self.update_aliases_from_url) update_buttons_layout.addWidget(self._update_from_url_button) self._update_from_file_button = QPushButton( 'Update Aliases from Input File') self._update_from_file_button.setFixedWidth(225) self._update_from_file_button.clicked.connect( self.update_aliases_from_file) update_buttons_layout.addWidget(self._update_from_file_button) v_layout.addLayout(update_buttons_layout) return asin_browser_button, goodreads_browser_button def _initialize_navigation_buttons(self, v_layout, previous_button, next_button): '''Add previous, ok, cancel, and next buttons''' buttons_layout = QHBoxLayout(None) buttons_layout.setAlignment(Qt.AlignRight) if len(self._book_settings) > 1: buttons_layout.addWidget(previous_button) ok_button = QPushButton('OK') ok_button.setFixedWidth(100) ok_button.clicked.connect(self.ok_clicked) buttons_layout.addWidget(ok_button) cancel_button = QPushButton('Cancel') cancel_button.setFixedWidth(100) cancel_button.clicked.connect(self.cancel_clicked) buttons_layout.addWidget(cancel_button) if len(self._book_settings) > 1: buttons_layout.addWidget(next_button) v_layout.addLayout(buttons_layout) @property def book(self): return self._book_settings[self._index] def set_status_and_repaint(self, message): '''Sets the status text and redraws the status text box''' self._status.setText(message) self._status.repaint() def edit_asin(self, val, asin_browser_button): '''Set asin edit to specified value; update asin browser button accordingly''' self.book.asin = val if val == '': asin_browser_button.setEnabled(False) else: asin_browser_button.setEnabled(True) def edit_goodreads_url(self, val, goodreads_browser_button): '''Sets book's goodreads_url to val and warns if the url is invalid; update goodreads browser button accordingly''' self.book.goodreads_url = val if val == '': goodreads_browser_button.setEnabled(False) if self._status.text( ) == 'Warning: Invalid Goodreads URL. URL must have goodreads as the domain.': self._status.setText('') else: goodreads_browser_button.setEnabled(True) if 'goodreads.com' not in val: self._status.setText( 'Warning: Invalid Goodreads URL. URL must have goodreads as the domain.' ) def edit_sample_xray(self, val): '''Sets book's sample x-ray to val and warns if the file path is invalid''' self.book.sample_xray = val if os.path.isfile(val): if not val.lower().endwith('.json') and not val.lower().endswith( '.asc'): return self.update_aliases_from_file() def search_for_asin_clicked(self, asin_browser_button): '''Searches for current book's ASIN on amazon''' asin = None self.set_status_and_repaint('Searching for ASIN...') if self.book.title != 'Unknown' and self.book.author != 'Unknown': asin = self.book.search_for_asin_on_amazon( self.book.title_and_author) if asin: self._status.setText('ASIN found.') asin_browser_button.setEnabled(True) self.book.asin = asin self._asin_edit.setText(asin) else: self._status.setText('ASIN not found.') asin_browser_button.setEnabled(False) self._asin_edit.setText('') def browse_amazon_url(self): '''Opens Amazon page for current book's ASIN using user's local store''' # Try to use the nearest Amazon store to the user. # If this fails we'll default to .com, the user will have to manually # edit the preferences file to fix it (it is a simple text file). if not prefs['tld']: import json from collections import defaultdict from urllib2 import urlopen, URLError try: country = json.loads( urlopen('http://ipinfo.io/json').read())['country'] except (URLError, KeyError): country = 'unknown' country_tld = defaultdict( lambda: 'com', { 'AU': 'com.au', 'BR': 'com.br', 'CA': 'ca', 'CN': 'cn', 'FR': 'fr', 'DE': 'de', 'IN': 'in', 'IT': 'it', 'JP': 'co.jp', 'MX': 'com.mx', 'NL': 'nl', 'ES': 'es', 'GB': 'co.uk', 'US': 'com' }) prefs['tld'] = country_tld[country] webbrowser.open('https://www.amazon.{0}/gp/product/{1}/'.format( prefs['tld'], self._asin_edit.text())) def browse_goodreads_url(self): '''Opens url for current book's goodreads url''' webbrowser.open(self._goodreads_url_edit.text()) def browse_sample_xray(self): """Browse for a sample xray file to use during x-ray creation""" file_dialog = QFileDialog(self) sample_file = file_dialog.getOpenFileName( caption='Choose sample x-ray to use:', filter='X-Ray or JSON (*.asc *.json)')[0] self.book.sample_xray = sample_file self._sample_xray_edit.setText(sample_file) if sample_file: self.update_aliases_from_file() def search_for_goodreads_url(self, goodreads_browser_button): '''Searches for goodreads url using asin first then title and author if asin doesn't exist''' url = None self.set_status_and_repaint('Searching for Goodreads url...') if self.book.asin: url = self.book.search_for_goodreads_url(self.book.asin) if not url and self.book.title != 'Unknown' and self.book.author != 'Unknown': url = self.book.search_for_goodreads_url( self.book.title_and_author) if url: self._status.setText('Goodreads url found.') self._update_from_url_button.setEnabled(True) goodreads_browser_button.setEnabled(True) self.book.goodreads_url = url self._goodreads_url_edit.setText(url) else: self._status.setText('Goodreads url not found.') self._update_from_url_button.setEnabled(False) goodreads_browser_button.setEnabled(False) self._goodreads_url_edit.setText('') def update_aliases_from_url(self): '''Update aliases using goodreads''' if 'goodreads.com' not in self._goodreads_url_edit.text(): self._status.setText( 'Error: Invalid Goodreads URL. URL must have goodreads as the domain.' ) return self.update_aliases_from_goodreads() def update_aliases_from_file(self): '''Update aliases on the preferences dailog using the information in the specified file''' self.set_status_and_repaint('Updating aliases...') if os.path.exists(self.book.sample_xray): self.book.update_aliases(self.book.sample_xray, source_type=os.path.splitext( self.book.sample_xray)[1][1:]) self.update_aliases_on_gui() self._status.setText('Aliases updated.') else: self._status.setText('Error: Input file not found.') def update_aliases_from_goodreads(self): '''Updates aliases on the preferences dialog using the information on the current goodreads url''' try: self.set_status_and_repaint('Updating aliases...') self.book.update_aliases(self._goodreads_url_edit.text()) self.update_aliases_on_gui() self._status.setText('Aliases updated.') except PageDoesNotExist: self._status.setText('Invalid Goodreads url.') def edit_aliases(self, term, val): '''Sets book's aliases to tuple (term, val)''' self.book.set_aliases(term, val) def previous_clicked(self, previous_button, next_button, asin_browser_button, goodreads_browser_button): '''Goes to previous book''' self._status.setText('') self._index -= 1 next_button.setEnabled(True) if self._index == 0: previous_button.setEnabled(False) self.show_book_prefs(asin_browser_button, goodreads_browser_button) def ok_clicked(self): '''Saves book's settings using current settings''' for book in self._book_settings: book.save() self.close() def cancel_clicked(self): '''Closes dialog without saving settings''' self.close() def next_clicked(self, previous_button, next_button, asin_browser_button, goodreads_browser_button): '''Goes to next book''' self._status.setText('') self._index += 1 previous_button.setEnabled(True) if self._index == len(self._book_settings) - 1: next_button.setEnabled(False) self.show_book_prefs(asin_browser_button, goodreads_browser_button) def show_book_prefs(self, asin_browser_button, goodreads_browser_button): '''Shows current book's preferences''' self.setWindowTitle(self.book.title_and_author) self._asin_edit.setText(self.book.asin) if self._asin_edit.text() == '': asin_browser_button.setEnabled(False) else: asin_browser_button.setEnabled(True) self._goodreads_url_edit.setText(self.book.goodreads_url) if self._goodreads_url_edit.text() == '': goodreads_browser_button.setEnabled(False) else: goodreads_browser_button.setEnabled(True) self._sample_xray_edit.setText(self.book.sample_xray) self.update_aliases_on_gui() def update_aliases_on_gui(self): '''Updates aliases on the dialog using the info in the book's aliases dict''' aliases_widget = QWidget() aliases_layout = QGridLayout(aliases_widget) aliases_layout.setAlignment(Qt.AlignTop) # add aliases for current book for index, (character, aliases) in enumerate(sorted(self.book.aliases.items())): label = QLabel(character + ':') label.setFixedWidth(150) aliases_layout.addWidget(label, index, 0) line_edit = QLineEdit(', '.join( [self.TITLE_CASE(alias) for alias in aliases])) line_edit.setFixedWidth(350) line_edit.textEdited.connect( functools.partial(self.edit_aliases, character)) aliases_layout.addWidget(line_edit, index, 1) self._scroll_area.setWidget(aliases_widget)
def __init__(self, gui, initial_plugin=None, close_after_initial=False): QDialog.__init__(self, gui) self.gui = gui self.must_restart = False self.do_restart = False self.committed = False self.close_after_initial = close_after_initial self.resize(930, 720) nh, nw = min_available_height() - 25, available_width() - 10 if nh < 0: nh = 800 if nw < 0: nw = 600 nh = min(self.height(), nh) nw = min(self.width(), nw) self.resize(nw, nh) geom = gprefs.get("preferences dialog geometry", None) if geom is not None: self.restoreGeometry(geom) # Center if islinux: self.move(gui.rect().center() - self.rect().center()) self.setWindowModality(Qt.ApplicationModal) self.setWindowTitle(__appname__ + " - " + _("Preferences")) self.setWindowIcon(QIcon(I("config.png"))) self.l = l = QVBoxLayout(self) self.stack = QStackedWidget(self) self.bb = QDialogButtonBox( QDialogButtonBox.Close | QDialogButtonBox.Apply | QDialogButtonBox.Discard | QDialogButtonBox.RestoreDefaults ) self.bb.button(self.bb.Apply).clicked.connect(self.accept) self.bb.button(self.bb.Discard).clicked.connect(self.reject) self.bb.button(self.bb.RestoreDefaults).setIcon(QIcon(I("clear_left.png"))) self.bb.button(self.bb.RestoreDefaults).clicked.connect(self.restore_defaults) self.wizard_button = self.bb.addButton(_("Run welcome wizard"), self.bb.ActionRole) self.wizard_button.setIcon(QIcon(I("wizard.png"))) self.wizard_button.clicked.connect(self.run_wizard, type=Qt.QueuedConnection) self.wizard_button.setAutoDefault(False) self.bb.rejected.connect(self.reject) self.browser = Browser(self) self.browser.show_plugin.connect(self.show_plugin) self.stack.addWidget(self.browser) self.scroll_area = QScrollArea(self) self.stack.addWidget(self.scroll_area) self.scroll_area.setWidgetResizable(True) self.setContextMenuPolicy(Qt.NoContextMenu) self.title_bar = TitleBar(self) for ac, tt in [(self.bb.Apply, _("Save changes")), (self.bb.Discard, _("Cancel and return to overview"))]: self.bb.button(ac).setToolTip(tt) l.addWidget(self.title_bar), l.addWidget(self.stack), l.addWidget(self.bb) if initial_plugin is not None: category, name = initial_plugin plugin = get_plugin(category, name) if plugin is not None: self.show_plugin(plugin) else: self.hide_plugin()
class Preferences(QMainWindow): run_wizard_requested = pyqtSignal() def __init__(self, gui, initial_plugin=None, close_after_initial=False): QMainWindow.__init__(self, gui) self.gui = gui self.must_restart = False self.committed = False self.close_after_initial = close_after_initial self.resize(930, 720) nh, nw = min_available_height()-25, available_width()-10 if nh < 0: nh = 800 if nw < 0: nw = 600 nh = min(self.height(), nh) nw = min(self.width(), nw) self.resize(nw, nh) self.esc_action = QAction(self) self.addAction(self.esc_action) self.esc_action.setShortcut(QKeySequence(Qt.Key_Escape)) self.esc_action.triggered.connect(self.esc) geom = gprefs.get('preferences_window_geometry', None) if geom is not None: self.restoreGeometry(geom) # Center if islinux: self.move(gui.rect().center() - self.rect().center()) self.setWindowModality(Qt.WindowModal) self.setWindowTitle(__appname__ + ' - ' + _('Preferences')) self.setWindowIcon(QIcon(I('config.png'))) self.status_bar = StatusBar(self) self.setStatusBar(self.status_bar) self.stack = QStackedWidget(self) self.cw = QWidget(self) self.cw.setLayout(QVBoxLayout()) self.cw.layout().addWidget(self.stack) self.bb = QDialogButtonBox(QDialogButtonBox.Close) self.wizard_button = self.bb.addButton(_('Run welcome wizard'), self.bb.ActionRole) self.wizard_button.setIcon(QIcon(I('wizard.png'))) self.wizard_button.clicked.connect(self.run_wizard, type=Qt.QueuedConnection) self.cw.layout().addWidget(self.bb) self.bb.button(self.bb.Close).setDefault(True) self.bb.rejected.connect(self.close, type=Qt.QueuedConnection) self.setCentralWidget(self.cw) self.browser = Browser(self) self.browser.show_plugin.connect(self.show_plugin) self.stack.addWidget(self.browser) self.scroll_area = QScrollArea(self) self.stack.addWidget(self.scroll_area) self.scroll_area.setWidgetResizable(True) self.setContextMenuPolicy(Qt.NoContextMenu) self.bar = QToolBar(self) self.addToolBar(self.bar) self.bar.setVisible(False) self.bar.setIconSize(QSize(ICON_SIZE, ICON_SIZE)) self.bar.setMovable(False) self.bar.setFloatable(False) self.bar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.apply_action = self.bar.addAction(QIcon(I('ok.png')), _('&Apply'), self.commit) self.cancel_action = self.bar.addAction(QIcon(I('window-close.png')), _('&Cancel'), self.cancel) self.bar_title = BarTitle(self.bar) self.bar.addWidget(self.bar_title) self.restore_action = self.bar.addAction(QIcon(I('clear_left.png')), _('Restore &defaults'), self.restore_defaults) for ac, tt in [('apply', _('Save changes')), ('cancel', _('Cancel and return to overview'))]: ac = getattr(self, ac+'_action') ac.setToolTip(tt) ac.setWhatsThis(tt) ac.setStatusTip(tt) for ch in self.bar.children(): if isinstance(ch, QToolButton): ch.setCursor(Qt.PointingHandCursor) ch.setAutoRaise(True) self.stack.setCurrentIndex(0) if initial_plugin is not None: category, name = initial_plugin plugin = get_plugin(category, name) if plugin is not None: self.show_plugin(plugin) def run_wizard(self): self.close() self.run_wizard_requested.emit() def set_tooltips_for_labels(self): def process_child(child): for g in child.children(): if isinstance(g, QLabel): buddy = g.buddy() if buddy is not None and hasattr(buddy, 'toolTip'): htext = unicode(buddy.toolTip()).strip() etext = unicode(g.toolTip()).strip() if htext and not etext: g.setToolTip(htext) g.setWhatsThis(htext) else: process_child(g) process_child(self.showing_widget) def show_plugin(self, plugin): self.showing_widget = plugin.create_widget(self.scroll_area) self.showing_widget.genesis(self.gui) self.showing_widget.initialize() self.set_tooltips_for_labels() self.scroll_area.setWidget(self.showing_widget) self.stack.setCurrentIndex(1) self.showing_widget.show() self.setWindowTitle(__appname__ + ' - ' + _('Preferences') + ' - ' + plugin.gui_name) self.apply_action.setEnabled(False) self.showing_widget.changed_signal.connect(lambda : self.apply_action.setEnabled(True)) self.showing_widget.restart_now.connect(self.restart_now) self.restore_action.setEnabled(self.showing_widget.supports_restoring_to_defaults) tt = self.showing_widget.restore_defaults_desc if not self.restore_action.isEnabled(): tt = _('Restoring to defaults not supported for') + ' ' + \ plugin.gui_name self.restore_action.setToolTip(textwrap.fill(tt)) self.restore_action.setWhatsThis(textwrap.fill(tt)) self.restore_action.setStatusTip(tt) self.bar_title.show_plugin(plugin) self.setWindowIcon(QIcon(plugin.icon)) self.bar.setVisible(True) self.bb.setVisible(False) def hide_plugin(self): self.showing_widget = QWidget(self.scroll_area) self.scroll_area.setWidget(self.showing_widget) self.setWindowTitle(__appname__ + ' - ' + _('Preferences')) self.bar.setVisible(False) self.stack.setCurrentIndex(0) self.setWindowIcon(QIcon(I('config.png'))) self.bb.setVisible(True) def esc(self, *args): if self.stack.currentIndex() == 1: self.cancel() elif self.stack.currentIndex() == 0: self.close() def restart_now(self): try: self.showing_widget.commit() except AbortCommit: return self.hide_plugin() self.close() self.gui.quit(restart=True) def commit(self, *args): try: must_restart = self.showing_widget.commit() except AbortCommit: return rc = self.showing_widget.restart_critical self.committed = True do_restart = False if must_restart: self.must_restart = True msg = _('Some of the changes you made require a restart.' ' Please restart calibre as soon as possible.') if rc: msg = _('The changes you have made require calibre be ' 'restarted immediately. You will not be allowed to ' 'set any more preferences, until you restart.') do_restart = show_restart_warning(msg, parent=self) self.showing_widget.refresh_gui(self.gui) self.hide_plugin() if self.close_after_initial or (must_restart and rc) or do_restart: self.close() if do_restart: self.gui.quit(restart=True) def cancel(self, *args): if self.close_after_initial: self.close() else: self.hide_plugin() def restore_defaults(self, *args): self.showing_widget.restore_defaults() def closeEvent(self, *args): gprefs.set('preferences_window_geometry', bytearray(self.saveGeometry())) if self.committed: self.gui.must_restart_before_config = self.must_restart self.gui.tags_view.recount() self.gui.create_device_menu() self.gui.set_device_menu_items_state(bool(self.gui.device_connected)) self.gui.bars_manager.apply_settings() self.gui.bars_manager.update_bars() self.gui.build_context_menus() return QMainWindow.closeEvent(self, *args)
def __init__(self, fm, pref_name, parent=None): QDialog.__init__(self, parent) self.fm = fm if pref_name == 'column_color_rules': self.rule_kind = 'color' rule_text = _('column coloring') elif pref_name == 'column_icon_rules': self.rule_kind = 'icon' rule_text = _('column icon') elif pref_name == 'cover_grid_icon_rules': self.rule_kind = 'emblem' rule_text = _('Cover grid emblem') self.setWindowIcon(QIcon(I('format-fill-color.png'))) self.setWindowTitle(_('Create/edit a {0} rule').format(rule_text)) self.l = l = QGridLayout(self) self.setLayout(l) self.l1 = l1 = QLabel(_('Create a {0} rule by' ' filling in the boxes below').format(rule_text)) l.addWidget(l1, 0, 0, 1, 8) self.f1 = QFrame(self) self.f1.setFrameShape(QFrame.HLine) l.addWidget(self.f1, 1, 0, 1, 8) self.l2 = l2 = QLabel(_('Add the emblem:') if self.rule_kind == 'emblem' else _('Set the')) l.addWidget(l2, 2, 0) if self.rule_kind == 'color': l.addWidget(QLabel(_('color'))) elif self.rule_kind == 'icon': self.kind_box = QComboBox(self) for tt, t in icon_rule_kinds: self.kind_box.addItem(tt, t) l.addWidget(self.kind_box, 2, 1) self.kind_box.setToolTip(textwrap.fill(_( 'If you choose composed icons and multiple rules match, then all the' ' matching icons will be combined, otherwise the icon from the' ' first rule to match will be used.'))) else: pass self.l3 = l3 = QLabel(_('of the column:')) l.addWidget(l3, 2, 2) self.column_box = QComboBox(self) l.addWidget(self.column_box, 2, 3) self.l4 = l4 = QLabel(_('to')) l.addWidget(l4, 2, 4) if self.rule_kind == 'emblem': l3.setVisible(False), self.column_box.setVisible(False), l4.setVisible(False) def create_filename_box(): self.filename_box = f = QComboBox() self.filenamebox_view = v = QListView() v.setIconSize(QSize(32, 32)) self.filename_box.setView(v) self.orig_filenamebox_view = f.view() f.setMinimumContentsLength(20), f.setSizeAdjustPolicy(f.AdjustToMinimumContentsLengthWithIcon) self.populate_icon_filenames() if self.rule_kind == 'color': self.color_box = ColorButton(parent=self) self.color_label = QLabel('Sample text Sample text') self.color_label.setTextFormat(Qt.RichText) l.addWidget(self.color_box, 2, 5) l.addWidget(self.color_label, 2, 6) l.addItem(QSpacerItem(10, 10, QSizePolicy.Expanding), 2, 7) elif self.rule_kind == 'emblem': create_filename_box() self.update_filename_box() self.filename_button = QPushButton(QIcon(I('document_open.png')), _('&Add new image')) l.addWidget(self.filename_box) l.addWidget(self.filename_button, 2, 6) l.addWidget(QLabel(_('(Images should be square-ish)')), 2, 7) l.setColumnStretch(7, 10) else: create_filename_box() vb = QVBoxLayout() self.multiple_icon_cb = QCheckBox(_('Choose &more than one icon')) vb.addWidget(self.multiple_icon_cb) self.update_filename_box() self.multiple_icon_cb.clicked.connect(self.multiple_box_clicked) vb.addWidget(self.filename_box) l.addLayout(vb, 2, 5) self.filename_button = QPushButton(QIcon(I('document_open.png')), _('&Add icon')) l.addWidget(self.filename_button, 2, 6) l.addWidget(QLabel(_('Icons should be square or landscape')), 2, 7) l.setColumnStretch(7, 10) self.l5 = l5 = QLabel( _('Only if the following conditions are all satisfied:')) l.addWidget(l5, 3, 0, 1, 7) self.scroll_area = sa = QScrollArea(self) sa.setMinimumHeight(300) sa.setMinimumWidth(950) sa.setWidgetResizable(True) l.addWidget(sa, 4, 0, 1, 8) self.add_button = b = QPushButton(QIcon(I('plus.png')), _('Add &another condition')) l.addWidget(b, 5, 0, 1, 8) b.clicked.connect(self.add_blank_condition) self.l6 = l6 = QLabel(_('You can disable a condition by' ' blanking all of its boxes')) l.addWidget(l6, 6, 0, 1, 8) self.bb = bb = QDialogButtonBox( QDialogButtonBox.Ok|QDialogButtonBox.Cancel) bb.accepted.connect(self.accept) bb.rejected.connect(self.reject) l.addWidget(bb, 7, 0, 1, 8) if self.rule_kind != 'color': self.remove_button = b = bb.addButton(_('&Remove icon'), bb.ActionRole) b.setIcon(QIcon(I('minus.png'))) b.setMenu(QMenu()) b.setToolTip('<p>' + _('Remove a previously added icon. Note that doing so will cause rules that use it to stop working.')) self.update_remove_button() self.conditions_widget = QWidget(self) sa.setWidget(self.conditions_widget) self.conditions_widget.setLayout(QVBoxLayout()) self.conditions_widget.layout().setAlignment(Qt.AlignTop) self.conditions = [] if self.rule_kind == 'color': for b in (self.column_box, ): b.setSizeAdjustPolicy(b.AdjustToMinimumContentsLengthWithIcon) b.setMinimumContentsLength(15) for key in sorted(displayable_columns(fm), key=lambda k: sort_key(fm[k]['name']) if k != color_row_key else 0): if key == color_row_key and self.rule_kind != 'color': continue name = all_columns_string if key == color_row_key else fm[key]['name'] if name: self.column_box.addItem(name, key) self.column_box.setCurrentIndex(0) if self.rule_kind == 'color': self.color_box.color = '#000' self.update_color_label() self.color_box.color_changed.connect(self.update_color_label) else: self.rule_icon_files = [] self.filename_button.clicked.connect(self.filename_button_clicked) self.resize(self.sizeHint())
def __init__(self, widget=None, parent=None): QScrollArea.__init__(self, parent) self.setFrameShape(self.NoFrame) self.setWidgetResizable(True) if widget is not None: self.setWidget(widget)
class SearchTheInternet(QWidget): changed_signal = pyqtSignal() def __init__(self, parent): QWidget.__init__(self, parent) self.sa = QScrollArea(self) self.lw = QWidget(self) self.l = QVBoxLayout(self.lw) self.sa.setWidget(self.lw), self.sa.setWidgetResizable(True) self.gl = gl = QVBoxLayout(self) self.la = QLabel(_( 'Add new locations to search for books or authors using the "Search the internet" feature' ' of the Content server. The URLs should contain {author} which will be' ' replaced by the author name and, for book URLs, {title} which will' ' be replaced by the book title.')) self.la.setWordWrap(True) gl.addWidget(self.la) self.h = QHBoxLayout() gl.addLayout(self.h) self.add_url_button = b = QPushButton(QIcon(I('plus.png')), _('&Add URL')) b.clicked.connect(self.add_url) self.h.addWidget(b) self.export_button = b = QPushButton(_('Export URLs')) b.clicked.connect(self.export_urls) self.h.addWidget(b) self.import_button = b = QPushButton(_('Import URLs')) b.clicked.connect(self.import_urls) self.h.addWidget(b) self.clear_button = b = QPushButton(_('Clear')) b.clicked.connect(self.clear) self.h.addWidget(b) self.h.addStretch(10) gl.addWidget(self.sa, stretch=10) self.items = [] def genesis(self): self.current_urls = search_the_net_urls() or [] @property def current_urls(self): return [item.as_dict for item in self.items if not item.is_empty] def append_item(self, item_as_dict): self.items.append(URLItem(item_as_dict, self)) self.l.addWidget(self.items[-1]) def clear(self): [(self.l.removeWidget(w), w.setParent(None), w.deleteLater()) for w in self.items] self.items = [] self.changed_signal.emit() @current_urls.setter def current_urls(self, val): self.clear() for entry in val: self.append_item(entry) def add_url(self): self.items.append(URLItem(None, self)) self.l.addWidget(self.items[-1]) QTimer.singleShot(100, self.scroll_to_bottom) def scroll_to_bottom(self): sb = self.sa.verticalScrollBar() if sb: sb.setValue(sb.maximum()) self.items[-1].name_widget.setFocus(Qt.OtherFocusReason) @property def serialized_urls(self): return json.dumps(self.current_urls, indent=2) def commit(self): for item in self.items: if not item.validate(): return False cu = self.current_urls if cu: with lopen(search_the_net_urls.path, 'wb') as f: f.write(self.serialized_urls) else: try: os.remove(search_the_net_urls.path) except EnvironmentError as err: if err.errno != errno.ENOENT: raise return True def export_urls(self): path = choose_save_file( self, 'search-net-urls', _('Choose URLs file'), filters=[(_('URL files'), ['json'])], initial_filename='search-urls.json') if path: with lopen(path, 'wb') as f: f.write(self.serialized_urls) def import_urls(self): paths = choose_files(self, 'search-net-urls', _('Choose URLs file'), filters=[(_('URL files'), ['json'])], all_files=False, select_only_single_file=True) if paths: with lopen(paths[0], 'rb') as f: items = json.loads(f.read()) [self.append_item(x) for x in items] self.changed_signal.emit()
def __init__(self, plugin_action): QWidget.__init__(self) self.plugin_action = plugin_action self.gui = plugin_action.gui self.library = get_library_config(self.gui.current_db) self.views = self.library[KEY_VIEWS] self.all_columns = self.get_current_columns() self.view_name = None self.has_pin_view = hasattr(self.gui.library_view, 'pin_view') toplayout = QVBoxLayout(self) self.setLayout(toplayout) ## wrap config in a scrollable area for smaller displays. scrollable = QScrollArea() scrollcontent = QWidget() scrollable.setWidget(scrollcontent) scrollable.setWidgetResizable(True) toplayout.addWidget(scrollable) layout = QVBoxLayout() scrollcontent.setLayout(layout) select_view_layout = QHBoxLayout() layout.addLayout(select_view_layout) select_view_label = QLabel('Select view to customize:', self) select_view_layout.addWidget(select_view_label) self.select_view_combo = ViewComboBox(self, self.views) self.select_view_combo.setMinimumSize(150, 20) select_view_layout.addWidget(self.select_view_combo) self.add_view_button = QtGui.QToolButton(self) self.add_view_button.setToolTip('Add view') self.add_view_button.setIcon(QIcon(I('plus.png'))) self.add_view_button.clicked.connect(self.add_view) select_view_layout.addWidget(self.add_view_button) self.delete_view_button = QtGui.QToolButton(self) self.delete_view_button.setToolTip('Delete view') self.delete_view_button.setIcon(QIcon(I('minus.png'))) self.delete_view_button.clicked.connect(self.delete_view) select_view_layout.addWidget(self.delete_view_button) self.rename_view_button = QtGui.QToolButton(self) self.rename_view_button.setToolTip('Rename view') self.rename_view_button.setIcon(QIcon(I('edit-undo.png'))) self.rename_view_button.clicked.connect(self.rename_view) select_view_layout.addWidget(self.rename_view_button) select_view_layout.insertStretch(-1) view_group_box = QGroupBox('Column Options', self) layout.addWidget(view_group_box) view_group_box_layout = QVBoxLayout() view_group_box.setLayout(view_group_box_layout) customise_layout = QGridLayout() view_group_box_layout.addLayout(customise_layout, 1) if self.has_pin_view: columns_label = 'Columns in Default (Left) pane' else: columns_label = 'Columns in view' self.columns_label = QLabel(columns_label, self) self.columns_list = ColumnListWidget(self, self.gui) self.move_column_up_button = QtGui.QToolButton(self) self.move_column_up_button.setToolTip('Move column up') self.move_column_up_button.setIcon(QIcon(I('arrow-up.png'))) self.move_column_down_button = QtGui.QToolButton(self) self.move_column_down_button.setToolTip('Move column down') self.move_column_down_button.setIcon(QIcon(I('arrow-down.png'))) self.move_column_up_button.clicked.connect( self.columns_list.move_column_up) self.move_column_down_button.clicked.connect( self.columns_list.move_column_down) if self.has_pin_view: self.apply_pin_columns_checkbox = QCheckBox( 'Columns in Split (Right) Pane', self) self.apply_pin_columns_checkbox.setToolTip( 'Split Book List will <i>only</i> be shown if this is checked. This will be checked if you save a Split View.' ) self.pin_columns_list = ColumnListWidget(self, self.gui) self.move_pin_column_up_button = QtGui.QToolButton(self) self.move_pin_column_up_button.setToolTip('Move column up') self.move_pin_column_up_button.setIcon(QIcon(I('arrow-up.png'))) self.move_pin_column_down_button = QtGui.QToolButton(self) self.move_pin_column_down_button.setToolTip('Move column down') self.move_pin_column_down_button.setIcon(QIcon( I('arrow-down.png'))) self.move_pin_column_up_button.clicked.connect( self.pin_columns_list.move_column_up) self.move_pin_column_down_button.clicked.connect( self.pin_columns_list.move_column_down) def group_abled(elems, cb): for el in elems: el.setEnabled(cb.isChecked()) pin_abled = partial(group_abled, [ self.pin_columns_list, self.move_pin_column_up_button, self.move_pin_column_down_button ], self.apply_pin_columns_checkbox) pin_abled() self.apply_pin_columns_checkbox.stateChanged.connect(pin_abled) self.sort_label = QLabel('Sort order', self) self.sort_list = SortColumnListWidget(self, self.gui) self.move_sort_up_button = QtGui.QToolButton(self) self.move_sort_up_button.setToolTip('Move sort column up') self.move_sort_up_button.setIcon(QIcon(I('arrow-up.png'))) self.move_sort_down_button = QtGui.QToolButton(self) self.move_sort_down_button.setToolTip('Move sort down') self.move_sort_down_button.setIcon(QIcon(I('arrow-down.png'))) self.move_sort_up_button.clicked.connect(self.sort_list.move_column_up) self.move_sort_down_button.clicked.connect( self.sort_list.move_column_down) layout_col = 0 # calculate layout because pin column only shown if available. customise_layout.addWidget(self.columns_label, 0, layout_col, 1, 1) customise_layout.addWidget(self.columns_list, 1, layout_col, 3, 1) layout_col = layout_col + 1 customise_layout.addWidget(self.move_column_up_button, 1, layout_col, 1, 1) customise_layout.addWidget(self.move_column_down_button, 3, layout_col, 1, 1) layout_col = layout_col + 1 if self.has_pin_view: customise_layout.addWidget(self.apply_pin_columns_checkbox, 0, layout_col, 1, 1) customise_layout.addWidget(self.pin_columns_list, 1, layout_col, 3, 1) layout_col = layout_col + 1 customise_layout.addWidget(self.move_pin_column_up_button, 1, layout_col, 1, 1) customise_layout.addWidget(self.move_pin_column_down_button, 3, layout_col, 1, 1) layout_col = layout_col + 1 customise_layout.addWidget(self.sort_label, 0, layout_col, 1, 1) customise_layout.addWidget(self.sort_list, 1, layout_col, 3, 1) layout_col = layout_col + 1 customise_layout.addWidget(self.move_sort_up_button, 1, layout_col, 1, 1) customise_layout.addWidget(self.move_sort_down_button, 3, layout_col, 1, 1) layout_col = layout_col + 1 search_group_box = QGroupBox("Search and Virtual Library Options", self) layout.addWidget(search_group_box) search_group_box_layout = QVBoxLayout() search_group_box.setLayout(search_group_box_layout) other_layout = QGridLayout() search_group_box_layout.addLayout(other_layout) self.apply_search_checkbox = QCheckBox('Apply saved &search', self) self.apply_search_checkbox.setToolTip( "Apply the selected saved search when the View is activated.") self.saved_search_combo = SearchComboBox( self, entries=saved_searches().names(), empty="(Clear Search)") self.saved_search_combo.setToolTip("Saved search to apply.") # enable/disable combo based on check. self.saved_search_combo.setEnabled( self.apply_search_checkbox.isChecked()) self.apply_search_checkbox.stateChanged.connect( lambda x: self.saved_search_combo.setEnabled( self.apply_search_checkbox.isChecked())) self.apply_virtlib_checkbox = QCheckBox('Switch to &Virtual library', self) self.apply_virtlib_checkbox.setToolTip( "Switch to the selected Virtual library when the View is activated." ) self.virtlib_combo = SearchComboBox( self, entries=self.gui.library_view.model().db.prefs.get( 'virtual_libraries', {}), empty="(No Virtual library)") self.virtlib_combo.setToolTip("Virtual library to switch to.") # enable/disable combo based on check. self.virtlib_combo.setEnabled(self.apply_virtlib_checkbox.isChecked()) self.apply_virtlib_checkbox.stateChanged.connect( lambda x: self.virtlib_combo.setEnabled(self.apply_virtlib_checkbox .isChecked())) self.apply_restriction_checkbox = QCheckBox( 'Apply VL additional search &restriction', self) self.apply_restriction_checkbox.setToolTip( "Apply the selected saved search as a Virtual library additional restriction when the View is activated." ) self.search_restriction_combo = SearchComboBox( self, entries=saved_searches().names(), empty="(Clear VL restriction search)") self.search_restriction_combo.setToolTip( "Saved search to apply as VL additional search restriction.") # enable/disable combo based on check. self.search_restriction_combo.setEnabled( self.apply_restriction_checkbox.isChecked()) self.apply_restriction_checkbox.stateChanged.connect( lambda x: self.search_restriction_combo.setEnabled( self.apply_restriction_checkbox.isChecked())) other_layout.addWidget(self.apply_search_checkbox, 0, 0, 1, 1) other_layout.addWidget(self.saved_search_combo, 0, 1, 1, 1) other_layout.addWidget(self.apply_virtlib_checkbox, 1, 0, 1, 1) other_layout.addWidget(self.virtlib_combo, 1, 1, 1, 1) other_layout.addWidget(self.apply_restriction_checkbox, 2, 0, 1, 1) other_layout.addWidget(self.search_restriction_combo, 2, 1, 1, 1) # other_layout.setRowStretch(4, 1) #layout.addSpacing(10) other_group_box = QGroupBox('General Options', self) layout.addWidget(other_group_box) other_group_box_layout = QGridLayout() other_group_box.setLayout(other_group_box_layout) self.jump_to_top_checkbox = QCheckBox( 'Jump to the top when applying this View', self) jump_to_top = self.library.get(KEY_JUMP_TO_TOP, False) self.jump_to_top_checkbox.setCheckState( Qt.Checked if jump_to_top else Qt.Unchecked) restart_label = QLabel( 'When restarting Calibre or switching to this library...') self.auto_apply_checkbox = QCheckBox('&Automatically apply view:', self) auto_apply = self.library.get(KEY_AUTO_APPLY_VIEW, False) self.auto_apply_checkbox.setCheckState( Qt.Checked if auto_apply else Qt.Unchecked) self.auto_view_combo = ViewComboBox(self, self.views, special=LAST_VIEW_ITEM) self.auto_view_combo.select_view( self.library.get(KEY_VIEW_TO_APPLY, LAST_VIEW_ITEM)) self.auto_view_combo.setMinimumSize(150, 20) info_apply_label = QLabel( 'Enabling this option may override any startup search restriction or ' 'title sort set in Preferences -> Behaviour/Tweaks.') info_apply_label.setWordWrap(True) other_group_box_layout.addWidget(self.jump_to_top_checkbox, 0, 0, 1, 2) other_group_box_layout.addWidget(restart_label, 1, 0, 1, 2) other_group_box_layout.addWidget(self.auto_apply_checkbox, 2, 0, 1, 1) other_group_box_layout.addWidget(self.auto_view_combo, 2, 1, 1, 1) other_group_box_layout.addWidget(info_apply_label, 3, 0, 1, 2) #other_group_box.setMaximumHeight(other_group_box.sizeHint().height()) keyboard_layout = QHBoxLayout() layout.addLayout(keyboard_layout) keyboard_shortcuts_button = QPushButton('Keyboard shortcuts...', self) keyboard_shortcuts_button.setToolTip( _('Edit the keyboard shortcuts associated with this plugin')) keyboard_shortcuts_button.clicked.connect(self.edit_shortcuts) view_prefs_button = QPushButton('&View library preferences...', self) view_prefs_button.setToolTip( _('View data stored in the library database for this plugin')) view_prefs_button.clicked.connect(self.view_prefs) keyboard_layout.addWidget(keyboard_shortcuts_button) keyboard_layout.addWidget(view_prefs_button) keyboard_layout.addStretch(1) # Force an initial display of view information if KEY_LAST_VIEW in list(self.library.keys()): last_view = self.library[KEY_LAST_VIEW] if last_view in self.views: self.select_view_combo.select_view(self.library[KEY_LAST_VIEW]) self.select_view_combo_index_changed(save_previous=False) self.select_view_combo.currentIndexChanged.connect( partial(self.select_view_combo_index_changed, save_previous=True))
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()