class BasicTab(QWidget): 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.copycommentstitle = QCheckBox(_('Copy Source Title to Comments'), self) self.copycommentstitle.setToolTip( _('Copy Title from the source Epub to the Split Epub Comments.')) self.copycommentstitle.setChecked(prefs['copycommentstitle']) self.sl.addWidget(self.copycommentstitle) self.copycommentscallink = QCheckBox( _('Link to Source book in Calibre in Comments'), self) self.copycommentscallink.setToolTip( _('Include a Calibre link to the source Epub in the Split Epub Comments.' )) self.copycommentscallink.setChecked(prefs['copycommentscallink']) self.sl.addWidget(self.copycommentscallink) self.copycommentsidurl = QCheckBox( _('Link to Source URL Identifier in Comments'), self) self.copycommentsidurl.setToolTip( _("Include a link to the source Epub's URL Identifier(if present) in the Split Epub Comments." )) self.copycommentsidurl.setChecked(prefs['copycommentsidurl']) self.sl.addWidget(self.copycommentsidurl) 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 view_prefs(self): d = PrefsViewerDialog(self.plugin_action.gui, PREFS_NAMESPACE) d.exec_() def reset_dialogs(self): for key in dynamic.keys(): if key.startswith('epubsplit_') and key.endswith('_again') \ and dynamic[key] is False: dynamic[key] = True info_dialog(self, _('Done'), _('Confirmation dialogs have all been reset'), show=True, show_copy_button=False)
class BasicTab(QWidget): 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 Summing Columns, Calculate:')) 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.showsums = QCheckBox(_('Sum'), self) self.showsums.setToolTip( _('Sum of numeric columns for selected books.')) self.showsums.setChecked(prefs['showsums']) self.sl.addWidget(self.showsums) if 'Reading List' not in plugin_action.gui.iactions: self.showsums.setEnabled(False) self.showaverages = QCheckBox(_('Average'), self) self.showaverages.setToolTip( _('Average of numeric columns for selected books.')) self.showaverages.setChecked(prefs['showaverages']) self.sl.addWidget(self.showaverages) self.showstds = QCheckBox(_('Standard Deviation'), self) self.showstds.setToolTip( _('Standard Deviation of numeric columns for selected books.')) self.showstds.setChecked(prefs['showstds']) self.sl.addWidget(self.showstds) 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 view_prefs(self): d = PrefsViewerDialog(self.plugin_action.gui, PREFS_NAMESPACE) d.exec_() def reset_dialogs(self): for key in dynamic.keys(): if key.startswith('columnsum_') and key.endswith('_again') \ and dynamic[key] is False: dynamic[key] = True info_dialog(self, _('Done'), _('Confirmation dialogs have all been reset'), show=True, show_copy_button=False)
def setup_ui(self): from calibre.gui2.convert.look_and_feel_ui import Ui_Form f, w = Ui_Form(), QWidget() f.setupUi(w) self.l = l = QFormLayout(self) self.setLayout(l) l.addRow(QLabel(_('Select what style information you want completely removed:'))) self.h = h = QHBoxLayout() for name, text in ( ('fonts', _('&Fonts')), ('margins', _('&Margins')), ('padding', _('&Padding')), ('floats', _('Flo&ats')), ('colors', _('&Colors')), ): c = QCheckBox(text) setattr(self, 'opt_' + name, c) h.addWidget(c) c.setToolTip(getattr(f, 'filter_css_' + name).toolTip()) l.addRow(h) self.others = o = QLineEdit(self) l.addRow(_('&Other CSS properties:'), o) o.setToolTip(f.filter_css_others.toolTip()) if self.current_name is not None: self.filter_current = c = QCheckBox(_('Only filter CSS in the current file (%s)') % self.current_name) l.addRow(c) l.addRow(self.bb)
class SaveOption(Option): """docstring for Save.""" saveOptionChanged = pyqtSignal(bool) def __init__(self, parent, autosaveStatus): super(SaveOption, self).__init__(parent) self.__autosaveStatus = autosaveStatus self.checkBox = QCheckBox(self) self.checkBox.setTristate(False) self.checkBox.setChecked(self.__autosaveStatus) self.checkBox.setText("Autosave") self.checkBox.setToolTip("Autosave") self.checkBox.stateChanged.connect(self.onCheckBoxStateChanged) self.addWidget(self.checkBox) def onCheckBoxStateChanged(self, state): self.saveOptionChanged.emit(self.checkBox.isChecked()) def rollback(self): self.onCheckBoxStateChanged(self.__autosaveStatus) def commit(self): self.__autosaveStatus = self.checkBox.isChecked()
def setup_ui(self): from calibre.gui2.convert.look_and_feel_ui import Ui_Form f, w = Ui_Form(), QWidget() f.setupUi(w) self.l = l = QFormLayout(self) self.setLayout(l) l.addRow(QLabel(_("Select what style information you want completely removed:"))) self.h = h = QHBoxLayout() for name, text in { "fonts": _("&Fonts"), "margins": _("&Margins"), "padding": _("&Padding"), "floats": _("Flo&ats"), "colors": _("&Colors"), }.iteritems(): c = QCheckBox(text) setattr(self, "opt_" + name, c) h.addWidget(c) c.setToolTip(getattr(f, "filter_css_" + name).toolTip()) l.addRow(h) self.others = o = QLineEdit(self) l.addRow(_("&Other CSS properties:"), o) o.setToolTip(f.filter_css_others.toolTip()) if self.current_name is not None: self.filter_current = c = QCheckBox(_("Only filter CSS in the current file (%s)") % self.current_name) l.addRow(c) l.addRow(self.bb)
def do_user_config(self, parent=None): ''' This method shows a configuration dialog for this plugin. It returns True if the user clicks OK, False otherwise. The changes are automatically applied. ''' from PyQt5.Qt import (QDialog, QDialogButtonBox, QVBoxLayout, QLabel, Qt, QLineEdit, QCheckBox) config_dialog = QDialog(parent) button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) v = QVBoxLayout(config_dialog) def size_dialog(): config_dialog.resize(config_dialog.sizeHint()) button_box.accepted.connect(config_dialog.accept) button_box.rejected.connect(config_dialog.reject) config_dialog.setWindowTitle(_('Customize') + ' ' + self.name) from calibre.customize.ui import (plugin_customization, customize_plugin) help_text = self.customization_help(gui=True) help_text = QLabel(help_text, config_dialog) help_text.setWordWrap(True) help_text.setTextInteractionFlags( Qt.TextInteractionFlag.LinksAccessibleByMouse | Qt.TextInteractionFlag.LinksAccessibleByKeyboard) help_text.setOpenExternalLinks(True) v.addWidget(help_text) bf = QCheckBox(_('Add linked files in breadth first order')) bf.setToolTip( _('Normally, when following links in HTML files' ' calibre does it depth first, i.e. if file A links to B and ' ' C, but B links to D, the files are added in the order A, B, D, C. ' ' With this option, they will instead be added as A, B, C, D')) sc = plugin_customization(self) if not sc: sc = '' sc = sc.strip() enc = sc.partition('|')[0] bfs = sc.partition('|')[-1] bf.setChecked(bfs == 'bf') sc = QLineEdit(enc, config_dialog) v.addWidget(sc) v.addWidget(bf) v.addWidget(button_box) size_dialog() config_dialog.exec_() if config_dialog.result() == QDialog.DialogCode.Accepted: sc = unicode_type(sc.text()).strip() if bf.isChecked(): sc += '|bf' customize_plugin(self, sc) return config_dialog.result()
def cb(name, text, tt=''): ans = QCheckBox(text) l.addWidget(ans) prefs_key = ans.prefs_key = 'choose-merge-cb-' + name ans.setChecked(gprefs.get(prefs_key, True)) connect_lambda(ans.stateChanged, self, lambda self, state: self.state_changed(getattr(self, name), state), type=Qt.QueuedConnection) if tt: ans.setToolTip(tt) setattr(self, name, ans) return ans
def do_user_config(self, parent=None): ''' This method shows a configuration dialog for this plugin. It returns True if the user clicks OK, False otherwise. The changes are automatically applied. ''' from PyQt5.Qt import (QDialog, QDialogButtonBox, QVBoxLayout, QLabel, Qt, QLineEdit, QCheckBox) config_dialog = QDialog(parent) button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) v = QVBoxLayout(config_dialog) def size_dialog(): config_dialog.resize(config_dialog.sizeHint()) button_box.accepted.connect(config_dialog.accept) button_box.rejected.connect(config_dialog.reject) config_dialog.setWindowTitle(_('Customize') + ' ' + self.name) from calibre.customize.ui import (plugin_customization, customize_plugin) help_text = self.customization_help(gui=True) help_text = QLabel(help_text, config_dialog) help_text.setWordWrap(True) help_text.setTextInteractionFlags(Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard) help_text.setOpenExternalLinks(True) v.addWidget(help_text) bf = QCheckBox(_('Add linked files in breadth first order')) bf.setToolTip(_('Normally, when following links in HTML files' ' calibre does it depth first, i.e. if file A links to B and ' ' C, but B links to D, the files are added in the order A, B, D, C. ' ' With this option, they will instead be added as A, B, C, D')) sc = plugin_customization(self) if not sc: sc = '' sc = sc.strip() enc = sc.partition('|')[0] bfs = sc.partition('|')[-1] bf.setChecked(bfs == 'bf') sc = QLineEdit(enc, config_dialog) v.addWidget(sc) v.addWidget(bf) v.addWidget(button_box) size_dialog() config_dialog.exec_() if config_dialog.result() == QDialog.Accepted: sc = unicode(sc.text()).strip() if bf.isChecked(): sc += '|bf' customize_plugin(self, sc) return config_dialog.result()
class ConfigWidget(DefaultConfigWidget): def __init__(self, plugin): DefaultConfigWidget.__init__(self, plugin) c = plugin_prefs[STORE_NAME] other_group_box = QGroupBox('Other options', self) self.l.addWidget(other_group_box, self.l.rowCount(), 0, 1, 2) other_group_box_layout = QGridLayout() other_group_box.setLayout(other_group_box_layout) # Maximum # of title/author searches to review. max_label = QLabel( 'Maximum title/author search matches to evaluate (1 = fastest):', self) max_label.setToolTip( 'ISFDB doesn\'t always have links to large covers for every ISBN\n' 'of the same book. Increasing this value will take effect when doing\n' 'title/author searches to consider more ISBN editions.\n\n' 'This will increase the potential likelihood of getting a larger cover,\n' 'though does not guarantee it.') other_group_box_layout.addWidget(max_label, 0, 0, 1, 1) self.max_downloads_spin = QtGui.QSpinBox(self) self.max_downloads_spin.setMinimum(1) self.max_downloads_spin.setMaximum(5) self.max_downloads_spin.setProperty( 'value', c.get(KEY_MAX_DOWNLOADS, DEFAULT_STORE_VALUES[KEY_MAX_DOWNLOADS])) other_group_box_layout.addWidget(self.max_downloads_spin, 0, 1, 1, 1) other_group_box_layout.setColumnStretch(2, 1) # Contents field, if possible. self.contents_checkbox = QCheckBox( 'Append Contents if available to comments', self) self.contents_checkbox.setToolTip( 'Choosing this option will write the Contents section to the comments\n' 'field, if such a section exists.') self.contents_checkbox.setChecked( c.get(KEY_APPEND_CONTENTS, DEFAULT_STORE_VALUES[KEY_APPEND_CONTENTS])) other_group_box_layout.addWidget(self.contents_checkbox, 2, 0, 1, 3) def commit(self): DefaultConfigWidget.commit(self) new_prefs = {} new_prefs[KEY_MAX_DOWNLOADS] = int( unicode(self.max_downloads_spin.value())) new_prefs[KEY_APPEND_CONTENTS] = self.contents_checkbox.checkState( ) == Qt.Checked plugin_prefs[STORE_NAME] = new_prefs
class AddOverDiscardDialog(QDialog): def __init__(self, parent, icon, text, over=True): QDialog.__init__(self, parent) self.state = None layout = QVBoxLayout(self) self.setLayout(layout) self.setWindowTitle(_('UnMerge Epub')) label = QLabel(text) label.setOpenExternalLinks(True) label.setWordWrap(True) layout.addWidget(label) self.applyall = QCheckBox(_('Apply to all EPUBs?'), self) self.applyall.setToolTip( _('Apply the same action to the rest of the EPUBs after this.')) layout.addWidget(self.applyall) button_box = QDialogButtonBox(self) button = button_box.addButton(_("Add"), button_box.AcceptRole) button.clicked.connect(self.add) if over: button = button_box.addButton(_("Overwrite"), button_box.AcceptRole) button.clicked.connect(self.over) button = button_box.addButton(_("Discard"), button_box.AcceptRole) button.clicked.connect(self.discard) button_box.accepted.connect(self.accept) layout.addWidget(button_box) def get_applyall(self): return self.applyall.isChecked() def add(self): self.state = "add" def over(self): self.state = "over" def discard(self): self.state = "discard"
class AddOverDiscardDialog(QDialog): def __init__(self, parent, icon, text, over=True): QDialog.__init__(self, parent) self.state=None layout = QVBoxLayout(self) self.setLayout(layout) self.setWindowTitle(_('UnMerge Epub')) label = QLabel(text) label.setOpenExternalLinks(True) label.setWordWrap(True) layout.addWidget(label) self.applyall = QCheckBox(_('Apply to all EPUBs?'),self) self.applyall.setToolTip(_('Apply the same action to the rest of the EPUBs after this.')) layout.addWidget(self.applyall) button_box = QDialogButtonBox(self) button = button_box.addButton(_("Add"), button_box.AcceptRole) button.clicked.connect(self.add) if over: button = button_box.addButton(_("Overwrite"), button_box.AcceptRole) button.clicked.connect(self.over) button = button_box.addButton(_("Discard"), button_box.AcceptRole) button.clicked.connect(self.discard) button_box.accepted.connect(self.accept) layout.addWidget(button_box) def get_applyall(self): return self.applyall.isChecked() def add(self): self.state="add" def over(self): self.state="over" def discard(self): self.state="discard"
class LibraryCodesTab(QWidget): def __init__(self, mygui, myguidb, mymainprefs, myparam_dict, myuiexit, mysavedialoggeometry): super(LibraryCodesTab, self).__init__() #----------------------------------------------------- #----------------------------------------------------- self.gui = mygui #----------------------------------------------------- #----------------------------------------------------- self.guidb = myguidb #----------------------------------------------------- #----------------------------------------------------- self.lib_path = self.gui.library_view.model().db.library_path #----------------------------------------------------- #----------------------------------------------------- self.mytabprefs = mymainprefs #----------------------------------------------------- #----------------------------------------------------- self.param_dict = myparam_dict #----------------------------------------------------- #----------------------------------------------------- self.ui_exit = myuiexit #----------------------------------------------------- #----------------------------------------------------- self.save_dialog_geometry = mysavedialoggeometry #----------------------------------------------------- #----------------------------------------------------- font = QFont() font.setBold(False) font.setPointSize(10) #----------------------------------------------------- self.layout_top = QVBoxLayout() self.layout_top.setSpacing(0) self.layout_top.setAlignment(Qt.AlignLeft) self.setLayout(self.layout_top) #----------------------------------------------------- self.scroll_area_frame = QScrollArea() self.scroll_area_frame.setAlignment(Qt.AlignLeft) self.scroll_area_frame.setWidgetResizable(True) self.scroll_area_frame.ensureVisible(400, 400) self.layout_top.addWidget( self.scroll_area_frame ) # the scroll area is now the child of the parent of self.layout_top # NOTE: the self.scroll_area_frame.setWidget(self.scroll_widget) is at the end of the init() AFTER all children have been created and assigned to a layout... #----------------------------------------------------- self.scroll_widget = QWidget() self.layout_top.addWidget( self.scroll_widget ) # causes automatic reparenting of QWidget to the parent of self.layout_top, which is: self . #----------------------------------------------------- self.layout_frame = QVBoxLayout() self.layout_frame.setSpacing(0) self.layout_frame.setAlignment(Qt.AlignLeft) self.scroll_widget.setLayout( self.layout_frame ) # causes automatic reparenting of any widget later added to self.layout_frame to the parent of self.layout_frame, which is: QWidget . #----------------------------------------------------- self.lc_groupbox = QGroupBox('Settings:') self.lc_groupbox.setMaximumWidth(400) self.lc_groupbox.setToolTip( "<p style='white-space:wrap'>The settings that control 'Library Codes'. Using only ISBN or ISSN or Author/Title, Library Codes for selected books will be derived using the Current Settings." ) self.layout_frame.addWidget(self.lc_groupbox) self.lc_layout = QGridLayout() self.lc_groupbox.setLayout(self.lc_layout) #----------------------------------------------------- #----------------------------------------------------- #----------------------------------------------------- #----------------------------------------------------- self.spacing0 = QLabel() self.layout_frame.addWidget(self.spacing0) self.spacing0.setMaximumHeight(20) #----------------------------------------------------- #----------------------------------------------------- #----------------------------------------------------- self.button_box = QDialogButtonBox() self.button_box.setOrientation(Qt.Horizontal) self.button_box.setCenterButtons(True) self.layout_frame.addWidget(self.button_box) #----------------------------------------------------- #----------------------------------------------------- #----------------------------------------------------- #----------------------------------------------------- #----------------------------------------------------- #----------------------------------------------------- self.push_button_save_only = QPushButton("Save") self.push_button_save_only.clicked.connect(self.save_settings) self.push_button_save_only.setDefault(True) self.push_button_save_only.setFont(font) self.push_button_save_only.setToolTip( "<p style='white-space:wrap'>Save all user settings.") self.button_box.addButton(self.push_button_save_only, 0) self.push_button_exit_only = QPushButton("Exit") self.push_button_exit_only.clicked.connect(self.exit_only) self.push_button_exit_only.setDefault(False) self.push_button_exit_only.setFont(font) self.push_button_exit_only.setToolTip( "<p style='white-space:wrap'>Exit immediately without saving anything." ) self.button_box.addButton(self.push_button_exit_only, 0) #----------------------------------------------------- #----------------------------------------------------- #----------------------------------------------------- #----------------------------------------------------- r = 4 self.ddc_labelname = QLineEdit(self) self.ddc_labelname.setText(self.mytabprefs['DDC']) self.ddc_labelname.setFont(font) self.ddc_labelname.setToolTip( "<p style='white-space:wrap'>Custom Column Search/Lookup #name for DDC.<br><br>See: https://www.oclc.org/dewey/features/summaries.en.html" ) self.ddc_labelname.setMaximumWidth(100) self.lc_layout.addWidget(self.ddc_labelname, r, 0) self.ddc_activate_checkbox = QCheckBox( "Activate 'Dewey Decimal Code' Classification?") self.ddc_activate_checkbox.setToolTip( "<p style='white-space:wrap'>Do you want to derive DDC?") r = r + 1 self.lc_layout.addWidget(self.ddc_activate_checkbox, r, 0) if prefs['DDC_IS_ACTIVE'] == unicode_type(S_TRUE): self.ddc_activate_checkbox.setChecked(True) else: self.ddc_activate_checkbox.setChecked(False) #----------------------------------------------------- self.spacing1 = QLabel() r = r + 1 self.lc_layout.addWidget(self.spacing1, r, 0) self.spacing1.setMaximumHeight(10) #----------------------------------------------------- self.lcc_labelname = QLineEdit(self) self.lcc_labelname.setText(self.mytabprefs['LCC']) self.lcc_labelname.setFont(font) self.lcc_labelname.setToolTip( "<p style='white-space:wrap'>Custom Column Search/Lookup #name for LCC.<br><br>See: http://www.loc.gov/catdir/cpso/lcco/ " ) self.lcc_labelname.setMaximumWidth(100) r = r + 4 self.lc_layout.addWidget(self.lcc_labelname, r, 0) self.lcc_activate_checkbox = QCheckBox( "Activate 'Library of Congress Code' Classification?") self.lcc_activate_checkbox.setToolTip( "<p style='white-space:wrap'>Do you want to derive LCC?") r = r + 1 self.lc_layout.addWidget(self.lcc_activate_checkbox, r, 0) if prefs['LCC_IS_ACTIVE'] == unicode_type(S_TRUE): self.lcc_activate_checkbox.setChecked(True) else: self.lcc_activate_checkbox.setChecked(False) #----------------------------------------------------- self.spacing2 = QLabel("") r = r + 1 self.lc_layout.addWidget(self.spacing2, r, 0) self.spacing2.setMaximumHeight(10) #----------------------------------------------------- self.fast_labelname = QLineEdit(self) self.fast_labelname.setText(self.mytabprefs['FAST']) self.fast_labelname.setFont(font) self.fast_labelname.setToolTip( "<p style='white-space:wrap'>Custom Column Search/Lookup #name for FAST Tag Values. " ) self.fast_labelname.setMinimumWidth(100) self.fast_labelname.setMaximumWidth(100) r = r + 4 self.lc_layout.addWidget(self.fast_labelname, r, 0) self.fast_activate_checkbox = QCheckBox("Activate 'FAST' Tags?") self.fast_activate_checkbox.setToolTip( "<p style='white-space:wrap'>Do you want to derive FAST Tags?\ <br><br>Text. Behaves like Tags. Not Names.<br><br>" ) r = r + 1 self.lc_layout.addWidget(self.fast_activate_checkbox, r, 0) if prefs['FAST_IS_ACTIVE'] == unicode_type(S_TRUE): self.fast_activate_checkbox.setChecked(True) else: self.fast_activate_checkbox.setChecked(False) #----------------------------------------------------- self.spacing6 = QLabel("") r = r + 1 self.lc_layout.addWidget(self.spacing6, r, 0) self.spacing6.setMaximumHeight(10) #----------------------------------------------------- self.oclc_labelname = QLineEdit(self) self.oclc_labelname.setText(self.mytabprefs['OCLC']) self.oclc_labelname.setFont(font) self.oclc_labelname.setToolTip( "<p style='white-space:wrap'>Custom Column Search/Lookup #name for OCLC-OWI.<br><br>See: #http://classify.oclc.org/classify2/ " ) self.oclc_labelname.setMaximumWidth(100) r = r + 4 self.lc_layout.addWidget(self.oclc_labelname, r, 0) self.oclc_activate_checkbox = QCheckBox( "Activate 'Online Computer Library Center' Work ID Code?") self.oclc_activate_checkbox.setToolTip( "<p style='white-space:wrap'>Do you want to derive OCLC-OWI?") r = r + 1 self.lc_layout.addWidget(self.oclc_activate_checkbox, r, 0) if self.mytabprefs['OCLC_IS_ACTIVE'] == unicode_type(S_TRUE): self.oclc_activate_checkbox.setChecked(True) else: self.oclc_activate_checkbox.setChecked(False) #----------------------------------------------------- self.spacing5 = QLabel("") r = r + 1 self.lc_layout.addWidget(self.spacing5, r, 0) self.spacing5.setMaximumHeight(10) #----------------------------------------------------- self.lc_author_details_labelname = QLineEdit(self) self.lc_author_details_labelname.setText( self.mytabprefs['EXTRA_AUTHOR_DETAILS']) self.lc_author_details_labelname.setFont(font) self.lc_author_details_labelname.setToolTip( "<p style='white-space:wrap'>Custom Column Search/Lookup #name for 'LC Extra Author Details'.\ <br><br>Text. Behaves like Tags. Not Names.<br><br>" ) self.lc_author_details_labelname.setMaximumWidth(100) r = r + 4 self.lc_layout.addWidget(self.lc_author_details_labelname, r, 0) self.lc_author_details_checkbox = QCheckBox( "Activate 'Library Codes Extra Author Details'?") self.lc_author_details_checkbox.setToolTip( "<p style='white-space:wrap'>Do you want to add (never delete or replace) any available Tag-like values to this Custom Column if they are associated with the OCLC-OWI Identifier?" ) r = r + 1 self.lc_layout.addWidget(self.lc_author_details_checkbox, r, 0) if self.mytabprefs['EXTRA_AUTHOR_DETAILS_IS_ACTIVE'] == unicode_type( S_TRUE): self.lc_author_details_checkbox.setChecked(True) else: self.lc_author_details_checkbox.setChecked(False) #----------------------------------------------------- #----------------------------------------------------- self.spacing4 = QLabel() r = r + 1 self.lc_layout.addWidget(self.spacing4, r, 0) self.spacing4.setMaximumHeight(10) #----------------------------------------------------- font.setBold(False) font.setPointSize(7) #----------------------------------------------------- self.push_button_autoadd_custom_columns = QPushButton( "Automatically Add Activated Custom Columns?") self.push_button_autoadd_custom_columns.clicked.connect( self.autoadd_custom_columns) self.push_button_autoadd_custom_columns.setDefault(False) self.push_button_autoadd_custom_columns.setFont(font) self.push_button_autoadd_custom_columns.setToolTip( "<p style='white-space:wrap'>Do you want to automatically add the Custom Columns selected above?<br><br>If you have any issues, please add them manually." ) r = r + 4 self.lc_layout.addWidget(self.push_button_autoadd_custom_columns, r, 0) self.push_button_autoadd_custom_columns.setMaximumWidth(250) #----------------------------------------------------- self.lc_custom_columns_generation_label = QLabel() r = r + 1 self.lc_layout.addWidget(self.lc_custom_columns_generation_label, r, 0) self.lc_custom_columns_generation_label.setText( " ") self.lc_custom_columns_generation_label.setMaximumHeight(10) self.lc_custom_columns_generation_label.setFont(font) self.oclc_identifier_only_checkbox = QCheckBox( "Always Create OCLC-OWI as an 'Identifier' (à la ISBN)?") self.oclc_identifier_only_checkbox.setToolTip( "<p style='white-space:wrap'>Do you want to update Calibre's Identifiers for an Identifier of 'OCLC-OWI',\ regardless of whether you want its own Custom Column updated?\ <br><br>REQUIRED to derive DDC/LCC using Author/Title." ) r = r + 2 self.lc_layout.addWidget(self.oclc_identifier_only_checkbox, r, 0) if prefs['OCLC_IDENTIFIER'] == unicode_type(S_TRUE): self.oclc_identifier_only_checkbox.setChecked(True) else: self.oclc_identifier_only_checkbox.setChecked(False) #----------------------------------------------------- self.spacing3 = QLabel("") r = r + 1 self.lc_layout.addWidget(self.spacing3, r, 0) self.spacing3.setMaximumHeight(10) #----------------------------------------------------- font.setBold(False) font.setPointSize(10) #----------------------------------------------------- self.lc_genre_labelname = QLineEdit(self) self.lc_genre_labelname.setText(self.mytabprefs['GENRE']) self.lc_genre_labelname.setFont(font) self.lc_genre_labelname.setToolTip( "<p style='white-space:wrap'>Custom Column Search/Lookup #name for 'Genre'.\ <br><br>Text. Behaves like Tags.<br><br>" ) self.lc_genre_labelname.setMaximumWidth(100) r = r + 1 self.lc_layout.addWidget(self.lc_genre_labelname, r, 0) self.lc_checkbox_buttongroup = QButtonGroup() self.lc_checkbox_buttongroup.setExclusive(True) self.lc_genre_ddc_checkbox = QCheckBox( "Update 'Genre' using DDC-to-Genre Mappings?") self.lc_genre_ddc_checkbox.setToolTip( "<p style='white-space:wrap'>Do you want LC to update 'Genre' using the DDC-to-Genre mapping in Table _lc_genre_mapping?" ) r = r + 1 self.lc_layout.addWidget(self.lc_genre_ddc_checkbox, r, 0) self.lc_genre_lcc_checkbox = QCheckBox( "Update 'Genre' using LCC-to-Genre Mappings?") self.lc_genre_lcc_checkbox.setToolTip( "<p style='white-space:wrap'>Do you want LC to update 'Genre' using the LCC-to-Genre mapping in Table _lc_genre_mapping?" ) r = r + 1 self.lc_layout.addWidget(self.lc_genre_lcc_checkbox, r, 0) self.lc_genre_inactive_checkbox = QCheckBox( "Do not update 'Genre' at all") self.lc_genre_inactive_checkbox.setToolTip( "<p style='white-space:wrap'>Do no 'Genre' processing at all?") r = r + 1 self.lc_layout.addWidget(self.lc_genre_inactive_checkbox, r, 0) self.lc_checkbox_buttongroup.addButton(self.lc_genre_ddc_checkbox) self.lc_checkbox_buttongroup.addButton(self.lc_genre_lcc_checkbox) self.lc_checkbox_buttongroup.addButton(self.lc_genre_inactive_checkbox) if self.mytabprefs['GENRE_DDC_IS_ACTIVE'] == unicode_type(S_TRUE): self.lc_genre_ddc_checkbox.setChecked(True) elif self.mytabprefs['GENRE_LCC_IS_ACTIVE'] == unicode_type(S_TRUE): self.lc_genre_lcc_checkbox.setChecked(True) elif self.mytabprefs['GENRE_IS_INACTIVE'] == unicode_type(S_TRUE): self.lc_genre_inactive_checkbox.setChecked(True) self.lc_exact_match_checkbox = QCheckBox( "DDC: Require an 'Exact Match', not a 'Best Match'?") self.lc_exact_match_checkbox.setToolTip( "<p style='white-space:wrap'>Check this checkbox if you want an exact DDC match to be required in Table _lc_genre_mapping. Otherwise, a 'best match' will be used via progressive shortening from right to left, but not past any decimal point. If there is no decimal point in a book's DDC, then no progressive shortening will be performed at all." ) r = r + 1 self.lc_layout.addWidget(self.lc_exact_match_checkbox, r, 0) if self.mytabprefs['GENRE_EXACT_MATCH'] == unicode_type(S_TRUE): self.lc_exact_match_checkbox.setChecked(True) self.spin_lcc = QSpinBox(self) self.spin_lcc.setMinimum(1) self.spin_lcc.setMaximum(50) self.spin_lcc.setProperty('value', prefs['GENRE_LCC_MATCH_LENGTH']) self.spin_lcc.setMaximumWidth(250) self.spin_lcc.setSuffix(" LCC: Maximum Length to Match") self.spin_lcc.setToolTip( "<p style='white-space:nowrap'>Maximum number of characters in the LCC that should be used to map to the 'Genre', starting from the left. A maximum of 1 guarantees a (broad) match.\ <br><br>LCCs are structured with either 1 or 2 beginning letters, so 2-character LCCs have special matching logic.\ <br><br>Example: Assume maximum = 2 for a LCC of 'Q1': Q1 would be attempted. If it failed, because the 2nd digit is a number, 'Q' would be attempted.\ <br><br>Example: Assume maximum = 2 for a LCC of 'PN1969.C65': PN would be attempted. If it failed, nothing else would be attempted.\ <br><br>Example: Assume maximum = 4 for a LCC of 'PN1969.C65': PN19 would be attempted. If it failed, nothing else would be attempted.\ <br><br>Example: Assume maximum = 4 for a LCC of 'Q1': Q1 would be attempted. If it failed, because the 2nd digit is a number, 'Q' would be attempted.\ <br><br>Example: Assume maximum = 4 for a LCC of 'Q389': Q389 would be attempted. If it failed, nothing else would be attempted." ) r = r + 2 self.lc_layout.addWidget(self.spin_lcc, r, 0) #----------------------------------------------------- #----------------------------------------------------- #----------------------------------------------------- #----------------------------------------------------- #----------------------------------------------------- #----------------------------------------------------- self.scroll_widget.resize(self.sizeHint()) #----------------------------------------------------- #----------------------------------------------------- self.scroll_area_frame.setWidget( self.scroll_widget ) # now that all widgets have been created and assigned to a layout... #----------------------------------------------------- #----------------------------------------------------- self.scroll_area_frame.resize(self.sizeHint()) #----------------------------------------------------- #----------------------------------------------------- self.resize(self.sizeHint()) #----------------------------------------------------------------------------------------- def validate(self): return True #----------------------------------------------------------------------------------------- def autoadd_custom_columns(self): number_active = self.save_settings() if number_active == 0: return error_dialog( self.gui, _('Automatically Add Custom Columns'), _('No Activated Library Codes Custom Columns Found. Nothing to Add.' ), show=True) self.cli_param_list = self.create_cli_parameters() is_valid, restart_required = self.create_new_lc_custom_columns( self.cli_param_list) if is_valid: if restart_required: self.lc_custom_columns_generation_label.setText( "Addition of Custom Columns Complete. Restart Calibre Now." ) self.repaint() info_dialog( self.gui, 'Automatically Add Custom Columns', 'All Selected Custom Customs Were Added If They Did Not Already Exist. Please Restart Calibre now.' ).show() else: self.lc_custom_columns_generation_label.setText( "Selected Custom Columns Already Exist. Nothing Done.") self.repaint() else: self.lc_custom_columns_generation_label.setText( "Not Completed. Please Restart Calibre, then Add Manually.") self.repaint() msg = "Fatal error experienced in adding new Custom Columns." error_dialog(self.gui, _('Automatically Add Custom Columns'), _(msg), show=True) #----------------------------------------------------------------------------------------- def create_cli_parameters(self): try: del self.cli_param_list except: pass self.cli_param_list = [] temp_list = [] cc_taglike_list = [] cc_fast_name = "" if self.mytabprefs['DDC_IS_ACTIVE'] == unicode_type(S_TRUE): cc = self.mytabprefs['DDC'] if cc > '#': cc = cc.replace('#', "").strip() cc = unicode_type(cc) temp_list.append(cc) else: error_dialog(self.gui, _('Automatically Add Custom Columns'), _('Illogical DDC Settings. Please Correct.'), show=True) return self.cli_param_list if self.mytabprefs['LCC_IS_ACTIVE'] == unicode_type(S_TRUE): cc = self.mytabprefs['LCC'] if cc > '#': cc = cc.replace('#', "").strip() cc = unicode_type(cc) temp_list.append(cc) else: error_dialog(self.gui, _('Automatically Add Custom Columns'), _('Illogical LCC Settings. Please Correct.'), show=True) return self.cli_param_list if self.mytabprefs['FAST_IS_ACTIVE'] == unicode_type(S_TRUE): cc = self.mytabprefs['FAST'] if cc > '#': cc = cc.replace('#', "").strip() cc = unicode_type(cc) temp_list.append(cc) cc_taglike_list.append(cc) cc_fast_name = cc else: error_dialog(self.gui, _('Automatically Add Custom Columns'), _('Illogical FAST Settings. Please Correct.'), show=True) return self.cli_param_list if self.mytabprefs['OCLC_IS_ACTIVE'] == unicode_type(S_TRUE): cc = self.mytabprefs['OCLC'] if cc > '#': cc = cc.replace('#', "").strip() cc = unicode_type(cc) temp_list.append(cc) else: error_dialog(self.gui, _('Automatically Add Custom Columns'), _('Illogical OCLC Settings. Please Correct.'), show=True) return self.cli_param_list if self.mytabprefs['EXTRA_AUTHOR_DETAILS_IS_ACTIVE'] == unicode_type( S_TRUE): cc = self.mytabprefs['EXTRA_AUTHOR_DETAILS'] if cc > '#': cc = cc.replace('#', "").strip() cc = unicode_type(cc) temp_list.append(cc) cc_taglike_list.append(cc) else: error_dialog( self.gui, _('Automatically Add Custom Columns'), _('Illogical LC Extra Author Details Settings. Please Correct.' ), show=True) return self.cli_param_list else: pass if len(temp_list) == 0: del temp_list error_dialog(self.gui, _('Automatically Add Custom Columns'), _('Nothing to do. Please Review Settings.'), show=True) return self.cli_param_list cc_to_add_list = [] # for each cc currently set to active, create a parameter...but only if the cc does NOT already exist... my_db, my_cursor, is_valid = self.apsw_connect_to_library() if not is_valid: error_dialog(self.gui, _('Automatically Add Custom Columns'), _('Database Connection Error. Restart Calibre.'), show=True) return self.lc_custom_columns_generation_label.setText( "...Adding Custom Columns...") self.repaint() mysql = "SELECT label,name FROM custom_columns" my_cursor.execute(mysql) tmp_rows = my_cursor.fetchall() if not tmp_rows: for cc in temp_list: cc_to_add_list.append(cc) #END FOR else: if len(tmp_rows) == 0: for cc in temp_list: cc_to_add_list.append(cc) #END FOR else: for cc in temp_list: label_already_exists = False for row in tmp_rows: label, name = row if unicode_type(label) == unicode_type(cc): label_already_exists = True break else: continue #END FOR if not label_already_exists: cc_to_add_list.append(cc) #END FOR del tmp_rows del temp_list if len(cc_to_add_list) == 0: return self.cli_param_list cc_to_add_list.sort() for label in cc_to_add_list: label = unicodedata.normalize('NFKD', label).encode('ascii', 'ignore') label = unicode_type(label) label = label.lower() name = label.upper() datatype = 'text' if label in cc_taglike_list: is_multiple = "--is-multiple" if label == cc_fast_name: name = "FAST Tags" else: name = '"LC Extra Author Details"' param = is_multiple + '|||' + label + '|||' + name + '|||' + datatype else: param = label + '|||' + name + '|||' + datatype param = param.replace("[LIBRARY]", self.lib_path) self.cli_param_list.append(param) #END FOR del cc_to_add_list return self.cli_param_list #----------------------------------------------------------------------------------------- #----------------------------------------------------------------------------------------- def apsw_connect_to_library(self): my_db = self.gui.library_view.model().db self.lib_path = my_db.library_path self.lib_path = self.lib_path.replace(os.sep, '/') if isbytestring(self.lib_path): self.lib_path = self.lib_path.decode(filesystem_encoding) path = my_db.library_path if isbytestring(path): path = path.decode(filesystem_encoding) path = path.replace(os.sep, '/') path = os.path.join(path, 'metadata.db') path = path.replace(os.sep, '/') if isbytestring(path): path = path.decode(filesystem_encoding) if path.endswith("/"): path = path[0:-1] if path.count("metadata.db") == 0: path = path + "/metadata.db" try: my_db = apsw.Connection(path) is_valid = True except Exception as e: if DEBUG: print("path to metadata.db is: ", path) if DEBUG: print("error: ", as_unicode(e)) is_valid = False return None, None, is_valid my_cursor = my_db.cursor() mysql = "PRAGMA main.busy_timeout = 5000;" #PRAGMA busy_timeout = milliseconds; my_cursor.execute(mysql) return my_db, my_cursor, is_valid #----------------------------------------------------------------------------------------- def exit_only(self): self.save_dialog_geometry() # inherited from SizePersistedDialog self.ui_exit() #----------------------------------------------------------------------------------------- def save_settings(self): self.save_dialog_geometry() # inherited from SizePersistedDialog self.mytabprefs['DDC'] = self.ddc_labelname.text() self.mytabprefs['LCC'] = self.lcc_labelname.text() self.mytabprefs['FAST'] = self.fast_labelname.text() self.mytabprefs['OCLC'] = self.oclc_labelname.text() self.mytabprefs['DDC_IS_ACTIVE'] = unicode_type( self.ddc_activate_checkbox.isChecked()) self.mytabprefs['LCC_IS_ACTIVE'] = unicode_type( self.lcc_activate_checkbox.isChecked()) self.mytabprefs['FAST_IS_ACTIVE'] = unicode_type( self.fast_activate_checkbox.isChecked()) self.mytabprefs['OCLC_IS_ACTIVE'] = unicode_type( self.oclc_activate_checkbox.isChecked()) self.mytabprefs['OCLC_IDENTIFIER'] = unicode_type( self.oclc_identifier_only_checkbox.isChecked()) label = self.mytabprefs['DDC'] label = unicode_type(label) label = unicodedata.normalize('NFKD', label).encode('ascii', 'ignore') label = label.lower().strip() if not label.startswith("#"): label = "#" + label if label == "#": label = "" self.ddc_activate_checkbox.setChecked(False) self.mytabprefs['DDC'] = unicode_type(label) label = self.mytabprefs['LCC'] label = unicode_type(label) label = unicodedata.normalize('NFKD', label).encode('ascii', 'ignore') label = label.lower().strip() if not label.startswith("#"): label = "#" + label if label == "#": label = "" self.lcc_activate_checkbox.setChecked(False) self.mytabprefs['LCC'] = unicode_type(label) label = self.mytabprefs['FAST'] label = unicode_type(label) label = unicodedata.normalize('NFKD', label).encode('ascii', 'ignore') label = label.lower().strip() if not label.startswith("#"): label = "#" + label if label == "#": label = "" self.fast_activate_checkbox.setChecked(False) self.mytabprefs['FAST'] = unicode_type(label) label = self.mytabprefs['OCLC'] label = unicode_type(label) label = unicodedata.normalize('NFKD', label).encode('ascii', 'ignore') label = label.lower().strip() if not label.startswith("#"): label = "#" + label if label == "#": label = "" self.oclc_activate_checkbox.setChecked(False) self.mytabprefs['OCLC'] = unicode_type(label) if self.mytabprefs['DDC'] == unicode_type( "") and self.mytabprefs['LCC'] == unicode_type( "") and self.mytabprefs['FAST'] == unicode_type( "") and self.mytabprefs['OCLC'] == unicode_type(""): self.mytabprefs['DDC'] = unicode_type("#ddc") self.mytabprefs['LCC'] = unicode_type("#lcc") self.mytabprefs['FAST'] = unicode_type("#fast") self.mytabprefs['OCLC'] = unicode_type("#oclc_owi") else: if self.mytabprefs['DDC'] == unicode_type( "") and self.mytabprefs['LCC'] == unicode_type(""): self.oclc_identifier_only_checkbox.setChecked(False) #--------------------------------------- s = unicode_type(self.lc_genre_labelname.text()) s = s.strip() if s.startswith("#") and len(s) > 1: self.mytabprefs['GENRE'] = unicode_type(s) self.mytabprefs['GENRE_DDC_IS_ACTIVE'] = unicode_type( self.lc_genre_ddc_checkbox.isChecked()) self.mytabprefs['GENRE_LCC_IS_ACTIVE'] = unicode_type( self.lc_genre_lcc_checkbox.isChecked()) self.mytabprefs['GENRE_IS_INACTIVE'] = unicode_type( self.lc_genre_inactive_checkbox.isChecked()) self.mytabprefs['GENRE_EXACT_MATCH'] = unicode_type( self.lc_exact_match_checkbox.isChecked()) self.mytabprefs['GENRE_LCC_MATCH_LENGTH'] = self.spin_lcc.value() else: self.mytabprefs['GENRE'] = unicode_type("#genre") self.lc_genre_labelname.setText(unicode_type("#genre")) self.lc_genre_ddc_checkbox.setChecked(False) self.lc_genre_lcc_checkbox.setChecked(False) self.lc_genre_inactive_checkbox.setChecked(True) self.mytabprefs['GENRE_DDC_IS_ACTIVE'] = unicode_type(S_FALSE) self.mytabprefs['GENRE_LCC_IS_ACTIVE'] = unicode_type(S_FALSE) self.mytabprefs['GENRE_IS_INACTIVE'] = unicode_type(S_TRUE) self.mytabprefs['GENRE_EXACT_MATCH'] = unicode_type(S_TRUE) self.mytabprefs['GENRE_LCC_MATCH_LENGTH'] = 2 self.repaint() sleep(2) #--------------------------------------- #~ for k,v in self.mytabprefs.iteritems(): for k, v in iteritems(self.mytabprefs): v = unicode_type(v) v = v.strip() prefs[k] = v #END FOR prefs #--------------------------------------- self.ddc_labelname.setText(self.mytabprefs['DDC']) self.lcc_labelname.setText(self.mytabprefs['LCC']) self.fast_labelname.setText(self.mytabprefs['FAST']) self.oclc_labelname.setText(self.mytabprefs['OCLC']) self.repaint() sleep(0) #~ for k,v in self.mytabprefs.iteritems(): for k, v in iteritems(self.mytabprefs): self.param_dict[k] = v #END FOR number_active = 0 if self.mytabprefs['DDC_IS_ACTIVE'] == unicode_type(S_TRUE): number_active = number_active + 1 if self.mytabprefs['LCC_IS_ACTIVE'] == unicode_type(S_TRUE): number_active = number_active + 1 if self.mytabprefs['FAST_IS_ACTIVE'] == unicode_type(S_TRUE): number_active = number_active + 1 if self.mytabprefs['OCLC_IS_ACTIVE'] == unicode_type(S_TRUE): number_active = number_active + 1 self.ddc_name = self.mytabprefs['DDC'].replace("#", "").strip() self.lcc_name = self.mytabprefs['LCC'].replace("#", "").strip() self.fast_name = self.mytabprefs['FAST'].replace("#", "").strip() self.oclc_name = self.mytabprefs['OCLC'].replace("#", "").strip() if self.oclc_identifier_only_checkbox.isChecked(): self.oclc_identifier_is_desired = True else: self.oclc_identifier_is_desired = False return number_active #----------------------------------------------------------------------------------------- #----------------------------------------------------------------------------------------- def create_new_lc_custom_columns(self, execution_param_list): if len(self.cli_param_list) == 0: return True, False # successful since the labels already exist; no restart is required. dbpath = self.lib_path was_successful = True restart_required = True for param in execution_param_list: try: lc_cli_add_custom_column(self.guidb, param, dbpath) except Exception as e: if DEBUG: print("Exception: ", as_unicode(e)) was_successful = False break #END FOR return was_successful, restart_required #----------------------------------------------------------------------------------------- #----------------------------------------------------------------------------------------- #----------------------------------------------------------------------------------------- #----------------------------------------------------------------------------------------- #----------------------------------------------------------------------------------------- #----------------------------------------------------------------------------------------- #END of library_codes_dialog.py
class ConfigWidget(QWidget, Logger): # Manually managed controls when saving/restoring EXCLUDED_CONTROLS = [ 'cfg_annotations_destination_comboBox' ] #LOCATION_TEMPLATE = "{cls}:{func}({arg1}) {arg2}" WIZARD_PROFILES = { 'Annotations': { 'label': 'mm_annotations', 'datatype': 'comments', 'display': {}, 'is_multiple': False } } def __init__(self, plugin_action): self.gui = plugin_action.gui self.opts = plugin_action.opts QWidget.__init__(self) self.l = QVBoxLayout() self.setLayout(self.l) # ~~~~~~~~ Create the runtime options group box ~~~~~~~~ self.cfg_runtime_options_gb = QGroupBox(self) self.cfg_runtime_options_gb.setTitle(_('Runtime options')) self.l.addWidget(self.cfg_runtime_options_gb) self.cfg_runtime_options_qvl = QVBoxLayout(self.cfg_runtime_options_gb) # ~~~~~~~~ Disable caching checkbox ~~~~~~~~ self.cfg_disable_caching_checkbox = QCheckBox(_('Disable caching')) self.cfg_disable_caching_checkbox.setObjectName('cfg_disable_caching_checkbox') self.cfg_disable_caching_checkbox.setToolTip(_('Force reload of reader database')) self.cfg_disable_caching_checkbox.setChecked(False) self.cfg_runtime_options_qvl.addWidget(self.cfg_disable_caching_checkbox) # ~~~~~~~~ plugin logging checkbox ~~~~~~~~ self.cfg_plugin_debug_log_checkbox = QCheckBox(_('Enable debug logging for Annotations plugin')) self.cfg_plugin_debug_log_checkbox.setObjectName('cfg_plugin_debug_log_checkbox') self.cfg_plugin_debug_log_checkbox.setToolTip(_('Print plugin diagnostic messages to console')) self.cfg_plugin_debug_log_checkbox.setChecked(False) self.cfg_runtime_options_qvl.addWidget(self.cfg_plugin_debug_log_checkbox) # ~~~~~~~~ libiMobileDevice logging checkbox ~~~~~~~~ self.cfg_libimobiledevice_debug_log_checkbox = QCheckBox(_('Enable debug logging for libiMobileDevice')) self.cfg_libimobiledevice_debug_log_checkbox.setObjectName('cfg_libimobiledevice_debug_log_checkbox') self.cfg_libimobiledevice_debug_log_checkbox.setToolTip(_('Print libiMobileDevice debug messages to console')) self.cfg_libimobiledevice_debug_log_checkbox.setChecked(False) self.cfg_libimobiledevice_debug_log_checkbox.setEnabled(LIBIMOBILEDEVICE_AVAILABLE) self.cfg_runtime_options_qvl.addWidget(self.cfg_libimobiledevice_debug_log_checkbox) # ~~~~~~~~ Create the Annotations options group box ~~~~~~~~ self.cfg_annotation_options_gb = QGroupBox(self) self.cfg_annotation_options_gb.setTitle(_('Annotation options')) self.l.addWidget(self.cfg_annotation_options_gb) self.cfg_annotation_options_qgl = QGridLayout(self.cfg_annotation_options_gb) current_row = 0 # Add the label/combobox for annotations destination self.cfg_annotations_destination_label = QLabel(_('<b>Add fetched annotations to<b>')) self.cfg_annotations_destination_label.setAlignment(Qt.AlignLeft) self.cfg_annotation_options_qgl.addWidget(self.cfg_annotations_destination_label, current_row, 0) current_row += 1 self.cfg_annotations_destination_comboBox = QComboBox(self.cfg_annotation_options_gb) self.cfg_annotations_destination_comboBox.setObjectName('cfg_annotations_destination_comboBox') self.cfg_annotations_destination_comboBox.setToolTip(_('Custom field to store annotations')) self.cfg_annotation_options_qgl.addWidget(self.cfg_annotations_destination_comboBox, current_row, 0) # Populate annotations_field combobox db = self.gui.current_db all_custom_fields = db.custom_field_keys() self.custom_fields = {} for custom_field in all_custom_fields: field_md = db.metadata_for_field(custom_field) if field_md['datatype'] in ['comments']: self.custom_fields[field_md['name']] = {'field': custom_field, 'datatype': field_md['datatype']} all_fields = self.custom_fields.keys() + ['Comments'] for cf in sorted(all_fields): self.cfg_annotations_destination_comboBox.addItem(cf) # Add CC Wizard self.cfg_annotations_wizard = QToolButton() self.cfg_annotations_wizard.setIcon(QIcon(I('wizard.png'))) self.cfg_annotations_wizard.setToolTip(_("Create a custom column to store annotations")) self.cfg_annotations_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Annotations')) self.cfg_annotation_options_qgl.addWidget(self.cfg_annotations_wizard, current_row, 2) current_row += 1 # ~~~~~~~~ Add a horizontal line ~~~~~~~~ self.cfg_appearance_hl = QFrame(self) self.cfg_appearance_hl.setGeometry(QRect(0, 0, 1, 3)) self.cfg_appearance_hl.setFrameShape(QFrame.HLine) self.cfg_appearance_hl.setFrameShadow(QFrame.Raised) self.cfg_annotation_options_qgl.addWidget(self.cfg_appearance_hl, current_row, 0) current_row += 1 # ~~~~~~~~ Add the Modify… button ~~~~~~~~ self.cfg_annotations_appearance_pushbutton = QPushButton(_("Modify appearance…")) self.cfg_annotations_appearance_pushbutton.clicked.connect(self.configure_appearance) self.cfg_annotation_options_qgl.addWidget(self.cfg_annotations_appearance_pushbutton, current_row, 0) current_row += 1 self.spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.cfg_annotation_options_qgl.addItem(self.spacerItem, current_row, 0, 1, 1) # ~~~~~~~~ Compilations group box ~~~~~~~~ self.cfg_compilation_options_gb = QGroupBox(self) self.cfg_compilation_options_gb.setTitle(_('Compilations')) self.l.addWidget(self.cfg_compilation_options_gb) self.cfg_compilation_options_qgl = QGridLayout(self.cfg_compilation_options_gb) current_row = 0 # News clippings self.cfg_news_clippings_checkbox = QCheckBox(_('Collect News clippings')) self.cfg_news_clippings_checkbox.setObjectName('cfg_news_clippings_checkbox') self.cfg_compilation_options_qgl.addWidget(self.cfg_news_clippings_checkbox, current_row, 0) self.cfg_news_clippings_lineEdit = QLineEdit() self.cfg_news_clippings_lineEdit.setObjectName('cfg_news_clippings_lineEdit') self.cfg_news_clippings_lineEdit.setToolTip(_('Title for collected news clippings')) self.cfg_compilation_options_qgl.addWidget(self.cfg_news_clippings_lineEdit, current_row, 1) # ~~~~~~~~ End of construction zone ~~~~~~~~ self.resize(self.sizeHint()) # Restore state of controls, populate annotations combobox self.controls = inventory_controls(self, dump_controls=False) restore_state(self) self.populate_annotations() # Hook changes to annotations_destination_combobox # self.connect(self.cfg_annotations_destination_comboBox, # pyqtSignal('currentIndexChanged(const QString &)'), # self.annotations_destination_changed) self.cfg_annotations_destination_comboBox.currentIndexChanged.connect(self.annotations_destination_changed) # Hook changes to diagnostic checkboxes self.cfg_disable_caching_checkbox.stateChanged.connect(self.restart_required) self.cfg_libimobiledevice_debug_log_checkbox.stateChanged.connect(self.restart_required) self.cfg_plugin_debug_log_checkbox.stateChanged.connect(self.restart_required) # Hook changes to News clippings, initialize self.cfg_news_clippings_checkbox.stateChanged.connect(self.news_clippings_toggled) self.news_clippings_toggled(self.cfg_news_clippings_checkbox.checkState()) self.cfg_news_clippings_lineEdit.editingFinished.connect(self.news_clippings_destination_changed) # Launch the annotated_books_scanner field = get_cc_mapping('annotations', 'field', 'Comments') self.annotated_books_scanner = InventoryAnnotatedBooks(self.gui, field) self.annotated_books_scanner.signal.connect(self.inventory_complete) # self.connect(self.annotated_books_scanner, self.annotated_books_scanner.signal, # self.inventory_complete) QTimer.singleShot(1, self.start_inventory) def annotations_destination_changed(self, qs_new_destination_name): ''' If the destination field changes, move all existing annotations from old to new ''' self._log_location(repr(qs_new_destination_name)) self._log("self.custom_fields: %s" % self.custom_fields) old_destination_field = get_cc_mapping('annotations', 'field', None) if old_destination_field and not (old_destination_field in self.gui.current_db.custom_field_keys() or old_destination_field == 'Comments'): return old_destination_name = get_cc_mapping('annotations', 'combobox', None) self._log("old_destination_field: %s" % old_destination_field) self._log("old_destination_name: %s" % old_destination_name) # Catch initial change from None to Comments - first run only if old_destination_field is None: return # new_destination_name = unicode(qs_new_destination_name) new_destination_name = unicode(self.cfg_annotations_destination_comboBox.currentText()) self._log("new_destination_name: %s" % new_destination_name) if old_destination_name == new_destination_name: self._log_location("old_destination_name = new_destination_name, no changes") return new_destination_field = None if new_destination_name == 'Comments': new_destination_field = 'Comments' else: new_destination_field = self.custom_fields[new_destination_name]['field'] if existing_annotations(self.opts.parent, old_destination_field): command = self.launch_new_destination_dialog(old_destination_name, new_destination_name) if command == 'move': set_cc_mapping('annotations', field=new_destination_field, combobox=new_destination_name) if self.annotated_books_scanner.isRunning(): self.annotated_books_scanner.wait() move_annotations(self, self.annotated_books_scanner.annotation_map, old_destination_field, new_destination_field) elif command == 'change': # Keep the updated destination field, but don't move annotations pass elif command == 'cancel': # Restore previous destination self.cfg_annotations_destination_comboBox.blockSignals(True) old_index = self.cfg_annotations_destination_comboBox.findText(old_destination_name) self.cfg_annotations_destination_comboBox.setCurrentIndex(old_index) self.cfg_annotations_destination_comboBox.blockSignals(False) """ # Warn user that change will move existing annotations to new field title = 'Move annotations?' msg = ("<p>Existing annotations will be moved from <b>%s</b> to <b>%s</b>.</p>" % (old_destination_name, new_destination_name) + "<p>New annotations will be added to <b>%s</b>.</p>" % new_destination_name + "<p>Proceed?</p>") d = MessageBox(MessageBox.QUESTION, title, msg, show_copy_button=False) self._log_location("QUESTION: %s" % msg) if d.exec_(): set_cc_mapping('annotations', field=new_destination_field, combobox=new_destination_name) if self.annotated_books_scanner.isRunning(): self.annotated_books_scanner.wait() move_annotations(self, self.annotated_books_scanner.annotation_map, old_destination_field, new_destination_field) else: self.cfg_annotations_destination_comboBox.blockSignals(True) old_index = self.cfg_annotations_destination_comboBox.findText(old_destination_name) self.cfg_annotations_destination_comboBox.setCurrentIndex(old_index) self.cfg_annotations_destination_comboBox.blockSignals(False) """ else: # No existing annotations, just update prefs set_cc_mapping('annotations', field=new_destination_field, combobox=new_destination_name) def configure_appearance(self): ''' ''' from calibre_plugins.annotations.appearance import default_elements from calibre_plugins.annotations.appearance import default_timestamp appearance_settings = { 'appearance_css': default_elements, 'appearance_hr_checkbox': False, 'appearance_timestamp_format': default_timestamp } # Save, hash the original settings original_settings = {} osh = hashlib.md5() for setting in appearance_settings: original_settings[setting] = plugin_prefs.get(setting, appearance_settings[setting]) osh.update(repr(plugin_prefs.get(setting, appearance_settings[setting]))) # Display the appearance dialog aa = AnnotationsAppearance(self, get_icon('images/annotations.png'), plugin_prefs) cancelled = False if aa.exec_(): # appearance_hr_checkbox and appearance_timestamp_format changed live to prefs during previews plugin_prefs.set('appearance_css', aa.elements_table.get_data()) # Generate a new hash nsh = hashlib.md5() for setting in appearance_settings: nsh.update(repr(plugin_prefs.get(setting, appearance_settings[setting]))) else: for setting in appearance_settings: plugin_prefs.set(setting, original_settings[setting]) nsh = osh # If there were changes, and there are existing annotations, offer to re-render field = get_cc_mapping('annotations', 'field', None) if osh.digest() != nsh.digest() and existing_annotations(self.opts.parent,field): title = _('Update annotations?') msg = _('<p>Update existing annotations to new appearance settings?</p>') d = MessageBox(MessageBox.QUESTION, title, msg, show_copy_button=False) self._log_location("QUESTION: %s" % msg) if d.exec_(): self._log_location("Updating existing annotations to modified appearance") if self.annotated_books_scanner.isRunning(): self.annotated_books_scanner.wait() move_annotations(self, self.annotated_books_scanner.annotation_map, field, field, window_title=_("Updating appearance")) def inventory_complete(self, msg): self._log_location(msg) def launch_cc_wizard(self, column_type): ''' ''' def _update_combo_box(comboBox, destination, previous): ''' ''' self._log_location() cb = getattr(self, comboBox) cb.blockSignals(True) all_items = [str(cb.itemText(i)) for i in range(cb.count())] if previous and previous in all_items: all_items.remove(previous) all_items.append(destination) cb.clear() cb.addItems(sorted(all_items, key=lambda s: s.lower())) # Select the new destination in the comboBox idx = cb.findText(destination) if idx > -1: cb.setCurrentIndex(idx) # Process the changed destination self.annotations_destination_changed(destination) cb.blockSignals(False) klass = os.path.join(dialog_resources_path, 'cc_wizard.py') if os.path.exists(klass): #self._log("importing CC Wizard dialog from '%s'" % klass) sys.path.insert(0, dialog_resources_path) this_dc = importlib.import_module('cc_wizard') sys.path.remove(dialog_resources_path) dlg = this_dc.CustomColumnWizard(self, column_type, self.WIZARD_PROFILES[column_type], verbose=True) dlg.exec_() if dlg.modified_column: self._log("modified_column: %s" % dlg.modified_column) destination = dlg.modified_column['destination'] label = dlg.modified_column['label'] previous = dlg.modified_column['previous'] source = dlg.modified_column['source'] self._log("destination: %s" % destination) self._log("label: %s" % label) self._log("previous: %s" % previous) self._log("source: %s" % source) if source == "Annotations": # Add/update the new destination so save_settings() can find it if destination in self.custom_fields: self.custom_fields[destination]['field'] = label else: self.custom_fields[destination] = {'field': label} _update_combo_box('cfg_annotations_destination_comboBox', destination, previous) # Save field manually in case user cancels #self.prefs.set('cfg_annotations_destination_comboBox', destination) #self.prefs.set('cfg_annotations_destination_field', label) set_cc_mapping('annotations', field=label, combobox=destination) # Inform user to restart self.restart_required('custom_column') else: self._log("ERROR: Can't import from '%s'" % klass) def launch_new_destination_dialog(self, old, new): ''' Return 'move', 'change' or 'cancel' ''' self._log_location() klass = os.path.join(dialog_resources_path, 'new_destination.py') if os.path.exists(klass): self._log("importing new destination dialog from '%s'" % klass) sys.path.insert(0, dialog_resources_path) this_dc = importlib.import_module('new_destination') sys.path.remove(dialog_resources_path) dlg = this_dc.NewDestinationDialog(self, old, new) dlg.exec_() return dlg.command def news_clippings_destination_changed(self): qs_new_destination_name = self.cfg_news_clippings_lineEdit.text() if not re.match(r'^\S+[A-Za-z0-9 ]+$', qs_new_destination_name): # Complain about News clippings title title = _('Invalid title for News clippings') msg = _("Supply a valid title for News clippings, for example 'My News Clippings'.") d = MessageBox(MessageBox.WARNING, title, msg, show_copy_button=False) self._log_location("WARNING: %s" % msg) d.exec_() def news_clippings_toggled(self, state): if state == Qt.Checked: self.cfg_news_clippings_lineEdit.setEnabled(True) else: self.cfg_news_clippings_lineEdit.setEnabled(False) def populate_annotations(self): ''' Restore annotations combobox ''' self._log_location() target = 'Comments' existing = get_cc_mapping('annotations', 'combobox') if existing: target = existing ci = self.cfg_annotations_destination_comboBox.findText(target) self.cfg_annotations_destination_comboBox.setCurrentIndex(ci) def restart_required(self, state): title = _('Restart required') msg = _('To apply changes, restart calibre.') d = MessageBox(MessageBox.WARNING, title, msg, show_copy_button=False) self._log_location("WARNING: %s" % (msg)) d.exec_() def save_settings(self): save_state(self) # Save the annotation destination field ann_dest = unicode(self.cfg_annotations_destination_comboBox.currentText()) self._log_location("INFO: ann_dest=%s" % (ann_dest)) self._log_location("INFO: self.custom_fields=%s" % (self.custom_fields)) if ann_dest == 'Comments': set_cc_mapping('annotations', field='Comments', combobox='Comments') elif ann_dest: set_cc_mapping('annotations', field=self.custom_fields[ann_dest]['field'], combobox=ann_dest) def start_inventory(self): self.annotated_books_scanner.start()
class ImageControlDialog(QDialog): def __init__(self, parent, rc, imgman): """An ImageControlDialog is initialized with a parent widget, a RenderControl object, and an ImageManager object""" QDialog.__init__(self, parent) image = rc.image self.setWindowTitle("%s: Colour Controls" % image.name) self.setWindowIcon(pixmaps.colours.icon()) self.setModal(False) self.image = image self._rc = rc self._imgman = imgman self._currier = PersistentCurrier() # init internal state self._prev_range = self._display_range = None, None self._hist = None self._geometry = None # create layouts lo0 = QVBoxLayout(self) # lo0.setContentsMargins(0,0,0,0) # histogram plot whide = self.makeButton("Hide", self.hide, width=128) whide.setShortcut(Qt.Key_F9) lo0.addWidget(Separator(self, "Histogram and ITF", extra_widgets=[whide])) lo1 = QHBoxLayout() lo1.setContentsMargins(0, 0, 0, 0) self._histplot = QwtPlot(self) self._histplot.setAutoDelete(False) lo1.addWidget(self._histplot, 1) lo2 = QHBoxLayout() lo2.setContentsMargins(0, 0, 0, 0) lo2.setSpacing(2) lo0.addLayout(lo2) lo0.addLayout(lo1) self._wautozoom = QCheckBox("autozoom", self) self._wautozoom.setChecked(True) self._wautozoom.setToolTip("""<P>If checked, then the histrogram plot will zoom in automatically when you narrow the current intensity range.</P>""") self._wlogy = QCheckBox("log Y", self) self._wlogy.setChecked(True) self._ylogscale = True self._wlogy.setToolTip( """<P>If checked, a log-scale Y axis is used for the histogram plot instead of a linear one.""") self._wlogy.toggled[bool].connect(self._setHistLogScale) self._whistunzoom = self.makeButton("", self._unzoomHistogram, icon=pixmaps.full_range.icon()) self._whistzoomout = self.makeButton("-", self._currier.curry(self._zoomHistogramByFactor, math.sqrt(.1))) self._whistzoomin = self.makeButton("+", self._currier.curry(self._zoomHistogramByFactor, math.sqrt(10))) self._whistzoomin.setToolTip("""<P>Click to zoom into the histogram plot by one step. This does not change the current intensity range.</P>""") self._whistzoomout.setToolTip("""<P>Click to zoom out of the histogram plot by one step. This does not change the current intensity range.</P>""") self._whistunzoom.setToolTip("""<P>Click to reset the histogram plot back to its full extent. This does not change the current intensity range.</P>""") self._whistzoom = QwtWheel(self) self._whistzoom.setMass(0.5) self._whistzoom.setOrientation(Qt.Horizontal) self._whistzoom.setMaximumWidth(80) self._whistzoom.setRange(0, 10) self._whistzoom.setSingleStep(0.1) self._whistzoom.setPageStepCount(1) self._whistzoom.setTickCount(30) self._whistzoom.setTracking(False) self._whistzoom.valueChanged['double'].connect(self._zoomHistogramFinalize) self._whistzoom.wheelMoved['double'].connect(self._zoomHistogramPreview) self._whistzoom.setToolTip("""<P>Use this wheel control to zoom in/out of the histogram plot. This does not change the current intensity range. Note that the zoom wheel should also respond to your mouse wheel, if you have one.</P>""") # This works around a stupid bug in QwtSliders -- when using the mousewheel, only sliderMoved() signals are emitted, # with no final valueChanged(). If we want to do a fast preview of something on sliderMoved(), and a "slow" final # step on valueChanged(), we're in trouble. So we start a timer on sliderMoved(), and if the timer expires without # anything else happening, do a valueChanged(). # Here we use a timer to call zoomHistogramFinalize() w/o an argument. self._whistzoom_timer = QTimer(self) self._whistzoom_timer.setSingleShot(True) self._whistzoom_timer.setInterval(500) self._whistzoom_timer.timeout.connect(self._zoomHistogramFinalize) # set same size for all buttons and controls width = 24 for w in self._whistunzoom, self._whistzoomin, self._whistzoomout: w.setMinimumSize(width, width) w.setMaximumSize(width, width) self._whistzoom.setMinimumSize(80, width) self._wlab_histpos_text = "(hover for help)" self._wlab_histpos = QLabel(self._wlab_histpos_text, self) help_font = QFont() help_font.setPointSize(8) self._wlab_histpos.setFont(help_font) self._wlab_histpos.setToolTip(""" <P>The plot shows a histogram of either the full image or its selected subset (as per the "Data subset" section below).</P> <P>The current intensity range is indicated by the grey box in the plot.</P> <P>Use the left mouse button to change the low intensity limit, and the right button (on Macs, use Ctrl-click) to change the high limit.</P> <P>Use Shift with the left mouse button to zoom into an area of the histogram, or else use the "zoom wheel" control or the plus/minus toolbuttons above the histogram to zoom in or out. To zoom back out to the full extent of the histogram, click on the rightmost button above the histogram.</P> """) lo2.addWidget(self._wlab_histpos, 1) lo2.addWidget(self._wautozoom) lo2.addWidget(self._wlogy, 0) lo2.addWidget(self._whistzoomin, 0) lo2.addWidget(self._whistzoom, 0) lo2.addWidget(self._whistzoomout, 0) lo2.addWidget(self._whistunzoom, 0) self._zooming_histogram = False sliced_axes = rc.slicedAxes() dprint(1, "sliced axes are", sliced_axes) self._stokes_axis = None # subset indication lo0.addWidget(Separator(self, "Data subset")) # sliced axis selectors self._wslicers = [] if sliced_axes: lo1 = QHBoxLayout() lo1.setContentsMargins(0, 0, 0, 0) lo1.setSpacing(2) lo0.addLayout(lo1) lo1.addWidget(QLabel("Current slice: ", self)) for i, (iextra, name, labels) in enumerate(sliced_axes): lo1.addWidget(QLabel("%s:" % name, self)) if name == "STOKES": self._stokes_axis = iextra # add controls wslicer = QComboBox(self) self._wslicers.append(wslicer) wslicer.addItems(labels) wslicer.setToolTip("""<P>Selects current slice along the %s axis.</P>""" % name) wslicer.setCurrentIndex(self._rc.currentSlice()[iextra]) wslicer.activated[int].connect(self._currier.curry(self._rc.changeSlice, iextra)) lo2 = QVBoxLayout() lo1.addLayout(lo2) lo2.setContentsMargins(0, 0, 0, 0) lo2.setSpacing(0) wminus = QToolButton(self) wminus.setArrowType(Qt.UpArrow) wminus.clicked.connect(self._currier.curry(self._rc.incrementSlice, iextra, 1)) if i == 0: wminus.setShortcut(Qt.SHIFT + Qt.Key_F7) elif i == 1: wminus.setShortcut(Qt.SHIFT + Qt.Key_F8) wplus = QToolButton(self) wplus.setArrowType(Qt.DownArrow) wplus.clicked.connect(self._currier.curry(self._rc.incrementSlice, iextra, -1)) if i == 0: wplus.setShortcut(Qt.Key_F7) elif i == 1: wplus.setShortcut(Qt.Key_F8) wminus.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) wplus.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) sz = QSize(12, 8) wminus.setMinimumSize(sz) wplus.setMinimumSize(sz) wminus.resize(sz) wplus.resize(sz) lo2.addWidget(wminus) lo2.addWidget(wplus) lo1.addWidget(wslicer) lo1.addSpacing(5) lo1.addStretch(1) # subset indicator lo1 = QHBoxLayout() lo1.setContentsMargins(0, 0, 0, 0) lo1.setSpacing(2) lo0.addLayout(lo1) self._wlab_subset = QLabel("Subset: xxx", self) self._wlab_subset.setToolTip("""<P>This indicates the current data subset to which the histogram and the stats given here apply. Use the "Reset to" control on the right to change the current subset and recompute the histogram and stats.</P>""") lo1.addWidget(self._wlab_subset, 1) self._wreset_full = self.makeButton("\u2192 full", self._rc.setFullSubset) lo1.addWidget(self._wreset_full) if sliced_axes: # if self._stokes_axis is not None and len(sliced_axes)>1: # self._wreset_stokes = self.makeButton(u"\u21920Stokes",self._rc.setFullSubset) self._wreset_slice = self.makeButton("\u2192 slice", self._rc.setSliceSubset) lo1.addWidget(self._wreset_slice) else: self._wreset_slice = None # min/max controls lo1 = QHBoxLayout() lo1.setContentsMargins(0, 0, 0, 0) lo0.addLayout(lo1, 0) self._wlab_stats = QLabel(self) self._wlab_stats.setWordWrap(True) self._wlab_stats.setMinimumWidth(384) lo1.addWidget(self._wlab_stats, 0) self._wmore_stats = self.makeButton("more...", self._showMeanStd) self._wlab_stats.setMinimumHeight(self._wmore_stats.height()) lo1.addWidget(self._wmore_stats, 0) lo1.addStretch(1) # intensity controls lo0.addWidget(Separator(self, "Intensity mapping")) lo1 = QHBoxLayout() lo1.setContentsMargins(0, 0, 0, 0) lo1.setSpacing(2) lo0.addLayout(lo1, 0) self._range_validator = FloatValidator(self) self._wrange = QLineEdit(self), QLineEdit(self) self._wrange[0].setToolTip("""<P>This is the low end of the intensity range.</P>""") self._wrange[1].setToolTip("""<P>This is the high end of the intensity range.</P>""") for w in self._wrange: w.setValidator(self._range_validator) w.editingFinished.connect(self._changeDisplayRange) lo1.addWidget(QLabel("low:", self), 0) lo1.addWidget(self._wrange[0], 1) self._wrangeleft0 = self.makeButton("\u21920", self._setZeroLeftLimit, width=32) self._wrangeleft0.setToolTip("""<P>Click this to set the low end of the intensity range to 0.</P>""") lo1.addWidget(self._wrangeleft0, 0) lo1.addSpacing(8) lo1.addWidget(QLabel("high:", self), 0) lo1.addWidget(self._wrange[1], 1) lo1.addSpacing(8) self._wrange_full = self.makeButton(None, self._setHistDisplayRange, icon=pixmaps.intensity_graph.icon()) lo1.addWidget(self._wrange_full) self._wrange_full.setToolTip( """<P>Click this to reset the intensity range to the current extent of the histogram plot.</P>""") # add menu for display range range_menu = QMenu(self) wrange_menu = QToolButton(self) wrange_menu.setText("Reset to") wrange_menu.setToolTip("""<P>Use this to reset the intensity range to various pre-defined settings.</P>""") lo1.addWidget(wrange_menu) self._qa_range_full = range_menu.addAction(pixmaps.full_range.icon(), "Full subset", self._rc.resetSubsetDisplayRange) self._qa_range_hist = range_menu.addAction(pixmaps.intensity_graph.icon(), "Current histogram limits", self._setHistDisplayRange) for percent in (99.99, 99.9, 99.5, 99, 98, 95): range_menu.addAction("%g%%" % percent, self._currier.curry(self._changeDisplayRangeToPercent, percent)) wrange_menu.setMenu(range_menu) wrange_menu.setPopupMode(QToolButton.InstantPopup) lo1 = QGridLayout() lo1.setContentsMargins(0, 0, 0, 0) lo0.addLayout(lo1, 0) self._wimap = QComboBox(self) lo1.addWidget(QLabel("Intensity policy:", self), 0, 0) lo1.addWidget(self._wimap, 1, 0) self._wimap.addItems(rc.getIntensityMapNames()) self._wimap.currentIndexChanged[int].connect(self._rc.setIntensityMapNumber) self._wimap.setToolTip("""<P>Use this to change the type of the intensity transfer function (ITF).</P>""") # log cycles control lo1.setColumnStretch(1, 1) self._wlogcycles_label = QLabel("Log cycles: ", self) lo1.addWidget(self._wlogcycles_label, 0, 1) # self._wlogcycles = QwtWheel(self) # self._wlogcycles.setTotalAngle(360) self._wlogcycles = QwtSlider(self) self._wlogcycles.setToolTip( """<P>Use this to change the log-base for the logarithmic intensity transfer function (ITF).</P>""") # This works around a stupid bug in QwtSliders -- see comments on histogram zoom wheel above self._wlogcycles_timer = QTimer(self) self._wlogcycles_timer.setSingleShot(True) self._wlogcycles_timer.setInterval(500) self._wlogcycles_timer.timeout.connect(self._setIntensityLogCycles) lo1.addWidget(self._wlogcycles, 1, 1) # self._wlogcycles.setRange(1., 10) # need to find 6.1.5 change from v5 self._wlogcycles.setScale(1., 10) # self._wlogcycles.setStep(0.1) # need to find 6.1.5 change from v5 # self._wlogcycles.setScaleStepSize(0.1) self._wlogcycles.setTracking(False) self._wlogcycles.valueChanged.connect(self._setIntensityLogCycles) self._wlogcycles.sliderMoved.connect(self._previewIntensityLogCycles) self._updating_imap = False # lock intensity map lo1 = QHBoxLayout() lo1.setContentsMargins(0, 0, 0, 0) lo0.addLayout(lo1, 0) # lo1.addWidget(QLabel("Lock range accross",self)) wlock = QCheckBox("Lock display range", self) wlock.setMinimumWidth(192) wlock.setToolTip("""<P>If checked, then the intensity range will be locked. The ranges of all locked images change simultaneously.</P>""") lo1.addWidget(wlock) wlockall = QToolButton(self) wlockall.setIcon(pixmaps.locked.icon()) wlockall.setText("Lock all to this") wlockall.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) wlockall.setAutoRaise(True) wlockall.setToolTip("""<P>Click this to lock together the intensity ranges of all images.</P>""") lo1.addWidget(wlockall) wunlockall = QToolButton(self) wunlockall.setIcon(pixmaps.unlocked.icon()) wunlockall.setText("Unlock all") wunlockall.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) wunlockall.setAutoRaise(True) wunlockall.setToolTip("""<P>Click this to unlock the intensity ranges of all images.</P>""") lo1.addWidget(wunlockall) wlock.setChecked(self._rc.isDisplayRangeLocked()) wlock.clicked[bool].connect(self._rc.lockDisplayRange) wlockall.clicked.connect(self._currier.curry(self._imgman.lockAllDisplayRanges, self._rc)) wunlockall.clicked.connect(self._imgman.unlockAllDisplayRanges) self._rc.displayRangeLocked.connect(wlock.setChecked) # self._wlock_imap_axis = [ QCheckBox(name,self) for iaxis,name,labels in sliced_axes ] # for iw,w in enumerate(self._wlock_imap_axis): # QObject.connect(w,pyqtSignal("toggled(bool)"),self._currier.curry(self._rc.lockDisplayRangeForAxis,iw)) # lo1.addWidget(w,0) lo1.addStretch(1) # lo0.addWidget(Separator(self,"Colourmap")) # color bar self._colorbar = QwtPlot(self) lo0.addWidget(self._colorbar) self._colorbar.setAutoDelete(False) self._colorbar.setMinimumHeight(32) self._colorbar.enableAxis(QwtPlot.yLeft, False) self._colorbar.enableAxis(QwtPlot.xBottom, False) # color plot self._colorplot = QwtPlot(self) lo0.addWidget(self._colorplot) self._colorplot.setAutoDelete(False) self._colorplot.setMinimumHeight(64) self._colorplot.enableAxis(QwtPlot.yLeft, False) self._colorplot.enableAxis(QwtPlot.xBottom, False) # self._colorplot.setSizePolicy(QSizePolicy.Expanding,QSizePolicy.Preferred) self._colorbar.hide() self._colorplot.hide() # color controls lo1 = QHBoxLayout() lo1.setContentsMargins(0, 0, 0, 0) lo0.addLayout(lo1, 1) lo1.addWidget(QLabel("Colourmap:", self)) # colormap list ### NB: use setIconSize() and icons in QComboBox!!! self._wcolmaps = QComboBox(self) self._wcolmaps.setIconSize(QSize(128, 16)) self._wcolmaps.setToolTip("""<P>Use this to select a different colourmap.</P>""") for cmap in self._rc.getColormapList(): self._wcolmaps.addItem(QIcon(cmap.makeQPixmap(128, 16)), cmap.name) lo1.addWidget(self._wcolmaps) self._wcolmaps.activated[int].connect(self._rc.setColorMapNumber) # add widgetstack for colormap controls self._wcolmap_control_stack = QStackedWidget(self) self._wcolmap_control_blank = QWidget(self._wcolmap_control_stack) self._wcolmap_control_stack.addWidget(self._wcolmap_control_blank) lo0.addWidget(self._wcolmap_control_stack) self._colmap_controls = [] # add controls to stack for index, cmap in enumerate(self._rc.getColormapList()): if isinstance(cmap, Colormaps.ColormapWithControls): controls = cmap.makeControlWidgets(self._wcolmap_control_stack) self._wcolmap_control_stack.addWidget(controls) cmap.colormapChanged.connect(self._currier.curry(self._previewColormapParameters, index, cmap)) cmap.colormapPreviewed.connect(self._currier.curry(self._previewColormapParameters, index, cmap)) self._colmap_controls.append(controls) else: self._colmap_controls.append(self._wcolmap_control_blank) # connect updates from renderControl and image self.image.signalSlice.connect(self._updateImageSlice) self._rc.intensityMapChanged.connect(self._updateIntensityMap) self._rc.colorMapChanged.connect(self._updateColorMap) self._rc.dataSubsetChanged.connect(self._updateDataSubset) self._rc.displayRangeChanged.connect(self._updateDisplayRange) # update widgets self._setupHistogramPlot() self._updateDataSubset(*self._rc.currentSubset()) self._updateColorMap(image.colorMap()) self._updateIntensityMap(rc.currentIntensityMap(), rc.currentIntensityMapNumber()) self._updateDisplayRange(*self._rc.displayRange()) def makeButton(self, label, callback=None, width=None, icon=None): btn = QToolButton(self) # btn.setAutoRaise(True) label and btn.setText(label) icon and btn.setIcon(icon) # btn = QPushButton(label,self) # btn.setFlat(True) if width: btn.setMinimumWidth(width) btn.setMaximumWidth(width) if icon: btn.setIcon(icon) if callback: btn.clicked.connect(callback) return btn # def closeEvent (self,ev): # ev.ignore() # self.hide() def hide(self): self._geometry = self.geometry() QDialog.hide(self) self.parent().setVisible(False) def show(self): dprint(4, "show entrypoint") if self._geometry: dprint(4, "setting geometry") self.setGeometry(self._geometry) if self._hist is None: busy = BusyIndicator() dprint(4, "updating histogram") self._updateHistogram() dprint(4, "updating stats") self._updateStats(self._subset, self._subset_range) busy.reset_cursor() dprint(4, "calling QDialog.show") QDialog.show(self) # number of bins used to compute intensity transfer function NumItfBins = 1000 # number of bins used for displaying histograms NumHistBins = 500 # number of bins used for high-res histograms NumHistBinsHi = 10000 # colorbar height, as fraction of plot area ColorBarHeight = 0.1 class HistLimitPicker(QwtPlotPicker): """Auguments QwtPlotPicker with functions for selecting hist min/max values""" def __init__(self, plot, label, color="green", mode=QwtPickerClickPointMachine(), rubber_band=QwtPicker.VLineRubberBand, tracker_mode=QwtPicker.ActiveOnly, track=None): QwtPlotPicker.__init__(self, QwtPlot.xBottom, QwtPlot.yRight, rubber_band, tracker_mode, plot.canvas()) self.setStateMachine(mode) self.plot = plot self.label = label self.track = track self.color = QColor(color) self.setRubberBandPen(QPen(self.color)) self.setRubberBand(rubber_band) def trackerText(self, pos): x, y = self.plot.invTransform(QwtPlot.xBottom, pos.x()), self.plot.invTransform(QwtPlot.yLeft, pos.y()) if self.track: text = self.track(x, y) if text is not None: return text if self.label: text = QwtText(self.label % dict(x=x, y=y)) text.setColor(self.color) return text return QwtText() def widgetLeaveEvent(self, ev): if self.track: self.track(None, None) QwtPlotPicker.widgetLeaveEvent(self, ev) class ColorBarPlotItem(QwtPlotItem): def __init__(self, y0, y1, *args): QwtPlotItem.__init__(self, *args) self.RenderAntialiased self.imap = None self.cmap = None self._y0 = y1 self._dy = y1 - y0 def setIntensityMap(self, imap): self.imap = imap def setColorMap(self, cmap): self.cmap = cmap def draw(self, painter, xmap, ymap, rect): """Implements QwtPlotItem.draw(), to render the colorbar on the given painter.""" xp1, xp2, xdp, xs1, xs2, xds = xinfo = xmap.p1(), xmap.p2(), xmap.pDist(), xmap.s1(), xmap.s2(), xmap.sDist() yp1, yp2, ydp, ys1, ys2, yds = yinfo = ymap.p1(), ymap.p2(), ymap.pDist(), ymap.s1(), ymap.s2(), ymap.sDist() # xp: coordinates of pixels xp1...xp2 in data units xp = xs1 + (xds / xdp) * (0.5 + numpy.arange(int(xdp))) # convert y0 and y1 into pixel coordinates y0 = yp1 - (self._y0 - ys1) * (ydp / yds) dy = self._dy * (ydp / yds) # remap into an Nx1 image qimg = self.cmap.colorize(self.imap.remap(xp.reshape((len(xp), 1)))) # plot image painter.drawImage(QRect(xp1, y0, xdp, dy), qimg) class HistogramLineMarker: """Helper class implementing a line marker for a histogram plot""" def __init__(self, plot, color="black", linestyle=Qt.DotLine, align=Qt.AlignBottom | Qt.AlignRight, z=90, label="", zlabel=None, linewidth=1, spacing=2, yaxis=QwtPlot.yRight): self.line = TiggerPlotCurve() self.line.setRenderHint(QwtPlotItem.RenderAntialiased) self.color = color = color if isinstance(color, QColor) else QColor(color) self.line.setPen(QPen(color, linewidth, linestyle)) self.marker = TiggerPlotMarker() self.marker.setRenderHint(QwtPlotItem.RenderAntialiased) self.marker.setLabelAlignment(align) try: self.marker.setSpacing(spacing) except AttributeError: pass self.setText(label) self.line.setZ(z) self.marker.setZ(zlabel if zlabel is not None else z) # set axes -- using yRight, since that is the "markup" z-axis self.line.setAxes(QwtPlot.xBottom, yaxis) self.marker.setAxes(QwtPlot.xBottom, yaxis) # attach to plot self.line.attach(plot) self.marker.attach(plot) def show(self): self.line.show() self.marker.show() def hide(self): self.line.hide() self.marker.hide() def setText(self, text): label = QwtText(text) label.setColor(self.color) self.marker.setLabel(label) def _setupHistogramPlot(self): self._histplot.setCanvasBackground(QColor("lightgray")) self._histplot.setAxisFont(QwtPlot.yLeft, QApplication.font()) self._histplot.setAxisFont(QwtPlot.xBottom, QApplication.font()) # add histogram curves self._histcurve1 = TiggerPlotCurve() self._histcurve1.setRenderHint(QwtPlotItem.RenderAntialiased) self._histcurve2 = TiggerPlotCurve() self._histcurve2.setRenderHint(QwtPlotItem.RenderAntialiased) self._histcurve1.setStyle(QwtPlotCurve.Steps) self._histcurve2.setStyle(QwtPlotCurve.Steps) self._histcurve1.setPen(QPen(Qt.NoPen)) self._histcurve1.setBrush(QBrush(QColor("slategrey"))) pen = QPen(QColor("red")) pen.setWidth(1) self._histcurve2.setPen(pen) self._histcurve1.setZ(0) self._histcurve2.setZ(100) # self._histcurve1.attach(self._histplot) self._histcurve2.attach(self._histplot) # add maxbin and half-max curves self._line_0 = self.HistogramLineMarker(self._histplot, color="grey50", linestyle=Qt.SolidLine, align=Qt.AlignTop | Qt.AlignLeft, z=90) self._line_mean = self.HistogramLineMarker(self._histplot, color="black", linestyle=Qt.SolidLine, align=Qt.AlignBottom | Qt.AlignRight, z=91, label="mean", zlabel=151) self._line_std = self.HistogramLineMarker(self._histplot, color="black", linestyle=Qt.SolidLine, align=Qt.AlignTop | Qt.AlignRight, z=91, label="std", zlabel=151) sym = QwtSymbol() sym.setStyle(QwtSymbol.VLine) sym.setSize(8) self._line_std.line.setSymbol(sym) self._line_maxbin = self.HistogramLineMarker(self._histplot, color="green", linestyle=Qt.DotLine, align=Qt.AlignTop | Qt.AlignRight, z=92, label="max bin", zlabel=150) self._line_halfmax = self.HistogramLineMarker(self._histplot, color="green", linestyle=Qt.DotLine, align=Qt.AlignBottom | Qt.AlignRight, z=90, label="half-max", yaxis=QwtPlot.yLeft) # add current range self._rangebox = TiggerPlotCurve() self._rangebox.setRenderHint(QwtPlotItem.RenderAntialiased) self._rangebox.setStyle(QwtPlotCurve.Steps) self._rangebox.setYAxis(QwtPlot.yRight) self._rangebox.setPen(QPen(Qt.NoPen)) self._rangebox.setBrush(QBrush(QColor("darkgray"))) self._rangebox.setZ(50) self._rangebox.attach(self._histplot) self._rangebox2 = TiggerPlotCurve() self._rangebox2.setRenderHint(QwtPlotItem.RenderAntialiased) self._rangebox2.setStyle(QwtPlotCurve.Sticks) self._rangebox2.setYAxis(QwtPlot.yRight) self._rangebox2.setZ(60) # self._rangebox2.attach(self._histplot) # add intensity transfer function self._itfcurve = TiggerPlotCurve() self._itfcurve.setRenderHint(QwtPlotItem.RenderAntialiased) self._itfcurve.setStyle(QwtPlotCurve.Lines) self._itfcurve.setPen(QPen(QColor("blue"))) self._itfcurve.setYAxis(QwtPlot.yRight) self._itfcurve.setZ(120) self._itfcurve.attach(self._histplot) self._itfmarker = TiggerPlotMarker() self._itfmarker.setRenderHint(QwtPlotItem.RenderAntialiased) label = QwtText("ITF") label.setColor(QColor("blue")) self._itfmarker.setLabel(label) try: self._itfmarker.setSpacing(0) except AttributeError: pass self._itfmarker.setLabelAlignment(Qt.AlignTop | Qt.AlignRight) self._itfmarker.setZ(120) self._itfmarker.attach(self._histplot) # add colorbar self._cb_item = self.ColorBarPlotItem(1, 1 + self.ColorBarHeight) self._cb_item.setYAxis(QwtPlot.yRight) self._cb_item.attach(self._histplot) # add pickers self._hist_minpicker = self.HistLimitPicker(self._histplot, "low: %(x).4g") self._hist_minpicker.setMousePattern(QwtEventPattern.MouseSelect1, Qt.LeftButton) self._hist_minpicker.selected.connect(self._selectLowLimit) self._hist_maxpicker = self.HistLimitPicker(self._histplot, "high: %(x).4g") self._hist_maxpicker.setMousePattern(QwtEventPattern.MouseSelect1, Qt.RightButton) self._hist_maxpicker.selected.connect(self._selectHighLimit) self._hist_maxpicker1 = self.HistLimitPicker(self._histplot, "high: %(x).4g") self._hist_maxpicker1.setMousePattern(QwtEventPattern.MouseSelect1, Qt.LeftButton, Qt.ControlModifier) self._hist_maxpicker1.selected.connect(self._selectHighLimit) self._hist_zoompicker = self.HistLimitPicker(self._histplot, label="zoom", tracker_mode=QwtPicker.AlwaysOn, track=self._trackHistCoordinates, color="black", mode=QwtPickerClickRectMachine(), rubber_band=QwtPicker.RectRubberBand) self._hist_zoompicker.setMousePattern(QwtEventPattern.MouseSelect1, Qt.LeftButton, Qt.ShiftModifier) # self._hist_zoompicker.selected[QRectF].connect(self._zoomHistogramIntoRect) self._hist_zoompicker.selected.connect(self._zoomHistogramIntoRect) def _trackHistCoordinates(self, x, y): self._wlab_histpos.setText((DataValueFormat + " %d") % (x, y) if x is not None else self._wlab_histpos_text) return QwtText() def _updateITF(self): """Updates current ITF array.""" # do nothing if no histogram -- means we're not visible if self._hist is not None: xdata = self._itf_bins ydata = self.image.intensityMap().remap(xdata) self._rangebox.setData(self._rc.displayRange(), [1, 1]) self._rangebox2.setData(self._rc.displayRange(), [1, 1]) self._itfcurve.setData(xdata, ydata) self._itfmarker.setValue(xdata[0], 1) def _updateHistogram(self, hmin=None, hmax=None): """Recomputes histogram. If no arguments, computes full histogram for data subset. If hmin/hmax is specified, computes zoomed-in histogram.""" busy = BusyIndicator() self._prev_range = self._display_range dmin, dmax = self._subset_range hmin0, hmax0 = dmin, dmax if hmin0 >= hmax0: hmax0 = hmin0 + 1 subset, mask = self.image.optimalRavel(self._subset) # compute full-subset hi-res histogram, if we don't have one (for percentile stats) if self._hist_hires is None: dprint(1, "computing histogram for full subset range", hmin0, hmax0) self._hist_hires = measurements.histogram(subset, hmin0, hmax0, self.NumHistBinsHi, labels=mask, index=None if mask is None else False) self._hist_bins_hires = hmin0 + (hmax0 - hmin0) * (numpy.arange(self.NumHistBinsHi) + 0.5) / float( self.NumHistBinsHi) self._hist_binsize_hires = (hmax0 - hmin0) / self.NumHistBins # if hist limits not specified, then compute lo-res histogram based on the hi-res one if hmin is None: hmin, hmax = hmin0, hmax0 # downsample to low-res histogram self._hist = self._hist_hires.reshape((self.NumHistBins, int(self.NumHistBinsHi / self.NumHistBins))).sum(1) else: # zoomed-in low-res histogram # bracket limits at subset range hmin, hmax = max(hmin, dmin), min(hmax, dmax) if hmin >= hmax: hmax = hmin + 1 dprint(1, "computing histogram for", self._subset.shape, self._subset.dtype, hmin, hmax) self._hist = measurements.histogram(subset, hmin, hmax, self.NumHistBins, labels=mask, index=None if mask is None else False) dprint(1, "histogram computed") # compute bins self._itf_bins = hmin + (hmax - hmin) * (numpy.arange(self.NumItfBins)) / (float(self.NumItfBins) - 1) self._hist_bins = hmin + (hmax - hmin) * (numpy.arange(self.NumHistBins) + 0.5) / float(self.NumHistBins) # histogram range and position of peak self._hist_range = hmin, hmax self._hist_min, self._hist_max, self._hist_imin, self._hist_imax = measurements.extrema(self._hist) self._hist_peak = self._hist_bins[self._hist_imax] # set controls accordingly if dmin >= dmax: dmax = dmin + 1 zoom = math.log10((dmax - dmin) / (hmax - hmin)) self._whistzoom.setValue(zoom) self._whistunzoom.setEnabled(zoom > 0) self._whistzoomout.setEnabled(zoom > 0) # reset scales self._histplot.setAxisScale(QwtPlot.xBottom, hmin, hmax) self._histplot.setAxisScale(QwtPlot.yRight, 0, 1 + self.ColorBarHeight) # update curves # call _setHistLogScale() (with current setting) to update axis scales and set data self._setHistLogScale(self._ylogscale, replot=False) # set plot lines self._line_0.line.setData([0, 0], [0, 1]) self._line_0.marker.setValue(0, 0) self._line_maxbin.line.setData([self._hist_peak, self._hist_peak], [0, 1]) self._line_maxbin.marker.setValue(self._hist_peak, 0) self._line_maxbin.setText(("max bin:" + DataValueFormat) % self._hist_peak) # set half-max line self._line_halfmax.line.setData(self._hist_range, [self._hist_max / 2, self._hist_max / 2]) self._line_halfmax.marker.setValue(hmin, self._hist_max / 2) # update ITF self._updateITF() busy.reset_cursor() def _updateStats(self, subset, minmax): """Recomputes subset statistics.""" if subset.size <= (2048 * 2048): self._showMeanStd(busy=False) else: self._wlab_stats.setText( ("min: %s max: %s np: %d" % (DataValueFormat, DataValueFormat, self._subset.size)) % minmax) self._wmore_stats.show() def _updateDataSubset(self, subset, minmax, desc, subset_type): """Called when the displayed data subset is changed. Updates the histogram.""" self._subset = subset self._subset_range = minmax self._wlab_subset.setText("Subset: %s" % desc) self._hist = self._hist_hires = None self._wreset_full.setVisible(subset_type is not RenderControl.SUBSET_FULL) self._wreset_slice and self._wreset_slice.setVisible(subset_type is not RenderControl.SUBSET_SLICE) # hide the mean/std markers, they will only be shown when _showMeanStd() is called self._line_mean.hide() self._line_std.hide() # if we're visibile, recompute histograms and stats if self.isVisible(): # if subset is sufficiently small, compute extended stats on-the-fly. Else show the "more" button to compute them later self._updateHistogram() self._updateStats(subset, minmax) self._histplot.replot() def _showMeanStd(self, busy=True): if busy: busy = BusyIndicator() dmin, dmax = self._subset_range subset, mask = self.image.optimalRavel(self._subset) dprint(5, "computing mean") mean = measurements.mean(subset, labels=mask, index=None if mask is None else False) dprint(5, "computing std") std = measurements.standard_deviation(subset, labels=mask, index=None if mask is None else False) dprint(5, "done") text = " ".join([("%s: " + DataValueFormat) % (name, value) for name, value in (("min", dmin), ("max", dmax), ("mean", mean), ("\n std", std))] + ["np: %d" % self._subset.size]) self._wlab_stats.setText(text) self._wmore_stats.hide() # update markers ypos = 0.3 self._line_mean.line.setData([mean, mean], [0, 1]) self._line_mean.marker.setValue(mean, ypos) self._line_mean.setText(("\u03BC=" + DataValueFormat) % mean) self._line_mean.show() self._line_std.line.setData([mean - std, mean + std], [ypos, ypos]) self._line_std.marker.setValue(mean, ypos) self._line_std.setText(("\u03C3=" + DataValueFormat) % std) self._line_std.show() self._histplot.replot() if not isinstance(busy, bool): busy.reset_cursor() def _setIntensityLogCyclesLabel(self, value): self._wlogcycles_label.setText("Log cycles: %4.1f" % value) def _previewIntensityLogCycles(self, value): self._setIntensityLogCycles(value, notify_image=False, write_config=False) self._wlogcycles_timer.start(500) def _setIntensityLogCycles(self, value=None, notify_image=True, write_config=True): if value is None: value = self._wlogcycles.value() # stop timer if being called to finalize the change in value if notify_image: self._wlogcycles_timer.stop() if not self._updating_imap: self._setIntensityLogCyclesLabel(value) self._rc.setIntensityMapLogCycles(value, notify_image=notify_image, write_config=write_config) self._updateITF() self._histplot.replot() def _updateDisplayRange(self, dmin, dmax): self._rangebox.setData([dmin, dmax], [.9, .9]) self._wrange[0].setText(DataValueFormat % dmin) self._wrange[1].setText(DataValueFormat % dmax) self._wrangeleft0.setEnabled(dmin != 0) self._display_range = dmin, dmax # if auto-zoom is on, zoom the histogram # try to be a little clever about this. Zoom only if (a) both limits have changed (so that adjusting one end of the range # does not cause endless rezooms), or (b) display range is < 1/10 of the histogram range if self._wautozoom.isChecked() and self._hist is not None: if (dmax - dmin) / (self._hist_range[1] - self._hist_range[0]) < .1 or ( dmin != self._prev_range[0] and dmax != self._prev_range[1]): margin = (dmax - dmin) / 8 self._updateHistogram(dmin - margin, dmax + margin) self._updateITF() self._histplot.replot() def _updateIntensityMap(self, imap, index): self._updating_imap = True try: self._cb_item.setIntensityMap(imap) self._updateITF() self._histplot.replot() self._wimap.setCurrentIndex(index) if isinstance(imap, Colormaps.LogIntensityMap): self._wlogcycles.setValue(imap.log_cycles) self._setIntensityLogCyclesLabel(imap.log_cycles) self._wlogcycles.show() self._wlogcycles_label.show() else: self._wlogcycles.hide() self._wlogcycles_label.hide() finally: self._updating_imap = False def _updateColorMap(self, cmap): self._cb_item.setColorMap(cmap) self._histplot.replot() try: index = self._rc.getColormapList().index(cmap) except: return self._setCurrentColormapNumber(index, cmap) def _previewColormapParameters(self, index, cmap): """Called to preview a new colormap parameter value""" self._histplot.replot() self._wcolmaps.setItemIcon(index, QIcon(cmap.makeQPixmap(128, 16))) def _setCurrentColormapNumber(self, index, cmap): self._wcolmaps.setCurrentIndex(index) # show controls for colormap self._wcolmap_control_stack.setCurrentWidget(self._colmap_controls[index]) def _changeDisplayRange(self): """Gets display range from widgets and updates the image with it.""" try: newrange = [float(str(w.text())) for w in self._wrange] except ValueError: return self._rc.setDisplayRange(*newrange) def _setHistDisplayRange(self): self._rc.setDisplayRange(*self._hist_range) def _updateImageSlice(self, _slice): for i, (iextra, name, labels) in enumerate(self._rc.slicedAxes()): self._wslicers[i].setCurrentIndex(_slice[iextra]) def _changeDisplayRangeToPercent(self, percent): busy = BusyIndicator() if self._hist is None: self._updateHistogram() self._updateStats(self._subset, self._subset_range) # delta: we need the [delta,100-delta] interval of the total distribution delta = self._subset.size * ((100. - percent) / 200.) # get F(x): cumulative sum cumsum = numpy.zeros(len(self._hist_hires) + 1, dtype=int) cumsum[1:] = numpy.cumsum(self._hist_hires) bins = numpy.zeros(len(self._hist_hires) + 1, dtype=float) bins[0] = self._subset_range[0] bins[1:] = self._hist_bins_hires + self._hist_binsize_hires / 2 # use interpolation to find value interval corresponding to [delta,100-delta] of the distribution dprint(2, self._subset.size, delta, self._subset.size - delta) dprint(2, cumsum, self._hist_bins_hires) # if first bin is already > delta, then set colour range to first bin x0, x1 = numpy.interp([delta, self._subset.size - delta], cumsum, bins) # and change the display range (this will also cause a histplot.replot() via _updateDisplayRange above) self._rc.setDisplayRange(x0, x1) busy.reset_cursor() def _setZeroLeftLimit(self): self._rc.setDisplayRange(0., self._rc.displayRange()[1]) def _selectLowLimit(self, pos): self._rc.setDisplayRange(pos.x(), self._rc.displayRange()[1]) def _selectHighLimit(self, pos): self._rc.setDisplayRange(self._rc.displayRange()[0], pos.x()) def _unzoomHistogram(self): self._updateHistogram() self._histplot.replot() def _zoomHistogramByFactor(self, factor, curry=None): """ Changes histogram limits by specified factor. curry=None is due to an error raised from the signal to zoom and is unused. """ # get max distance of plot limit from peak dprint(1, "zooming histogram by", factor) halfdist = (self._hist_range[1] - self._hist_range[0]) / (factor * 2) self._updateHistogram(self._hist_peak - halfdist, self._hist_peak + halfdist) self._histplot.replot() def _zoomHistogramIntoRect(self, rect): hmin, hmax = rect.bottomLeft().x(), rect.bottomRight().x() if hmax > hmin: self._updateHistogram(rect.bottomLeft().x(), rect.bottomRight().x()) self._histplot.replot() def _zoomHistogramPreview(self, value): dprint(2, "wheel moved to", value) self._zoomHistogramFinalize(value, preview=True) self._whistzoom_timer.start() def _zoomHistogramFinalize(self, value=None, preview=False): if self._zooming_histogram: return self._zooming_histogram = True try: if value is not None: dmin, dmax = self._subset_range dist = max(dmax - self._hist_peak, self._hist_peak - dmin) / 10 ** value self._preview_hist_range = max(self._hist_peak - dist, dmin), min(self._hist_peak + dist, dmax) if preview: self._histplot.setAxisScale(QwtPlot.xBottom, *self._preview_hist_range) else: dprint(2, "wheel finalized at", value) self._whistzoom_timer.stop() self._updateHistogram(*self._preview_hist_range) self._histplot.replot() finally: self._zooming_histogram = False def _setHistLogScale(self, logscale, replot=True): self._ylogscale = logscale if logscale: self._histplot.setAxisScaleEngine(QwtPlot.yLeft, QwtLogScaleEngine()) ymax = max(1, self._hist_max) self._histplot.setAxisScale(QwtPlot.yLeft, 1, 10 ** (math.log10(ymax) * (1 + self.ColorBarHeight))) y = self._hist.copy() y[y == 0] = 1 self._histcurve1.setData(self._hist_bins, y) self._histcurve2.setData(self._hist_bins, y) else: self._histplot.setAxisScaleEngine(QwtPlot.yLeft, QwtLinearScaleEngine()) self._histplot.setAxisScale(QwtPlot.yLeft, 0, self._hist_max * (1 + self.ColorBarHeight)) self._histcurve1.setData(self._hist_bins, self._hist) self._histcurve2.setData(self._hist_bins, self._hist) if replot: self._histplot.replot()
class BasicTab(QWidget): 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 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) # 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.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 view_prefs(self): d = PrefsViewerDialog(self.plugin_action.gui, PREFS_NAMESPACE) d.exec_() def reset_dialogs(self): for key in dynamic.keys(): if key.startswith('epubsplit_') and key.endswith('_again') \ and dynamic[key] is False: dynamic[key] = True info_dialog(self, _('Done'), _('Confirmation dialogs have all been reset'), show=True, show_copy_button=False)
def _exportImageToPNG(self, filename=None): if not filename: if not self._export_png_dialog: dialog = self._export_png_dialog = QFileDialog( self, "Export image to PNG", ".", "*.png") dialog.setDefaultSuffix("png") dialog.setFileMode(QFileDialog.AnyFile) dialog.setAcceptMode(QFileDialog.AcceptSave) dialog.setModal(True) dialog.filesSelected['QStringList'].connect( self._exportImageToPNG) # attempt to add limit 4K option - not available on Ubuntu Unity layout = dialog.layout() if layout is not None: checkbox = QCheckBox("Limit to 4K image") checkbox.setChecked(False) checkbox.setToolTip("Limits the image output to 4K") checkbox.toggled.connect(self._exportImageResolution) layout.addWidget(checkbox) dialog.setLayout(layout) return self._export_png_dialog.exec_() == QDialog.Accepted busy = BusyIndicator() if isinstance(filename, QStringList): filename = filename[0] filename = str(filename) # get image dimensions nx, ny = self.image.imageDims() # export either max resolution possible or default to 4K. If image is small then no scaling occurs. if not self._exportMaxRes: # get free memory. Note: Linux only! import os total_memory, used_memory, free_memory = map( int, os.popen('free -t -m').readlines()[-1].split()[1:]) # use 90% of free memory available free_memory = free_memory * 0.9 # use an approximation to find the max image size that can be generated if nx >= ny and nx > free_memory: scale_factor = round(free_memory / nx, 1) elif ny > nx and ny > free_memory: scale_factor = round(free_memory / ny, 1) else: scale_factor = 1 else: # default to 4K if nx > 4000: scale_factor = 4000 / nx elif ny > nx and ny > 4000: scale_factor = 4000 / ny else: scale_factor = 1 # make QPixmap nx = nx * scale_factor ny = ny * scale_factor (l0, l1), (m0, m1) = self.image.getExtents() pixmap = QPixmap(nx, ny) painter = QPainter(pixmap) # use QwtPlot implementation of draw canvas, since we want to avoid caching xmap = QwtScaleMap() xmap.setPaintInterval(0, nx) xmap.setScaleInterval(l1, l0) ymap = QwtScaleMap() ymap.setPaintInterval(ny, 0) ymap.setScaleInterval(m0, m1) # call painter with clear cache option for consistent file size output. self.image.draw(painter, xmap, ymap, pixmap.rect(), use_cache=False) painter.end() # save to file try: pixmap.save(filename, "PNG") # clean up export items pixmap.detach() del xmap del ymap del pixmap del painter except Exception as exc: self._imgman.signalShowErrorMessage[str, int].emit( "Error writing %s: %s" % (filename, str(exc)), 3000) busy.reset_cursor() else: busy.reset_cursor() self._imgman.signalShowMessage[str, int].emit( "Exported image to file %s" % filename, 3000)
def create_checkbox(title, tt, state): cb = QCheckBox(title) cb.setToolTip(wrap_msg(tt)) cb.setChecked(bool(state)) return cb
class MakeBrickDialog(QDialog): def __init__(self, parent, modal=True, flags=Qt.WindowFlags()): QDialog.__init__(self, parent, flags) self.model = None self._model_dir = None self.setModal(modal) self.setWindowTitle("Convert sources to FITS brick") lo = QVBoxLayout(self) lo.setContentsMargins(10, 10, 10, 10) lo.setSpacing(5) # file selector self.wfile = FileSelector(self, label="FITS filename:", dialog_label="Output FITS file", default_suffix="fits", file_types="FITS files (*.fits *.FITS)", file_mode=QFileDialog.ExistingFile) lo.addWidget(self.wfile) # reference frequency lo1 = QHBoxLayout() lo.addLayout(lo1) lo1.setContentsMargins(0, 0, 0, 0) label = QLabel("Frequency, MHz:", self) lo1.addWidget(label) tip = """<P>If your sky model contains spectral information (such as spectral indices), then a brick may be generated for a specific frequency. If a frequency is not specified here, the reference frequency of the model sources will be assumed.</P>""" self.wfreq = QLineEdit(self) self.wfreq.setValidator(QDoubleValidator(self)) label.setToolTip(tip) self.wfreq.setToolTip(tip) lo1.addWidget(self.wfreq) # beam gain lo1 = QHBoxLayout() lo.addLayout(lo1) lo1.setContentsMargins(0, 0, 0, 0) self.wpb_apply = QCheckBox("Apply primary beam expression:", self) self.wpb_apply.setChecked(True) lo1.addWidget(self.wpb_apply) tip = """<P>If this option is specified, a primary power beam gain will be applied to the sources before inserting them into the brick. This can be any valid Python expression making use of the variables 'r' (corresponding to distance from field centre, in radians) and 'fq' (corresponding to frequency.)</P>""" self.wpb_exp = QLineEdit(self) self.wpb_apply.setToolTip(tip) self.wpb_exp.setToolTip(tip) lo1.addWidget(self.wpb_exp) # overwrite or add mode lo1 = QHBoxLayout() lo.addLayout(lo1) lo1.setContentsMargins(0, 0, 0, 0) self.woverwrite = QRadioButton("overwrite image", self) self.woverwrite.setChecked(True) lo1.addWidget(self.woverwrite) self.waddinto = QRadioButton("add into image", self) lo1.addWidget(self.waddinto) # add to model self.wadd = QCheckBox( "Add resulting brick to sky model as a FITS image component", self) lo.addWidget(self.wadd) lo1 = QHBoxLayout() lo.addLayout(lo1) lo1.setContentsMargins(0, 0, 0, 0) self.wpad = QLineEdit(self) self.wpad.setValidator(QDoubleValidator(self)) self.wpad.setText("1.1") lab = QLabel("...with padding factor:", self) lab.setToolTip( """<P>The padding factor determines the amount of null padding inserted around the image during the prediction stage. Padding alleviates the effects of tapering and detapering in the uv-brick, which can show up towards the edges of the image. For a factor of N, the image will be padded out to N times its original size. This increases memory use, so if you have no flux at the edges of the image anyway, then a pad factor of 1 is perfectly fine.</P>""") self.wpad.setToolTip(lab.toolTip()) self.wadd.toggled[bool].connect(self.wpad.setEnabled) self.wadd.toggled[bool].connect(lab.setEnabled) self.wpad.setEnabled(False) lab.setEnabled(False) lo1.addStretch(1) lo1.addWidget(lab, 0) lo1.addWidget(self.wpad, 1) self.wdel = QCheckBox( "Remove from the sky model sources that go into the brick", self) lo.addWidget(self.wdel) # OK/cancel buttons lo.addSpacing(10) lo2 = QHBoxLayout() lo.addLayout(lo2) lo2.setContentsMargins(5, 5, 5, 5) self.wokbtn = QPushButton("OK", self) self.wokbtn.setMinimumWidth(128) self.wokbtn.clicked.connect(self.accept) self.wokbtn.setEnabled(False) cancelbtn = QPushButton("Cancel", self) cancelbtn.setMinimumWidth(128) cancelbtn.clicked.connect(self.reject) lo2.addWidget(self.wokbtn) lo2.addStretch(1) lo2.addWidget(cancelbtn) self.setMinimumWidth(384) # signals self.wfile.filenameSelected.connect(self._fileSelected) # internal state self.qerrmsg = QErrorMessage(self) def setModel(self, model): self.model = model pb = self.model.primaryBeam() if pb: self.wpb_exp.setText(pb) else: self.wpb_apply.setChecked(False) self.wpb_exp.setText("") if model.filename(): self._model_dir = os.path.dirname(os.path.abspath( model.filename())) else: self._model_dir = os.path.abspath('.') self.wfile.setDirectory(self._model_dir) self._fileSelected(self.wfile.filename(), quiet=True) def _fileSelected(self, filename, quiet=False): self.wokbtn.setEnabled(False) if not filename: return None # check that filename matches model if not os.path.samefile(self._model_dir, os.path.dirname(filename)): self.wfile.setFilename('') if not quiet: QMessageBox.warning( self, "Directory mismatch", """<P>The FITS file must reside in the same directory as the current sky model.</P>""") self.wfile.setDirectory(self._model_dir) return None # read fits file busy = BusyIndicator() try: input_hdu = pyfits.open(filename)[0] hdr = input_hdu.header # get frequency, if specified for axis in range(1, hdr['NAXIS'] + 1): if hdr['CTYPE%d' % axis].upper() == 'FREQ': self.wfreq.setText(str(hdr['CRVAL%d' % axis] / 1e+6)) break except Exception as err: busy.reset_cursor() self.wfile.setFilename('') if not quiet: QMessageBox.warning( self, "Error reading FITS", "Error reading FITS file %s: %s" % (filename, str(err))) return None self.wokbtn.setEnabled(True) # if filename is not in model already, enable the "add to model" control for src in self.model.sources: if isinstance(getattr(src, 'shape', None), ModelClasses.FITSImage) \ and os.path.exists(src.shape.filename) and os.path.exists(filename) \ and os.path.samefile(src.shape.filename, filename): self.wadd.setChecked(True) self.wadd.setEnabled(False) self.wadd.setText("image already in sky model") break else: self.wadd.setText("add image to sky model") busy.reset_cursor() return filename def accept(self): """Tries to make a brick, and closes the dialog if successful.""" sources = [ src for src in self.model.sources if src.selected and src.typecode == 'pnt' ] filename = self.wfile.filename() if not self._fileSelected(filename): return # get PB expression pbfunc = None if self.wpb_apply.isChecked(): pbexp = str(self.wpb_exp.text()) try: pbfunc = eval("lambda r,fq:" + pbexp) except Exception as err: QMessageBox.warning( self, "Error parsing PB experssion", "Error parsing primary beam expression %s: %s" % (pbexp, str(err))) return # get frequency freq = str(self.wfreq.text()) freq = float(freq) * 1e+6 if freq else None # get pad factor pad = str(self.wpad.text()) pad = max(float(pad), 1) if pad else 1 # read fits file busy = BusyIndicator() try: input_hdu = pyfits.open(filename)[0] except Exception as err: busy.reset_cursor() QMessageBox.warning( self, "Error reading FITS", "Error reading FITS file %s: %s" % (filename, str(err))) return # reset data if asked to if self.woverwrite.isChecked(): input_hdu.data[...] = 0 # insert sources Imaging.restoreSources(input_hdu, sources, 0, primary_beam=pbfunc, freq=freq) # save fits file try: # pyfits seems to produce an exception: # TypeError: formatwarning() takes exactly 4 arguments (5 given) # when attempting to overwrite a file. As a workaround, remove the file first. if os.path.exists(filename): os.remove(filename) input_hdu.writeto(filename) except Exception as err: traceback.print_exc() busy.reset_cursor() QMessageBox.warning( self, "Error writing FITS", "Error writing FITS file %s: %s" % (filename, str(err))) return changed = False sources = self.model.sources # remove sources from model if asked to if self.wdel.isChecked(): sources = [ src for src in sources if not (src.selected and src.typecode == 'pnt') ] changed = True # add image to model if asked to if self.wadd.isChecked(): hdr = input_hdu.header # get image parameters max_flux = float(input_hdu.data.max()) wcs = WCS(hdr, mode='pyfits') # Get reference pixel coordinates # wcs.getCentreWCSCoords() doesn't work, as that gives us the middle of the image # So scan the header to get the CRPIX values ra0 = dec0 = 1 for iaxis in range(hdr['NAXIS']): axs = str(iaxis + 1) name = hdr.get('CTYPE' + axs, axs).upper() if name.startswith("RA"): ra0 = hdr.get('CRPIX' + axs, 1) - 1 elif name.startswith("DEC"): dec0 = hdr.get('CRPIX' + axs, 1) - 1 # convert pixel to degrees ra0, dec0 = wcs.pix2wcs(ra0, dec0) ra0 *= DEG dec0 *= DEG sx, sy = wcs.getHalfSizeDeg() sx *= DEG sy *= DEG nx, ny = input_hdu.data.shape[-1:-3:-1] # check if this image is already contained in the model for src in sources: if isinstance(getattr(src, 'shape', None), ModelClasses.FITSImage) and os.path.samefile( src.shape.filename, filename): # update source parameters src.pos.ra, src.pos.dec = ra0, dec0 src.flux.I = max_flux src.shape.ex, src.shape.ey = sx, sy src.shape.nx, src.shape.ny = nx, ny src.shape.pad = pad break # not contained, make new source object else: pos = ModelClasses.Position(ra0, dec0) flux = ModelClasses.Flux(max_flux) shape = ModelClasses.FITSImage(sx, sy, 0, os.path.basename(filename), nx, ny, pad=pad) img_src = SkyModel.Source(os.path.splitext( os.path.basename(filename))[0], pos, flux, shape=shape) sources.append(img_src) changed = True if changed: self.model.setSources(sources) self.model.emitUpdate(SkyModel.SkyModel.UpdateAll, origin=self) self.parent().showMessage("Wrote %d sources to FITS file %s" % (len(sources), filename)) busy.reset_cursor() return QDialog.accept(self)
class BasicTab(QWidget): 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( _('These settings control the basic features of the plugin.')) label.setWordWrap(True) self.l.addWidget(label) self.l.addSpacing(5) self.titlenavpoints = QCheckBox( _('Insert Table of Contents entry for each title?'), self) self.titlenavpoints.setToolTip( _('''If set, a new TOC entry will be made for each title and it's existing TOC nested underneath it.''')) self.titlenavpoints.setChecked(prefs['titlenavpoints']) self.l.addWidget(self.titlenavpoints) self.originalnavpoints = QCheckBox( _('Copy Table of Contents entries from each input epub?'), self) self.originalnavpoints.setToolTip( _('''If set, the original TOC entries will be merged into the new epub.''' )) self.originalnavpoints.setChecked(prefs['originalnavpoints']) self.l.addWidget(self.originalnavpoints) self.flattentoc = QCheckBox(_('Flatten Table of Contents?'), self) self.flattentoc.setToolTip( _('Remove nesting and make TOC all on one level.')) self.flattentoc.setChecked(prefs['flattentoc']) self.l.addWidget(self.flattentoc) self.includecomments = QCheckBox(_("Include Books' Comments?"), self) self.includecomments.setToolTip( _('''Include all the merged books' comments in the new book's comments. Default is a list of included titles only.''')) self.includecomments.setChecked(prefs['includecomments']) self.l.addWidget(self.includecomments) self.keepmeta = QCheckBox(_('Keep UnMerge Metadata?'), self) self.keepmeta.setToolTip( _('''If set, a copy of the original metadata for each merged book will be included, allowing for UnMerge. This includes your calibre custom columns. Leave off if you plan to distribute the epub to others.''')) self.keepmeta.setChecked(prefs['keepmeta']) self.l.addWidget(self.keepmeta) # self.showunmerge = QCheckBox(_('Show UnMerge Option?'),self) # self.showunmerge.setToolTip(_('''If set, the UnMerge Epub option will be shown on the EpubMerge menu. # Only Epubs merged with 'Keep UnMerge Metadata' can be UnMerged.''')) # self.showunmerge.setChecked(prefs['showunmerge']) # self.l.addWidget(self.showunmerge) horz = QHBoxLayout() horz.addWidget(QLabel(_("Add tags to merged books:"))) self.mergetags = QLineEdit(self) self.mergetags.setText(prefs['mergetags']) self.mergetags.setToolTip( _('Tags you enter here will be added to all new merged books')) horz.addWidget(self.mergetags) self.l.addLayout(horz) horz = QHBoxLayout() horz.addWidget(QLabel(_("Merged Book Word:"))) self.mergeword = QLineEdit(self) self.mergeword.setText(prefs['mergeword']) self.mergeword.setToolTip( _('''Word use to describe merged books in default title and summary. For people who don't like the word Anthology.''')) ## Defaults back to Anthology if cleared. horz.addWidget(self.mergeword) self.l.addLayout(horz) 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 EpubMerge 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 EpubMerge 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) self.l.insertStretch(-1) def view_prefs(self): d = PrefsViewerDialog(self.plugin_action.gui, PREFS_NAMESPACE) d.exec_() def reset_dialogs(self): for key in dynamic.keys(): if key.startswith('epubmerge_') and key.endswith('_again') \ and dynamic[key] is False: dynamic[key] = True info_dialog(self, _('Done'), _('Confirmation dialogs have all been reset'), show=True, show_copy_button=False)
def __init__(self, parent, rc, imgman): """An ImageControlDialog is initialized with a parent widget, a RenderControl object, and an ImageManager object""" QDialog.__init__(self, parent) image = rc.image self.setWindowTitle("%s: Colour Controls" % image.name) self.setWindowIcon(pixmaps.colours.icon()) self.setModal(False) self.image = image self._rc = rc self._imgman = imgman self._currier = PersistentCurrier() # init internal state self._prev_range = self._display_range = None, None self._hist = None self._geometry = None # create layouts lo0 = QVBoxLayout(self) # lo0.setContentsMargins(0,0,0,0) # histogram plot whide = self.makeButton("Hide", self.hide, width=128) whide.setShortcut(Qt.Key_F9) lo0.addWidget(Separator(self, "Histogram and ITF", extra_widgets=[whide])) lo1 = QHBoxLayout() lo1.setContentsMargins(0, 0, 0, 0) self._histplot = QwtPlot(self) self._histplot.setAutoDelete(False) lo1.addWidget(self._histplot, 1) lo2 = QHBoxLayout() lo2.setContentsMargins(0, 0, 0, 0) lo2.setSpacing(2) lo0.addLayout(lo2) lo0.addLayout(lo1) self._wautozoom = QCheckBox("autozoom", self) self._wautozoom.setChecked(True) self._wautozoom.setToolTip("""<P>If checked, then the histrogram plot will zoom in automatically when you narrow the current intensity range.</P>""") self._wlogy = QCheckBox("log Y", self) self._wlogy.setChecked(True) self._ylogscale = True self._wlogy.setToolTip( """<P>If checked, a log-scale Y axis is used for the histogram plot instead of a linear one.""") self._wlogy.toggled[bool].connect(self._setHistLogScale) self._whistunzoom = self.makeButton("", self._unzoomHistogram, icon=pixmaps.full_range.icon()) self._whistzoomout = self.makeButton("-", self._currier.curry(self._zoomHistogramByFactor, math.sqrt(.1))) self._whistzoomin = self.makeButton("+", self._currier.curry(self._zoomHistogramByFactor, math.sqrt(10))) self._whistzoomin.setToolTip("""<P>Click to zoom into the histogram plot by one step. This does not change the current intensity range.</P>""") self._whistzoomout.setToolTip("""<P>Click to zoom out of the histogram plot by one step. This does not change the current intensity range.</P>""") self._whistunzoom.setToolTip("""<P>Click to reset the histogram plot back to its full extent. This does not change the current intensity range.</P>""") self._whistzoom = QwtWheel(self) self._whistzoom.setMass(0.5) self._whistzoom.setOrientation(Qt.Horizontal) self._whistzoom.setMaximumWidth(80) self._whistzoom.setRange(0, 10) self._whistzoom.setSingleStep(0.1) self._whistzoom.setPageStepCount(1) self._whistzoom.setTickCount(30) self._whistzoom.setTracking(False) self._whistzoom.valueChanged['double'].connect(self._zoomHistogramFinalize) self._whistzoom.wheelMoved['double'].connect(self._zoomHistogramPreview) self._whistzoom.setToolTip("""<P>Use this wheel control to zoom in/out of the histogram plot. This does not change the current intensity range. Note that the zoom wheel should also respond to your mouse wheel, if you have one.</P>""") # This works around a stupid bug in QwtSliders -- when using the mousewheel, only sliderMoved() signals are emitted, # with no final valueChanged(). If we want to do a fast preview of something on sliderMoved(), and a "slow" final # step on valueChanged(), we're in trouble. So we start a timer on sliderMoved(), and if the timer expires without # anything else happening, do a valueChanged(). # Here we use a timer to call zoomHistogramFinalize() w/o an argument. self._whistzoom_timer = QTimer(self) self._whistzoom_timer.setSingleShot(True) self._whistzoom_timer.setInterval(500) self._whistzoom_timer.timeout.connect(self._zoomHistogramFinalize) # set same size for all buttons and controls width = 24 for w in self._whistunzoom, self._whistzoomin, self._whistzoomout: w.setMinimumSize(width, width) w.setMaximumSize(width, width) self._whistzoom.setMinimumSize(80, width) self._wlab_histpos_text = "(hover for help)" self._wlab_histpos = QLabel(self._wlab_histpos_text, self) help_font = QFont() help_font.setPointSize(8) self._wlab_histpos.setFont(help_font) self._wlab_histpos.setToolTip(""" <P>The plot shows a histogram of either the full image or its selected subset (as per the "Data subset" section below).</P> <P>The current intensity range is indicated by the grey box in the plot.</P> <P>Use the left mouse button to change the low intensity limit, and the right button (on Macs, use Ctrl-click) to change the high limit.</P> <P>Use Shift with the left mouse button to zoom into an area of the histogram, or else use the "zoom wheel" control or the plus/minus toolbuttons above the histogram to zoom in or out. To zoom back out to the full extent of the histogram, click on the rightmost button above the histogram.</P> """) lo2.addWidget(self._wlab_histpos, 1) lo2.addWidget(self._wautozoom) lo2.addWidget(self._wlogy, 0) lo2.addWidget(self._whistzoomin, 0) lo2.addWidget(self._whistzoom, 0) lo2.addWidget(self._whistzoomout, 0) lo2.addWidget(self._whistunzoom, 0) self._zooming_histogram = False sliced_axes = rc.slicedAxes() dprint(1, "sliced axes are", sliced_axes) self._stokes_axis = None # subset indication lo0.addWidget(Separator(self, "Data subset")) # sliced axis selectors self._wslicers = [] if sliced_axes: lo1 = QHBoxLayout() lo1.setContentsMargins(0, 0, 0, 0) lo1.setSpacing(2) lo0.addLayout(lo1) lo1.addWidget(QLabel("Current slice: ", self)) for i, (iextra, name, labels) in enumerate(sliced_axes): lo1.addWidget(QLabel("%s:" % name, self)) if name == "STOKES": self._stokes_axis = iextra # add controls wslicer = QComboBox(self) self._wslicers.append(wslicer) wslicer.addItems(labels) wslicer.setToolTip("""<P>Selects current slice along the %s axis.</P>""" % name) wslicer.setCurrentIndex(self._rc.currentSlice()[iextra]) wslicer.activated[int].connect(self._currier.curry(self._rc.changeSlice, iextra)) lo2 = QVBoxLayout() lo1.addLayout(lo2) lo2.setContentsMargins(0, 0, 0, 0) lo2.setSpacing(0) wminus = QToolButton(self) wminus.setArrowType(Qt.UpArrow) wminus.clicked.connect(self._currier.curry(self._rc.incrementSlice, iextra, 1)) if i == 0: wminus.setShortcut(Qt.SHIFT + Qt.Key_F7) elif i == 1: wminus.setShortcut(Qt.SHIFT + Qt.Key_F8) wplus = QToolButton(self) wplus.setArrowType(Qt.DownArrow) wplus.clicked.connect(self._currier.curry(self._rc.incrementSlice, iextra, -1)) if i == 0: wplus.setShortcut(Qt.Key_F7) elif i == 1: wplus.setShortcut(Qt.Key_F8) wminus.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) wplus.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) sz = QSize(12, 8) wminus.setMinimumSize(sz) wplus.setMinimumSize(sz) wminus.resize(sz) wplus.resize(sz) lo2.addWidget(wminus) lo2.addWidget(wplus) lo1.addWidget(wslicer) lo1.addSpacing(5) lo1.addStretch(1) # subset indicator lo1 = QHBoxLayout() lo1.setContentsMargins(0, 0, 0, 0) lo1.setSpacing(2) lo0.addLayout(lo1) self._wlab_subset = QLabel("Subset: xxx", self) self._wlab_subset.setToolTip("""<P>This indicates the current data subset to which the histogram and the stats given here apply. Use the "Reset to" control on the right to change the current subset and recompute the histogram and stats.</P>""") lo1.addWidget(self._wlab_subset, 1) self._wreset_full = self.makeButton("\u2192 full", self._rc.setFullSubset) lo1.addWidget(self._wreset_full) if sliced_axes: # if self._stokes_axis is not None and len(sliced_axes)>1: # self._wreset_stokes = self.makeButton(u"\u21920Stokes",self._rc.setFullSubset) self._wreset_slice = self.makeButton("\u2192 slice", self._rc.setSliceSubset) lo1.addWidget(self._wreset_slice) else: self._wreset_slice = None # min/max controls lo1 = QHBoxLayout() lo1.setContentsMargins(0, 0, 0, 0) lo0.addLayout(lo1, 0) self._wlab_stats = QLabel(self) self._wlab_stats.setWordWrap(True) self._wlab_stats.setMinimumWidth(384) lo1.addWidget(self._wlab_stats, 0) self._wmore_stats = self.makeButton("more...", self._showMeanStd) self._wlab_stats.setMinimumHeight(self._wmore_stats.height()) lo1.addWidget(self._wmore_stats, 0) lo1.addStretch(1) # intensity controls lo0.addWidget(Separator(self, "Intensity mapping")) lo1 = QHBoxLayout() lo1.setContentsMargins(0, 0, 0, 0) lo1.setSpacing(2) lo0.addLayout(lo1, 0) self._range_validator = FloatValidator(self) self._wrange = QLineEdit(self), QLineEdit(self) self._wrange[0].setToolTip("""<P>This is the low end of the intensity range.</P>""") self._wrange[1].setToolTip("""<P>This is the high end of the intensity range.</P>""") for w in self._wrange: w.setValidator(self._range_validator) w.editingFinished.connect(self._changeDisplayRange) lo1.addWidget(QLabel("low:", self), 0) lo1.addWidget(self._wrange[0], 1) self._wrangeleft0 = self.makeButton("\u21920", self._setZeroLeftLimit, width=32) self._wrangeleft0.setToolTip("""<P>Click this to set the low end of the intensity range to 0.</P>""") lo1.addWidget(self._wrangeleft0, 0) lo1.addSpacing(8) lo1.addWidget(QLabel("high:", self), 0) lo1.addWidget(self._wrange[1], 1) lo1.addSpacing(8) self._wrange_full = self.makeButton(None, self._setHistDisplayRange, icon=pixmaps.intensity_graph.icon()) lo1.addWidget(self._wrange_full) self._wrange_full.setToolTip( """<P>Click this to reset the intensity range to the current extent of the histogram plot.</P>""") # add menu for display range range_menu = QMenu(self) wrange_menu = QToolButton(self) wrange_menu.setText("Reset to") wrange_menu.setToolTip("""<P>Use this to reset the intensity range to various pre-defined settings.</P>""") lo1.addWidget(wrange_menu) self._qa_range_full = range_menu.addAction(pixmaps.full_range.icon(), "Full subset", self._rc.resetSubsetDisplayRange) self._qa_range_hist = range_menu.addAction(pixmaps.intensity_graph.icon(), "Current histogram limits", self._setHistDisplayRange) for percent in (99.99, 99.9, 99.5, 99, 98, 95): range_menu.addAction("%g%%" % percent, self._currier.curry(self._changeDisplayRangeToPercent, percent)) wrange_menu.setMenu(range_menu) wrange_menu.setPopupMode(QToolButton.InstantPopup) lo1 = QGridLayout() lo1.setContentsMargins(0, 0, 0, 0) lo0.addLayout(lo1, 0) self._wimap = QComboBox(self) lo1.addWidget(QLabel("Intensity policy:", self), 0, 0) lo1.addWidget(self._wimap, 1, 0) self._wimap.addItems(rc.getIntensityMapNames()) self._wimap.currentIndexChanged[int].connect(self._rc.setIntensityMapNumber) self._wimap.setToolTip("""<P>Use this to change the type of the intensity transfer function (ITF).</P>""") # log cycles control lo1.setColumnStretch(1, 1) self._wlogcycles_label = QLabel("Log cycles: ", self) lo1.addWidget(self._wlogcycles_label, 0, 1) # self._wlogcycles = QwtWheel(self) # self._wlogcycles.setTotalAngle(360) self._wlogcycles = QwtSlider(self) self._wlogcycles.setToolTip( """<P>Use this to change the log-base for the logarithmic intensity transfer function (ITF).</P>""") # This works around a stupid bug in QwtSliders -- see comments on histogram zoom wheel above self._wlogcycles_timer = QTimer(self) self._wlogcycles_timer.setSingleShot(True) self._wlogcycles_timer.setInterval(500) self._wlogcycles_timer.timeout.connect(self._setIntensityLogCycles) lo1.addWidget(self._wlogcycles, 1, 1) # self._wlogcycles.setRange(1., 10) # need to find 6.1.5 change from v5 self._wlogcycles.setScale(1., 10) # self._wlogcycles.setStep(0.1) # need to find 6.1.5 change from v5 # self._wlogcycles.setScaleStepSize(0.1) self._wlogcycles.setTracking(False) self._wlogcycles.valueChanged.connect(self._setIntensityLogCycles) self._wlogcycles.sliderMoved.connect(self._previewIntensityLogCycles) self._updating_imap = False # lock intensity map lo1 = QHBoxLayout() lo1.setContentsMargins(0, 0, 0, 0) lo0.addLayout(lo1, 0) # lo1.addWidget(QLabel("Lock range accross",self)) wlock = QCheckBox("Lock display range", self) wlock.setMinimumWidth(192) wlock.setToolTip("""<P>If checked, then the intensity range will be locked. The ranges of all locked images change simultaneously.</P>""") lo1.addWidget(wlock) wlockall = QToolButton(self) wlockall.setIcon(pixmaps.locked.icon()) wlockall.setText("Lock all to this") wlockall.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) wlockall.setAutoRaise(True) wlockall.setToolTip("""<P>Click this to lock together the intensity ranges of all images.</P>""") lo1.addWidget(wlockall) wunlockall = QToolButton(self) wunlockall.setIcon(pixmaps.unlocked.icon()) wunlockall.setText("Unlock all") wunlockall.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) wunlockall.setAutoRaise(True) wunlockall.setToolTip("""<P>Click this to unlock the intensity ranges of all images.</P>""") lo1.addWidget(wunlockall) wlock.setChecked(self._rc.isDisplayRangeLocked()) wlock.clicked[bool].connect(self._rc.lockDisplayRange) wlockall.clicked.connect(self._currier.curry(self._imgman.lockAllDisplayRanges, self._rc)) wunlockall.clicked.connect(self._imgman.unlockAllDisplayRanges) self._rc.displayRangeLocked.connect(wlock.setChecked) # self._wlock_imap_axis = [ QCheckBox(name,self) for iaxis,name,labels in sliced_axes ] # for iw,w in enumerate(self._wlock_imap_axis): # QObject.connect(w,pyqtSignal("toggled(bool)"),self._currier.curry(self._rc.lockDisplayRangeForAxis,iw)) # lo1.addWidget(w,0) lo1.addStretch(1) # lo0.addWidget(Separator(self,"Colourmap")) # color bar self._colorbar = QwtPlot(self) lo0.addWidget(self._colorbar) self._colorbar.setAutoDelete(False) self._colorbar.setMinimumHeight(32) self._colorbar.enableAxis(QwtPlot.yLeft, False) self._colorbar.enableAxis(QwtPlot.xBottom, False) # color plot self._colorplot = QwtPlot(self) lo0.addWidget(self._colorplot) self._colorplot.setAutoDelete(False) self._colorplot.setMinimumHeight(64) self._colorplot.enableAxis(QwtPlot.yLeft, False) self._colorplot.enableAxis(QwtPlot.xBottom, False) # self._colorplot.setSizePolicy(QSizePolicy.Expanding,QSizePolicy.Preferred) self._colorbar.hide() self._colorplot.hide() # color controls lo1 = QHBoxLayout() lo1.setContentsMargins(0, 0, 0, 0) lo0.addLayout(lo1, 1) lo1.addWidget(QLabel("Colourmap:", self)) # colormap list ### NB: use setIconSize() and icons in QComboBox!!! self._wcolmaps = QComboBox(self) self._wcolmaps.setIconSize(QSize(128, 16)) self._wcolmaps.setToolTip("""<P>Use this to select a different colourmap.</P>""") for cmap in self._rc.getColormapList(): self._wcolmaps.addItem(QIcon(cmap.makeQPixmap(128, 16)), cmap.name) lo1.addWidget(self._wcolmaps) self._wcolmaps.activated[int].connect(self._rc.setColorMapNumber) # add widgetstack for colormap controls self._wcolmap_control_stack = QStackedWidget(self) self._wcolmap_control_blank = QWidget(self._wcolmap_control_stack) self._wcolmap_control_stack.addWidget(self._wcolmap_control_blank) lo0.addWidget(self._wcolmap_control_stack) self._colmap_controls = [] # add controls to stack for index, cmap in enumerate(self._rc.getColormapList()): if isinstance(cmap, Colormaps.ColormapWithControls): controls = cmap.makeControlWidgets(self._wcolmap_control_stack) self._wcolmap_control_stack.addWidget(controls) cmap.colormapChanged.connect(self._currier.curry(self._previewColormapParameters, index, cmap)) cmap.colormapPreviewed.connect(self._currier.curry(self._previewColormapParameters, index, cmap)) self._colmap_controls.append(controls) else: self._colmap_controls.append(self._wcolmap_control_blank) # connect updates from renderControl and image self.image.signalSlice.connect(self._updateImageSlice) self._rc.intensityMapChanged.connect(self._updateIntensityMap) self._rc.colorMapChanged.connect(self._updateColorMap) self._rc.dataSubsetChanged.connect(self._updateDataSubset) self._rc.displayRangeChanged.connect(self._updateDisplayRange) # update widgets self._setupHistogramPlot() self._updateDataSubset(*self._rc.currentSubset()) self._updateColorMap(image.colorMap()) self._updateIntensityMap(rc.currentIntensityMap(), rc.currentIntensityMapNumber()) self._updateDisplayRange(*self._rc.displayRange())
class ConvertDialog(QDialog): hide_text = _('&Hide styles') show_text = _('&Show styles') prince_log = '' prince_file = '' prince_css = '' # GUI definition def __init__(self, mi, fmt, opf, oeb, icon): ''' :param mi: The book metadata :param fmt: The source format used for conversion :param opf: The path to the OPF file :param oeb: An OEB object for the unpacked book :param icon: The window icon ''' self.opf = opf self.oeb = oeb self.mi = mi # The unpacked book needs to be parsed before, to read the contents # of the prince-style file, if it exists self.parse() QDialog.__init__(self) self.setAttribute(Qt.WA_DeleteOnClose) self.setWindowTitle(_('Convert to PDF with Prince')) self.setWindowIcon(icon) self.l = QVBoxLayout() self.setLayout(self.l) self.title_label = QLabel(_('<b>Title:</b> %s') % self.mi.title) self.l.addWidget(self.title_label) self.format_label = QLabel(_('<b>Source format:</b> %s') % fmt) self.l.addWidget(self.format_label) self.add_book = QCheckBox(_('&Add PDF to the book record')) self.add_book.setToolTip(_('<qt>Add the converted PDF to the selected book record</qt>')) self.add_book.setChecked(prefs['add_book']) self.add_book.stateChanged.connect(self.set_add_book) self.l.addWidget(self.add_book) self.ll = QHBoxLayout() self.ll.setAlignment(Qt.AlignLeft) self.l.addLayout(self.ll) self.label_css = QLabel(_('&Custom style:')) self.ll.addWidget(self.label_css) self.css_list = QComboBox() self.css_list.setToolTip(_('<qt>Select one style to use. Additional styles can be created in the plugin configuration</qt>')) for key in sorted(prefs['custom_CSS_list'], key=lambda x: x.lower()): self.css_list.addItem(key, key) self.css_list.setCurrentIndex(self.css_list.findText(prefs['default_CSS'])) self.css_list.currentIndexChanged.connect(self.set_css) self.ll.addWidget(self.css_list) self.label_css.setBuddy(self.css_list) self.ll_ = QHBoxLayout() self.l.addLayout(self.ll_) self.label_args = QLabel(_('A&dditional command-line arguments:')) self.ll_.addWidget(self.label_args) self.args = QLineEdit(self) self.args.setText(prefs['custom_args_list'][prefs['default_CSS']]) self.args.setToolTip(_('<qt>Specify additional command-line arguments for the conversion</qt>')) self.ll_.addWidget(self.args) self.label_args.setBuddy(self.args) self.css = QTabWidget() self.l.addWidget(self.css) self.css1 = TextEditWithTooltip(self, expected_geometry=(80,20)) self.css1.setLineWrapMode(TextEditWithTooltip.NoWrap) self.css1.load_text(self.replace_templates(prefs['custom_CSS_list'][prefs['default_CSS']]),'css') self.css1.setToolTip(_('<qt>This stylesheet can be modified<br/>The default can be configured</qt>')) i = self.css.addTab(self.css1, _('C&ustom CSS')) self.css.setTabToolTip(i, _('<qt>Custom CSS stylesheet to be used for this conversion</qt>')) monofont = QFont('') monofont.setStyleHint(QFont.TypeWriter) if (self.prince_css): self.css2 = QPlainTextEdit() self.css2.setStyleSheet('* { font-family: monospace }') self.css2.setLineWrapMode(QPlainTextEdit.NoWrap) self.css2.setPlainText(self.prince_css) self.css2.setReadOnly(True) self.css2.setToolTip(_('<qt>This stylesheet cannot be modified</qt>')) i = self.css.addTab(self.css2, _('&Book CSS')) self.css.setTabToolTip(i, _('<qt>Book-specific CSS stylesheet included in the ebook file</qt>')) self.ll = QHBoxLayout() self.l.addLayout(self.ll) if (prefs['show_CSS']): self.toggle = QPushButton(self.hide_text, self) else: self.toggle = QPushButton(self.show_text, self) self.toggle.setToolTip(_('<qt>Show/hide the additional styles used for the conversion</qt>')) self.toggle.clicked.connect(self.toggle_tabs) self.convert = QPushButton(_('Con&vert'), self) self.convert.setToolTip(_('<qt>Run the conversion with Prince</qt>')) self.convert.setDefault(True) self.buttons = QDialogButtonBox(QDialogButtonBox.Cancel) self.buttons.addButton(self.toggle, QDialogButtonBox.ResetRole) self.buttons.addButton(self.convert, QDialogButtonBox.AcceptRole) self.l.addWidget(self.buttons) self.buttons.accepted.connect(self.prince_convert) self.buttons.rejected.connect(self.reject) if (not prefs['show_CSS']): self.css.hide() self.adjustSize() def toggle_tabs(self): ''' Enable/disable the CSS tabs, and store the setting ''' if (self.css.isVisible()): self.css.hide() self.label_args.hide() self.args.hide() self.toggle.setText(self.show_text) self.adjustSize() else: self.css.show() self.label_args.show() self.args.show() self.toggle.setText(self.hide_text) self.adjustSize() prefs['show_CSS'] = self.css.isVisible() def set_add_book(self): ''' Save the status of the add_book checkbox ''' prefs['add_book'] = self.add_book.isChecked() def set_css(self): ''' Fill the custom CSS text box with the selected stylesheet (and command-line arguments) ''' style = unicode(self.css_list.currentText()) self.css1.load_text(self.replace_templates(prefs['custom_CSS_list'][style]),'css') self.args.setText(prefs['custom_args_list'][style]) prefs['default_CSS'] = style def parse(self): ''' Parse the unpacked OPF file to find and read the prince-style file ''' from calibre.constants import DEBUG from os.path import dirname, join from lxml import etree import codecs if DEBUG: print(_('Parsing book...')) opf_dir = dirname(self.opf) root = etree.parse(self.opf).getroot() metadata = root.find('{*}metadata') for meta in metadata.findall("{*}meta[@name='prince-style']"): prince_id = meta.get('content') for item in self.oeb.manifest: if (item.id == prince_id): self.prince_file = item.href break if (self.prince_file): fl = codecs.open(join(opf_dir, self.prince_file), 'rb', 'utf-8') self.prince_css = fl.read() fl.close() def replace_templates(self, text): ''' Replace templates (enclosed by '@{@', '@}@') in the input text ''' import re import json from calibre.ebooks.metadata.book.formatter import SafeFormat from calibre.constants import DEBUG matches = list(re.finditer('@{@(.+?)@}@',text,re.DOTALL)) results = {} for match in reversed(matches): result = SafeFormat().safe_format(match.group(1), self.mi, ('EXCEPTION: '), self.mi) # Escape quotes, backslashes and newlines result = re.sub(r'''['"\\]''', r'\\\g<0>', result) result = re.sub('\n', r'\A ', result) results[match.group(1)] = result text = text[:match.start(0)] + result + text[match.end(0):] if DEBUG: print(_('Replacing templates')) for match in matches: print(_('Found: %s (%d-%d)') % (match.group(1), match.start(0), match.end(0))) print(_('Replace with: %s') % results[match.group(1)]) return text def prince_convert(self): ''' Call the actual Prince command to convert to PDF ''' from os import makedirs from os.path import dirname, join, exists from calibre.ptempfile import PersistentTemporaryFile from calibre.constants import DEBUG from shlex import split as shsplit # All files are relative to the OPF location opf_dir = dirname(self.opf) base_dir = dirname(self.pdf_file) base_dir = join(opf_dir, base_dir) try: makedirs(base_dir) except BaseException: if not exists(base_dir): raise # Create a temporary CSS file with the box contents custom_CSS = PersistentTemporaryFile() custom_CSS.write(unicode(self.css1.toPlainText())) custom_CSS.close() # Create a temporary file with the list of input files file_list = PersistentTemporaryFile() for item in self.oeb.spine: file_list.write(item.href + "\n") file_list.close() # Build the command line command = prefs['prince_exe'] args = ['-v'] if self.prince_file: args.append('-s') args.append(self.prince_file) args.append('-s') args.append(custom_CSS.name) args.append('-l') args.append(file_list.name) args.append('-o') args.append(self.pdf_file) # Additional command-line arguments args.extend(shsplit(self.args.text())) # Hide the convert button and show a busy indicator self.convert.setEnabled(False) self.progress_bar = QProgressBar() self.progress_bar.setRange(0,0) self.progress_bar.setValue(0) self.l.addWidget(self.progress_bar) # Run the command and return the path to the PDF file if DEBUG: print(_('Converting book...')) process = QProcess(self) process.setWorkingDirectory(opf_dir) process.setProcessChannelMode(QProcess.MergedChannels); process.error.connect(self.error) process.finished.connect(self.end) self.process = process if DEBUG: from subprocess import list2cmdline line = list2cmdline([command] + args) print(_('Command line: %s') % line) process.start(command, args) def error(self, rc): ''' Show a message when there is an error in the command :param rc: The error code ''' from calibre.gui2 import error_dialog # Remove the progress bar while the error message is displayed self.progress_bar.hide() self.progress_bar.deleteLater() error_dialog(self, _('Process error'), _('<p>Error code: %s' '<p>make sure Prince (<a href="http://www.princexml.com">www.princexml.com</a>) is installed ' 'and the correct command-line-interface executable is set in the configuration of this plugin, ' 'which is usually:' '<ul><li>In Windows: <code><i>Prince_folder</i>\\Engine\\bin\\prince.exe</code>' ' <li>In Linux: <code>prince</code>' '</ul>') % rc, show=True) self.pdf_file = None self.accept() def end(self, rc): ''' Close and return the filename when the process ends :param rc: The return code (0 if successful) ''' from os.path import join self.prince_log = unicode(self.process.readAllStandardOutput().data()) opf_dir = unicode(self.process.workingDirectory()) if (rc == 0): self.pdf_file = join(opf_dir, self.pdf_file) else: self.pdf_file = None self.accept()
class BasicTab(QWidget): 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 view_prefs(self): d = PrefsViewerDialog(self.plugin_action.gui, PREFS_NAMESPACE) d.exec_() def reset_dialogs(self): for key in dynamic.keys(): if key.startswith('epubsplit_') and key.endswith('_again') \ and dynamic[key] is False: dynamic[key] = True info_dialog(self, _('Done'), _('Confirmation dialogs have all been reset'), show=True, show_copy_button=False)
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)
class ConvertDialog(QDialog): hide_text = _('&Hide styles') show_text = _('&Show styles') prince_log = '' prince_file = '' prince_css = '' # GUI definition def __init__(self, mi, fmt, opf, oeb, icon): ''' :param mi: The book metadata :param fmt: The source format used for conversion :param opf: The path to the OPF file :param oeb: An OEB object for the unpacked book :param icon: The window icon ''' self.opf = opf self.oeb = oeb self.mi = mi # The unpacked book needs to be parsed before, to read the contents # of the prince-style file, if it exists self.parse() QDialog.__init__(self) self.setAttribute(Qt.WA_DeleteOnClose) self.setWindowTitle(_('Convert to PDF with Prince')) self.setWindowIcon(icon) self.l = QVBoxLayout() self.setLayout(self.l) self.title_label = QLabel(_('<b>Title:</b> %s') % self.mi.title) self.l.addWidget(self.title_label) self.format_label = QLabel(_('<b>Source format:</b> %s') % fmt) self.l.addWidget(self.format_label) self.add_book = QCheckBox(_('&Add PDF to the book record')) self.add_book.setToolTip( _('<qt>Add the converted PDF to the selected book record</qt>')) self.add_book.setChecked(prefs['add_book']) self.add_book.stateChanged.connect(self.set_add_book) self.l.addWidget(self.add_book) self.ll = QHBoxLayout() self.ll.setAlignment(Qt.AlignLeft) self.l.addLayout(self.ll) self.label_css = QLabel(_('&Custom style:')) self.ll.addWidget(self.label_css) self.css_list = QComboBox() self.css_list.setToolTip( _('<qt>Select one style to use. Additional styles can be created in the plugin configuration</qt>' )) for key in sorted(prefs['custom_CSS_list'], key=lambda x: x.lower()): self.css_list.addItem(key, key) self.css_list.setCurrentIndex( self.css_list.findText(prefs['default_CSS'])) self.css_list.currentIndexChanged.connect(self.set_css) self.ll.addWidget(self.css_list) self.label_css.setBuddy(self.css_list) self.ll_ = QHBoxLayout() self.l.addLayout(self.ll_) self.label_args = QLabel(_('A&dditional command-line arguments:')) self.ll_.addWidget(self.label_args) self.args = QLineEdit(self) self.args.setText(prefs['custom_args_list'][prefs['default_CSS']]) self.args.setToolTip( _('<qt>Specify additional command-line arguments for the conversion</qt>' )) self.ll_.addWidget(self.args) self.label_args.setBuddy(self.args) self.css = QTabWidget() self.l.addWidget(self.css) self.css1 = TextEditWithTooltip(self, expected_geometry=(80, 20)) self.css1.setLineWrapMode(TextEditWithTooltip.NoWrap) self.css1.load_text( self.replace_templates( prefs['custom_CSS_list'][prefs['default_CSS']]), 'css') self.css1.setToolTip( _('<qt>This stylesheet can be modified<br/>The default can be configured</qt>' )) i = self.css.addTab(self.css1, _('C&ustom CSS')) self.css.setTabToolTip( i, _('<qt>Custom CSS stylesheet to be used for this conversion</qt>')) monofont = QFont('') monofont.setStyleHint(QFont.TypeWriter) if (self.prince_css): self.css2 = QPlainTextEdit() self.css2.setStyleSheet('* { font-family: monospace }') self.css2.setLineWrapMode(QPlainTextEdit.NoWrap) self.css2.setPlainText(self.prince_css) self.css2.setReadOnly(True) self.css2.setToolTip( _('<qt>This stylesheet cannot be modified</qt>')) i = self.css.addTab(self.css2, _('&Book CSS')) self.css.setTabToolTip( i, _('<qt>Book-specific CSS stylesheet included in the ebook file</qt>' )) self.ll = QHBoxLayout() self.l.addLayout(self.ll) if (prefs['show_CSS']): self.toggle = QPushButton(self.hide_text, self) else: self.toggle = QPushButton(self.show_text, self) self.toggle.setToolTip( _('<qt>Show/hide the additional styles used for the conversion</qt>' )) self.toggle.clicked.connect(self.toggle_tabs) self.convert = QPushButton(_('Con&vert'), self) self.convert.setToolTip(_('<qt>Run the conversion with Prince</qt>')) self.convert.setDefault(True) self.buttons = QDialogButtonBox(QDialogButtonBox.Cancel) self.buttons.addButton(self.toggle, QDialogButtonBox.ResetRole) self.buttons.addButton(self.convert, QDialogButtonBox.AcceptRole) self.l.addWidget(self.buttons) self.buttons.accepted.connect(self.prince_convert) self.buttons.rejected.connect(self.reject) if (not prefs['show_CSS']): self.css.hide() self.adjustSize() def toggle_tabs(self): ''' Enable/disable the CSS tabs, and store the setting ''' if (self.css.isVisible()): self.css.hide() self.label_args.hide() self.args.hide() self.toggle.setText(self.show_text) self.adjustSize() else: self.css.show() self.label_args.show() self.args.show() self.toggle.setText(self.hide_text) self.adjustSize() prefs['show_CSS'] = self.css.isVisible() def set_add_book(self): ''' Save the status of the add_book checkbox ''' prefs['add_book'] = self.add_book.isChecked() def set_css(self): ''' Fill the custom CSS text box with the selected stylesheet (and command-line arguments) ''' style = unicode(self.css_list.currentText()) self.css1.load_text( self.replace_templates(prefs['custom_CSS_list'][style]), 'css') self.args.setText(prefs['custom_args_list'][style]) prefs['default_CSS'] = style def parse(self): ''' Parse the unpacked OPF file to find and read the prince-style file ''' from calibre.constants import DEBUG from os.path import dirname, join from lxml import etree import codecs if DEBUG: print(_('Parsing book...')) opf_dir = dirname(self.opf) root = etree.parse(self.opf).getroot() metadata = root.find('{*}metadata') for meta in metadata.findall("{*}meta[@name='prince-style']"): prince_id = meta.get('content') for item in self.oeb.manifest: if (item.id == prince_id): self.prince_file = item.href break if (self.prince_file): fl = codecs.open(join(opf_dir, self.prince_file), 'rb', 'utf-8') self.prince_css = fl.read() fl.close() def replace_templates(self, text): ''' Replace templates (enclosed by '@{@', '@}@') in the input text ''' import re import json from calibre.ebooks.metadata.book.formatter import SafeFormat from calibre.constants import DEBUG matches = list(re.finditer('@{@(.+?)@}@', text, re.DOTALL)) results = {} for match in reversed(matches): result = SafeFormat().safe_format(match.group(1), self.mi, ('EXCEPTION: '), self.mi) # Escape quotes, backslashes and newlines result = re.sub(r'''['"\\]''', r'\\\g<0>', result) result = re.sub('\n', r'\\A ', result) results[match.group(1)] = result text = text[:match.start(0)] + result + text[match.end(0):] if DEBUG: print(_('Replacing templates')) for match in matches: print( _('Found: %s (%d-%d)') % (match.group(1), match.start(0), match.end(0))) print(_('Replace with: %s') % results[match.group(1)]) return text def prince_convert(self): ''' Call the actual Prince command to convert to PDF ''' from os import makedirs from os.path import dirname, join, exists from calibre.ptempfile import PersistentTemporaryFile from calibre.constants import DEBUG from shlex import split as shsplit # All files are relative to the OPF location opf_dir = dirname(self.opf) base_dir = dirname(self.pdf_file) base_dir = join(opf_dir, base_dir) try: makedirs(base_dir) except BaseException: if not exists(base_dir): raise # Create a temporary CSS file with the box contents custom_CSS = PersistentTemporaryFile(mode='w+') custom_CSS.write(unicode(self.css1.toPlainText())) custom_CSS.close() # Create a temporary file with the list of input files file_list = PersistentTemporaryFile(mode='w+') for item in self.oeb.spine: file_list.write(item.href + "\n") file_list.close() # Build the command line command = prefs['prince_exe'] args = ['-v'] if self.prince_file: args.append('-s') args.append(self.prince_file) args.append('-s') args.append(custom_CSS.name) args.append('-l') args.append(file_list.name) args.append('-o') args.append(self.pdf_file) # Additional command-line arguments args.extend(shsplit(self.args.text())) # Hide the convert button and show a busy indicator self.convert.setEnabled(False) self.progress_bar = QProgressBar() self.progress_bar.setRange(0, 0) self.progress_bar.setValue(0) self.l.addWidget(self.progress_bar) # Run the command and return the path to the PDF file if DEBUG: print(_('Converting book...')) process = QProcess(self) process.setWorkingDirectory(opf_dir) process.setProcessChannelMode(QProcess.MergedChannels) process.error.connect(self.error) process.finished.connect(self.end) self.process = process if DEBUG: from subprocess import list2cmdline line = list2cmdline([command] + args) print(_('Command line: %s') % line) process.start(command, args) def error(self, rc): ''' Show a message when there is an error in the command :param rc: The error code ''' from calibre.gui2 import error_dialog # Remove the progress bar while the error message is displayed self.progress_bar.hide() self.progress_bar.deleteLater() error_dialog( self, _('Process error'), _('<p>Error code: %s' '<p>make sure Prince (<a href="http://www.princexml.com">www.princexml.com</a>) is installed ' 'and the correct command-line-interface executable is set in the configuration of this plugin, ' 'which is usually:' '<ul><li>In Windows: <code><i>Prince_folder</i>\\Engine\\bin\\prince.exe</code>' ' <li>In Linux: <code>prince</code>' '</ul>') % rc, show=True) self.pdf_file = None self.accept() def end(self, rc): ''' Close and return the filename when the process ends :param rc: The return code (0 if successful) ''' from os.path import join self.prince_log = unicode(self.process.readAllStandardOutput().data()) opf_dir = unicode(self.process.workingDirectory()) if (rc == 0): self.pdf_file = join(opf_dir, self.pdf_file) else: self.pdf_file = None self.accept()
class ConfigWidget(QWidget, Logger): ''' Config dialog for Marvin Manager ''' WIZARD_PROFILES = { 'Annotations': { 'label': 'mm_annotations', 'datatype': 'comments', 'display': {}, 'is_multiple': False }, 'Collections': { 'label': 'mm_collections', 'datatype': 'text', 'display': {u'is_names': False}, 'is_multiple': True }, 'Last read': { 'label': 'mm_date_read', 'datatype': 'datetime', 'display': {}, 'is_multiple': False }, 'Locked': { 'label': 'mm_locked', 'datatype': 'bool', 'display': {}, 'is_multiple': False }, 'Progress': { 'label': 'mm_progress', 'datatype': 'float', 'display': {u'number_format': u'{0:.0f}%'}, 'is_multiple': False }, 'Read': { 'label': 'mm_read', 'datatype': 'bool', 'display': {}, 'is_multiple': False }, 'Reading list': { 'label': 'mm_reading_list', 'datatype': 'bool', 'display': {}, 'is_multiple': False }, 'Word count': { 'label': 'mm_word_count', 'datatype': 'int', 'display': {u'number_format': u'{0:n}'}, 'is_multiple': False } } def __init__(self, plugin_action): QWidget.__init__(self) self.parent = plugin_action self.gui = get_gui() self.icon = plugin_action.icon self.opts = plugin_action.opts self.prefs = plugin_prefs self.resources_path = plugin_action.resources_path self.verbose = plugin_action.verbose self.restart_required = False self._log_location() self.l = QGridLayout() self.setLayout(self.l) self.column1_layout = QVBoxLayout() self.l.addLayout(self.column1_layout, 0, 0) self.column2_layout = QVBoxLayout() self.l.addLayout(self.column2_layout, 0, 1) # ----------------------------- Column 1 ----------------------------- # ~~~~~~~~ Create the Custom fields options group box ~~~~~~~~ self.cfg_custom_fields_gb = QGroupBox(self) self.cfg_custom_fields_gb.setTitle('Custom column assignments') self.column1_layout.addWidget(self.cfg_custom_fields_gb) self.cfg_custom_fields_qgl = QGridLayout(self.cfg_custom_fields_gb) current_row = 0 # ++++++++ Labels + HLine ++++++++ self.marvin_source_label = QLabel("Marvin source") self.cfg_custom_fields_qgl.addWidget(self.marvin_source_label, current_row, 0) self.calibre_destination_label = QLabel("calibre destination") self.cfg_custom_fields_qgl.addWidget(self.calibre_destination_label, current_row, 1) current_row += 1 self.sd_hl = QFrame(self.cfg_custom_fields_gb) self.sd_hl.setFrameShape(QFrame.HLine) self.sd_hl.setFrameShadow(QFrame.Raised) self.cfg_custom_fields_qgl.addWidget(self.sd_hl, current_row, 0, 1, 3) current_row += 1 # ++++++++ Annotations ++++++++ self.cfg_annotations_label = QLabel('Annotations') self.cfg_annotations_label.setAlignment(Qt.AlignLeft) self.cfg_custom_fields_qgl.addWidget(self.cfg_annotations_label, current_row, 0) self.annotations_field_comboBox = QComboBox(self.cfg_custom_fields_gb) self.annotations_field_comboBox.setObjectName('annotations_field_comboBox') self.annotations_field_comboBox.setToolTip('Select a custom column to store Marvin annotations') self.cfg_custom_fields_qgl.addWidget(self.annotations_field_comboBox, current_row, 1) self.cfg_highlights_wizard = QToolButton() self.cfg_highlights_wizard.setIcon(QIcon(I('wizard.png'))) self.cfg_highlights_wizard.setToolTip("Create a custom column to store Marvin annotations") self.cfg_highlights_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Annotations')) self.cfg_custom_fields_qgl.addWidget(self.cfg_highlights_wizard, current_row, 2) current_row += 1 # ++++++++ Collections ++++++++ self.cfg_collections_label = QLabel('Collections') self.cfg_collections_label.setAlignment(Qt.AlignLeft) self.cfg_custom_fields_qgl.addWidget(self.cfg_collections_label, current_row, 0) self.collection_field_comboBox = QComboBox(self.cfg_custom_fields_gb) self.collection_field_comboBox.setObjectName('collection_field_comboBox') self.collection_field_comboBox.setToolTip('Select a custom column to store Marvin collection assignments') self.cfg_custom_fields_qgl.addWidget(self.collection_field_comboBox, current_row, 1) self.cfg_collections_wizard = QToolButton() self.cfg_collections_wizard.setIcon(QIcon(I('wizard.png'))) self.cfg_collections_wizard.setToolTip("Create a custom column for Marvin collection assignments") self.cfg_collections_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Collections')) self.cfg_custom_fields_qgl.addWidget(self.cfg_collections_wizard, current_row, 2) current_row += 1 # ++++++++ Last read ++++++++ self.cfg_date_read_label = QLabel("Last read") self.cfg_date_read_label.setAlignment(Qt.AlignLeft) self.cfg_custom_fields_qgl.addWidget(self.cfg_date_read_label, current_row, 0) self.date_read_field_comboBox = QComboBox(self.cfg_custom_fields_gb) self.date_read_field_comboBox.setObjectName('date_read_field_comboBox') self.date_read_field_comboBox.setToolTip('Select a custom column to store Last read date') self.cfg_custom_fields_qgl.addWidget(self.date_read_field_comboBox, current_row, 1) self.cfg_collections_wizard = QToolButton() self.cfg_collections_wizard.setIcon(QIcon(I('wizard.png'))) self.cfg_collections_wizard.setToolTip("Create a custom column to store Last read date") self.cfg_collections_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Last read')) self.cfg_custom_fields_qgl.addWidget(self.cfg_collections_wizard, current_row, 2) current_row += 1 # ++++++++ Locked ++++++++ self.cfg_locked_label = QLabel("Locked") self.cfg_locked_label.setAlignment(Qt.AlignLeft) self.cfg_custom_fields_qgl.addWidget(self.cfg_locked_label, current_row, 0) self.locked_field_comboBox = QComboBox(self.cfg_custom_fields_gb) self.locked_field_comboBox.setObjectName('locked_field_comboBox') self.locked_field_comboBox.setToolTip('Select a custom column to store Locked status') self.cfg_custom_fields_qgl.addWidget(self.locked_field_comboBox, current_row, 1) self.cfg_locked_wizard = QToolButton() self.cfg_locked_wizard.setIcon(QIcon(I('wizard.png'))) self.cfg_locked_wizard.setToolTip("Create a custom column to store Locked status") self.cfg_locked_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Locked')) self.cfg_custom_fields_qgl.addWidget(self.cfg_locked_wizard, current_row, 2) current_row += 1 # ++++++++ Progress ++++++++ self.cfg_progress_label = QLabel('Progress') self.cfg_progress_label.setAlignment(Qt.AlignLeft) self.cfg_custom_fields_qgl.addWidget(self.cfg_progress_label, current_row, 0) self.progress_field_comboBox = QComboBox(self.cfg_custom_fields_gb) self.progress_field_comboBox.setObjectName('progress_field_comboBox') self.progress_field_comboBox.setToolTip('Select a custom column to store Marvin reading progress') self.cfg_custom_fields_qgl.addWidget(self.progress_field_comboBox, current_row, 1) self.cfg_progress_wizard = QToolButton() self.cfg_progress_wizard.setIcon(QIcon(I('wizard.png'))) self.cfg_progress_wizard.setToolTip("Create a custom column to store Marvin reading progress") self.cfg_progress_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Progress')) self.cfg_custom_fields_qgl.addWidget(self.cfg_progress_wizard, current_row, 2) current_row += 1 # ++++++++ Read flag ++++++++ self.cfg_read_label = QLabel('Read') self.cfg_read_label.setAlignment(Qt.AlignLeft) self.cfg_custom_fields_qgl.addWidget(self.cfg_read_label, current_row, 0) self.read_field_comboBox = QComboBox(self.cfg_custom_fields_gb) self.read_field_comboBox.setObjectName('read_field_comboBox') self.read_field_comboBox.setToolTip('Select a custom column to store Marvin Read status') self.cfg_custom_fields_qgl.addWidget(self.read_field_comboBox, current_row, 1) self.cfg_read_wizard = QToolButton() self.cfg_read_wizard.setIcon(QIcon(I('wizard.png'))) self.cfg_read_wizard.setToolTip("Create a custom column to store Marvin Read status") self.cfg_read_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Read')) self.cfg_custom_fields_qgl.addWidget(self.cfg_read_wizard, current_row, 2) current_row += 1 # ++++++++ Reading list flag ++++++++ self.cfg_reading_list_label = QLabel('Reading list') self.cfg_reading_list_label.setAlignment(Qt.AlignLeft) self.cfg_custom_fields_qgl.addWidget(self.cfg_reading_list_label, current_row, 0) self.reading_list_field_comboBox = QComboBox(self.cfg_custom_fields_gb) self.reading_list_field_comboBox.setObjectName('reading_list_field_comboBox') self.reading_list_field_comboBox.setToolTip('Select a custom column to store Marvin Reading list status') self.cfg_custom_fields_qgl.addWidget(self.reading_list_field_comboBox, current_row, 1) self.cfg_reading_list_wizard = QToolButton() self.cfg_reading_list_wizard.setIcon(QIcon(I('wizard.png'))) self.cfg_reading_list_wizard.setToolTip("Create a custom column to store Marvin Reading list status") self.cfg_reading_list_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Reading list')) self.cfg_custom_fields_qgl.addWidget(self.cfg_reading_list_wizard, current_row, 2) current_row += 1 # ++++++++ Word count ++++++++ self.cfg_word_count_label = QLabel('Word count') self.cfg_word_count_label.setAlignment(Qt.AlignLeft) self.cfg_custom_fields_qgl.addWidget(self.cfg_word_count_label, current_row, 0) self.word_count_field_comboBox = QComboBox(self.cfg_custom_fields_gb) self.word_count_field_comboBox.setObjectName('word_count_field_comboBox') self.word_count_field_comboBox.setToolTip('Select a custom column to store Marvin word counts') self.cfg_custom_fields_qgl.addWidget(self.word_count_field_comboBox, current_row, 1) self.cfg_word_count_wizard = QToolButton() self.cfg_word_count_wizard.setIcon(QIcon(I('wizard.png'))) self.cfg_word_count_wizard.setToolTip("Create a custom column to store Marvin word counts") self.cfg_word_count_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Word count')) self.cfg_custom_fields_qgl.addWidget(self.cfg_word_count_wizard, current_row, 2) current_row += 1 self.spacerItem1 = QSpacerItem(20, 20, QSizePolicy.Minimum, QSizePolicy.Expanding) self.column1_layout.addItem(self.spacerItem1) # ----------------------------- Column 2 ----------------------------- # ~~~~~~~~ Create the CSS group box ~~~~~~~~ self.cfg_css_options_gb = QGroupBox(self) self.cfg_css_options_gb.setTitle('CSS') self.column2_layout.addWidget(self.cfg_css_options_gb) self.cfg_css_options_qgl = QGridLayout(self.cfg_css_options_gb) current_row = 0 # ++++++++ Annotations appearance ++++++++ self.annotations_icon = QIcon(os.path.join(self.resources_path, 'icons', 'annotations_hiliter.png')) self.cfg_annotations_appearance_toolbutton = QToolButton() self.cfg_annotations_appearance_toolbutton.setIcon(self.annotations_icon) self.cfg_annotations_appearance_toolbutton.clicked.connect(self.configure_appearance) self.cfg_css_options_qgl.addWidget(self.cfg_annotations_appearance_toolbutton, current_row, 0) self.cfg_annotations_label = ClickableQLabel("Book notes, Bookmark notes and Annotations") self.cfg_annotations_label.clicked.connect(self.configure_appearance) self.cfg_css_options_qgl.addWidget(self.cfg_annotations_label, current_row, 1) current_row += 1 # ++++++++ Injected CSS ++++++++ self.css_editor_icon = QIcon(I('format-text-heading.png')) self.cfg_css_editor_toolbutton = QToolButton() self.cfg_css_editor_toolbutton.setIcon(self.css_editor_icon) self.cfg_css_editor_toolbutton.clicked.connect(self.edit_css) self.cfg_css_options_qgl.addWidget(self.cfg_css_editor_toolbutton, current_row, 0) self.cfg_css_editor_label = ClickableQLabel("Articles, Vocabulary") self.cfg_css_editor_label.clicked.connect(self.edit_css) self.cfg_css_options_qgl.addWidget(self.cfg_css_editor_label, current_row, 1) """ # ~~~~~~~~ Create the Dropbox syncing group box ~~~~~~~~ self.cfg_dropbox_syncing_gb = QGroupBox(self) self.cfg_dropbox_syncing_gb.setTitle('Dropbox') self.column2_layout.addWidget(self.cfg_dropbox_syncing_gb) self.cfg_dropbox_syncing_qgl = QGridLayout(self.cfg_dropbox_syncing_gb) current_row = 0 # ++++++++ Syncing enabled checkbox ++++++++ self.dropbox_syncing_checkbox = QCheckBox('Enable Dropbox updates') self.dropbox_syncing_checkbox.setObjectName('dropbox_syncing') self.dropbox_syncing_checkbox.setToolTip('Refresh custom column content from Marvin metadata') self.cfg_dropbox_syncing_qgl.addWidget(self.dropbox_syncing_checkbox, current_row, 0, 1, 3) current_row += 1 # ++++++++ Dropbox folder picker ++++++++ self.dropbox_folder_icon = QIcon(os.path.join(self.resources_path, 'icons', 'dropbox.png')) self.cfg_dropbox_folder_toolbutton = QToolButton() self.cfg_dropbox_folder_toolbutton.setIcon(self.dropbox_folder_icon) self.cfg_dropbox_folder_toolbutton.setToolTip("Specify Dropbox folder location on your computer") self.cfg_dropbox_folder_toolbutton.clicked.connect(self.select_dropbox_folder) self.cfg_dropbox_syncing_qgl.addWidget(self.cfg_dropbox_folder_toolbutton, current_row, 1) # ++++++++ Dropbox location lineedit ++++++++ self.dropbox_location_lineedit = QLineEdit() self.dropbox_location_lineedit.setPlaceholderText("Dropbox folder location") self.cfg_dropbox_syncing_qgl.addWidget(self.dropbox_location_lineedit, current_row, 2) """ # ~~~~~~~~ Create the General options group box ~~~~~~~~ self.cfg_runtime_options_gb = QGroupBox(self) self.cfg_runtime_options_gb.setTitle('General options') self.column2_layout.addWidget(self.cfg_runtime_options_gb) self.cfg_runtime_options_qvl = QVBoxLayout(self.cfg_runtime_options_gb) # ++++++++ Temporary markers: Duplicates ++++++++ self.duplicate_markers_checkbox = QCheckBox('Apply temporary markers to duplicate books') self.duplicate_markers_checkbox.setObjectName('apply_markers_to_duplicates') self.duplicate_markers_checkbox.setToolTip('Books with identical content will be flagged in the Library window') self.cfg_runtime_options_qvl.addWidget(self.duplicate_markers_checkbox) # ++++++++ Temporary markers: Updated ++++++++ self.updated_markers_checkbox = QCheckBox('Apply temporary markers to books with updated content') self.updated_markers_checkbox.setObjectName('apply_markers_to_updated') self.updated_markers_checkbox.setToolTip('Books with updated content will be flagged in the Library window') self.cfg_runtime_options_qvl.addWidget(self.updated_markers_checkbox) # ++++++++ Auto refresh checkbox ++++++++ self.auto_refresh_checkbox = QCheckBox('Automatically refresh custom column content') self.auto_refresh_checkbox.setObjectName('auto_refresh_at_startup') self.auto_refresh_checkbox.setToolTip('Update calibre custom column when Marvin XD is opened') self.cfg_runtime_options_qvl.addWidget(self.auto_refresh_checkbox) # ++++++++ Progress as percentage checkbox ++++++++ self.reading_progress_checkbox = QCheckBox('Show reading progress as percentage') self.reading_progress_checkbox.setObjectName('show_progress_as_percentage') self.reading_progress_checkbox.setToolTip('Display percentage in Progress column') self.cfg_runtime_options_qvl.addWidget(self.reading_progress_checkbox) # ~~~~~~~~ Create the Debug options group box ~~~~~~~~ self.cfg_debug_options_gb = QGroupBox(self) self.cfg_debug_options_gb.setTitle('Debug options') self.column2_layout.addWidget(self.cfg_debug_options_gb) self.cfg_debug_options_qvl = QVBoxLayout(self.cfg_debug_options_gb) # ++++++++ Debug logging checkboxes ++++++++ self.debug_plugin_checkbox = QCheckBox('Enable debug logging for Marvin XD') self.debug_plugin_checkbox.setObjectName('debug_plugin_checkbox') self.debug_plugin_checkbox.setToolTip('Print plugin diagnostic messages to console') self.cfg_debug_options_qvl.addWidget(self.debug_plugin_checkbox) self.debug_libimobiledevice_checkbox = QCheckBox('Enable debug logging for libiMobileDevice') self.debug_libimobiledevice_checkbox.setObjectName('debug_libimobiledevice_checkbox') self.debug_libimobiledevice_checkbox.setToolTip('Print libiMobileDevice diagnostic messages to console') self.cfg_debug_options_qvl.addWidget(self.debug_libimobiledevice_checkbox) self.spacerItem2 = QSpacerItem(20, 20, QSizePolicy.Minimum, QSizePolicy.Expanding) self.column2_layout.addItem(self.spacerItem2) # ~~~~~~~~ End of construction zone ~~~~~~~~ self.resize(self.sizeHint()) # ~~~~~~~~ Populate/restore config options ~~~~~~~~ # Annotations comboBox self.populate_annotations() self.populate_collections() self.populate_date_read() self.populate_locked() self.populate_progress() self.populate_read() self.populate_reading_list() self.populate_word_count() """ # Restore Dropbox settings, hook changes dropbox_syncing = self.prefs.get('dropbox_syncing', False) self.dropbox_syncing_checkbox.setChecked(dropbox_syncing) self.set_dropbox_syncing(dropbox_syncing) self.dropbox_syncing_checkbox.clicked.connect(partial(self.set_dropbox_syncing)) self.dropbox_location_lineedit.setText(self.prefs.get('dropbox_folder', '')) """ # Restore general settings self.duplicate_markers_checkbox.setChecked(self.prefs.get('apply_markers_to_duplicates', True)) self.updated_markers_checkbox.setChecked(self.prefs.get('apply_markers_to_updated', True)) self.auto_refresh_checkbox.setChecked(self.prefs.get('auto_refresh_at_startup', False)) self.reading_progress_checkbox.setChecked(self.prefs.get('show_progress_as_percentage', False)) # Restore debug settings, hook changes self.debug_plugin_checkbox.setChecked(self.prefs.get('debug_plugin', False)) self.debug_plugin_checkbox.stateChanged.connect(self.set_restart_required) self.debug_libimobiledevice_checkbox.setChecked(self.prefs.get('debug_libimobiledevice', False)) self.debug_libimobiledevice_checkbox.stateChanged.connect(self.set_restart_required) # Hook changes to Annotations comboBox # self.annotations_field_comboBox.currentIndexChanged.connect( # partial(self.save_combobox_setting, 'annotations_field_comboBox')) # self.connect(self.annotations_field_comboBox, # SIGNAL('currentIndexChanged(const QString &)'), # self.annotations_destination_changed) self.annotations_field_comboBox.currentIndexChanged.connect(self.annotations_destination_changed) # Launch the annotated_books_scanner field = get_cc_mapping('annotations', 'field', None) self.annotated_books_scanner = InventoryAnnotatedBooks(self.gui, field) self.annotated_books_scanner.signal.connect(self.inventory_complete) QTimer.singleShot(1, self.start_inventory) def annotations_destination_changed(self, qs_new_destination_name): ''' If the destination field changes, move all existing annotations from old to new ''' self._log_location(str(qs_new_destination_name)) self._log("self.eligible_annotations_fields: %s" % self.eligible_annotations_fields) old_destination_field = get_cc_mapping('annotations', 'field', None) old_destination_name = get_cc_mapping('annotations', 'combobox', None) self._log("old_destination_field: %s" % old_destination_field) self._log("old_destination_name: %s" % old_destination_name) # new_destination_name = unicode(qs_new_destination_name) # Signnls available have changed. Now receivin an indec rather than a name. Can get the name # from the combobox new_destination_name = unicode(self.annotations_field_comboBox.currentText()) self._log("new_destination_name: %s" % repr(new_destination_name)) if old_destination_name == new_destination_name: self._log_location("old_destination_name = new_destination_name, no changes") return if new_destination_name == '': self._log_location("annotations storage disabled") set_cc_mapping('annotations', field=None, combobox=new_destination_name) return new_destination_field = self.eligible_annotations_fields[new_destination_name] if existing_annotations(self.parent, old_destination_field): command = self.launch_new_destination_dialog(old_destination_name, new_destination_name) if command == 'move': set_cc_mapping('annotations', field=new_destination_field, combobox=new_destination_name) if self.annotated_books_scanner.isRunning(): self.annotated_books_scanner.wait() move_annotations(self, self.annotated_books_scanner.annotation_map, old_destination_field, new_destination_field) elif command == 'change': # Keep the updated destination field, but don't move annotations pass elif command == 'cancel': # Restore previous destination self.annotations_field_comboBox.blockSignals(True) old_index = self.annotations_field_comboBox.findText(old_destination_name) self.annotations_field_comboBox.setCurrentIndex(old_index) self.annotations_field_comboBox.blockSignals(False) else: # No existing annotations, just update prefs self._log("no existing annotations, updating destination to '{0}'".format(new_destination_name)) set_cc_mapping('annotations', field=new_destination_field, combobox=new_destination_name) def configure_appearance(self): ''' ''' self._log_location() appearance_settings = { 'appearance_css': default_elements, 'appearance_hr_checkbox': False, 'appearance_timestamp_format': default_timestamp } # Save, hash the original settings original_settings = {} osh = hashlib.md5() for setting in appearance_settings: original_settings[setting] = plugin_prefs.get(setting, appearance_settings[setting]) osh.update(repr(plugin_prefs.get(setting, appearance_settings[setting]))) # Display the Annotations appearance dialog aa = AnnotationsAppearance(self, self.annotations_icon, plugin_prefs) cancelled = False if aa.exec_(): # appearance_hr_checkbox and appearance_timestamp_format changed live to prefs during previews plugin_prefs.set('appearance_css', aa.elements_table.get_data()) # Generate a new hash nsh = hashlib.md5() for setting in appearance_settings: nsh.update(repr(plugin_prefs.get(setting, appearance_settings[setting]))) else: for setting in appearance_settings: plugin_prefs.set(setting, original_settings[setting]) nsh = osh # If there were changes, and there are existing annotations, # and there is an active Annotations field, offer to re-render field = get_cc_mapping('annotations', 'field', None) if osh.digest() != nsh.digest() and existing_annotations(self.parent, field): title = 'Update annotations?' msg = '<p>Update existing annotations to new appearance settings?</p>' d = MessageBox(MessageBox.QUESTION, title, msg, show_copy_button=False) self._log_location("QUESTION: %s" % msg) if d.exec_(): self._log_location("Updating existing annotations to modified appearance") # Wait for indexing to complete while not self.annotated_books_scanner.isFinished(): Application.processEvents() move_annotations(self, self.annotated_books_scanner.annotation_map, field, field, window_title="Updating appearance") def edit_css(self): ''' ''' self._log_location() from calibre_plugins.marvin_manager.book_status import dialog_resources_path klass = os.path.join(dialog_resources_path, 'css_editor.py') if os.path.exists(klass): sys.path.insert(0, dialog_resources_path) this_dc = importlib.import_module('css_editor') sys.path.remove(dialog_resources_path) dlg = this_dc.CSSEditorDialog(self, 'css_editor') dlg.initialize(self) dlg.exec_() def get_eligible_custom_fields(self, eligible_types=[], is_multiple=None): ''' Discover qualifying custom fields for eligible_types[] ''' #self._log_location(eligible_types) eligible_custom_fields = {} for cf in self.gui.current_db.custom_field_keys(): cft = self.gui.current_db.metadata_for_field(cf)['datatype'] cfn = self.gui.current_db.metadata_for_field(cf)['name'] cfim = self.gui.current_db.metadata_for_field(cf)['is_multiple'] #self._log("cf: %s cft: %s cfn: %s cfim: %s" % (cf, cft, cfn, cfim)) if cft in eligible_types: if is_multiple is not None: if bool(cfim) == is_multiple: eligible_custom_fields[cfn] = cf else: eligible_custom_fields[cfn] = cf return eligible_custom_fields def inventory_complete(self, msg): self._log_location(msg) def launch_cc_wizard(self, column_type): ''' ''' def _update_combo_box(comboBox, destination, previous): ''' ''' cb = getattr(self, comboBox) cb.blockSignals(True) all_items = [str(cb.itemText(i)) for i in range(cb.count())] if previous and previous in all_items: all_items.remove(previous) all_items.append(destination) cb.clear() cb.addItems(sorted(all_items, key=lambda s: s.lower())) # Select the new destination in the comboBox idx = cb.findText(destination) if idx > -1: cb.setCurrentIndex(idx) cb.blockSignals(False) from calibre_plugins.marvin_manager.book_status import dialog_resources_path klass = os.path.join(dialog_resources_path, 'cc_wizard.py') if os.path.exists(klass): #self._log("importing CC Wizard dialog from '%s'" % klass) sys.path.insert(0, dialog_resources_path) this_dc = importlib.import_module('cc_wizard') sys.path.remove(dialog_resources_path) dlg = this_dc.CustomColumnWizard(self, column_type, self.WIZARD_PROFILES[column_type], verbose=True) dlg.exec_() if dlg.modified_column: self._log("modified_column: %s" % dlg.modified_column) self.restart_required = True destination = dlg.modified_column['destination'] label = dlg.modified_column['label'] previous = dlg.modified_column['previous'] source = dlg.modified_column['source'] if source == "Annotations": _update_combo_box("annotations_field_comboBox", destination, previous) # Add/update the new destination so save_settings() can find it self.eligible_annotations_fields[destination] = label # Save manually in case user cancels set_cc_mapping('annotations', combobox=destination, field=label) elif source == 'Collections': _update_combo_box("collection_field_comboBox", destination, previous) # Add/update the new destination so save_settings() can find it self.eligible_collection_fields[destination] = label # Save manually in case user cancels set_cc_mapping('collections', combobox=destination, field=label) elif source == 'Last read': _update_combo_box("date_read_field_comboBox", destination, previous) # Add/update the new destination so save_settings() can find it self.eligible_date_read_fields[destination] = label # Save manually in case user cancels set_cc_mapping('date_read', combobox=destination, field=label) elif source == 'Locked': _update_combo_box("locked_field_comboBox", destination, previous) # Add/update the new destination so save_settings() can find it self.eligible_locked_fields[destination] = label # Save manually in case user cancels set_cc_mapping('locked', combobox=destination, field=label) elif source == "Progress": _update_combo_box("progress_field_comboBox", destination, previous) # Add/update the new destination so save_settings() can find it self.eligible_progress_fields[destination] = label # Save manually in case user cancels set_cc_mapping('progress', combobox=destination, field=label) elif source == "Read": _update_combo_box("read_field_comboBox", destination, previous) # Add/update the new destination so save_settings() can find it self.eligible_read_fields[destination] = label # Save manually in case user cancels set_cc_mapping('read', combobox=destination, field=label) elif source == "Reading list": _update_combo_box("reading_list_field_comboBox", destination, previous) # Add/update the new destination so save_settings() can find it self.eligible_reading_list_fields[destination] = label # Save manually in case user cancels set_cc_mapping('reading_list', combobox=destination, field=label) elif source == "Word count": _update_combo_box("word_count_field_comboBox", destination, previous) # Add/update the new destination so save_settings() can find it self.eligible_word_count_fields[destination] = label # Save manually in case user cancels set_cc_mapping('word_count', combobox=destination, field=label) else: self._log("ERROR: Can't import from '%s'" % klass) def launch_new_destination_dialog(self, old, new): ''' Return 'move', 'change' or 'cancel' ''' from calibre_plugins.marvin_manager.book_status import dialog_resources_path self._log_location() klass = os.path.join(dialog_resources_path, 'new_destination.py') if os.path.exists(klass): self._log("importing new destination dialog from '%s'" % klass) sys.path.insert(0, dialog_resources_path) this_dc = importlib.import_module('new_destination') sys.path.remove(dialog_resources_path) dlg = this_dc.NewDestinationDialog(self, old, new) dlg.exec_() return dlg.command def populate_annotations(self): datatype = self.WIZARD_PROFILES['Annotations']['datatype'] self.eligible_annotations_fields = self.get_eligible_custom_fields([datatype]) self.annotations_field_comboBox.addItems(['']) ecf = sorted(self.eligible_annotations_fields.keys(), key=lambda s: s.lower()) self.annotations_field_comboBox.addItems(ecf) # Retrieve stored value existing = get_cc_mapping('annotations', 'combobox') if existing: ci = self.annotations_field_comboBox.findText(existing) self.annotations_field_comboBox.setCurrentIndex(ci) def populate_collections(self): datatype = self.WIZARD_PROFILES['Collections']['datatype'] self.eligible_collection_fields = self.get_eligible_custom_fields([datatype], is_multiple=True) self.collection_field_comboBox.addItems(['']) ecf = sorted(self.eligible_collection_fields.keys(), key=lambda s: s.lower()) self.collection_field_comboBox.addItems(ecf) # Retrieve stored value existing = get_cc_mapping('collections', 'combobox') if existing: ci = self.collection_field_comboBox.findText(existing) self.collection_field_comboBox.setCurrentIndex(ci) def populate_date_read(self): #self.eligible_date_read_fields = self.get_eligible_custom_fields(['datetime']) datatype = self.WIZARD_PROFILES['Last read']['datatype'] self.eligible_date_read_fields = self.get_eligible_custom_fields([datatype]) self.date_read_field_comboBox.addItems(['']) ecf = sorted(self.eligible_date_read_fields.keys(), key=lambda s: s.lower()) self.date_read_field_comboBox.addItems(ecf) # Retrieve stored value existing = get_cc_mapping('date_read', 'combobox') if existing: ci = self.date_read_field_comboBox.findText(existing) self.date_read_field_comboBox.setCurrentIndex(ci) def populate_locked(self): datatype = self.WIZARD_PROFILES['Locked']['datatype'] self.eligible_locked_fields = self.get_eligible_custom_fields([datatype]) self.locked_field_comboBox.addItems(['']) ecf = sorted(self.eligible_locked_fields.keys(), key=lambda s: s.lower()) self.locked_field_comboBox.addItems(ecf) # Retrieve stored value existing = get_cc_mapping('locked', 'combobox') if existing: ci = self.locked_field_comboBox.findText(existing) self.locked_field_comboBox.setCurrentIndex(ci) def populate_progress(self): #self.eligible_progress_fields = self.get_eligible_custom_fields(['float']) datatype = self.WIZARD_PROFILES['Progress']['datatype'] self.eligible_progress_fields = self.get_eligible_custom_fields([datatype]) self.progress_field_comboBox.addItems(['']) ecf = sorted(self.eligible_progress_fields.keys(), key=lambda s: s.lower()) self.progress_field_comboBox.addItems(ecf) # Retrieve stored value existing = get_cc_mapping('progress', 'combobox') if existing: ci = self.progress_field_comboBox.findText(existing) self.progress_field_comboBox.setCurrentIndex(ci) def populate_read(self): datatype = self.WIZARD_PROFILES['Read']['datatype'] self.eligible_read_fields = self.get_eligible_custom_fields([datatype]) self.read_field_comboBox.addItems(['']) ecf = sorted(self.eligible_read_fields.keys(), key=lambda s: s.lower()) self.read_field_comboBox.addItems(ecf) # Retrieve stored value existing = get_cc_mapping('read', 'combobox') if existing: ci = self.read_field_comboBox.findText(existing) self.read_field_comboBox.setCurrentIndex(ci) def populate_reading_list(self): datatype = self.WIZARD_PROFILES['Reading list']['datatype'] self.eligible_reading_list_fields = self.get_eligible_custom_fields([datatype]) self.reading_list_field_comboBox.addItems(['']) ecf = sorted(self.eligible_reading_list_fields.keys(), key=lambda s: s.lower()) self.reading_list_field_comboBox.addItems(ecf) # Retrieve stored value existing = get_cc_mapping('reading_list', 'combobox') if existing: ci = self.reading_list_field_comboBox.findText(existing) self.reading_list_field_comboBox.setCurrentIndex(ci) def populate_word_count(self): #self.eligible_word_count_fields = self.get_eligible_custom_fields(['int']) datatype = self.WIZARD_PROFILES['Word count']['datatype'] self.eligible_word_count_fields = self.get_eligible_custom_fields([datatype]) self.word_count_field_comboBox.addItems(['']) ecf = sorted(self.eligible_word_count_fields.keys(), key=lambda s: s.lower()) self.word_count_field_comboBox.addItems(ecf) # Retrieve stored value existing = get_cc_mapping('word_count', 'combobox') if existing: ci = self.word_count_field_comboBox.findText(existing) self.word_count_field_comboBox.setCurrentIndex(ci) """ def select_dropbox_folder(self): ''' ''' self._log_location() dropbox_location = QFileDialog.getExistingDirectory( self, "Dropbox folder", os.path.expanduser("~"), QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks) self.dropbox_location_lineedit.setText(unicode(dropbox_location)) def set_dropbox_syncing(self, state): ''' Called when checkbox changes state, or when restoring state Set enabled state of Dropbox folder picker to match ''' self.cfg_dropbox_folder_toolbutton.setEnabled(state) self.dropbox_location_lineedit.setEnabled(state) """ def set_restart_required(self, state): ''' Set restart_required flag to show show dialog when closing dialog ''' self.restart_required = True """ def save_combobox_setting(self, cb, index): ''' Apply changes immediately ''' cf = str(getattr(self, cb).currentText()) self._log_location("%s => %s" % (cb, repr(cf))) if cb == 'annotations_field_comboBox': field = None if cf: field = self.eligible_annotations_fields[cf] set_cc_mapping('annotations', combobox=cf, field=field) """ def save_settings(self): self._log_location() # Annotations field cf = unicode(self.annotations_field_comboBox.currentText()) field = None if cf: field = self.eligible_annotations_fields[cf] set_cc_mapping('annotations', combobox=cf, field=field) # Collections field cf = unicode(self.collection_field_comboBox.currentText()) field = None if cf: field = self.eligible_collection_fields[cf] set_cc_mapping('collections', combobox=cf, field=field) # Date read field cf = unicode(self.date_read_field_comboBox.currentText()) field = None if cf: field = self.eligible_date_read_fields[cf] set_cc_mapping('date_read', combobox=cf, field=field) # Locked field cf = unicode(self.locked_field_comboBox.currentText()) field = None if cf: field = self.eligible_locked_fields[cf] set_cc_mapping('locked', combobox=cf, field=field) # Progress field cf = unicode(self.progress_field_comboBox.currentText()) field = None if cf: field = self.eligible_progress_fields[cf] set_cc_mapping('progress', combobox=cf, field=field) # Read field cf = unicode(self.read_field_comboBox.currentText()) field = None if cf: field = self.eligible_read_fields[cf] set_cc_mapping('read', combobox=cf, field=field) # Reading list field cf = unicode(self.reading_list_field_comboBox.currentText()) field = None if cf: field = self.eligible_reading_list_fields[cf] set_cc_mapping('reading_list', combobox=cf, field=field) # Word count field cf = unicode(self.word_count_field_comboBox.currentText()) field = None if cf: field = self.eligible_word_count_fields[cf] set_cc_mapping('word_count', combobox=cf, field=field) ''' # Save Dropbox settings self.prefs.set('dropbox_syncing', self.dropbox_syncing_checkbox.isChecked()) self.prefs.set('dropbox_folder', unicode(self.dropbox_location_lineedit.text())) ''' # Save general settings self.prefs.set('apply_markers_to_duplicates', self.duplicate_markers_checkbox.isChecked()) self.prefs.set('apply_markers_to_updated', self.updated_markers_checkbox.isChecked()) self.prefs.set('auto_refresh_at_startup', self.auto_refresh_checkbox.isChecked()) self.prefs.set('show_progress_as_percentage', self.reading_progress_checkbox.isChecked()) # Save debug settings self.prefs.set('debug_plugin', self.debug_plugin_checkbox.isChecked()) self.prefs.set('debug_libimobiledevice', self.debug_libimobiledevice_checkbox.isChecked()) # If restart needed, inform user if self.restart_required: do_restart = show_restart_warning('Restart calibre for the changes to be applied.', parent=self.gui) if do_restart: self.gui.quit(restart=True) def start_inventory(self): self._log_location() self.annotated_books_scanner.start()
class ConfigWidget(QWidget): def __init__(self, plugin_action): QWidget.__init__(self) self.plugin_action = plugin_action layout = QVBoxLayout(self) self.setLayout(layout) self.help_anchor = "configuration" title_layout = ImageTitleLayout(self, 'images/icon.png', 'Sony Utilities Options') layout.addLayout(title_layout) # c = plugin_prefs[STORE_NAME] library_config = get_library_config(self.plugin_action.gui.current_db) custom_column_group = QGroupBox(_('Custom Columns'), self) layout.addWidget(custom_column_group ) options_layout = QGridLayout() custom_column_group.setLayout(options_layout) avail_text_columns = self.get_text_custom_columns() avail_number_columns = self.get_number_custom_columns() avail_rating_columns = self.get_rating_custom_columns() avail_date_columns = self.get_date_custom_columns() # debug_print("avail_rating_columns=", avail_rating_columns) # debug_print("default columns=", self.plugin_action.gui.library_view.model().orig_headers) current_Location_column = library_config.get(KEY_CURRENT_LOCATION_CUSTOM_COLUMN, DEFAULT_LIBRARY_VALUES[KEY_CURRENT_LOCATION_CUSTOM_COLUMN]) precent_read_column = library_config.get(KEY_PERCENT_READ_CUSTOM_COLUMN, DEFAULT_LIBRARY_VALUES[KEY_PERCENT_READ_CUSTOM_COLUMN]) rating_column = library_config.get(KEY_RATING_CUSTOM_COLUMN, DEFAULT_LIBRARY_VALUES[KEY_RATING_CUSTOM_COLUMN]) last_read_column = library_config.get(KEY_LAST_READ_CUSTOM_COLUMN, DEFAULT_LIBRARY_VALUES[KEY_LAST_READ_CUSTOM_COLUMN]) store_on_connect = get_plugin_pref(COMMON_OPTIONS_STORE_NAME, KEY_STORE_ON_CONNECT) prompt_to_store = get_plugin_pref(COMMON_OPTIONS_STORE_NAME, KEY_PROMPT_TO_STORE) store_if_more_recent = get_plugin_pref(COMMON_OPTIONS_STORE_NAME, KEY_STORE_IF_MORE_RECENT) do_not_store_if_reopened = get_plugin_pref(COMMON_OPTIONS_STORE_NAME, KEY_DO_NOT_STORE_IF_REOPENED) # do_check_for_firmware_updates = get_plugin_pref(UPDATE_OPTIONS_STORE_NAME, KEY_DO_UPDATE_CHECK) # do_early_firmware_updates = get_plugin_pref(UPDATE_OPTIONS_STORE_NAME, KEY_DO_EARLY_FIRMWARE_CHECK) # self.update_check_last_time = get_plugin_pref(UPDATE_OPTIONS_STORE_NAME, KEY_LAST_FIRMWARE_CHECK_TIME) do_daily_backup = get_plugin_pref(BACKUP_OPTIONS_STORE_NAME, KEY_DO_DAILY_BACKUP) dest_directory = get_plugin_pref(BACKUP_OPTIONS_STORE_NAME, KEY_BACKUP_DEST_DIRECTORY) copies_to_keep = get_plugin_pref(BACKUP_OPTIONS_STORE_NAME, KEY_BACKUP_COPIES_TO_KEEP) # debug_print("current_Location_column=%s, precent_read_column=%s, rating_column=%s" % (current_Location_column, precent_read_column, rating_column)) current_Location_label = QLabel(_('Current Reading Location Column:'), self) current_Location_label.setToolTip(_("Select a custom column to store the current reading location. The column type must be 'text'. Leave this blank if you do not want to store or restore the current reading location.")) self.current_Location_combo = CustomColumnComboBox(self, avail_text_columns, current_Location_column) current_Location_label.setBuddy(self.current_Location_combo) options_layout.addWidget(current_Location_label, 0, 0, 1, 1) options_layout.addWidget(self.current_Location_combo, 0, 1, 1, 1) percent_read_label = QLabel(_('Percent Read Column:'), self) percent_read_label.setToolTip(_("Column used to store the current percent read. The column type must be a 'integer'. Leave this blank if you do not want to store or restore the percentage read.")) self.percent_read_combo = CustomColumnComboBox(self, avail_number_columns, precent_read_column) percent_read_label.setBuddy(self.percent_read_combo) options_layout.addWidget(percent_read_label, 2, 0, 1, 1) options_layout.addWidget(self.percent_read_combo, 2, 1, 1, 1) rating_label = QLabel(_('Rating Column:'), self) rating_label.setToolTip(_("Column used to store the rating. The column type must be a 'integer'. Leave this blank if you do not want to store or restore the rating.")) self.rating_combo = CustomColumnComboBox(self, avail_rating_columns, rating_column) rating_label.setBuddy(self.rating_combo) options_layout.addWidget(rating_label, 3, 0, 1, 1) options_layout.addWidget(self.rating_combo, 3, 1, 1, 1) last_read_label = QLabel(_('Last Read Column:'), self) last_read_label.setToolTip(_("Column used to store when the book was last read. The column type must be a 'Date'. Leave this blank if you do not want to store the last read timestamp.")) self.last_read_combo = CustomColumnComboBox(self, avail_date_columns, last_read_column) last_read_label.setBuddy(self.last_read_combo) options_layout.addWidget(last_read_label, 4, 0, 1, 1) options_layout.addWidget(self.last_read_combo, 4, 1, 1, 1) auto_store_group = QGroupBox(_('Store on connect'), self) layout.addWidget(auto_store_group ) options_layout = QGridLayout() auto_store_group.setLayout(options_layout) self.store_on_connect_checkbox = QCheckBox(_("Store current bookmarks on connect"), self) self.store_on_connect_checkbox.setToolTip(_("When this is checked, the library will be updated with the current bookmark for all books on the device.")) self.store_on_connect_checkbox.setCheckState(Qt.Checked if store_on_connect else Qt.Unchecked) self.store_on_connect_checkbox.clicked.connect(self.store_on_connect_checkbox_clicked) options_layout.addWidget(self.store_on_connect_checkbox, 0, 0, 1, 3) self.prompt_to_store_checkbox = QCheckBox(_("Prompt to store any changes"), self) self.prompt_to_store_checkbox.setToolTip(_("Enable this to be prompted to save the changed bookmarks after an automatic store is done.")) self.prompt_to_store_checkbox.setCheckState(Qt.Checked if prompt_to_store else Qt.Unchecked) self.prompt_to_store_checkbox.setEnabled(store_on_connect) options_layout.addWidget(self.prompt_to_store_checkbox, 1, 0, 1, 1) self.store_if_more_recent_checkbox = QCheckBox(_("Only if more recent"), self) self.store_if_more_recent_checkbox.setToolTip(_("Only store the reading position if the last read timestamp on the device is more recent than in the library.")) self.store_if_more_recent_checkbox.setCheckState(Qt.Checked if store_if_more_recent else Qt.Unchecked) self.store_if_more_recent_checkbox.setEnabled(store_on_connect) options_layout.addWidget(self.store_if_more_recent_checkbox, 1, 1, 1, 1) self.do_not_store_if_reopened_checkbox = QCheckBox(_("Not if finished in library"), self) self.do_not_store_if_reopened_checkbox.setToolTip(_("Do not store the reading position if the library has the book as finished. This is if the percent read is 100%.")) self.do_not_store_if_reopened_checkbox.setCheckState(Qt.Checked if do_not_store_if_reopened else Qt.Unchecked) self.do_not_store_if_reopened_checkbox.setEnabled(store_on_connect) options_layout.addWidget(self.do_not_store_if_reopened_checkbox, 1, 2, 1, 1) # update_options_group = QGroupBox(_('Firmware Update Options'), self) # layout.addWidget(update_options_group) # options_layout = QGridLayout() # update_options_group.setLayout(options_layout) # # self.do_update_check = QCheckBox(_('Check for Sony firmware updates daily?'), self) # self.do_update_check.setToolTip(_('If this is selected the plugin will check for Sony firmware updates when your Sony device is plugged in, once per 24-hour period.')) # self.do_update_check.setCheckState(Qt.Checked if do_check_for_firmware_updates else Qt.Unchecked) # options_layout.addWidget(self.do_update_check, 0, 0, 1, 1) # # self.do_early_firmware_check = QCheckBox(_('Use early firmware adopter affiliate?'), self) # self.do_early_firmware_check.setToolTip(_('WARNING: THIS OPTION RISKS DOWNLOADING THE WRONG FIRMWARE FOR YOUR DEVICE! YOUR DEVICE MAY NOT FUNCTION PROPERLY IF THIS HAPPENS! Choose this option to attempt to download Sony firmware updates before they are officially available for your device.')) # self.do_early_firmware_check.setCheckState(Qt.Checked if do_early_firmware_updates else Qt.Unchecked) # options_layout.addWidget(self.do_early_firmware_check, 0, 1, 1, 1) backup_options_group = QGroupBox(_('Device Database Backup'), self) layout.addWidget(backup_options_group) options_layout = QGridLayout() backup_options_group.setLayout(options_layout) self.do_daily_backp_checkbox = QCheckBox(_('Backup the device database daily'), self) self.do_daily_backp_checkbox.setToolTip(_('If this is selected the plugin will backup the device database the first time it is connected each day.')) self.do_daily_backp_checkbox.setCheckState(Qt.Checked if do_daily_backup else Qt.Unchecked) self.do_daily_backp_checkbox.clicked.connect(self.do_daily_backp_checkbox_clicked) options_layout.addWidget(self.do_daily_backp_checkbox, 0, 0, 1, 3) self.dest_directory_label = QLabel(_("Destination:"), self) self.dest_directory_label.setToolTip(_("Select the destination the annotations files are to be backed up in.")) self.dest_directory_edit = QLineEdit(self) self.dest_directory_edit.setMinimumSize(150, 0) self.dest_directory_edit.setText(dest_directory) self.dest_directory_label.setBuddy(self.dest_directory_edit) self.dest_pick_button = QPushButton(_("..."), self) self.dest_pick_button.setMaximumSize(24, 20) self.dest_pick_button.clicked.connect(self._get_dest_directory_name) options_layout.addWidget(self.dest_directory_label, 1, 0, 1, 1) options_layout.addWidget(self.dest_directory_edit, 1, 1, 1, 1) options_layout.addWidget(self.dest_pick_button, 1, 2, 1, 1) self.copies_to_keep_checkbox = QCheckBox(_('Copies to keep'), self) self.copies_to_keep_checkbox.setToolTip(_("Select this to limit the number of backup kept. If not set, the backup files must be manually deleted.")) self.copies_to_keep_spin = QSpinBox(self) self.copies_to_keep_spin.setMinimum(2) self.copies_to_keep_spin.setToolTip(_("The number of backup copies of the database to keep. The minimum is 2.")) options_layout.addWidget(self.copies_to_keep_checkbox, 1, 3, 1, 1) options_layout.addWidget(self.copies_to_keep_spin, 1, 4, 1, 1) self.copies_to_keep_checkbox.clicked.connect(self.copies_to_keep_checkbox_clicked) if copies_to_keep == -1: self.copies_to_keep_checkbox.setCheckState(not Qt.Checked) else: self.copies_to_keep_checkbox.setCheckState(Qt.Checked) self.copies_to_keep_spin.setProperty('value', copies_to_keep) self.do_daily_backp_checkbox_clicked(do_daily_backup) other_options_group = QGroupBox(_('Other Options'), self) layout.addWidget(other_options_group ) options_layout = QGridLayout() other_options_group.setLayout(options_layout) library_default_label = QLabel(_('&Library Button default:'), self) library_default_label.setToolTip(_('If plugin is placed as a toolbar button, choose a default action when clicked on')) self.library_default_combo = KeyComboBox(self, self.plugin_action.library_actions_map, unicode(get_plugin_pref(COMMON_OPTIONS_STORE_NAME, KEY_BUTTON_ACTION_LIBRARY))) library_default_label.setBuddy(self.library_default_combo) options_layout.addWidget(library_default_label, 0, 0, 1, 1) options_layout.addWidget(self.library_default_combo, 0, 1, 1, 2) device_default_label = QLabel(_('&Device Button default:'), self) device_default_label.setToolTip(_('If plugin is placed as a toolbar button, choose a default action when clicked on')) self.device_default_combo = KeyComboBox(self, self.plugin_action.device_actions_map, unicode(get_plugin_pref(COMMON_OPTIONS_STORE_NAME, KEY_BUTTON_ACTION_DEVICE))) device_default_label.setBuddy(self.device_default_combo) options_layout.addWidget(device_default_label, 1, 0, 1, 1) options_layout.addWidget(self.device_default_combo, 1, 1, 1, 2) 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) layout.addWidget(keyboard_shortcuts_button) layout.addStretch(1) def store_on_connect_checkbox_clicked(self, checked): self.prompt_to_store_checkbox.setEnabled(checked) self.store_if_more_recent_checkbox.setEnabled(checked) self.do_not_store_if_reopened_checkbox.setEnabled(checked) def do_daily_backp_checkbox_clicked(self, checked): self.dest_directory_edit.setEnabled(checked) self.dest_pick_button.setEnabled(checked) self.dest_directory_label.setEnabled(checked) self.copies_to_keep_checkbox.setEnabled(checked) self.copies_to_keep_checkbox_clicked(checked and self.copies_to_keep_checkbox.checkState() == Qt.Checked) def copies_to_keep_checkbox_clicked(self, checked): self.copies_to_keep_spin.setEnabled(checked) # Called by Calibre before save_settings def validate(self): # import traceback # traceback.print_stack() debug_print('BEGIN Validate') valid = True # Only save if we were able to get data to avoid corrupting stored data # if self.do_daily_backp_checkbox.checkState() == Qt.Checked and not len(self.dest_directory_edit.text()): # error_dialog(self, 'No destination directory', # 'If the automatic device backup is set, there must be a destination directory.', # show=True, show_copy_button=False) # valid = False debug_print('END Validate, status = %s' % valid) return valid def save_settings(self): new_prefs = {} new_prefs[KEY_BUTTON_ACTION_DEVICE] = unicode(self.device_default_combo.currentText()) new_prefs[KEY_BUTTON_ACTION_LIBRARY] = unicode(self.library_default_combo.currentText()) new_prefs[KEY_STORE_ON_CONNECT] = self.store_on_connect_checkbox.checkState() == Qt.Checked new_prefs[KEY_PROMPT_TO_STORE] = self.prompt_to_store_checkbox.checkState() == Qt.Checked new_prefs[KEY_STORE_IF_MORE_RECENT] = self.store_if_more_recent_checkbox.checkState() == Qt.Checked new_prefs[KEY_DO_NOT_STORE_IF_REOPENED] = self.do_not_store_if_reopened_checkbox.checkState() == Qt.Checked plugin_prefs[COMMON_OPTIONS_STORE_NAME] = new_prefs new_update_prefs = {} # new_update_prefs[KEY_DO_UPDATE_CHECK] = self.do_update_check.checkState() == Qt.Checked # new_update_prefs[KEY_DO_EARLY_FIRMWARE_CHECK] = self.do_early_firmware_check.checkState() == Qt.Checked # new_update_prefs[KEY_LAST_FIRMWARE_CHECK_TIME] = self.update_check_last_time plugin_prefs[UPDATE_OPTIONS_STORE_NAME] = new_update_prefs backup_prefs = {} backup_prefs[KEY_DO_DAILY_BACKUP] = self.do_daily_backp_checkbox.checkState() == Qt.Checked backup_prefs[KEY_BACKUP_DEST_DIRECTORY] = unicode(self.dest_directory_edit.text()) backup_prefs[KEY_BACKUP_COPIES_TO_KEEP] = int(unicode(self.copies_to_keep_spin.value())) if self.copies_to_keep_checkbox.checkState() == Qt.Checked else -1 plugin_prefs[BACKUP_OPTIONS_STORE_NAME] = backup_prefs db = self.plugin_action.gui.current_db library_config = get_library_config(db) library_config[KEY_CURRENT_LOCATION_CUSTOM_COLUMN] = self.current_Location_combo.get_selected_column() library_config[KEY_PERCENT_READ_CUSTOM_COLUMN] = self.percent_read_combo.get_selected_column() library_config[KEY_RATING_CUSTOM_COLUMN] = self.rating_combo.get_selected_column() library_config[KEY_LAST_READ_CUSTOM_COLUMN] = self.last_read_combo.get_selected_column() set_library_config(db, library_config) def get_number_custom_columns(self): column_types = ['float','int'] return self.get_custom_columns(column_types) def get_rating_custom_columns(self): column_types = ['rating','int'] custom_columns = self.get_custom_columns(column_types) ratings_column_name = self.plugin_action.gui.library_view.model().orig_headers['rating'] custom_columns['rating'] = {'name': ratings_column_name} return custom_columns def get_text_custom_columns(self): column_types = ['text'] return self.get_custom_columns(column_types) def get_date_custom_columns(self): column_types = ['datetime'] return self.get_custom_columns(column_types) def get_custom_columns(self, column_types): custom_columns = self.plugin_action.gui.library_view.model().custom_columns available_columns = {} for key, column in custom_columns.iteritems(): typ = column['datatype'] if typ in column_types and not column['is_multiple']: available_columns[key] = column return available_columns def help_link_activated(self, url): self.plugin_action.show_help(anchor="configuration") def edit_shortcuts(self): d = KeyboardConfigDialog(self.plugin_action.gui, self.plugin_action.action_spec[0]) if d.exec_() == d.Accepted: self.plugin_action.gui.keyboard.finalize() def _get_dest_directory_name(self): path = choose_dir(self, 'backup annotations destination dialog','Choose destination directory') if path: self.dest_directory_edit.setText(path)
class ConfigWidget(QWidget): 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)) def save_settings(self): # We only need to update the store for the current view, as switching views # will have updated the other stores self.persist_view_config() library_config = get_library_config(self.gui.current_db) library_config[KEY_VIEWS] = self.views library_config[ KEY_AUTO_APPLY_VIEW] = self.auto_apply_checkbox.checkState( ) == Qt.Checked library_config[KEY_VIEW_TO_APPLY] = unicode( self.auto_view_combo.currentText()) set_library_config(self.gui.current_db, library_config) def persist_view_config(self): if not self.view_name: return # Update all of the current user information in the store view_info = self.views[self.view_name] view_info[KEY_COLUMNS] = self.columns_list.get_data() view_info[KEY_SORT] = self.sort_list.get_data() if self.has_pin_view: view_info[ KEY_APPLY_PIN_COLUMNS] = self.apply_pin_columns_checkbox.checkState( ) == Qt.Checked view_info[KEY_PIN_COLUMNS] = self.pin_columns_list.get_data() view_info[ KEY_APPLY_RESTRICTION] = self.apply_restriction_checkbox.checkState( ) == Qt.Checked if view_info[KEY_APPLY_RESTRICTION]: view_info[KEY_RESTRICTION] = unicode( self.search_restriction_combo.currentText()).strip() else: view_info[KEY_RESTRICTION] = '' view_info[KEY_APPLY_SEARCH] = self.apply_search_checkbox.checkState( ) == Qt.Checked if view_info[KEY_APPLY_SEARCH]: view_info[KEY_SEARCH] = unicode( self.saved_search_combo.currentText()).strip() else: view_info[KEY_SEARCH] = '' view_info[KEY_APPLY_VIRTLIB] = self.apply_virtlib_checkbox.checkState( ) == Qt.Checked if view_info[KEY_APPLY_VIRTLIB]: view_info[KEY_VIRTLIB] = unicode( self.virtlib_combo.currentText()).strip() else: view_info[KEY_VIRTLIB] = '' view_info[KEY_JUMP_TO_TOP] = self.jump_to_top_checkbox.checkState( ) == Qt.Checked self.views[self.view_name] = view_info def select_view_combo_index_changed(self, save_previous=True): # Update the dialog contents with data for the selected view if save_previous: # Switching views, persist changes made to the other view self.persist_view_config() if self.select_view_combo.count() == 0: self.view_name = None else: self.view_name = unicode( self.select_view_combo.currentText()).strip() columns = [] sort_columns = [] all_columns = [] pin_columns = [] apply_columns = True apply_pin_columns = False apply_sort = True apply_restriction = False restriction_to_apply = '' apply_search = False search_to_apply = '' apply_virtlib = False virtlib_to_apply = '' jump_to_top = False if self.view_name != None: view_info = self.views[self.view_name] columns = copy.deepcopy(view_info[KEY_COLUMNS]) pin_columns = copy.deepcopy(view_info.get(KEY_PIN_COLUMNS, {})) sort_columns = copy.deepcopy(view_info[KEY_SORT]) all_columns = self.all_columns apply_pin_columns = view_info.get(KEY_APPLY_PIN_COLUMNS, False) apply_restriction = view_info[KEY_APPLY_RESTRICTION] restriction_to_apply = view_info[KEY_RESTRICTION] apply_search = view_info[KEY_APPLY_SEARCH] search_to_apply = view_info[KEY_SEARCH] apply_virtlib = view_info.get(KEY_APPLY_VIRTLIB, False) virtlib_to_apply = view_info.get(KEY_VIRTLIB, '') jump_to_top = view_info.get(KEY_JUMP_TO_TOP, False) self.columns_list.populate(columns, all_columns) self.sort_list.populate(sort_columns, all_columns) if self.has_pin_view: self.pin_columns_list.populate(pin_columns, all_columns) self.apply_pin_columns_checkbox.setCheckState( Qt.Checked if apply_pin_columns else Qt.Unchecked) self.apply_restriction_checkbox.setCheckState( Qt.Checked if apply_restriction else Qt.Unchecked) self.search_restriction_combo.select_value(restriction_to_apply) self.apply_search_checkbox.setCheckState( Qt.Checked if apply_search else Qt.Unchecked) self.saved_search_combo.select_value(search_to_apply) self.apply_virtlib_checkbox.setCheckState( Qt.Checked if apply_virtlib else Qt.Unchecked) self.virtlib_combo.select_value(virtlib_to_apply) self.jump_to_top_checkbox.setCheckState( Qt.Checked if jump_to_top else Qt.Unchecked) def add_view(self): # Display a prompt allowing user to specify a new view new_view_name, ok = QInputDialog.getText( self, 'Add new view', 'Enter a unique display name for this view:', text='Default') if not ok: # Operation cancelled return new_view_name = unicode(new_view_name).strip() # Verify it does not clash with any other views in the list for view_name in self.views.keys(): if view_name.lower() == new_view_name.lower(): return error_dialog(self, 'Add Failed', 'A view with the same name already exists', show=True) self.persist_view_config() view_info = get_empty_view() if self.view_name != None: # We will copy values from the currently selected view old_view_info = self.views[self.view_name] view_info[KEY_COLUMNS] = copy.deepcopy(old_view_info[KEY_COLUMNS]) view_info[KEY_APPLY_PIN_COLUMNS] = copy.deepcopy( old_view_info.get(KEY_APPLY_PIN_COLUMNS, False)) view_info[KEY_PIN_COLUMNS] = copy.deepcopy( old_view_info.get(KEY_PIN_COLUMNS, {})) view_info[KEY_SORT] = copy.deepcopy(old_view_info[KEY_SORT]) view_info[KEY_APPLY_RESTRICTION] = copy.deepcopy( old_view_info[KEY_APPLY_RESTRICTION]) view_info[KEY_RESTRICTION] = copy.deepcopy( old_view_info[KEY_RESTRICTION]) view_info[KEY_APPLY_SEARCH] = copy.deepcopy( old_view_info[KEY_APPLY_SEARCH]) view_info[KEY_SEARCH] = copy.deepcopy(old_view_info[KEY_SEARCH]) view_info[KEY_APPLY_VIRTLIB] = copy.deepcopy( old_view_info.get(KEY_APPLY_VIRTLIB, False)) view_info[KEY_VIRTLIB] = copy.deepcopy( old_view_info.get(KEY_VIRTLIB, '')) view_info[KEY_JUMP_TO_TOP] = copy.deepcopy( old_view_info[KEY_JUMP_TO_TOP]) else: # We will copy values from the current library view view_info[KEY_COLUMNS] = self.get_current_columns( visible_only=True) self.view_name = new_view_name self.views[new_view_name] = view_info # Now update the views combobox self.select_view_combo.populate_combo(self.views, new_view_name) self.select_view_combo_index_changed(save_previous=False) self.auto_view_combo.populate_combo( self.views, unicode(self.auto_view_combo.currentText())) def rename_view(self): if not self.view_name != None: return # Display a prompt allowing user to specify a rename view old_view_name = self.view_name new_view_name, ok = QInputDialog.getText( self, 'Rename view', 'Enter a new display name for this view:', text=old_view_name) if not ok: # Operation cancelled return new_view_name = unicode(new_view_name).strip() if new_view_name == old_view_name: return # Verify it does not clash with any other views in the list for view_name in self.views.keys(): if view_name == old_view_name: continue if view_name.lower() == new_view_name.lower(): return error_dialog(self, 'Add Failed', 'A view with the same name already exists', show=True) # Ensure any changes are persisted self.persist_view_config() view_info = self.views[old_view_name] del self.views[old_view_name] self.view_name = new_view_name self.views[new_view_name] = view_info # Now update the views combobox self.select_view_combo.populate_combo(self.views, new_view_name) self.select_view_combo_index_changed(save_previous=False) if unicode(self.auto_view_combo.currentText()) == old_view_name: self.auto_view_combo.populate_combo(self.views, new_view_name) else: self.auto_view_combo.populate_combo(self.views) def delete_view(self): if self.view_name == None: return if not question_dialog( self, _('Are you sure?'), '<p>' + 'Do you want to delete the view named \'%s\'' % self.view_name, show_copy_button=False): return del self.views[self.view_name] # Now update the views combobox self.select_view_combo.populate_combo(self.views) self.select_view_combo_index_changed(save_previous=False) self.auto_view_combo.populate_combo(self.views) def get_current_columns(self, defaults=False, visible_only=False): model = self.gui.library_view.model() colmap = list(model.column_map) state = self.columns_state(defaults) positions = state['column_positions'] colmap.sort(key=lambda x: positions[x]) hidden_cols = state['hidden_columns'] if visible_only: colmap = [ col for col in colmap if col not in hidden_cols or col == 'ondevice' ] # Convert our list of column names into a list of tuples with column widths colsizemap = [] for col in colmap: if col in hidden_cols: colsizemap.append((col, -1)) else: colsizemap.append((col, state['column_sizes'].get(col, -1))) return colsizemap def columns_state(self, defaults=False): if defaults: return self.gui.library_view.get_default_state() return self.gui.library_view.get_state() def edit_shortcuts(self): self.save_settings() # Force the menus to be rebuilt immediately, so we have all our actions registered self.plugin_action.rebuild_menus() d = KeyboardConfigDialog(self.plugin_action.gui, self.plugin_action.action_spec[0]) if d.exec_() == d.Accepted: self.plugin_action.gui.keyboard.finalize() def view_prefs(self): d = PrefsViewerDialog(self.plugin_action.gui, PREFS_NAMESPACE) d.exec_()
class ConfigWidget(DefaultConfigWidget): def __init__(self, plugin): DefaultConfigWidget.__init__(self, plugin) c = plugin_prefs[STORE_NAME] all_tags = get_current_db().all_tags() self.gb.setMaximumHeight(80) genre_group_box = QGroupBox(_('Ridibooks genre to calibre tag mappings'), self) self.l.addWidget(genre_group_box, self.l.rowCount(), 0, 1, 2) genre_group_box_layout = QVBoxLayout() genre_group_box.setLayout(genre_group_box_layout) tags_layout = QHBoxLayout() genre_group_box_layout.addLayout(tags_layout) self.edit_table = GenreTagMappingsTableWidget(self, all_tags) tags_layout.addWidget(self.edit_table) button_layout = QVBoxLayout() tags_layout.addLayout(button_layout) add_mapping_button = QtGui.QToolButton(self) add_mapping_button.setToolTip(_('Add genre mapping')) add_mapping_button.setIcon(QIcon(I('plus.png'))) add_mapping_button.clicked.connect(self.add_mapping) button_layout.addWidget(add_mapping_button) spacerItem1 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) button_layout.addItem(spacerItem1) remove_mapping_button = QtGui.QToolButton(self) remove_mapping_button.setToolTip(_('Delete genre mapping')) remove_mapping_button.setIcon(QIcon(I('minus.png'))) remove_mapping_button.clicked.connect(self.delete_mapping) button_layout.addWidget(remove_mapping_button) spacerItem3 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) button_layout.addItem(spacerItem3) rename_genre_button = QtGui.QToolButton(self) rename_genre_button.setToolTip(_('Rename Goodreads genre')) rename_genre_button.setIcon(QIcon(I('edit-undo.png'))) rename_genre_button.clicked.connect(self.rename_genre) button_layout.addWidget(rename_genre_button) spacerItem2 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) button_layout.addItem(spacerItem2) reset_defaults_button = QtGui.QToolButton(self) reset_defaults_button.setToolTip(_('Reset to plugin default mappings')) reset_defaults_button.setIcon(QIcon(I('clear_left.png'))) reset_defaults_button.clicked.connect(self.reset_to_defaults) button_layout.addWidget(reset_defaults_button) self.l.setRowStretch(self.l.rowCount()-1, 2) other_group_box = QGroupBox(_('Other options'), self) self.l.addWidget(other_group_box, self.l.rowCount(), 0, 1, 2) other_group_box_layout = QVBoxLayout() other_group_box.setLayout(other_group_box_layout) self.get_editions_checkbox = QCheckBox('Scan multiple editions for title/author searches (slower)', self) self.get_editions_checkbox.setToolTip('When checked will perform an additional search to scan the top ranked\n' 'Goodreads editions (if available) to exclude audiobook editions.\n' 'Without this enabled you will get a faster search, using the "best".\n' 'edition ranked by Goodreads which can in some cases be an audiobook.') self.get_editions_checkbox.setChecked(c[KEY_GET_EDITIONS]) other_group_box_layout.addWidget(self.get_editions_checkbox) self.all_authors_checkbox = QCheckBox('Get all contributing authors (e.g. illustrators, series editors etc)', self) self.all_authors_checkbox.setToolTip('Goodreads for some books will list all of the contributing authors and\n' 'the type of contribution like (Editor), (Illustrator) etc.\n\n' 'When this option is checked, all contributing authors are retrieved.\n\n' 'When unchecked (default) only the primary author(s) are returned which\n' 'are those that either have no contribution type specified, or have the\n' 'value of (Goodreads Author).\n\n' 'If there is no primary author then only those with the same contribution\n' 'type as the first author are returned.\n' 'e.g. "A, B (Illustrator)" will return author A\n' 'e.g. "A (Goodreads Author)" will return author A\n' 'e.g. "A (Editor), B (Editor), C (Illustrator)" will return authors A & B\n' 'e.g. "A (Editor), B (Series Editor)" will return author A\n') self.all_authors_checkbox.setChecked(c[KEY_GET_ALL_AUTHORS]) other_group_box_layout.addWidget(self.all_authors_checkbox) self.edit_table.populate_table(c[KEY_GENRE_MAPPINGS]) def commit(self): DefaultConfigWidget.commit(self) new_prefs = {} new_prefs[KEY_GET_EDITIONS] = self.get_editions_checkbox.checkState() == Qt.Checked new_prefs[KEY_GET_ALL_AUTHORS] = self.all_authors_checkbox.checkState() == Qt.Checked new_prefs[KEY_GENRE_MAPPINGS] = self.edit_table.get_data() plugin_prefs[STORE_NAME] = new_prefs def add_mapping(self): new_genre_name, ok = QInputDialog.getText(self, 'Add new mapping', 'Enter a Goodreads genre name to create a mapping for:', text='') if not ok: # Operation cancelled return new_genre_name = unicode(new_genre_name).strip() if not new_genre_name: return # Verify it does not clash with any other mappings in the list data = self.edit_table.get_data() for genre_name in data.keys(): if genre_name.lower() == new_genre_name.lower(): return error_dialog(self, 'Add Failed', 'A genre with the same name already exists', show=True) data[new_genre_name] = [] self.edit_table.populate_table(data) self.edit_table.select_genre(new_genre_name) def delete_mapping(self): if not self.edit_table.selectionModel().hasSelection(): return if not question_dialog(self, _('Are you sure?'), '<p>'+ 'Are you sure you want to delete the selected genre mappings?', show_copy_button=False): return for row in reversed(sorted(self.edit_table.selectionModel().selectedRows())): self.edit_table.removeRow(row.row()) def rename_genre(self): selected_genre = self.edit_table.get_selected_genre() if not selected_genre: return new_genre_name, ok = QInputDialog.getText(self, 'Add new mapping', 'Enter a Goodreads genre name to create a mapping for:', text=selected_genre) if not ok: # Operation cancelled return new_genre_name = unicode(new_genre_name).strip() if not new_genre_name or new_genre_name == selected_genre: return data = self.edit_table.get_data() if new_genre_name.lower() != selected_genre.lower(): # Verify it does not clash with any other mappings in the list for genre_name in data.keys(): if genre_name.lower() == new_genre_name.lower(): return error_dialog(self, 'Rename Failed', 'A genre with the same name already exists', show=True) data[new_genre_name] = data[selected_genre] del data[selected_genre] self.edit_table.populate_table(data) self.edit_table.select_genre(new_genre_name) def reset_to_defaults(self): if not question_dialog(self, _('Are you sure?'), '<p>'+ 'Are you sure you want to reset to the plugin default genre mappings?', show_copy_button=False): return self.edit_table.populate_table(DEFAULT_GENRE_MAPPINGS)
class ConfigWidget(QWidget): def __init__(self): from calibre.library import current_library_name QWidget.__init__(self) actual_plugin = 'calibre_plugins.getfilename.action:GetFileNameAction' self.plugin_action = actual_plugin # get the prefs self.prefs = prefs_a.GetFileName_Prefs(current_library_name()) if (self.prefs['configured'] == False): try: from calibre.gui2.ui import get_gui db = get_gui().current_db self.prefs = get_library_config(db) except: try: from calibre.library import db self.prefs = get_library_config(db()) except: self.prefs = prefs_a.GetFileName_Prefs( current_library_name()) for key, col in six.iteritems(DEFAULT_MIGRATION): self.prefs.set(col, DEFAULT_LIBRARY_VALUES[key]) self.prefs.set('OPC_PREF', 'name') self.prefs.set('configured', True) self.prefs.set(KEY_SCHEMA_VERSION, DEFAULT_SCHEMA_VERSION) self.prefs.writeprefs() self.filename_col = self.prefs[NAME_PREF] self.extension_col = self.prefs[EXT_PREF] self.path_col = self.prefs[PATH_PREF] self.date_col = self.prefs[DATE_PREF] self.option_name = self.prefs[OPC_PREF] # Start Qt Gui dialog layout layout = QVBoxLayout(self) self.setLayout(layout) # -- Options -- # # --- File --- avail_columns_text = self.get_custom_columns_text() avail_columns_date = self.get_custom_columns_date() filename_group_box = QGroupBox(_('File name options:'), self) layout.addWidget(filename_group_box) filename_group_box_layout = QGridLayout() filename_group_box.setLayout(filename_group_box_layout) pos = 0 self.path_checkbox = QCheckBox(_("Include folder"), self) self.path_checkbox.setTristate(False) self.path_checkbox.setToolTip( _("It indicates it stores the folder with the filename.")) self.path_checkbox.stateChanged.connect(self.path_checkbox_clicked) filename_group_box_layout.addWidget(self.path_checkbox, pos, 0, 1, 1) pos = pos + 1 fname_column_label = QLabel(_('&File name:'), self) fname_column_label.setToolTip( _('Custom text column for storing the filename and folder if included' )) fname_col = self.filename_col self.fname_column_combo = CustomColumnComboBox(self, avail_columns_text, fname_col) fname_column_label.setBuddy(self.fname_column_combo) filename_group_box_layout.addWidget(fname_column_label, pos, 0, 1, 1) filename_group_box_layout.addWidget(self.fname_column_combo, pos, 1, 1, 2) self.fname_column_combo.currentIndexChanged.connect( self.filename_changed) pos = pos + 1 fexten_column_label = QLabel(_('File &Extension:'), self) fexten_column_label.setToolTip( _('Custom text column for storing the extension (if empty the filename is not splited). Not used if file name column is empty' )) fexten_col = self.extension_col self.fexten_column_combo = CustomColumnComboBox( self, avail_columns_text, fexten_col) fexten_column_label.setBuddy(self.fexten_column_combo) filename_group_box_layout.addWidget(fexten_column_label, pos, 0, 1, 1) filename_group_box_layout.addWidget(self.fexten_column_combo, pos, 1, 1, 2) if (self.filename_col == ""): self.fexten_column_combo.setEnabled(False) else: self.fexten_column_combo.setEnabled(True) pos = pos + 1 fpath_column_label = QLabel(_('File &Folder:'), self) fpath_column_label.setToolTip( _('Custom text column for storing the folder (if empty the filename is not splited). Not used if file name column is empty' )) fpath_col = self.path_col self.fpath_column_combo = CustomColumnComboBox(self, avail_columns_text, fpath_col) fpath_column_label.setBuddy(self.fpath_column_combo) filename_group_box_layout.addWidget(fpath_column_label, pos, 0, 1, 1) filename_group_box_layout.addWidget(self.fpath_column_combo, pos, 1, 1, 2) date_column_group = QGroupBox(self) layout.addWidget(date_column_group) date_layout = QGridLayout() date_column_group.setLayout(date_layout) fdate_column_label = QLabel(_('File &Date:'), self) fdate_column_label.setToolTip( _('Custom date column for storing the last modified date (if empty the date is not stored)' )) fdate_col = self.date_col self.fdate_column_combo = CustomColumnComboBox(self, avail_columns_date, fdate_col) fdate_column_label.setBuddy(self.fdate_column_combo) date_layout.addWidget(fdate_column_label, 2, 0, 1, 1) date_layout.addWidget(self.fdate_column_combo, 2, 1, 1, 2) if (self.option_name == 'path'): self.path_checkbox.setChecked(True) if (self.fname_column_combo.currentIndex() == 0): self.fpath_column_combo.setEnabled(False) else: self.fpath_column_combo.setEnabled(True) else: self.path_checkbox.setChecked(False) self.fpath_column_combo.setEnabled(False) layout.addStretch(1) def save_settings(self): self.prefs.set(NAME_PREF, self.fname_column_combo.get_selected_column()) self.prefs.set(EXT_PREF, self.fexten_column_combo.get_selected_column()) self.prefs.set(PATH_PREF, self.fpath_column_combo.get_selected_column()) self.prefs.set(DATE_PREF, self.fdate_column_combo.get_selected_column()) if (self.path_checkbox.isChecked()): self.prefs.set(OPC_PREF, 'path') else: self.prefs.set(OPC_PREF, 'name') self.prefs.writeprefs() def get_custom_columns_text(self): try: from calibre.gui2.ui import get_gui db = get_gui().current_db except: return None column_types = ['text', 'enumeration', 'custom', 'comments'] custom_fields = set(db.custom_field_keys()) available_columns = {} for field in list(custom_fields): column = db.field_metadata[field] typ = column['datatype'] if typ in column_types: available_columns[field] = column return available_columns def get_custom_columns_date(self): try: from calibre.gui2.ui import get_gui db = get_gui().current_db except: return None column_types = ['date', 'datetime'] custom_fields = set(db.custom_field_keys()) available_columns = {} for field in list(custom_fields): column = db.field_metadata[field] typ = column['datatype'] if typ in column_types: available_columns[field] = column return available_columns def file_radiobutton_clicked(self): self.fpath_column_combo.setEnabled(False) def path_radiobutton_clicked(self): if (self.fname_column_combo.get_selected_column() == ""): self.fpath_column_combo.setEnabled(True) def path_checkbox_clicked(self): if self.path_checkbox.isChecked(): if (self.fname_column_combo.currentIndex() != 0): self.fpath_column_combo.setEnabled(True) self.option_name = 'path' debug_print("Checkbox activado: ", self.fname_column_combo.currentIndex()) else: self.fpath_column_combo.setEnabled(False) self.option_name = 'name' debug_print("Checkbox desactivado") def filename_changed(self): if (self.fname_column_combo.currentIndex() == 0): self.fexten_column_combo.setEnabled(False) self.fpath_column_combo.setEnabled(False) debug_print("Vaciado nombre") else: self.fexten_column_combo.setEnabled(True) if (self.option_name == 'path'): self.fpath_column_combo.setEnabled(True) debug_print("Completado nombre: ", self.option_name)
class ConfigWidget(QWidget): # GUI definition def __init__(self): QWidget.__init__(self) self.l = QVBoxLayout() self.setLayout(self.l) self.ll = QHBoxLayout() self.l.addLayout(self.ll) self.label_exe = QLabel(_('&Prince executable:')) self.ll.addWidget(self.label_exe) self.exe = QLineEdit(self) self.exe.setText(prefs['prince_exe']) self.exe.setToolTip(_('<qt>Executable for the Prince program (command-line interface)</qt>')) self.ll.addWidget(self.exe) self.label_exe.setBuddy(self.exe) self.browse = QPushButton(_('&Browse') + '...', self) self.browse.setToolTip(_('<qt>Search the Prince executable in your computer</qt>')) self.browse.clicked.connect(self.select_exe) self.ll.addWidget(self.browse) self.lll = QHBoxLayout() self.l.addLayout(self.lll) self.label_fmts = QLabel(_('Preferred &formats:')) self.lll.addWidget(self.label_fmts) self.fmts = QLineEdit(self) self.fmts.setText(','.join(prefs['formats'])) self.fmts.setToolTip(_('<qt>Comma-separated list of preferred formats to use as source, the first that matches will be used</qt>')) self.lll.addWidget(self.fmts) self.label_fmts.setBuddy(self.fmts) self.add_book = QCheckBox(_('&Add PDF to the book record')) self.add_book.setToolTip(_('<qt>Add the converted PDF to the selected book record</qt>')) self.add_book.setChecked(prefs['add_book']) self.l.addWidget(self.add_book) self.show_css = QCheckBox(_('&Show CSS in the Convert dialog')) self.show_css.setToolTip(_('<qt>Show by default the stylesheets in the Convert dialog</qt>')) self.show_css.setChecked(prefs['show_CSS']) self.l.addWidget(self.show_css) self.css_layout = QVBoxLayout() self.llll = QHBoxLayout() self.css_layout.addLayout(self.llll) self.css_list = QComboBox() self.css_list.setToolTip(_('<qt>List of custom stylesheets defined. Select one to edit</qt>')) self.css_list.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.CSS_list = prefs['custom_CSS_list'].copy() self.default_CSS = prefs['default_CSS'] if 'custom_CSS' in prefs: self.CSS_list[_('old')] = prefs['custom_CSS'] self.default_CSS = _('old') if self.default_CSS not in self.CSS_list: self.default_CSS = sorted(self.CSS_list, key=lambda x: x.lower())[0] for key in sorted(self.CSS_list, key=lambda x: x.lower()): self.css_list.addItem(key, key) self.css_list.setCurrentIndex(self.css_list.findText(self.default_CSS)) self.css_list.currentIndexChanged.connect(self.set_css) self.llll.addWidget(self.css_list) self.css_rename = QPushButton(_('Re&name')) self.css_rename.setToolTip(_('<qt>Rename the current stylesheet to the name on the right</qt>')) self.css_rename.clicked.connect(self.rename_css) self.css_rename.setEnabled(False) self.llll.addWidget(self.css_rename) self.css_name = QLineEdit(self) self.css_name.setToolTip(_('<qt>Name for the new or renamed stylesheet</qt>')) self.css_name.setText(self.css_list.currentText()) self.css_name.textChanged.connect(self.check_names) self.llll.addWidget(self.css_name) self.css_add = QPushButton(_('A&dd')) self.css_add.setToolTip(_('<qt>Add a new empty stylesheet with the name on the left</qt>')) self.css_add.clicked.connect(self.add_css) self.css_add.setEnabled(False) self.llll.addWidget(self.css_add) self.css_remove = QPushButton(_('Re&move')) self.css_remove.setToolTip(_('<qt>Remove the current stylesheet</qt>')) self.css_remove.clicked.connect(self.remove_css) self.llll.addWidget(self.css_remove) self.css = TextEditWithTooltip() self.css.setLineWrapMode(TextEditWithTooltip.NoWrap) self.css.load_text(self.CSS_list[unicode(self.css_list.currentText())],'css') self.css.setToolTip(_('<qt>Custom stylesheet that will be applied, if selected, to all Prince PDF conversions</qt>')) self.css_layout.addWidget(self.css) self.css_templates = QLabel(_('Book metadata can be used in the stylesheet. Anything between %(s1)s and %(s2)s will be processed as a calibre template. For instance, %(s3)s in the stylesheet will be replaced with the book title in the conversion.') % \ {'s1':'<span style="font-family:monospace ; font-weight:bold">@{@</span>', \ 's2':'<span style="font-family:monospace ; font-weight:bold">@}@</span>', \ 's3':'<span style="font-family:monospace ; font-weight:bold">@{@{title}@}@</span>'}) self.css_templates.setWordWrap(True) self.css_layout.addWidget(self.css_templates) self.css_box = QGroupBox(_('&Custom CSS:')) self.css_box.setLayout(self.css_layout) self.l.addWidget(self.css_box) self.lllll = QHBoxLayout() self.lllll.setAlignment(Qt.AlignLeft) self.l.addLayout(self.lllll) self.defaults = QPushButton(_('&Restore defaults')) self.defaults.setToolTip(_('<qt>Restore the default settings</qt>')) self.defaults.clicked.connect(self.restore_defaults) self.lllll.addWidget(self.defaults, alignment=Qt.AlignLeft) self.warning = QLabel(_('<b>Warning</b>: Deletes modified stylesheets')) self.lllll.addWidget(self.warning) self.adjustSize() def select_exe(self): ''' Create a dialog to select the Prince executable ''' dialog = QFileDialog() dialog.setFileMode(QFileDialog.ExistingFile) filename = dialog.getOpenFileName(self, _('Select Prince executable'), '', '') if filename: try: self.exe.setText(filename) except(TypeError): self.exe.setText(filename[0]) def restore_defaults(self): ''' Restore the default settings ''' self.exe.setText(prefs.defaults['prince_exe']) self.fmts.setText(','.join(prefs.defaults['formats']).lower()) self.show_css.setChecked(prefs.defaults['show_CSS']) self.add_book.setChecked(prefs.defaults['add_book']) self.css_list.currentIndexChanged.disconnect() self.css_list.clear() self.CSS_list = prefs.defaults['custom_CSS_list'].copy() self.default_CSS = prefs.defaults['default_CSS'] for key in sorted(self.CSS_list, key=lambda x: x.lower()): self.css_list.addItem(key, key) self.css_list.setCurrentIndex(self.css_list.findText(self.default_CSS)) self.css_name.setText(self.default_CSS) self.css.load_text(self.CSS_list[unicode(self.css_list.currentText())],'css') self.css_list.currentIndexChanged.connect(self.set_css) def save_settings(self): ''' Save the current settings ''' prefs['prince_exe'] = unicode(self.exe.text()) prefs['formats'] = unicode(self.fmts.text().lower()).split(',') prefs['show_CSS'] = self.show_css.isChecked() prefs['add_book'] = self.add_book.isChecked() self.set_css() prefs['default_CSS'] = self.default_CSS prefs['custom_CSS_list'] = self.CSS_list.copy() if 'custom_CSS' in prefs: del prefs['custom_CSS'] def set_css(self): ''' Fill the CSS text box with the selected stylesheet ''' self.CSS_list[self.default_CSS] = unicode(self.css.toPlainText()) self.default_CSS = unicode(self.css_list.currentText()) self.css.load_text(self.CSS_list[self.default_CSS],'css') self.css_name.setText(self.css_list.currentText()) def add_css(self): ''' Add a new stylesheet ''' from calibre.gui2 import error_dialog name = unicode(self.css_name.text()) if name in self.CSS_list: error_dialog(self, _('Cannot add stylesheet'), _('A stylesheet with the name "%s" is already defined, use a different name.') % name, show=True) else: self.CSS_list[name] = '' self.css_list.addItem(name, name) self.css_list.setCurrentIndex(self.css_list.findText(name)) self.css_add.setEnabled(False) self.css_rename.setEnabled(False) def remove_css(self): ''' Remove an existing stylesheet ''' from calibre.gui2 import error_dialog if (self.css_list.count() > 1): self.css_list.currentIndexChanged.disconnect() self.css_list.removeItem(self.css_list.currentIndex()) del self.CSS_list[self.default_CSS] self.default_CSS = unicode(self.css_list.currentText()) self.css.load_text(self.CSS_list[self.default_CSS],'css') self.css_list.currentIndexChanged.connect(self.set_css) self.css_name.setText(self.css_list.currentText()) else: error_dialog(self, _('Cannot delete the last stylesheet'), _('The last stylesheet cannot be removed. You can rename it and/or remove its contents.'), show=True) def rename_css(self): ''' Rename a stylesheet ''' from calibre.gui2 import error_dialog name = unicode(self.css_name.text()) if name in self.CSS_list: error_dialog(self, _('Cannot rename stylesheet'), _('A stylesheet with the name "%s" is already defined, use a different name.') % name, show=True) else: self.CSS_list[name] = self.CSS_list.pop(self.default_CSS) self.css_list.setItemText(self.css_list.currentIndex(),name) self.default_CSS = name def check_names(self, text): name = unicode(text) if name in self.CSS_list: self.css_add.setEnabled(False) self.css_rename.setEnabled(False) else: self.css_add.setEnabled(True) self.css_rename.setEnabled(True)
class ConfigWidget(QWidget): def __init__(self, plugin_action): QWidget.__init__(self) self.plugin_action = plugin_action layout = QVBoxLayout(self) self.setLayout(layout) # --- Directory Options --- directory_group_box = QGroupBox(_('Default Unpack Directory:'), self) layout.addWidget(directory_group_box) directory_group_box_layout = QVBoxLayout() directory_group_box.setLayout(directory_group_box_layout) # Directory path Textbox # Load the textbox with the current preference setting self.directory_txtBox = QLineEdit(plugin_prefs['Unpack_Folder'], self) self.directory_txtBox.setToolTip(_('<p>Default directory to extract files to')) directory_group_box_layout.addWidget(self.directory_txtBox) self.directory_txtBox.setReadOnly(True) # Folder select button directory_button = QPushButton(_('Select/Change Unpack Directory'), self) directory_button.setToolTip(_('<p>Select/Change directory to extract files to.')) # Connect button to the getDirectory function directory_button.clicked.connect(self.getDirectory) directory_group_box_layout.addWidget(directory_button) self.default_folder_check = QCheckBox(_('Always use the Default Unpack Directory'), self) self.default_folder_check.setToolTip(_('<p>When unchecked... you will be prompted to select a destination '+ 'directory for the extracted content each time you use Mobiunpack.')) directory_group_box_layout.addWidget(self.default_folder_check) # Load the checkbox with the current preference setting self.default_folder_check.setChecked(plugin_prefs['Always_Use_Unpack_Folder']) misc_group_box = QGroupBox(_('Default settings:'), self) layout.addWidget(misc_group_box) misc_group_box_layout = QVBoxLayout() misc_group_box.setLayout(misc_group_box_layout) self.use_hd_images = QCheckBox(_('Always use HD images if present'), self) self.use_hd_images.setToolTip(_('<p>When checked... any HD images present in the kindlebook '+ 'will be used for creating the ePub.')) misc_group_box_layout.addWidget(self.use_hd_images) # Load the checkbox with the current preference setting self.use_hd_images.setChecked(plugin_prefs['Use_HD_Images']) combo_label = QLabel('Select epub version output:', self) misc_group_box_layout.addWidget(combo_label) self.epub_version_combobox = QComboBox() self.epub_version_combobox.setToolTip(_('<p>Select the type of OPF file to create.')) misc_group_box_layout.addWidget(self.epub_version_combobox) self.epub_version_combobox.addItems(['Auto-detect', 'ePub2', 'ePub3']) if plugin_prefs['Epub_Version'] == 'A': self.epub_version_combobox.setCurrentIndex(0) else: self.epub_version_combobox.setCurrentIndex(int(plugin_prefs['Epub_Version'])-1) def save_settings(self): # Save current dialog sttings back to JSON config file plugin_prefs['Unpack_Folder'] = unicode(self.directory_txtBox.displayText()) plugin_prefs['Always_Use_Unpack_Folder'] = self.default_folder_check.isChecked() plugin_prefs['Use_HD_Images'] = self.use_hd_images.isChecked() if unicode(self.epub_version_combobox.currentText()) == 'Auto-detect': plugin_prefs['Epub_Version'] = 'A' else: plugin_prefs['Epub_Version'] = unicode(self.epub_version_combobox.currentText())[4:] def getDirectory(self): c = choose_dir(self, _(PLUGIN_NAME + 'dir_chooser'), _('Select Default Directory To Unpack Kindle Book/Mobi To')) if c: self.directory_txtBox.setReadOnly(False) self.directory_txtBox.setText(c) self.directory_txtBox.setReadOnly(True) def validate(self): # This is just to catch the situation where somone might # manually enter a non-existent path in the Default path textbox. # Shouldn't be possible at this point. if not os.path.exists(self.directory_txtBox.text()): errmsg = '<p>The path specified for the Default Unpack folder does not exist.</p>' \ '<p>Your latest preference changes will <b>NOT</b> be saved!</p>' + \ '<p>You should configure again and make sure your settings are correct.' error_dialog(None, _(PLUGIN_NAME + ' v' + PLUGIN_VERSION), _(errmsg), show=True) return False return True
class TabRewards_gui(QWidget): def __init__(self, *args, **kwargs): QWidget.__init__(self) self.initRewardsForm() mainVertical = QVBoxLayout() mainVertical.addWidget(self.rewardsForm) buttonbox = QHBoxLayout() buttonbox.addStretch(1) buttonbox.addWidget(self.btn_Cancel) mainVertical.addLayout(buttonbox) self.setLayout(mainVertical) def initRewardsForm(self): self.collateralHidden = True self.rewardsForm = QGroupBox() self.rewardsForm.setTitle("Transfer Rewards") layout = QFormLayout() layout.setContentsMargins(10, 10, 10, 10) layout.setSpacing(13) layout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow) ##--- ROW 1 hBox = QHBoxLayout() self.mnSelect = QComboBox() self.mnSelect.setToolTip("Select Masternode") hBox.addWidget(self.mnSelect) label = QLabel("Total Address Balance") label.setAlignment(Qt.AlignRight | Qt.AlignVCenter) hBox.addWidget(label) self.addrAvailLine = QLabel() self.addrAvailLine.setToolTip("PIVX Address total balance") self.addrAvailLine.setText("--") hBox.addWidget(self.addrAvailLine) self.btn_toggleCollateral = QPushButton("Show Collateral") hBox.addWidget(self.btn_toggleCollateral) hBox.setStretch(0, 1) hBox.setStretch(1, 0) hBox.setStretch(2, 0) layout.addRow(QLabel("Masternode"), hBox) ## --- ROW 2: REWARDS self.rewardsList = QVBoxLayout() self.rewardsList.statusLabel = QLabel( '<b style="color:purple">Checking explorer...</b>') self.rewardsList.statusLabel.setVisible(True) self.rewardsList.addWidget(self.rewardsList.statusLabel) self.rewardsList.box = QTableWidget() self.rewardsList.box.setMinimumHeight(140) self.rewardsList.box.setMaximumHeight(140) self.rewardsList.box.setHorizontalScrollBarPolicy( Qt.ScrollBarAlwaysOff) self.rewardsList.box.setSelectionMode(QAbstractItemView.MultiSelection) self.rewardsList.box.setSelectionBehavior(QAbstractItemView.SelectRows) self.rewardsList.box.setShowGrid(True) self.rewardsList.box.setColumnCount(4) self.rewardsList.box.setRowCount(0) self.rewardsList.box.horizontalHeader().setSectionResizeMode( 2, QHeaderView.Stretch) self.rewardsList.box.verticalHeader().hide() item = QTableWidgetItem() item.setText("PIVs") item.setTextAlignment(Qt.AlignCenter) self.rewardsList.box.setHorizontalHeaderItem(0, item) item = QTableWidgetItem() item.setText("Confirmations") item.setTextAlignment(Qt.AlignCenter) self.rewardsList.box.setHorizontalHeaderItem(1, item) item = QTableWidgetItem() item.setText("TX Hash") item.setTextAlignment(Qt.AlignCenter) self.rewardsList.box.setHorizontalHeaderItem(2, item) item = QTableWidgetItem() item.setText("TX Output N") item.setTextAlignment(Qt.AlignCenter) self.rewardsList.box.setHorizontalHeaderItem(3, item) item = QTableWidgetItem() self.rewardsList.addWidget(self.rewardsList.box) layout.addRow(self.rewardsList) ##--- ROW 3 hBox2 = QHBoxLayout() self.btn_selectAllRewards = QPushButton("Select All") self.btn_selectAllRewards.setToolTip("Select all available UTXOs") hBox2.addWidget(self.btn_selectAllRewards) self.btn_deselectAllRewards = QPushButton("Deselect all") self.btn_deselectAllRewards.setToolTip("Deselect current selection") hBox2.addWidget(self.btn_deselectAllRewards) hBox2.addWidget(QLabel("Selected rewards")) self.selectedRewardsLine = QLabel() self.selectedRewardsLine.setMinimumWidth(200) self.selectedRewardsLine.setStyleSheet("color: purple") self.selectedRewardsLine.setToolTip("PIVX to move away") hBox2.addWidget(self.selectedRewardsLine) hBox2.addStretch(1) self.swiftxCheck = QCheckBox() self.swiftxCheck.setToolTip( "check for SwiftX instant transaction (flat fee rate of 0.01 PIV)") hBox2.addWidget(QLabel("Use SwiftX")) hBox2.addWidget(self.swiftxCheck) layout.addRow(hBox2) ##--- ROW 4 hBox3 = QHBoxLayout() self.destinationLine = QLineEdit() self.destinationLine.setToolTip("PIVX address to transfer rewards to") hBox3.addWidget(self.destinationLine) hBox3.addWidget(QLabel("Fee")) self.feeLine = QDoubleSpinBox() self.feeLine.setDecimals(8) self.feeLine.setPrefix("PIV ") self.feeLine.setToolTip("Insert a small fee amount") self.feeLine.setFixedWidth(150) self.feeLine.setSingleStep(0.001) hBox3.addWidget(self.feeLine) self.btn_sendRewards = QPushButton("Send") hBox3.addWidget(self.btn_sendRewards) layout.addRow(QLabel("Destination Address"), hBox3) ##--- ROW 5 hBox4 = QHBoxLayout() hBox4.addStretch(1) self.loadingLine = QLabel( "<b style='color:red'>Preparing TX.</b> Completed: ") self.loadingLinePercent = QProgressBar() self.loadingLinePercent.setMaximumWidth(200) self.loadingLinePercent.setMaximumHeight(10) self.loadingLinePercent.setRange(0, 100) hBox4.addWidget(self.loadingLine) hBox4.addWidget(self.loadingLinePercent) self.loadingLine.hide() self.loadingLinePercent.hide() layout.addRow(hBox4) #--- Set Layout self.rewardsForm.setLayout(layout) #--- ROW 5 self.btn_Cancel = QPushButton("Clear/Reload")
class BasicTab(QWidget): 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(_('These settings control the basic features of the plugin.')) label.setWordWrap(True) self.l.addWidget(label) self.l.addSpacing(5) no_toc_warning = _('''If both 'Insert Table of Contents entry' and 'Copy Table of Contents entries' are unchecked, there will be no Table of Contents in merged books.''') self.titlenavpoints = QCheckBox(_('Insert Table of Contents entry for each title?'),self) self.titlenavpoints.setToolTip(_('''If set, a new TOC entry will be made for each title and it's existing TOC nested underneath it.''')+'\n'+no_toc_warning) self.titlenavpoints.setChecked(prefs['titlenavpoints']) self.l.addWidget(self.titlenavpoints) self.originalnavpoints = QCheckBox(_('Copy Table of Contents entries from each title?'),self) self.originalnavpoints.setToolTip(_('''If set, the original TOC entries will be included the new epub.''')+'\n'+no_toc_warning) self.originalnavpoints.setChecked(prefs['originalnavpoints']) self.l.addWidget(self.originalnavpoints) def f(): if not self.originalnavpoints.isChecked() and not self.titlenavpoints.isChecked(): confirm("<br>"+no_toc_warning, # force HTML to get auto wrap. 'epubmerge_no_toc_warning_again', parent=self, show_cancel_button=False) self.originalnavpoints.stateChanged.connect(f) self.titlenavpoints.stateChanged.connect(f) self.flattentoc = QCheckBox(_('Flatten Table of Contents?'),self) self.flattentoc.setToolTip(_('Remove nesting and make TOC all on one level.')) self.flattentoc.setChecked(prefs['flattentoc']) self.l.addWidget(self.flattentoc) self.includecomments = QCheckBox(_("Include Books' Comments?"),self) self.includecomments.setToolTip(_('''Include all the merged books' comments in the new book's comments. Default is a list of included titles only.''')) self.includecomments.setChecked(prefs['includecomments']) self.l.addWidget(self.includecomments) self.keepmeta = QCheckBox(_('Keep UnMerge Metadata?'),self) self.keepmeta.setToolTip(_('''If set, a copy of the original metadata for each merged book will be included, allowing for UnMerge. This includes your calibre custom columns. Leave off if you plan to distribute the epub to others.''')) self.keepmeta.setChecked(prefs['keepmeta']) self.l.addWidget(self.keepmeta) # self.showunmerge = QCheckBox(_('Show UnMerge Option?'),self) # self.showunmerge.setToolTip(_('''If set, the UnMerge Epub option will be shown on the EpubMerge menu. # Only Epubs merged with 'Keep UnMerge Metadata' can be UnMerged.''')) # self.showunmerge.setChecked(prefs['showunmerge']) # self.l.addWidget(self.showunmerge) horz = QHBoxLayout() horz.addWidget(QLabel(_("Add tags to merged books:"))) self.mergetags = QLineEdit(self) self.mergetags.setText(prefs['mergetags']) self.mergetags.setToolTip(_('Tags you enter here will be added to all new merged books')) horz.addWidget(self.mergetags) self.l.addLayout(horz) horz = QHBoxLayout() horz.addWidget(QLabel(_("Merged Book Word:"))) self.mergeword = QLineEdit(self) self.mergeword.setText(prefs['mergeword']) self.mergeword.setToolTip(_('''Word use to describe merged books in default title and summary. For people who don't like the word Anthology.''')) ## Defaults back to Anthology if cleared. horz.addWidget(self.mergeword) self.l.addLayout(horz) 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 EpubMerge 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 EpubMerge 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) self.l.insertStretch(-1) def view_prefs(self): d = PrefsViewerDialog(self.plugin_action.gui, PREFS_NAMESPACE) d.exec_() def reset_dialogs(self): for key in dynamic.keys(): if key.startswith('epubmerge_') and key.endswith('_again') \ and dynamic[key] is False: dynamic[key] = True info_dialog(self, _('Done'), _('Confirmation dialogs have all been reset'), show=True, show_copy_button=False)
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 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 = {} custom_columns = self.plugin_action.gui.library_view.model( ).custom_columns 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) self.l.addSpacing(5) label = QLabel(_("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'): 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)
class ColumnsTab(QWidget): 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('<b>'+_('Standard Columns:')+'</b>') label.setWordWrap(True) self.l.addWidget(label) self.l.addSpacing(5) self.firstseries = QCheckBox(_('Take Series from first book'),self) self.firstseries.setToolTip(_('''If set, the Series name and index from the first book will be set on the merged book.''')) self.firstseries.setChecked(prefs['firstseries']) self.l.addWidget(self.firstseries) self.l.addSpacing(5) label = QLabel('<b>'+_('Custom Columns:')+'</b>') label.setWordWrap(True) self.l.addWidget(label) 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)
class ConfigWidget(DefaultConfigWidget): def __init__(self, plugin): DefaultConfigWidget.__init__(self, plugin) c = plugin_prefs[STORE_NAME] all_tags = get_current_db().all_tags() self.gb.setMaximumHeight(80) genre_group_box = QGroupBox( _('Ridibooks genre to calibre tag mappings'), self) self.l.addWidget(genre_group_box, self.l.rowCount(), 0, 1, 2) genre_group_box_layout = QVBoxLayout() genre_group_box.setLayout(genre_group_box_layout) tags_layout = QHBoxLayout() genre_group_box_layout.addLayout(tags_layout) self.edit_table = GenreTagMappingsTableWidget(self, all_tags) tags_layout.addWidget(self.edit_table) button_layout = QVBoxLayout() tags_layout.addLayout(button_layout) add_mapping_button = QtGui.QToolButton(self) add_mapping_button.setToolTip(_('Add genre mapping')) add_mapping_button.setIcon(QIcon(I('plus.png'))) add_mapping_button.clicked.connect(self.add_mapping) button_layout.addWidget(add_mapping_button) spacerItem1 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) button_layout.addItem(spacerItem1) remove_mapping_button = QtGui.QToolButton(self) remove_mapping_button.setToolTip(_('Delete genre mapping')) remove_mapping_button.setIcon(QIcon(I('minus.png'))) remove_mapping_button.clicked.connect(self.delete_mapping) button_layout.addWidget(remove_mapping_button) spacerItem3 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) button_layout.addItem(spacerItem3) rename_genre_button = QtGui.QToolButton(self) rename_genre_button.setToolTip(_('Rename Goodreads genre')) rename_genre_button.setIcon(QIcon(I('edit-undo.png'))) rename_genre_button.clicked.connect(self.rename_genre) button_layout.addWidget(rename_genre_button) spacerItem2 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) button_layout.addItem(spacerItem2) reset_defaults_button = QtGui.QToolButton(self) reset_defaults_button.setToolTip(_('Reset to plugin default mappings')) reset_defaults_button.setIcon(QIcon(I('clear_left.png'))) reset_defaults_button.clicked.connect(self.reset_to_defaults) button_layout.addWidget(reset_defaults_button) self.l.setRowStretch(self.l.rowCount() - 1, 2) other_group_box = QGroupBox(_('Other options'), self) self.l.addWidget(other_group_box, self.l.rowCount(), 0, 1, 2) other_group_box_layout = QVBoxLayout() other_group_box.setLayout(other_group_box_layout) self.get_editions_checkbox = QCheckBox( 'Scan multiple editions for title/author searches (slower)', self) self.get_editions_checkbox.setToolTip( 'When checked will perform an additional search to scan the top ranked\n' 'Goodreads editions (if available) to exclude audiobook editions.\n' 'Without this enabled you will get a faster search, using the "best".\n' 'edition ranked by Goodreads which can in some cases be an audiobook.' ) self.get_editions_checkbox.setChecked(c[KEY_GET_EDITIONS]) other_group_box_layout.addWidget(self.get_editions_checkbox) self.all_authors_checkbox = QCheckBox( 'Get all contributing authors (e.g. illustrators, series editors etc)', self) self.all_authors_checkbox.setToolTip( 'Goodreads for some books will list all of the contributing authors and\n' 'the type of contribution like (Editor), (Illustrator) etc.\n\n' 'When this option is checked, all contributing authors are retrieved.\n\n' 'When unchecked (default) only the primary author(s) are returned which\n' 'are those that either have no contribution type specified, or have the\n' 'value of (Goodreads Author).\n\n' 'If there is no primary author then only those with the same contribution\n' 'type as the first author are returned.\n' 'e.g. "A, B (Illustrator)" will return author A\n' 'e.g. "A (Goodreads Author)" will return author A\n' 'e.g. "A (Editor), B (Editor), C (Illustrator)" will return authors A & B\n' 'e.g. "A (Editor), B (Series Editor)" will return author A\n') self.all_authors_checkbox.setChecked(c[KEY_GET_ALL_AUTHORS]) other_group_box_layout.addWidget(self.all_authors_checkbox) self.edit_table.populate_table(c[KEY_GENRE_MAPPINGS]) def commit(self): DefaultConfigWidget.commit(self) new_prefs = {} new_prefs[KEY_GET_EDITIONS] = self.get_editions_checkbox.checkState( ) == Qt.Checked new_prefs[KEY_GET_ALL_AUTHORS] = self.all_authors_checkbox.checkState( ) == Qt.Checked new_prefs[KEY_GENRE_MAPPINGS] = self.edit_table.get_data() plugin_prefs[STORE_NAME] = new_prefs def add_mapping(self): new_genre_name, ok = QInputDialog.getText( self, 'Add new mapping', 'Enter a Goodreads genre name to create a mapping for:', text='') if not ok: # Operation cancelled return new_genre_name = unicode(new_genre_name).strip() if not new_genre_name: return # Verify it does not clash with any other mappings in the list data = self.edit_table.get_data() for genre_name in data.keys(): if genre_name.lower() == new_genre_name.lower(): return error_dialog( self, 'Add Failed', 'A genre with the same name already exists', show=True) data[new_genre_name] = [] self.edit_table.populate_table(data) self.edit_table.select_genre(new_genre_name) def delete_mapping(self): if not self.edit_table.selectionModel().hasSelection(): return if not question_dialog( self, _('Are you sure?'), '<p>' + 'Are you sure you want to delete the selected genre mappings?', show_copy_button=False): return for row in reversed( sorted(self.edit_table.selectionModel().selectedRows())): self.edit_table.removeRow(row.row()) def rename_genre(self): selected_genre = self.edit_table.get_selected_genre() if not selected_genre: return new_genre_name, ok = QInputDialog.getText( self, 'Add new mapping', 'Enter a Goodreads genre name to create a mapping for:', text=selected_genre) if not ok: # Operation cancelled return new_genre_name = unicode(new_genre_name).strip() if not new_genre_name or new_genre_name == selected_genre: return data = self.edit_table.get_data() if new_genre_name.lower() != selected_genre.lower(): # Verify it does not clash with any other mappings in the list for genre_name in data.keys(): if genre_name.lower() == new_genre_name.lower(): return error_dialog( self, 'Rename Failed', 'A genre with the same name already exists', show=True) data[new_genre_name] = data[selected_genre] del data[selected_genre] self.edit_table.populate_table(data) self.edit_table.select_genre(new_genre_name) def reset_to_defaults(self): if not question_dialog( self, _('Are you sure?'), '<p>' + 'Are you sure you want to reset to the plugin default genre mappings?', show_copy_button=False): return self.edit_table.populate_table(DEFAULT_GENRE_MAPPINGS)
class ConfigWidget(QWidget): # GUI definition def __init__(self): QWidget.__init__(self) self.l = QVBoxLayout() self.setLayout(self.l) self.ll = QHBoxLayout() self.l.addLayout(self.ll) self.label_exe = QLabel(_('&Prince executable:')) self.ll.addWidget(self.label_exe) self.exe = QLineEdit(self) self.exe.setText(prefs['prince_exe']) self.exe.setToolTip( _('<qt>Executable for the Prince program (command-line interface)</qt>' )) self.ll.addWidget(self.exe) self.label_exe.setBuddy(self.exe) self.browse = QPushButton(_('&Browse') + '...', self) self.browse.setToolTip( _('<qt>Search the Prince executable in your computer</qt>')) self.browse.clicked.connect(self.select_exe) self.ll.addWidget(self.browse) self.lll = QHBoxLayout() self.l.addLayout(self.lll) self.label_fmts = QLabel(_('Preferred &formats:')) self.lll.addWidget(self.label_fmts) self.fmts = QLineEdit(self) self.fmts.setText(','.join(prefs['formats'])) self.fmts.setToolTip( _('<qt>Comma-separated list of preferred formats to use as source, the first that matches will be used</qt>' )) self.lll.addWidget(self.fmts) self.label_fmts.setBuddy(self.fmts) self.add_book = QCheckBox(_('&Add PDF to the book record')) self.add_book.setToolTip( _('<qt>Add the converted PDF to the selected book record</qt>')) self.add_book.setChecked(prefs['add_book']) self.l.addWidget(self.add_book) self.show_css = QCheckBox(_('&Show CSS in the Convert dialog')) self.show_css.setToolTip( _('<qt>Show by default the styles in the Convert dialog</qt>')) self.show_css.setChecked(prefs['show_CSS']) self.l.addWidget(self.show_css) self.css_layout = QVBoxLayout() self.llll = QHBoxLayout() self.css_layout.addLayout(self.llll) self.css_list = QComboBox() self.css_list.setToolTip( _('<qt>List of custom styles defined. Select one to edit</qt>')) self.css_list.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.CSS_list = prefs['custom_CSS_list'].copy() self.default_CSS = prefs['default_CSS'] if 'custom_CSS' in prefs: self.CSS_list[_('old')] = prefs['custom_CSS'] self.default_CSS = _('old') if self.default_CSS not in self.CSS_list: self.default_CSS = sorted(self.CSS_list, key=lambda x: x.lower())[0] for key in sorted(self.CSS_list, key=lambda x: x.lower()): self.css_list.addItem(key, key) self.css_list.setCurrentIndex(self.css_list.findText(self.default_CSS)) self.css_list.currentIndexChanged.connect(self.set_css) self.llll.addWidget(self.css_list) self.css_rename = QPushButton(_('Re&name')) self.css_rename.setToolTip( _('<qt>Rename the current style to the name on the right</qt>')) self.css_rename.clicked.connect(self.rename_css) self.css_rename.setEnabled(False) self.llll.addWidget(self.css_rename) self.css_name = QLineEdit(self) self.css_name.setToolTip( _('<qt>Name for the new or renamed style</qt>')) self.css_name.setText(self.css_list.currentText()) self.css_name.textChanged.connect(self.check_names) self.llll.addWidget(self.css_name) self.css_add = QPushButton(_('A&dd')) self.css_add.setToolTip( _('<qt>Add a new empty style with the name on the left</qt>')) self.css_add.clicked.connect(self.add_css) self.css_add.setEnabled(False) self.llll.addWidget(self.css_add) self.css_remove = QPushButton(_('Re&move')) self.css_remove.setToolTip(_('<qt>Remove the current style</qt>')) self.css_remove.clicked.connect(self.remove_css) self.llll.addWidget(self.css_remove) self.llll_ = QHBoxLayout() self.css_layout.addLayout(self.llll_) self.label_args = QLabel(_('Addi&tional command-line arguments:')) self.llll_.addWidget(self.label_args) self.args = QLineEdit(self) # Make sure custom_CSS_list and custom_args_list have the same keys if 'custom_args_list' in prefs: self.args_list = prefs['custom_args_list'].copy() else: self.args_list = {} for key in self.CSS_list: if not key in self.args_list: self.args_list[key] = '' for key in self.args_list: if not key in self.CSS_list: del self.args_list[key] self.args.setText(self.args_list[unicode(self.css_list.currentText())]) self.args.setToolTip( _('<qt>Additional command-line arguments used in conversions with this style</qt>' )) self.llll_.addWidget(self.args) self.label_args.setBuddy(self.args) self.css = TextEditWithTooltip(self, expected_geometry=(80, 20)) self.css.setLineWrapMode(TextEditWithTooltip.NoWrap) self.css.load_text(self.CSS_list[unicode(self.css_list.currentText())], 'css') self.css.setToolTip( _('<qt>Custom stylesheet that will be applied, if selected, to all Prince PDF conversions</qt>' )) self.css_layout.addWidget(self.css) self.css_templates = QLabel(_('Book metadata can be used in the stylesheet. Anything between %(s1)s and %(s2)s will be processed as a calibre template. For instance, %(s3)s in the stylesheet will be replaced with the book title in the conversion.') % \ {'s1':'<span style="font-family:monospace ; font-weight:bold">@{@</span>', \ 's2':'<span style="font-family:monospace ; font-weight:bold">@}@</span>', \ 's3':'<span style="font-family:monospace ; font-weight:bold">@{@{title}@}@</span>'}) self.css_templates.setWordWrap(True) self.css_layout.addWidget(self.css_templates) self.css_box = QGroupBox(_('&Custom styles:')) self.css_box.setLayout(self.css_layout) self.l.addWidget(self.css_box) self.lllll = QHBoxLayout() self.lllll.setAlignment(Qt.AlignLeft) self.l.addLayout(self.lllll) self.defaults = QPushButton(_('&Restore defaults')) self.defaults.setToolTip(_('<qt>Restore the default settings</qt>')) self.defaults.clicked.connect(self.restore_defaults) self.lllll.addWidget(self.defaults, alignment=Qt.AlignLeft) self.warning = QLabel(_('<b>Warning</b>: Deletes modified styles')) self.lllll.addWidget(self.warning) self.adjustSize() def select_exe(self): ''' Create a dialog to select the Prince executable ''' dialog = QFileDialog() dialog.setFileMode(QFileDialog.ExistingFile) filename = dialog.getOpenFileName(self, _('Select Prince executable'), '', '') if filename: try: self.exe.setText(filename) except (TypeError): self.exe.setText(filename[0]) def restore_defaults(self): ''' Restore the default settings ''' self.exe.setText(prefs.defaults['prince_exe']) self.fmts.setText(','.join(prefs.defaults['formats']).lower()) self.show_css.setChecked(prefs.defaults['show_CSS']) self.add_book.setChecked(prefs.defaults['add_book']) self.css_list.currentIndexChanged.disconnect() self.css_list.clear() self.CSS_list = prefs.defaults['custom_CSS_list'].copy() self.args_list = prefs.defaults['custom_args_list'].copy() self.default_CSS = prefs.defaults['default_CSS'] for key in sorted(self.CSS_list, key=lambda x: x.lower()): self.css_list.addItem(key, key) self.css_list.setCurrentIndex(self.css_list.findText(self.default_CSS)) self.css_name.setText(self.default_CSS) self.css.load_text(self.CSS_list[unicode(self.css_list.currentText())], 'css') self.args.setText(self.args_list[unicode(self.css_list.currentText())]) self.css_list.currentIndexChanged.connect(self.set_css) def save_settings(self): ''' Save the current settings ''' prefs['prince_exe'] = unicode(self.exe.text()) prefs['formats'] = unicode(self.fmts.text().lower()).split(',') prefs['show_CSS'] = self.show_css.isChecked() prefs['add_book'] = self.add_book.isChecked() self.set_css() prefs['default_CSS'] = self.default_CSS prefs['custom_CSS_list'] = self.CSS_list.copy() prefs['custom_args_list'] = self.args_list.copy() if 'custom_CSS' in prefs: del prefs['custom_CSS'] def set_css(self): ''' Fill the CSS text box with the selected stylesheet, and similarly for command-line arguments ''' self.CSS_list[self.default_CSS] = unicode(self.css.toPlainText()) self.args_list[self.default_CSS] = unicode(self.args.text()) self.default_CSS = unicode(self.css_list.currentText()) self.css.load_text(self.CSS_list[self.default_CSS], 'css') self.args.setText(self.args_list[self.default_CSS]) self.css_name.setText(self.css_list.currentText()) def add_css(self): ''' Add a new style ''' from calibre.gui2 import error_dialog name = unicode(self.css_name.text()) if name in self.CSS_list: error_dialog( self, _('Cannot add style'), _('A style with the name "%s" is already defined, use a different name.' ) % name, show=True) else: self.CSS_list[name] = '' self.args_list[name] = '' self.css_list.addItem(name, name) self.css_list.setCurrentIndex(self.css_list.findText(name)) self.css_add.setEnabled(False) self.css_rename.setEnabled(False) def remove_css(self): ''' Remove an existing style ''' from calibre.gui2 import error_dialog if (self.css_list.count() > 1): self.css_list.currentIndexChanged.disconnect() self.css_list.removeItem(self.css_list.currentIndex()) del self.CSS_list[self.default_CSS] del self.args_list[self.default_CSS] self.default_CSS = unicode(self.css_list.currentText()) self.css.load_text(self.CSS_list[self.default_CSS], 'css') self.args.setText(self.args_list[self.default_CSS]) self.css_list.currentIndexChanged.connect(self.set_css) self.css_name.setText(self.css_list.currentText()) else: error_dialog( self, _('Cannot delete the last style'), _('The last style cannot be removed. You can rename it and/or remove its contents.' ), show=True) def rename_css(self): ''' Rename a style ''' from calibre.gui2 import error_dialog name = unicode(self.css_name.text()) if name in self.CSS_list: error_dialog( self, _('Cannot rename style'), _('A style with the name "%s" is already defined, use a different name.' ) % name, show=True) else: self.CSS_list[name] = self.CSS_list.pop(self.default_CSS) self.args_list[name] = self.args_list.pop(self.default_CSS) self.css_list.setItemText(self.css_list.currentIndex(), name) self.default_CSS = name def check_names(self, text): name = unicode(text) if name in self.CSS_list: self.css_add.setEnabled(False) self.css_rename.setEnabled(False) else: self.css_add.setEnabled(True) self.css_rename.setEnabled(True)
class ColumnsTab(QWidget): 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('<b>' + _('Standard Columns:') + '</b>') label.setWordWrap(True) self.l.addWidget(label) self.l.addSpacing(5) self.firstseries = QCheckBox(_('Take Series from first book'), self) self.firstseries.setToolTip( _('''If set, the Series name and index from the first book will be set on the merged book.''' )) self.firstseries.setChecked(prefs['firstseries']) self.l.addWidget(self.firstseries) self.l.addSpacing(5) label = QLabel('<b>' + _('Custom Columns:') + '</b>') label.setWordWrap(True) self.l.addWidget(label) 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)
class BasicTab(QWidget): 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 view_prefs(self): d = PrefsViewerDialog(self.plugin_action.gui, PREFS_NAMESPACE) d.exec_() def reset_dialogs(self): for key in dynamic.keys(): if key.startswith('smarteject_') and key.endswith('_again') \ and dynamic[key] is False: dynamic[key] = True info_dialog(self, _('Done'), _('Confirmation dialogs have all been reset'), show=True, show_copy_button=False)
class BulkSeries(BulkBase): def setup_ui(self, parent): self.make_widgets(parent, EditWithComplete) values = self.all_values = list(self.db.all_custom(num=self.col_id)) values.sort(key=sort_key) self.main_widget.setSizeAdjustPolicy(self.main_widget.AdjustToMinimumContentsLengthWithIcon) self.main_widget.setMinimumContentsLength(25) self.widgets.append(QLabel('', parent)) w = QWidget(parent) layout = QHBoxLayout(w) layout.setContentsMargins(0, 0, 0, 0) self.remove_series = QCheckBox(parent) self.remove_series.setText(_('Clear series')) layout.addWidget(self.remove_series) self.idx_widget = QCheckBox(parent) self.idx_widget.setText(_('Automatically number books')) self.idx_widget.setToolTip('<p>' + _( 'If not checked, the series number for the books will be set to 1. ' 'If checked, selected books will be automatically numbered, ' 'in the order you selected them. So if you selected ' 'Book A and then Book B, Book A will have series number 1 ' 'and Book B series number 2.') + '</p>') layout.addWidget(self.idx_widget) self.force_number = QCheckBox(parent) self.force_number.setText(_('Force numbers to start with ')) self.force_number.setToolTip('<p>' + _( 'Series will normally be renumbered from the highest ' 'number in the database for that series. Checking this ' 'box will tell calibre to start numbering from the value ' 'in the box') + '</p>') layout.addWidget(self.force_number) self.series_start_number = QDoubleSpinBox(parent) self.series_start_number.setMinimum(0.0) self.series_start_number.setMaximum(9999999.0) self.series_start_number.setProperty("value", 1.0) layout.addWidget(self.series_start_number) self.series_increment = QDoubleSpinBox(parent) self.series_increment.setMinimum(0.00) self.series_increment.setMaximum(99999.0) self.series_increment.setProperty("value", 1.0) self.series_increment.setToolTip('<p>' + _( 'The amount by which to increment the series number ' 'for successive books. Only applicable when using ' 'force series numbers.') + '</p>') self.series_increment.setPrefix('+') layout.addWidget(self.series_increment) layout.addItem(QSpacerItem(20, 10, QSizePolicy.Expanding, QSizePolicy.Minimum)) self.widgets.append(w) self.idx_widget.stateChanged.connect(self.a_c_checkbox_changed) self.force_number.stateChanged.connect(self.a_c_checkbox_changed) self.series_start_number.valueChanged.connect(self.a_c_checkbox_changed) self.series_increment.valueChanged.connect(self.a_c_checkbox_changed) self.remove_series.stateChanged.connect(self.a_c_checkbox_changed) self.main_widget self.ignore_change_signals = False def a_c_checkbox_changed(self): def disable_numbering_checkboxes(idx_widget_enable): if idx_widget_enable: self.idx_widget.setEnabled(True) else: self.idx_widget.setChecked(False) self.idx_widget.setEnabled(False) self.force_number.setChecked(False) self.force_number.setEnabled(False) self.series_start_number.setEnabled(False) self.series_increment.setEnabled(False) if self.ignore_change_signals: return self.ignore_change_signals = True apply_changes = False if self.remove_series.isChecked(): self.main_widget.setText('') self.main_widget.setEnabled(False) disable_numbering_checkboxes(idx_widget_enable=False) apply_changes = True elif self.main_widget.text(): self.remove_series.setEnabled(False) self.idx_widget.setEnabled(True) apply_changes = True else: # no text, no clear. Basically reinitialize self.main_widget.setEnabled(True) self.remove_series.setEnabled(True) disable_numbering_checkboxes(idx_widget_enable=False) apply_changes = False self.force_number.setEnabled(self.idx_widget.isChecked()) self.series_start_number.setEnabled(self.force_number.isChecked()) self.series_increment.setEnabled(self.force_number.isChecked()) self.ignore_change_signals = False self.a_c_checkbox.setChecked(apply_changes) def initialize(self, book_id): self.idx_widget.setChecked(False) self.main_widget.set_separator(None) self.main_widget.update_items_cache(self.all_values) self.main_widget.setEditText('') self.a_c_checkbox.setChecked(False) def getter(self): n = unicode(self.main_widget.currentText()).strip() autonumber = self.idx_widget.checkState() force = self.force_number.checkState() start = self.series_start_number.value() remove = self.remove_series.checkState() increment = self.series_increment.value() return n, autonumber, force, start, remove, increment def commit(self, book_ids, notify=False): if not self.a_c_checkbox.isChecked(): return val, update_indices, force_start, at_value, clear, increment = self.gui_val val = None if clear else self.normalize_ui_val(val) if clear or val != '': extras = [] for book_id in book_ids: if clear: extras.append(None) continue if update_indices: if force_start: s_index = at_value at_value += increment elif tweaks['series_index_auto_increment'] != 'const': s_index = self.db.get_next_cc_series_num_for(val, num=self.col_id) else: s_index = 1.0 else: s_index = self.db.get_custom_extra(book_id, num=self.col_id, index_is_id=True) extras.append(s_index) self.db.set_custom_bulk(book_ids, val, extras=extras, num=self.col_id, notify=notify)
class ConversionDialog(Dialog): def __init__(self, parent, force_entire_book=False): self.prefs = self.prefsPrep() self.parent = parent self.force_entire_book = force_entire_book self.criteria = None Dialog.__init__(self, _('Chinese Conversion'), 'chinese_conversion_dialog', parent) def setup_ui(self): self.quote_for_trad_target = _("Update quotes: "",'' -> 「」,『』") self.quote_for_simp_target = _("Update quotes: 「」,『』 -> "",''") # Create layout for entire dialog layout = QVBoxLayout(self) self.setLayout(layout) #Create a scroll area for the top part of the dialog self.scrollArea = QScrollArea(self) self.scrollArea.setWidgetResizable(True) # Create widget for all the contents of the dialog except the OK and Cancel buttons self.scrollContentWidget = QWidget(self.scrollArea) self.scrollArea.setWidget(self.scrollContentWidget) widgetLayout = QVBoxLayout(self.scrollContentWidget) # Add scrollArea to dialog layout.addWidget(self.scrollArea) self.operation_group_box = QGroupBox(_('Conversion Direction')) widgetLayout.addWidget(self.operation_group_box) operation_group_box_layout = QVBoxLayout() self.operation_group_box.setLayout(operation_group_box_layout) operation_group = QButtonGroup(self) self.no_conversion_button = QRadioButton(_('No Conversion')) operation_group.addButton(self.no_conversion_button) self.trad_to_simp_button = QRadioButton(_('Traditional to Simplified')) operation_group.addButton(self.trad_to_simp_button) self.simp_to_trad_button = QRadioButton(_('Simplified to Traditional')) operation_group.addButton(self.simp_to_trad_button) self.trad_to_trad_button = QRadioButton( _('Traditional to Traditional')) operation_group.addButton(self.trad_to_trad_button) operation_group_box_layout.addWidget(self.no_conversion_button) operation_group_box_layout.addWidget(self.trad_to_simp_button) operation_group_box_layout.addWidget(self.simp_to_trad_button) operation_group_box_layout.addWidget(self.trad_to_trad_button) self.no_conversion_button.toggled.connect(self.update_gui) self.trad_to_simp_button.toggled.connect(self.update_gui) self.simp_to_trad_button.toggled.connect(self.update_gui) self.trad_to_trad_button.toggled.connect(self.update_gui) self.style_group_box = QGroupBox(_('Language Styles')) widgetLayout.addWidget(self.style_group_box) style_group_box_layout = QVBoxLayout() self.style_group_box.setLayout(style_group_box_layout) input_layout = QHBoxLayout() style_group_box_layout.addLayout(input_layout) self.input_region_label = QLabel(_('Input:')) input_layout.addWidget(self.input_region_label) self.input_combo = QComboBox() input_layout.addWidget(self.input_combo) self.input_combo.addItems([_('Mainland'), _('Hong Kong'), _('Taiwan')]) self.input_combo.setToolTip(_('Select the origin region of the input')) self.input_combo.currentIndexChanged.connect(self.update_gui) output_layout = QHBoxLayout() style_group_box_layout.addLayout(output_layout) self.output_region_label = QLabel(_('Output:')) output_layout.addWidget(self.output_region_label) self.output_combo = QComboBox() output_layout.addWidget(self.output_combo) self.output_combo.addItems( [_('Mainland'), _('Hong Kong'), _('Taiwan')]) self.output_combo.setToolTip( _('Select the desired region of the output')) self.output_combo.currentIndexChanged.connect(self.update_gui) self.use_target_phrases = QCheckBox( _('Use output target phrases if possible')) self.use_target_phrases.setToolTip( _('Check to allow region specific word replacements if available')) style_group_box_layout.addWidget(self.use_target_phrases) self.use_target_phrases.stateChanged.connect(self.update_gui) self.quotation_group_box = QGroupBox(_('Quotation Marks')) widgetLayout.addWidget(self.quotation_group_box) quotation_group_box_layout = QVBoxLayout() self.quotation_group_box.setLayout(quotation_group_box_layout) quotation_group = QButtonGroup(self) self.quotation_no_conversion_button = QRadioButton(_('No Conversion')) quotation_group.addButton(self.quotation_no_conversion_button) self.quotation_trad_to_simp_button = QRadioButton( self.quote_for_simp_target) quotation_group.addButton(self.quotation_trad_to_simp_button) self.quotation_simp_to_trad_button = QRadioButton( self.quote_for_trad_target) quotation_group.addButton(self.quotation_simp_to_trad_button) quotation_group_box_layout.addWidget( self.quotation_no_conversion_button) quotation_group_box_layout.addWidget( self.quotation_simp_to_trad_button) quotation_group_box_layout.addWidget( self.quotation_trad_to_simp_button) self.quotation_no_conversion_button.toggled.connect(self.update_gui) self.quotation_trad_to_simp_button.toggled.connect(self.update_gui) self.quotation_simp_to_trad_button.toggled.connect(self.update_gui) self.use_smart_quotes = QCheckBox( """Use curved 'Smart" quotes if applicable""") self.use_smart_quotes.setToolTip( _('Use smart curved half-width quotes rather than straight full-width quotes' )) quotation_group_box_layout.addWidget(self.use_smart_quotes) self.use_smart_quotes.stateChanged.connect(self.update_gui) self.other_group_box = QGroupBox(_('Other Changes')) widgetLayout.addWidget(self.other_group_box) other_group_box_layout = QVBoxLayout() self.other_group_box.setLayout(other_group_box_layout) text_dir_layout = QHBoxLayout() other_group_box_layout.addLayout(text_dir_layout) direction_label = QLabel(_('Text Direction:')) text_dir_layout.addWidget(direction_label) self.text_dir_combo = QComboBox() text_dir_layout.addWidget(self.text_dir_combo) self.text_dir_combo.addItems( [_('No Conversion'), _('Horizontal'), _('Vertical')]) self.text_dir_combo.setToolTip( _('Select the desired text orientation')) self.text_dir_combo.currentIndexChanged.connect(self.update_gui) self.optimization_group_box = QGroupBox( _('Reader Device Optimization')) other_group_box_layout.addWidget(self.optimization_group_box) optimization_group_box_layout = QVBoxLayout() self.optimization_group_box.setLayout(optimization_group_box_layout) punc_group = QButtonGroup(self) self.text_dir_punc_none_button = QRadioButton( """No presentation optimization""") optimization_group_box_layout.addWidget(self.text_dir_punc_none_button) self.text_dir_punc_button = QRadioButton( """Optimize presentation for Readium reader""") self.text_dir_punc_button.setToolTip( _('Use vert/horiz punctuation presentation forms for Chrome Readium Epub3 reader' )) optimization_group_box_layout.addWidget(self.text_dir_punc_button) self.text_dir_punc_kindle_button = QRadioButton( """Optimize presentation for Kindle reader""") self.text_dir_punc_kindle_button.setToolTip( _('Use vert/horiz puncuation presentation forms for Kindle reader') ) optimization_group_box_layout.addWidget( self.text_dir_punc_kindle_button) self.text_dir_punc_none_button.toggled.connect(self.update_gui) self.text_dir_punc_button.toggled.connect(self.update_gui) self.text_dir_punc_kindle_button.toggled.connect(self.update_gui) source_group = QButtonGroup(self) self.file_source_button = QRadioButton(_('Selected File Only')) self.book_source_button = QRadioButton(_('Entire eBook')) source_group.addButton(self.file_source_button) source_group.addButton(self.book_source_button) self.source_group_box = QGroupBox(_('Source')) if not self.force_entire_book: widgetLayout.addWidget(self.source_group_box) source_group_box_layout = QVBoxLayout() self.source_group_box.setLayout(source_group_box_layout) source_group_box_layout.addWidget(self.file_source_button) source_group_box_layout.addWidget(self.book_source_button) layout.addSpacing(10) self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.button_box.accepted.connect(self._ok_clicked) self.button_box.rejected.connect(self.reject) layout.addWidget(self.button_box) self.input_combo.setCurrentIndex(self.prefs['input_format']) self.output_combo.setCurrentIndex(self.prefs['output_format']) self.no_conversion_button.setChecked(self.prefs['no_conversion']) self.trad_to_simp_button.setChecked(self.prefs['trad_to_simp']) self.simp_to_trad_button.setChecked(self.prefs['simp_to_trad']) self.trad_to_trad_button.setChecked(self.prefs['trad_to_trad']) if not self.force_entire_book: self.file_source_button.setChecked(self.prefs['use_html_file']) self.book_source_button.setChecked(self.prefs['use_entire_book']) else: self.file_source_button.setChecked(False) self.book_source_button.setChecked(True) self.quotation_no_conversion_button.setChecked( self.prefs['quote_no_conversion']) self.quotation_trad_to_simp_button.setChecked( self.prefs['quote_trad_to_simp']) self.quotation_simp_to_trad_button.setChecked( self.prefs['quote_simp_to_trad']) self.use_smart_quotes.setChecked(self.prefs['use_smart_quotes']) self.text_dir_combo.setCurrentIndex(self.prefs['orientation']) self.text_dir_punc_none_button.setChecked( self.prefs['no_optimization']) self.text_dir_punc_button.setChecked( self.prefs['readium_optimization']) self.text_dir_punc_kindle_button.setChecked( self.prefs['kindle_optimization']) self.update_gui() def update_gui(self): if (self.quotation_trad_to_simp_button.isChecked()): self.use_smart_quotes.setEnabled(True) else: self.use_smart_quotes.setEnabled(False) if self.text_dir_combo.currentIndex() == 0: self.optimization_group_box.setEnabled(False) self.text_dir_punc_none_button.setEnabled(False) self.text_dir_punc_button.setEnabled(False) self.text_dir_punc_kindle_button.setEnabled(False) else: self.optimization_group_box.setEnabled(True) self.text_dir_punc_none_button.setEnabled(True) self.text_dir_punc_button.setEnabled(True) self.text_dir_punc_kindle_button.setEnabled(True) if self.no_conversion_button.isChecked(): self.input_combo.setEnabled(False) self.output_combo.setEnabled(False) self.use_target_phrases.setEnabled(False) self.output_region_label.setEnabled(False) self.input_region_label.setEnabled(False) self.style_group_box.setEnabled(False) elif self.trad_to_simp_button.isChecked(): self.input_combo.setEnabled(True) #only mainland output locale for simplified output self.output_combo.setCurrentIndex(0) self.output_combo.setEnabled(False) self.use_target_phrases.setEnabled(True) self.output_region_label.setEnabled(False) self.input_region_label.setEnabled(True) self.style_group_box.setEnabled(True) elif self.simp_to_trad_button.isChecked(): #only mainland input locale for simplified input self.input_combo.setCurrentIndex(0) self.input_combo.setEnabled(False) self.output_combo.setEnabled(True) self.use_target_phrases.setEnabled(True) self.output_region_label.setEnabled(True) self.input_region_label.setEnabled(False) self.style_group_box.setEnabled(True) elif self.trad_to_trad_button.isChecked(): #Trad->Trad #currently only mainland input locale for Trad->Trad self.input_combo.setCurrentIndex(0) self.input_combo.setEnabled(False) self.output_combo.setEnabled(True) self.use_target_phrases.setEnabled(True) self.output_region_label.setEnabled(True) self.input_region_label.setEnabled(False) self.style_group_box.setEnabled(True) else: self.input_combo.setEnabled(True) self.output_combo.setEnabled(True) self.use_target_phrases.setEnabled(True) self.style_group_box.setEnabled(True) self.output_region_label.setEnabled(True) self.input_region_label.setEnabled(True) def _ok_clicked(self): output_mode = 0 if self.trad_to_simp_button.isChecked(): output_mode = 1 #trad -> simp if self.simp_to_trad_button.isChecked(): output_mode = 2 #simp -> trad elif self.trad_to_trad_button.isChecked(): output_mode = 3 #trad -> trad quote_mode = 0 if self.quotation_trad_to_simp_button.isChecked(): quote_mode = 1 #trad -> simp if self.quotation_simp_to_trad_button.isChecked(): quote_mode = 2 #simp -> trad optimization_mode = 0 if self.text_dir_punc_button.isChecked(): optimization_mode = 1 #Readium if self.text_dir_punc_kindle_button.isChecked(): optimization_mode = 2 #Kindle self.criteria = (self.file_source_button.isChecked(), output_mode, self.input_combo.currentIndex(), self.output_combo.currentIndex(), self.use_target_phrases.isChecked(), quote_mode, self.use_smart_quotes.isChecked(), self.text_dir_combo.currentIndex(), optimization_mode) self.savePrefs() self.accept() def getCriteria(self): return self.criteria def prefsPrep(self): from calibre.utils.config import JSONConfig plugin_prefs = JSONConfig( 'plugins/{0}_ChineseConversion_settings'.format(PLUGIN_SAFE_NAME)) plugin_prefs.defaults['input_format'] = 0 plugin_prefs.defaults['output_format'] = 0 plugin_prefs.defaults['no_conversion'] = True plugin_prefs.defaults['trad_to_simp'] = False plugin_prefs.defaults['use_html_file'] = True plugin_prefs.defaults['simp_to_trad'] = False plugin_prefs.defaults['trad_to_trad'] = False plugin_prefs.defaults['use_entire_book'] = True plugin_prefs.defaults['use_target_phrases'] = True plugin_prefs.defaults['quote_no_conversion'] = True plugin_prefs.defaults['quote_trad_to_simp'] = False plugin_prefs.defaults['quote_simp_to_trad'] = False plugin_prefs.defaults['use_smart_quotes'] = False plugin_prefs.defaults['orientation'] = 0 plugin_prefs.defaults['no_optimization'] = True plugin_prefs.defaults['readium_optimization'] = False plugin_prefs.defaults['kindle_optimization'] = False return plugin_prefs def savePrefs(self): self.prefs['input_format'] = self.input_combo.currentIndex() self.prefs['output_format'] = self.output_combo.currentIndex() self.prefs['no_conversion'] = self.no_conversion_button.isChecked() self.prefs['trad_to_simp'] = self.trad_to_simp_button.isChecked() self.prefs['use_html_file'] = self.file_source_button.isChecked() self.prefs['simp_to_trad'] = self.simp_to_trad_button.isChecked() self.prefs['trad_to_trad'] = self.trad_to_trad_button.isChecked() self.prefs['use_entire_book'] = self.book_source_button.isChecked() self.prefs['use_target_phrases'] = self.use_target_phrases.isChecked() self.prefs[ 'quote_no_conversion'] = self.quotation_no_conversion_button.isChecked( ) self.prefs[ 'quote_trad_to_simp'] = self.quotation_trad_to_simp_button.isChecked( ) self.prefs[ 'quote_simp_to_trad'] = self.quotation_simp_to_trad_button.isChecked( ) self.prefs['use_smart_quotes'] = self.use_smart_quotes.isChecked() self.prefs['orientation'] = self.text_dir_combo.currentIndex() self.prefs[ 'no_optimization'] = self.text_dir_punc_none_button.isChecked() self.prefs[ 'readium_optimization'] = self.text_dir_punc_button.isChecked() self.prefs[ 'kindle_optimization'] = self.text_dir_punc_kindle_button.isChecked( )
class ConfigWidget(QWidget): def __init__(self): self.plugin_prefs = prefs self.restart_required = False self.inspector = None QWidget.__init__(self) self.l = QVBoxLayout() self.setLayout(self.l) #add checkbox self.inspector_checkbox = QCheckBox('Show Inspector') self.inspector_checkbox.setChecked( self.plugin_prefs.get('inspector_enabled', False)) self.inspector_checkbox.stateChanged.connect( self._inspector_enabled_changed) self.inspector_checkbox.setToolTip( 'Enable Javascript Console for debugging WebView requests to QuietThyme API' ) self.l.addWidget(self.inspector_checkbox) self.beta_checkbox = QCheckBox('Beta Mode') self.beta_checkbox.setChecked(self.plugin_prefs.get( 'beta_mode', False)) self.beta_checkbox.stateChanged.connect(self._set_restart_required) self.beta_checkbox.stateChanged.connect(self._beta_mode_changed) self.beta_checkbox.setToolTip( 'Tell Calibre to communicate with the Beta version of QuietThyme') self.l.addWidget(self.beta_checkbox) self.config_url = QUrl(self.plugin_prefs.get('web_base') + '/storage') self.webview = QTWebView(bearer_token=self.plugin_prefs.get('token')) self.webview.load(self.config_url) self.global_settings = self.webview.page().settings( ) #QWebSettings.globalSettings() self.global_settings.setAttribute( QWebSettings.LocalStorageEnabled, True) #required since this is where we store tokens. self.global_settings.setAttribute(QWebSettings.PrivateBrowsingEnabled, False) self.global_settings.setAttribute(QWebSettings.JavascriptEnabled, True) self.global_settings.setAttribute( QWebSettings.JavascriptCanOpenWindows, True) self.global_settings.setAttribute( QWebSettings.JavascriptCanCloseWindows, True) self.global_settings.setAttribute(QWebSettings.PluginsEnabled, True) self.global_settings.setAttribute( QWebSettings.LocalContentCanAccessRemoteUrls, True) self.global_settings.setAttribute( QWebSettings.LocalContentCanAccessFileUrls, True) self.global_settings.setAttribute(QWebSettings.XSSAuditingEnabled, False) self.global_settings.setAttribute(QWebSettings.DeveloperExtrasEnabled, True) # ensure that we get a random offline/localstorage/cache path for the brower (so that the localstorage data is not persistent across sessions) path = None while True: potential_path = QTemporaryDir() if potential_path.isValid(): path = potential_path.path() break self.global_settings.setOfflineStoragePath(path) self.global_settings.setOfflineWebApplicationCachePath(path) self.global_settings.enablePersistentStorage(path) self.global_settings.setLocalStoragePath(path) self.webview.show() self.l.addWidget(self.webview) self.configure_webview_inspector_ui() self.webview.urlChanged.connect(self._url_changed) self.webview.loadFinished.connect(self._load_finished) ################################################################################################################ # Configure UI methods # def configure_webview_inspector_ui(self): if self.plugin_prefs.get('inspector_enabled'): self.webview.setMinimumSize(QSize(600, 300)) self.inspector = QWebInspector() self.inspector.setMinimumSize(QSize(600, 300)) self.inspector.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.l.addWidget(self.inspector) self.inspector.setPage(self.webview.page()) else: self.webview.setMinimumSize(QSize(600, 600)) if self.inspector is not None: self.l.removeWidget(self.inspector) self.inspector.setParent(None) self.inspector = None def save_settings(self): self.plugin_prefs.set('inspector_enabled', self.inspector_checkbox.isChecked()) self.plugin_prefs.set('beta_mode', self.beta_checkbox.isChecked()) # # If restart needed, inform user if self.restart_required: # do_restart = show_restart_warning('Restart calibre for the changes to be applied.', # parent=self.l) # if do_restart: # self.gui.quit(restart=True) # TODO: figure out if theres a way to call self.gui.quit() msg = QMessageBox() msg.setIcon(QMessageBox.Warning) msg.setText("Restart Required") msg.setInformativeText( "A configuration change requires you to restart Calibre, You should do so now," ) msg.setWindowTitle("Restart Required") msg.setStandardButtons(QMessageBox.Ok) msg.exec_() def validate(self): # TODO: validate the that the api_endpoint and web endpoint are valid urls. return True ################################################################################################################ # Event Handlers # def _url_changed(self, url): logger.debug(sys._getframe().f_code.co_name, url) def _load_finished(self, ok): logger.debug(sys._getframe().f_code.co_name, ok) if self.webview.page().mainFrame().url() == self.config_url: logger.debug( "Loading finished and the url is the same as the desired destination url, ", self.config_url) self.webview.page().mainFrame().evaluateJavaScript(""" console.log("GET LOCALSTORAGE TOKEN") """) token = self.webview.page().mainFrame().evaluateJavaScript(""" localStorage.getItem('id_token'); """) logger.debug("Got JWT Token", token) self.plugin_prefs.set('token', token) def _set_restart_required(self, state): ''' Set restart_required flag to show show dialog when closing dialog ''' logger.debug(sys._getframe().f_code.co_name, "restart required") self.restart_required = True def _beta_mode_changed(self, state): logger.debug(sys._getframe().f_code.co_name, self.beta_checkbox.isChecked()) self.plugin_prefs.set('beta_mode', self.beta_checkbox.isChecked()) # if the beta mode has changed, we need to reset the api endpoints, and then wipe the token (not valid between envs) self.plugin_prefs.set('token', '') if self.plugin_prefs.get('beta_mode'): self.plugin_prefs.set('api_base', beta_api_base) self.plugin_prefs.set('web_base', beta_web_base) else: self.plugin_prefs.set('api_base', master_api_base) self.plugin_prefs.set('web_base', master_web_base) # after we reset the token, we need to generate a new QTWebView with the new config, and then reload the login page. self.config_url = QUrl(self.plugin_prefs.get('web_base') + '/storage') self.webview.set_bearer_token(self.plugin_prefs.get('token')) self.webview.load(self.config_url) def _inspector_enabled_changed(self, state): logger.debug(sys._getframe().f_code.co_name, self.inspector_checkbox.isChecked()) self.plugin_prefs.set('inspector_enabled', self.inspector_checkbox.isChecked()) self.configure_webview_inspector_ui()