class ConfigWidget(QWidget): def __init__(self, plugin_action): QWidget.__init__(self) self.plugin_action = plugin_action layout = QVBoxLayout(self) self.setLayout(layout) # copy of preferences self.tmpserials = plugin_prefs['kobo_serials'] combo_label = QLabel(_('When should Obok try to insert EPUBs into existing calibre entries?'), self) layout.addWidget(combo_label) self.find_homes = QComboBox() self.find_homes.setToolTip(_('<p>Default behavior when duplicates are detected. None of the choices will cause calibre ebooks to be overwritten')) layout.addWidget(self.find_homes) self.find_homes.addItems([_('Ask'), _('Always'), _('Never')]) index = self.find_homes.findText(plugin_prefs['finding_homes_for_formats']) self.find_homes.setCurrentIndex(index) self.serials_button = QtGui.QPushButton(self) self.serials_button.setToolTip(_(u"Click to manage Kobo serial numbers for Kobo ebooks")) self.serials_button.setText(u"Kobo devices serials") self.serials_button.clicked.connect(self.edit_serials) layout.addWidget(self.serials_button) def edit_serials(self): d = ManageKeysDialog(self,u"Kobo device serial numbers",self.tmpserials, AddSerialDialog) d.exec_() def save_settings(self): plugin_prefs['finding_homes_for_formats'] = unicode(self.find_homes.currentText()) plugin_prefs['kobo_serials'] = self.tmpserials
class ConfigWidget(QWidget): def __init__(self, plugin_action): QWidget.__init__(self) self.plugin_action = plugin_action layout = QVBoxLayout(self) self.setLayout(layout) # copy of preferences self.tmpserials = plugin_prefs['kobo_serials'] self.kobodirectory = plugin_prefs['kobo_directory'] combo_label = QLabel( _('When should Obok try to insert EPUBs into existing calibre entries?' ), self) layout.addWidget(combo_label) self.find_homes = QComboBox() self.find_homes.setToolTip( _('<p>Default behavior when duplicates are detected. None of the choices will cause calibre ebooks to be overwritten' )) layout.addWidget(self.find_homes) self.find_homes.addItems([_('Ask'), _('Always'), _('Never')]) index = self.find_homes.findText( plugin_prefs['finding_homes_for_formats']) self.find_homes.setCurrentIndex(index) self.serials_button = QtGui.QPushButton(self) self.serials_button.setToolTip( _("Click to manage Kobo serial numbers for Kobo ebooks")) self.serials_button.setText("Kobo devices serials") self.serials_button.clicked.connect(self.edit_serials) layout.addWidget(self.serials_button) self.kobo_directory_button = QtGui.QPushButton(self) self.kobo_directory_button.setToolTip( _("Click to specify the Kobo directory")) self.kobo_directory_button.setText("Kobo directory") self.kobo_directory_button.clicked.connect(self.edit_kobo_directory) layout.addWidget(self.kobo_directory_button) def edit_serials(self): d = ManageKeysDialog(self, "Kobo device serial number", self.tmpserials, AddSerialDialog) d.exec_() def edit_kobo_directory(self): tmpkobodirectory = QFileDialog.getExistingDirectory( self, "Select Kobo directory", self.kobodirectory or "/home", QFileDialog.Option.ShowDirsOnly) if tmpkobodirectory != u"" and tmpkobodirectory is not None: self.kobodirectory = tmpkobodirectory def save_settings(self): plugin_prefs[ 'finding_homes_for_formats'] = self.find_homes.currentText() plugin_prefs['kobo_serials'] = self.tmpserials plugin_prefs['kobo_directory'] = self.kobodirectory
class GuiHeader(QWidget): def __init__(self, caller, *args, **kwargs): QWidget.__init__(self) myFont = QFont("Times", italic=True) layout = QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) # --- 1) Check Box self.centralBox = QGridLayout() self.centralBox.setContentsMargins(0, 0, 0, 5) # --- 1a) Select & Check RPC label1 = QLabel("PIVX server") self.centralBox.addWidget(label1, 0, 0) self.rpcClientsBox = QComboBox() self.rpcClientsBox.setToolTip( "Select RPC server.\nLocal must be configured.") rpcClients = ["Local Wallet"] self.rpcClientsBox.addItems(rpcClients) self.centralBox.addWidget(self.rpcClientsBox, 0, 1) self.button_checkRpc = QPushButton("Connect") self.button_checkRpc.setToolTip("try to connect to RPC server") self.button_checkRpc.clicked.connect(caller.onCheckRpc) self.centralBox.addWidget(self.button_checkRpc, 0, 2) self.rpcLed = QLabel() self.rpcLed.setToolTip("status: %s" % caller.rpcStatusMess) self.rpcLed.setPixmap(caller.ledGrayH_icon) self.centralBox.addWidget(self.rpcLed, 0, 3) label2 = QLabel("Last Ping Block:") self.centralBox.addWidget(label2, 0, 4) self.lastBlockLabel = QLabel() self.lastBlockLabel.setFont(myFont) self.centralBox.addWidget(self.lastBlockLabel, 0, 5) # -- 1b) Select & Check hardware label3 = QLabel("HW device") self.centralBox.addWidget(label3, 1, 0) self.hwDevices = QComboBox() self.hwDevices.setToolTip("Select hardware device") hwDevices = ["Ledger Nano S"] self.hwDevices.addItems(hwDevices) self.centralBox.addWidget(self.hwDevices, 1, 1) self.button_checkHw = QPushButton("Connect") self.button_checkHw.setToolTip("try to connect to Hardware Wallet") self.button_checkHw.clicked.connect(caller.onCheckHw) self.centralBox.addWidget(self.button_checkHw, 1, 2) self.hwLed = QLabel() self.hwLed.setToolTip("status: %s" % caller.hwStatusMess) self.hwLed.setPixmap(caller.ledGrayH_icon) self.centralBox.addWidget(self.hwLed, 1, 3) layout.addLayout(self.centralBox) layout.addStretch(1) # --- 3) logo Logo = QLabel() Logo_file = os.path.join(caller.imgDir, 'pet4lLogo_horiz.png') Logo.setPixmap( QPixmap(Logo_file).scaledToHeight(87, Qt.SmoothTransformation)) layout.addWidget(Logo) self.setLayout(layout)
def __init__(self, parent_dialog, plugin_action): self.parent_dialog = parent_dialog self.plugin_action = plugin_action QWidget.__init__(self) self.l = QVBoxLayout() self.setLayout(self.l) label = QLabel(_("If you have custom columns defined, they will be listed below. Choose how you would like these columns handled.")) label.setWordWrap(True) self.l.addWidget(label) self.l.addSpacing(5) scrollable = QScrollArea() scrollcontent = QWidget() scrollable.setWidget(scrollcontent) scrollable.setWidgetResizable(True) self.l.addWidget(scrollable) self.sl = QVBoxLayout() scrollcontent.setLayout(self.sl) self.custcol_dropdowns = {} custom_columns = self.plugin_action.gui.library_view.model().custom_columns grid = QGridLayout() self.sl.addLayout(grid) row=0 for key, column in custom_columns.iteritems(): if column['datatype'] in permitted_values: # logger.debug("\n============== %s ===========\n"%key) # for (k,v) in column.iteritems(): # logger.debug("column['%s'] => %s"%(k,v)) label = QLabel('%s(%s)'%(column['name'],key)) label.setToolTip(_("Set this %s column on new merged books...")%column['datatype']) grid.addWidget(label,row,0) dropdown = QComboBox(self) dropdown.addItem('','none') for md in permitted_values[column['datatype']]: # tags-like column also 'text' if md == 'union' and not column['is_multiple']: continue if md == 'concat' and column['is_multiple']: continue dropdown.addItem(titleLabels[md],md) self.custcol_dropdowns[key] = dropdown if key in prefs['custom_cols']: dropdown.setCurrentIndex(dropdown.findData(prefs['custom_cols'][key])) dropdown.setToolTip(_("How this column will be populated by default.")) grid.addWidget(dropdown,row,1) row+=1 self.sl.insertStretch(-1)
def __init__(self, parent_dialog, plugin_action): self.parent_dialog = parent_dialog self.plugin_action = plugin_action QWidget.__init__(self) self.l = QVBoxLayout() self.setLayout(self.l) label = QLabel(_("If you have custom columns defined, they will be listed below. Choose how you would like these columns handled.")) label.setWordWrap(True) self.l.addWidget(label) self.l.addSpacing(5) scrollable = QScrollArea() scrollcontent = QWidget() scrollable.setWidget(scrollcontent) scrollable.setWidgetResizable(True) self.l.addWidget(scrollable) self.sl = QVBoxLayout() scrollcontent.setLayout(self.sl) self.custcol_dropdowns = {} custom_columns = self.plugin_action.gui.library_view.model().custom_columns grid = QGridLayout() self.sl.addLayout(grid) row=0 for key, column in custom_columns.iteritems(): if column['datatype'] in permitted_values: # print("\n============== %s ===========\n"%key) # for (k,v) in column.iteritems(): # print("column['%s'] => %s"%(k,v)) label = QLabel('%s(%s)'%(column['name'],key)) label.setToolTip(_("Set this %s column on new merged books...")%column['datatype']) grid.addWidget(label,row,0) dropdown = QComboBox(self) dropdown.addItem('','none') for md in permitted_values[column['datatype']]: # tags-like column also 'text' if md == 'union' and not column['is_multiple']: continue if md == 'concat' and column['is_multiple']: continue dropdown.addItem(titleLabels[md],md) self.custcol_dropdowns[key] = dropdown if key in prefs['custom_cols']: dropdown.setCurrentIndex(dropdown.findData(prefs['custom_cols'][key])) dropdown.setToolTip(_("How this column will be populated by default.")) grid.addWidget(dropdown,row,1) row+=1 self.sl.insertStretch(-1)
class ConfigWidget(QWidget): def __init__(self, plugin_action): QWidget.__init__(self) self.plugin_action = plugin_action layout = QVBoxLayout(self) self.setLayout(layout) combo_label = QLabel(_('When should Obok try to insert EPUBs into existing calibre entries?'), self) layout.addWidget(combo_label) self.find_homes = QComboBox() self.find_homes.setToolTip(_('<p>Default behavior when duplicates are detected. None of the choices will cause calibre ebooks to be overwritten')) layout.addWidget(self.find_homes) self.find_homes.addItems([_('Ask'), _('Always'), _('Never')]) index = self.find_homes.findText(plugin_prefs['finding_homes_for_formats']) self.find_homes.setCurrentIndex(index) def save_settings(self): plugin_prefs['finding_homes_for_formats'] = unicode(self.find_homes.currentText())
class ConfigWidget(QWidget): def __init__(self, plugin_action): QWidget.__init__(self) self.plugin_action = plugin_action layout = QVBoxLayout(self) self.setLayout(layout) combo_label = QLabel(_("When should Obok try to insert EPUBs into existing calibre entries?"), self) layout.addWidget(combo_label) self.find_homes = QComboBox() self.find_homes.setToolTip( _( "<p>Default behavior when duplicates are detected. None of the choices will cause calibre ebooks to be overwritten" ) ) layout.addWidget(self.find_homes) self.find_homes.addItems([_("Ask"), _("Always"), _("Never")]) index = self.find_homes.findText(plugin_prefs["finding_homes_for_formats"]) self.find_homes.setCurrentIndex(index) def save_settings(self): plugin_prefs["finding_homes_for_formats"] = unicode(self.find_homes.currentText())
class CustomColumnsTab(QWidget): def __init__(self, parent_dialog, plugin_action): self.parent_dialog = parent_dialog self.plugin_action = plugin_action QWidget.__init__(self) custom_columns = self.plugin_action.gui.library_view.model().custom_columns self.l = QVBoxLayout() self.setLayout(self.l) label = QLabel(_("Save Source column:")) label.setToolTip(_("If set, the column below will be populated with the template below to record the source of the split file.")) label.setWordWrap(True) self.l.addWidget(label) horz = QHBoxLayout() self.sourcecol = QComboBox(self) self.sourcecol.setToolTip(_("Choose a column to populate with template on split.")) self.sourcecol.addItem('','none') for key, column in custom_columns.iteritems(): if column['datatype'] in ('text','comments','series'): self.sourcecol.addItem(column['name'],key) self.sourcecol.setCurrentIndex(self.sourcecol.findData(prefs['sourcecol'])) horz.addWidget(self.sourcecol) self.sourcetemplate = QLineEdit(self) self.sourcetemplate.setToolTip(_("Template from source book. Example: {title} by {authors}")) # if 'sourcetemplate' in prefs: self.sourcetemplate.setText(prefs['sourcetemplate']) # else: # self.sourcetemplate.setText("{title} by {authors}") horz.addWidget(self.sourcetemplate) self.l.addLayout(horz) self.l.addSpacing(5) label = QLabel(_("If you have custom columns defined, they will be listed below. Choose if you would like these columns copied to new split books.")) label.setWordWrap(True) self.l.addWidget(label) self.l.addSpacing(5) scrollable = QScrollArea() scrollcontent = QWidget() scrollable.setWidget(scrollcontent) scrollable.setWidgetResizable(True) self.l.addWidget(scrollable) self.sl = QVBoxLayout() scrollcontent.setLayout(self.sl) self.custcol_checkboxes = {} for key, column in custom_columns.iteritems(): # print("\n============== %s ===========\n"%key) # for (k,v) in column.iteritems(): # print("column['%s'] => %s"%(k,v)) checkbox = QCheckBox('%s(%s)'%(column['name'],key)) checkbox.setToolTip(_("Copy this %s column to new split books...")%column['datatype']) checkbox.setChecked(key in prefs['custom_cols'] and prefs['custom_cols'][key]) self.custcol_checkboxes[key] = checkbox self.sl.addWidget(checkbox) self.sl.insertStretch(-1)
def __init__(self, settings, all_formats, supports_subdirs, supports_non_english_characters, must_read_metadata, supports_use_author_sort, extra_customization_message, device, extra_customization_choices=None): QWidget.__init__(self) Ui_ConfigWidget.__init__(self) self.setupUi(self) self.settings = settings all_formats = set(all_formats) self.calibre_known_formats = device.FORMATS try: self.device_name = device.get_gui_name() except TypeError: self.device_name = getattr(device, 'gui_name', None) or _('Device') if device.USER_CAN_ADD_NEW_FORMATS: all_formats = set(all_formats) | set(BOOK_EXTENSIONS) format_map = settings.format_map disabled_formats = list(set(all_formats).difference(format_map)) for format in format_map + list(sorted(disabled_formats)): item = QListWidgetItem(format, self.columns) item.setData(Qt.UserRole, (format)) item.setFlags(Qt.ItemIsEnabled|Qt.ItemIsUserCheckable|Qt.ItemIsSelectable) item.setCheckState(Qt.Checked if format in format_map else Qt.Unchecked) self.column_up.clicked.connect(self.up_column) self.column_down.clicked.connect(self.down_column) if device.HIDE_FORMATS_CONFIG_BOX: self.groupBox.hide() if supports_subdirs: self.opt_use_subdirs.setChecked(self.settings.use_subdirs) else: self.opt_use_subdirs.hide() if supports_non_english_characters: self.opt_asciiize.setChecked(self.settings.asciiize) else: self.opt_asciiize.hide() if not must_read_metadata: self.opt_read_metadata.setChecked(self.settings.read_metadata) else: self.opt_read_metadata.hide() if supports_use_author_sort: self.opt_use_author_sort.setChecked(self.settings.use_author_sort) else: self.opt_use_author_sort.hide() if extra_customization_message: extra_customization_choices = extra_customization_choices or {} def parse_msg(m): msg, _, tt = m.partition(':::') if m else ('', '', '') return msg.strip(), textwrap.fill(tt.strip(), 100) if isinstance(extra_customization_message, list): self.opt_extra_customization = [] if len(extra_customization_message) > 6: row_func = lambda x, y: ((x/2) * 2) + y col_func = lambda x: x%2 else: row_func = lambda x, y: x*2 + y col_func = lambda x: 0 for i, m in enumerate(extra_customization_message): label_text, tt = parse_msg(m) if not label_text: self.opt_extra_customization.append(None) continue if isinstance(settings.extra_customization[i], bool): self.opt_extra_customization.append(QCheckBox(label_text)) self.opt_extra_customization[-1].setToolTip(tt) self.opt_extra_customization[i].setChecked(bool(settings.extra_customization[i])) elif i in extra_customization_choices: cb = QComboBox(self) self.opt_extra_customization.append(cb) l = QLabel(label_text) l.setToolTip(tt), cb.setToolTip(tt), l.setBuddy(cb), cb.setToolTip(tt) for li in sorted(extra_customization_choices[i]): self.opt_extra_customization[i].addItem(li) cb.setCurrentIndex(max(0, cb.findText(settings.extra_customization[i]))) else: self.opt_extra_customization.append(QLineEdit(self)) l = QLabel(label_text) l.setToolTip(tt) self.opt_extra_customization[i].setToolTip(tt) l.setBuddy(self.opt_extra_customization[i]) l.setWordWrap(True) self.opt_extra_customization[i].setText(settings.extra_customization[i]) self.opt_extra_customization[i].setCursorPosition(0) self.extra_layout.addWidget(l, row_func(i, 0), col_func(i)) self.extra_layout.addWidget(self.opt_extra_customization[i], row_func(i, 1), col_func(i)) else: self.opt_extra_customization = QLineEdit() label_text, tt = parse_msg(extra_customization_message) l = QLabel(label_text) l.setToolTip(tt) l.setBuddy(self.opt_extra_customization) l.setWordWrap(True) if settings.extra_customization: self.opt_extra_customization.setText(settings.extra_customization) self.opt_extra_customization.setCursorPosition(0) self.opt_extra_customization.setCursorPosition(0) self.extra_layout.addWidget(l, 0, 0) self.extra_layout.addWidget(self.opt_extra_customization, 1, 0) self.opt_save_template.setText(settings.save_template)
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 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 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()
def __init__(self, extra_customization_message, extra_customization_choices, device_settings): super(ExtraCustomization, self).__init__() debug_print("ExtraCustomization.__init__ - extra_customization_message=", extra_customization_message) debug_print("ExtraCustomization.__init__ - extra_customization_choices=", extra_customization_choices) debug_print("ExtraCustomization.__init__ - device_settings.extra_customization=", device_settings.extra_customization) debug_print("ExtraCustomization.__init__ - device_settings=", device_settings) self.extra_customization_message = extra_customization_message self.l = QVBoxLayout(self) self.setLayout(self.l) options_group = QGroupBox(_("Extra driver customization options"), self) self.l.addWidget(options_group) self.extra_layout = QGridLayout() self.extra_layout.setObjectName("extra_layout") options_group.setLayout(self.extra_layout) if extra_customization_message: extra_customization_choices = extra_customization_choices or {} def parse_msg(m): msg, _, tt = m.partition(':::') if m else ('', '', '') return msg.strip(), textwrap.fill(tt.strip(), 100) if isinstance(extra_customization_message, list): self.opt_extra_customization = [] if len(extra_customization_message) > 6: row_func = lambda x, y: ((x/2) * 2) + y col_func = lambda x: x%2 else: row_func = lambda x, y: x*2 + y col_func = lambda x: 0 for i, m in enumerate(extra_customization_message): label_text, tt = parse_msg(m) if not label_text: self.opt_extra_customization.append(None) continue if isinstance(device_settings.extra_customization[i], bool): self.opt_extra_customization.append(QCheckBox(label_text)) self.opt_extra_customization[-1].setToolTip(tt) self.opt_extra_customization[i].setChecked(bool(device_settings.extra_customization[i])) elif i in extra_customization_choices: cb = QComboBox(self) self.opt_extra_customization.append(cb) l = QLabel(label_text) l.setToolTip(tt), cb.setToolTip(tt), l.setBuddy(cb), cb.setToolTip(tt) for li in sorted(extra_customization_choices[i]): self.opt_extra_customization[i].addItem(li) cb.setCurrentIndex(max(0, cb.findText(device_settings.extra_customization[i]))) else: self.opt_extra_customization.append(QLineEdit(self)) l = QLabel(label_text) l.setToolTip(tt) self.opt_extra_customization[i].setToolTip(tt) l.setBuddy(self.opt_extra_customization[i]) l.setWordWrap(True) self.opt_extra_customization[i].setText(device_settings.extra_customization[i]) self.opt_extra_customization[i].setCursorPosition(0) self.extra_layout.addWidget(l, row_func(i + 2, 0), col_func(i)) self.extra_layout.addWidget(self.opt_extra_customization[i], row_func(i + 2, 1), col_func(i)) spacerItem1 = QSpacerItem(10, 10, QSizePolicy.Minimum, QSizePolicy.Expanding) self.extra_layout.addItem(spacerItem1, row_func(i + 2 + 2, 1), 0, 1, 2) self.extra_layout.setRowStretch(row_func(i + 2 + 2, 1), 2) else: self.opt_extra_customization = QLineEdit() label_text, tt = parse_msg(extra_customization_message) l = QLabel(label_text) l.setToolTip(tt) l.setBuddy(self.opt_extra_customization) l.setWordWrap(True) if device_settings.extra_customization: self.opt_extra_customization.setText(device_settings.extra_customization) self.opt_extra_customization.setCursorPosition(0) self.opt_extra_customization.setCursorPosition(0) self.extra_layout.addWidget(l, 0, 0) self.extra_layout.addWidget(self.opt_extra_customization, 1, 0)
def __init__(self, image, parent, imgman, name=None, save=False): QFrame.__init__(self, parent) self.setFrameStyle(QFrame.StyledPanel | QFrame.Raised) # init state self._border_pen = None self._image_label_text = None self._subset = None self.image = image self._imgman = imgman self._currier = PersistentCurrier() self._control_dialog = None # create widgets self._lo = lo = QHBoxLayout(self) lo.setContentsMargins(0, 0, 0, 0) lo.setSpacing(2) # raise button self._wraise = QToolButton(self) lo.addWidget(self._wraise) self._wraise.setIcon(pixmaps.raise_up.icon()) self._wraise.setAutoRaise(True) self._can_raise = False self._wraise.clicked.connect(self._raiseButtonPressed) self._wraise.setToolTip( """<P>Click here to raise this image above other images. Hold the button down briefly to show a menu of image operations.</P>""") # center label self._wcenter = QLabel(self) self._wcenter.setPixmap(pixmaps.center_image.pm()) self._wcenter.setToolTip( "<P>The plot is currently centered on (the reference pixel %d,%d) of this image.</P>" % self.image.referencePixel()) lo.addWidget(self._wcenter) # name/filename label self.name = image.name self._wlabel = QLabel(self.name, self) self._number = 0 self.setName(self.name) self._wlabel.setToolTip( "%s %s" % (image.filename, "\u00D7".join(map(str, image.data().shape)))) lo.addWidget(self._wlabel, 1) # if 'save' is specified, create a "save" button if save: self._wsave = QToolButton(self) lo.addWidget(self._wsave) self._wsave.setText("save") self._wsave.setAutoRaise(True) self._save_dir = save if isinstance(save, str) else "." self._wsave.clicked.connect(self._saveImage) self._wsave.setToolTip( """<P>Click here to write this image to a FITS file.</P>""") # render control self.image.connectRepaint(self.imageSignalRepaint) self.image.connectSlice(self.imageSignalSlice) self.image.connectRaise(self.imageSignalRaise) self.image.connectUnload(self.imageSignalUnload) self.image.connectCenter(self.imageSignalCenter) dprint(2, "creating RenderControl") self._rc = RenderControl(image, self) dprint(2, "done") # selectors for extra axes self._wslicers = [] curslice = self._rc.currentSlice( ) # this may be loaded from config, so not necessarily 0 for iextra, axisname, labels in self._rc.slicedAxes(): if axisname.upper() not in ["STOKES", "COMPLEX"]: lbl = QLabel("%s:" % axisname, self) lo.addWidget(lbl) else: lbl = None slicer = QComboBox(self) self._wslicers.append(slicer) lo.addWidget(slicer) slicer.addItems(labels) slicer.setToolTip( """<P>Selects current slice along the %s axis.</P>""" % axisname) slicer.setCurrentIndex(curslice[iextra]) slicer.activated[int].connect( self._currier.curry(self._rc.changeSlice, iextra)) # min/max display ranges lo.addSpacing(5) self._wrangelbl = QLabel(self) lo.addWidget(self._wrangelbl) self._minmaxvalidator = FloatValidator(self) self._wmin = QLineEdit(self) self._wmax = QLineEdit(self) width = self._wmin.fontMetrics().width("1.234567e-05") for w in self._wmin, self._wmax: lo.addWidget(w, 0) w.setValidator(self._minmaxvalidator) w.setMaximumWidth(width) w.setMinimumWidth(width) w.editingFinished.connect(self._changeDisplayRange) # full-range button self._wfullrange = QToolButton(self) lo.addWidget(self._wfullrange, 0) self._wfullrange.setIcon(pixmaps.zoom_range.icon()) self._wfullrange.setAutoRaise(True) self._wfullrange.clicked.connect( self.renderControl().resetSubsetDisplayRange) rangemenu = QMenu(self) rangemenu.addAction(pixmaps.full_range.icon(), "Full subset", self.renderControl().resetSubsetDisplayRange) for percent in (99.99, 99.9, 99.5, 99, 98, 95): rangemenu.addAction( "%g%%" % percent, self._currier.curry(self._changeDisplayRangeToPercent, percent)) self._wfullrange.setPopupMode(QToolButton.DelayedPopup) self._wfullrange.setMenu(rangemenu) # update widgets from current display range self._updateDisplayRange(*self._rc.displayRange()) # lock button self._wlock = QToolButton(self) self._wlock.setIcon(pixmaps.unlocked.icon()) self._wlock.setAutoRaise(True) self._wlock.setToolTip( """<P>Click to lock or unlock the intensity range. When the intensity range is locked across multiple images, any changes in the intensity range of one are propagated to the others. Hold the button down briefly for additional options.</P>""" ) lo.addWidget(self._wlock) self._wlock.clicked.connect(self._toggleDisplayRangeLock) self.renderControl().displayRangeLocked.connect( self._setDisplayRangeLock) self.renderControl().dataSubsetChanged.connect(self._dataSubsetChanged) lockmenu = QMenu(self) lockmenu.addAction( pixmaps.locked.icon(), "Lock all to this", self._currier.curry(imgman.lockAllDisplayRanges, self.renderControl())) lockmenu.addAction(pixmaps.unlocked.icon(), "Unlock all", imgman.unlockAllDisplayRanges) self._wlock.setPopupMode(QToolButton.DelayedPopup) self._wlock.setMenu(lockmenu) self._setDisplayRangeLock(self.renderControl().isDisplayRangeLocked()) # dialog button self._wshowdialog = QToolButton(self) lo.addWidget(self._wshowdialog) self._wshowdialog.setIcon(pixmaps.colours.icon()) self._wshowdialog.setAutoRaise(True) self._wshowdialog.setToolTip( """<P>Click for colourmap and intensity policy options.</P>""") self._wshowdialog.clicked.connect(self.showRenderControls) tooltip = """<P>You can change the currently displayed intensity range by entering low and high limits here.</P> <TABLE> <TR><TD><NOBR>Image min:</NOBR></TD><TD>%g</TD><TD>max:</TD><TD>%g</TD></TR> </TABLE>""" % self.image.imageMinMax() for w in self._wmin, self._wmax, self._wrangelbl: w.setToolTip(tooltip) # create image operations menu self._menu = QMenu(self.name, self) self._qa_raise = self._menu.addAction( pixmaps.raise_up.icon(), "Raise image", self._currier.curry(self.image.signalRaise.emit, None)) self._qa_center = self._menu.addAction( pixmaps.center_image.icon(), "Center plot on image", self._currier.curry(self.image.signalCenter.emit, True)) self._qa_show_rc = self._menu.addAction(pixmaps.colours.icon(), "Colours && Intensities...", self.showRenderControls) if save: self._qa_save = self._menu.addAction("Save image...", self._saveImage) self._menu.addAction("Export image to PNG file...", self._exportImageToPNG) self._export_png_dialog = None self._menu.addAction( "Unload image", self._currier.curry(self.image.signalUnload.emit, None)) self._wraise.setMenu(self._menu) self._wraise.setPopupMode(QToolButton.DelayedPopup) # connect updates from renderControl and image self.image.signalSlice.connect(self._updateImageSlice) self._rc.displayRangeChanged.connect(self._updateDisplayRange) # default plot depth of image markers self._z_markers = None # and the markers themselves self._image_border = QwtPlotCurve() self._image_border.setRenderHint(QwtPlotItem.RenderAntialiased) self._image_label = QwtPlotMarker() self._image_label.setRenderHint(QwtPlotItem.RenderAntialiased) # subset markers self._subset_pen = QPen(QColor("Light Blue")) self._subset_border = QwtPlotCurve() self._subset_border.setRenderHint(QwtPlotItem.RenderAntialiased) self._subset_border.setPen(self._subset_pen) self._subset_border.setVisible(False) self._subset_label = QwtPlotMarker() self._subset_label.setRenderHint(QwtPlotItem.RenderAntialiased) text = QwtText("subset") text.setColor(self._subset_pen.color()) self._subset_label.setLabel(text) self._subset_label.setLabelAlignment(Qt.AlignRight | Qt.AlignBottom) self._subset_label.setVisible(False) self._setting_lmrect = False self._all_markers = [ self._image_border, self._image_label, self._subset_border, self._subset_label ] self._exportMaxRes = False self._dockable_colour_ctrl = None
def _updateModel(self, what=SkyModel.UpdateAll, origin=None): if origin is self or not what & (SkyModel.UpdateTags | SkyModel.UpdateGroupStyle): return model = self.model self._setting_model = True # to ignore cellChanged() signals (in valueChanged()) # _item_cb is a dict (with row,col keys) containing the widgets (CheckBoxes ComboBoxes) per each cell self._item_cb = {} # lists of "list" and "plot" checkboxes per each grouping (excepting the default grouping); each entry is an (row,col,item) tuple. # used as argument to self._showControls() self._list_controls = [] self._plot_controls = [] # list of selection callbacks (to which signals are connected) self._callbacks = [] # set requisite number of rows,and start filling self.table.setRowCount(len(model.groupings)) for irow, group in enumerate(model.groupings): self.table.setItem(irow, 0, QTableWidgetItem(group.name)) if group is model.selgroup: self._irow_selgroup = irow # total # source in group: skip for "current" if group is not model.curgroup: self.table.setItem(irow, 1, QTableWidgetItem(str(group.total))) # selection controls: skip for current and selection if group not in (model.curgroup, model.selgroup): btns = QWidget() lo = QHBoxLayout(btns) lo.setContentsMargins(0, 0, 0, 0) lo.setSpacing(0) # make selector buttons (depending on which group we're in) if group is model.defgroup: Buttons = (("+", lambda src, grp=group: True, "select all sources"), ("-", lambda src, grp=group: False, "unselect all sources")) else: Buttons = ( ("=", lambda src, grp=group: grp.func(src), "select only this grouping"), ("+", lambda src, grp=group: src.selected or grp.func(src), "add grouping to selection"), ("-", lambda src, grp=group: src.selected and not grp. func(src), "remove grouping from selection"), ("&&", lambda src, grp=group: src.selected and grp.func(src), "intersect selection with grouping")) lo.addStretch(1) for label, predicate, tooltip in Buttons: btn = QToolButton(btns) btn.setText(label) btn.setMinimumWidth(24) btn.setMaximumWidth(24) btn.setToolTip(tooltip) lo.addWidget(btn) # add callback btn.clicked.connect( self._currier.curry(self.selectSources, predicate)) lo.addStretch(1) self.table.setCellWidget(irow, 2, btns) # "list" checkbox (not for current and selected groupings: these are always listed) if group not in (model.curgroup, model.selgroup): item = self._makeCheckItem("", group, "show_list") self.table.setItem(irow, self.ColList, item) item.setToolTip( """<P>If checked, sources in this grouping will be listed in the source table. If un-checked, sources will be excluded from the table. If partially checked, then the default list/no list setting of "all sources" will be in effect. </P>""") # "plot" checkbox (not for the current grouping, since that's always plotted) if group is not model.curgroup: item = self._makeCheckItem("", group, "show_plot") self.table.setItem(irow, self.ColPlot, item) item.setToolTip( """<P>If checked, sources in this grouping will be included in the plot. If un-checked, sources will be excluded from the plot. If partially checked, then the default plot/no plot setting of "all sources" will be in effect. </P>""") # custom style control # for default, current and selected, this is just a text label if group is model.defgroup: item = QTableWidgetItem("default:") item.setTextAlignment(Qt.AlignRight | Qt.AlignVCenter) item.setToolTip( """<P>This is the default plot style used for all sources for which a custom grouping style is not selected.</P>""" ) self.table.setItem(irow, self.ColApply, item) elif group is model.curgroup: item = QTableWidgetItem("") item.setTextAlignment(Qt.AlignRight | Qt.AlignVCenter) item.setToolTip( """<P>This is the plot style used for the highlighted source, if any.</P>""" ) self.table.setItem(irow, self.ColApply, item) elif group is model.selgroup: item = QTableWidgetItem("") item.setTextAlignment(Qt.AlignRight | Qt.AlignVCenter) item.setToolTip( """<P>This is the plot style used for the currently selected sources.</P>""" ) self.table.setItem(irow, self.ColApply, item) # for the rest, a combobox with custom priorities else: cb = QComboBox() cb.addItems(["default"] + ["custom %d" % p for p in range(1, 10)]) index = max(0, min(group.style.apply, 9)) # dprint(0,group.name,"apply",index) cb.setCurrentIndex(index) cb.activated[int].connect( self._currier.xcurry(self._valueChanged, (irow, self.ColApply))) self.table.setCellWidget(irow, self.ColApply, cb) cb.setToolTip( """<P>This controls whether sources within this group are plotted with a customized plot style. Customized styles have numeric priority; if a source belongs to multiple groups, then the style with the lowest priority takes precedence.<P>""") # attribute comboboxes for icol, attr in self.AttrByCol.items(): # get list of options for this style attribute. If dealing with first grouping (i==0), which is # the "all sources" grouping, then remove the "default" option (which is always first in the list) options = PlotStyles.StyleAttributeOptions[attr] if irow == 0: options = options[1:] # make combobox cb = QComboBox() cb.addItems(list(map(str, options))) # the "label" option is also editable if attr == "label": cb.setEditable(True) try: index = options.index(getattr(group.style, attr)) cb.setCurrentIndex(index) except ValueError: cb.setEditText(str(getattr(group.style, attr))) slot = self._currier.xcurry(self._valueChanged, (irow, icol)) cb.activated[int].connect(slot) cb.editTextChanged['QString'].connect(slot) cb.setEnabled(group is model.defgroup or group.style.apply) self.table.setCellWidget(irow, icol, cb) label = attr if irow: cb.setToolTip( """<P>This is the %s used to plot sources in this group, when a "custom" style for the group is enabled via the style control.<P>""" % label) else: cb.setToolTip( "<P>This is the default %s used for all sources for which a custom style is not specified below.<P>" % label) self.table.resizeColumnsToContents() # re-enable processing of cellChanged() signals self._setting_model = False
class FindAnnotationsDialog(SizePersistedDialog, Logger): GENERIC_STYLE = 'Any style' GENERIC_READER = 'Any reader' def __init__(self, opts): self.matched_ids = set() self.opts = opts self.prefs = opts.prefs super(FindAnnotationsDialog, self).__init__(self.opts.gui, 'find_annotations_dialog') self.setWindowTitle(_('Find Annotations')) self.setWindowIcon(self.opts.icon) self.l = QVBoxLayout(self) self.setLayout(self.l) self.search_criteria_gb = QGroupBox(self) self.search_criteria_gb.setTitle(_("Search criteria")) self.scgl = QGridLayout(self.search_criteria_gb) self.l.addWidget(self.search_criteria_gb) # addWidget(widget, row, col, rowspan, colspan) row = 0 # ~~~~~~~~ Create the Readers comboBox ~~~~~~~~ self.reader_label = QLabel(_('Reader')) self.reader_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter) self.scgl.addWidget(self.reader_label, row, 0, 1, 1) self.find_annotations_reader_comboBox = QComboBox() self.find_annotations_reader_comboBox.setObjectName('find_annotations_reader_comboBox') self.find_annotations_reader_comboBox.setToolTip(_('Reader annotations to search for')) self.find_annotations_reader_comboBox.addItem(self.GENERIC_READER) racs = ReaderApp.get_reader_app_classes() for ra in sorted(racs.keys()): self.find_annotations_reader_comboBox.addItem(ra) self.scgl.addWidget(self.find_annotations_reader_comboBox, row, 1, 1, 4) row += 1 # ~~~~~~~~ Create the Styles comboBox ~~~~~~~~ self.style_label = QLabel(_('Style')) self.style_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter) self.scgl.addWidget(self.style_label, row, 0, 1, 1) self.find_annotations_color_comboBox = QComboBox() self.find_annotations_color_comboBox.setObjectName('find_annotations_color_comboBox') self.find_annotations_color_comboBox.setToolTip(_('Annotation style to search for')) self.find_annotations_color_comboBox.addItem(self.GENERIC_STYLE) all_colors = COLOR_MAP.keys() all_colors.remove('Default') for color in sorted(all_colors): self.find_annotations_color_comboBox.addItem(color) self.scgl.addWidget(self.find_annotations_color_comboBox, row, 1, 1, 4) row += 1 # ~~~~~~~~ Create the Text LineEdit control ~~~~~~~~ self.text_label = QLabel(_('Text')) self.text_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter) self.scgl.addWidget(self.text_label, row, 0, 1, 1) self.find_annotations_text_lineEdit = MyLineEdit() self.find_annotations_text_lineEdit.setObjectName('find_annotations_text_lineEdit') self.scgl.addWidget(self.find_annotations_text_lineEdit, row, 1, 1, 3) self.reset_text_tb = QToolButton() self.reset_text_tb.setObjectName('reset_text_tb') self.reset_text_tb.setToolTip(_('Clear search criteria')) self.reset_text_tb.setIcon(QIcon(I('trash.png'))) self.reset_text_tb.clicked.connect(self.clear_text_field) self.scgl.addWidget(self.reset_text_tb, row, 4, 1, 1) row += 1 # ~~~~~~~~ Create the Note LineEdit control ~~~~~~~~ self.note_label = QLabel(_('Note')) self.note_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter) self.scgl.addWidget(self.note_label, row, 0, 1, 1) self.find_annotations_note_lineEdit = MyLineEdit() self.find_annotations_note_lineEdit.setObjectName('find_annotations_note_lineEdit') self.scgl.addWidget(self.find_annotations_note_lineEdit, row, 1, 1, 3) self.reset_note_tb = QToolButton() self.reset_note_tb.setObjectName('reset_note_tb') self.reset_note_tb.setToolTip(_('Clear search criteria')) self.reset_note_tb.setIcon(QIcon(I('trash.png'))) self.reset_note_tb.clicked.connect(self.clear_note_field) self.scgl.addWidget(self.reset_note_tb, row, 4, 1, 1) row += 1 # ~~~~~~~~ Create the Date range controls ~~~~~~~~ self.date_range_label = QLabel(_('Date range')) self.date_range_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter) self.scgl.addWidget(self.date_range_label, row, 0, 1, 1) # Date 'From' self.find_annotations_date_from_dateEdit = MyDateEdit(self, datetime(1970,1,1)) self.find_annotations_date_from_dateEdit.setObjectName('find_annotations_date_from_dateEdit') #self.find_annotations_date_from_dateEdit.current_val = datetime(1970,1,1) self.find_annotations_date_from_dateEdit.clear_button.clicked.connect(self.find_annotations_date_from_dateEdit.reset_from_date) self.scgl.addWidget(self.find_annotations_date_from_dateEdit, row, 1, 1, 1) self.scgl.addWidget(self.find_annotations_date_from_dateEdit.clear_button, row, 2, 1, 1) # Date 'To' self.find_annotations_date_to_dateEdit = MyDateEdit(self, datetime.today()) self.find_annotations_date_to_dateEdit.setObjectName('find_annotations_date_to_dateEdit') #self.find_annotations_date_to_dateEdit.current_val = datetime.today() self.find_annotations_date_to_dateEdit.clear_button.clicked.connect(self.find_annotations_date_to_dateEdit.reset_to_date) self.scgl.addWidget(self.find_annotations_date_to_dateEdit, row, 3, 1, 1) self.scgl.addWidget(self.find_annotations_date_to_dateEdit.clear_button, row, 4, 1, 1) row += 1 # ~~~~~~~~ Create a horizontal line ~~~~~~~~ self.hl = QFrame(self) self.hl.setGeometry(QRect(0, 0, 1, 3)) self.hl.setFrameShape(QFrame.HLine) self.hl.setFrameShadow(QFrame.Raised) self.scgl.addWidget(self.hl, row, 0, 1, 5) row += 1 # ~~~~~~~~ Create the results label field ~~~~~~~~ self.result_label = QLabel('<p style="color:red">{0}</p>'.format(_('scanning…'))) self.result_label.setAlignment(Qt.AlignCenter) self.result_label.setWordWrap(False) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Maximum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.result_label.sizePolicy().hasHeightForWidth()) self.result_label.setSizePolicy(sizePolicy) self.result_label.setMinimumSize(QtCore.QSize(250, 0)) self.scgl.addWidget(self.result_label, row, 0, 1, 5) row += 1 # ~~~~~~~~ Create the ButtonBox ~~~~~~~~ self.dialogButtonBox = QDialogButtonBox(self) self.dialogButtonBox.setOrientation(Qt.Horizontal) if False: self.update_button = QPushButton(_('Update results')) self.update_button.setDefault(True) self.update_button.setVisible(False) self.dialogButtonBox.addButton(self.update_button, QDialogButtonBox.ActionRole) self.cancel_button = self.dialogButtonBox.addButton(self.dialogButtonBox.Cancel) self.find_button = self.dialogButtonBox.addButton(self.dialogButtonBox.Ok) self.find_button.setText(_('Find Matching Books')) self.l.addWidget(self.dialogButtonBox) self.dialogButtonBox.clicked.connect(self.find_annotations_dialog_clicked) # ~~~~~~~~ Add a spacer ~~~~~~~~ self.spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.l.addItem(self.spacerItem) # ~~~~~~~~ Restore previously saved settings ~~~~~~~~ self.restore_settings() # ~~~~~~~~ Declare sizing ~~~~~~~~ sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth()) self.setSizePolicy(sizePolicy) self.resize_dialog() # ~~~~~~~~ Connect all signals ~~~~~~~~ self.find_annotations_reader_comboBox.currentIndexChanged.connect(partial(self.update_results, 'reader')) self.find_annotations_color_comboBox.currentIndexChanged.connect(partial(self.update_results, 'color')) self.find_annotations_text_lineEdit.editingFinished.connect(partial(self.update_results, 'text')) self.find_annotations_note_lineEdit.editingFinished.connect(partial(self.update_results, 'note')) # self.connect(self.find_annotations_text_lineEdit, pyqtSignal("return_pressed"), self.return_pressed) self.find_annotations_text_lineEdit.return_pressed.connect(self.return_pressed) # self.connect(self.find_annotations_note_lineEdit, pyqtSignal("return_pressed"), self.return_pressed) self.find_annotations_note_lineEdit.return_pressed.connect(self.return_pressed) # Date range signals connected in inventory_available() # ~~~~~~~~ Allow dialog to render before doing inventory ~~~~~~~~ #field = self.prefs.get('cfg_annotations_destination_field', None) field = get_cc_mapping('annotations', 'field', None) self.annotated_books_scanner = InventoryAnnotatedBooks(self.opts.gui, field, get_date_range=True) self.annotated_books_scanner.signal.connect(self.inventory_available) QTimer.singleShot(1, self.start_inventory_scan) def clear_note_field(self): if str(self.find_annotations_note_lineEdit.text()) > '': self.find_annotations_note_lineEdit.setText('') self.update_results('clear_note_field') def clear_text_field(self): if str(self.find_annotations_text_lineEdit.text()) > '': self.find_annotations_text_lineEdit.setText('') self.update_results('clear_text_field') def find_annotations_dialog_clicked(self, button): if self.dialogButtonBox.buttonRole(button) == QDialogButtonBox.AcceptRole: self.save_settings() self.accept() elif self.dialogButtonBox.buttonRole(button) == QDialogButtonBox.RejectRole: self.close() def inventory_available(self): ''' Update the Date range widgets with the rounded oldest, newest dates Don't connect date signals until date range available ''' self._log_location() # Reset the date range based on available annotations oldest = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.oldest_annotation)) oldest_day = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.oldest_annotation).replace(hour=0, minute=0, second=0)) newest = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.newest_annotation)) newest_day = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.newest_annotation).replace(hour=23, minute=59, second=59)) # Set 'From' date limits to inventory values self.find_annotations_date_from_dateEdit.setMinimumDateTime(oldest_day) self.find_annotations_date_from_dateEdit.current_val = oldest self.find_annotations_date_from_dateEdit.setMaximumDateTime(newest_day) # Set 'To' date limits to inventory values self.find_annotations_date_to_dateEdit.setMinimumDateTime(oldest_day) self.find_annotations_date_to_dateEdit.current_val = newest_day self.find_annotations_date_to_dateEdit.setMaximumDateTime(newest_day) # Connect the signals for date range changes self.find_annotations_date_from_dateEdit.dateTimeChanged.connect(partial(self.update_results, 'from_date')) self.find_annotations_date_to_dateEdit.dateTimeChanged.connect(partial(self.update_results, 'to_date')) self.update_results('inventory_available') def restore_settings(self): self.blockSignals(True) ra = self.prefs.get('find_annotations_reader_comboBox', self.GENERIC_READER) ra_index = self.find_annotations_reader_comboBox.findText(ra) self.find_annotations_reader_comboBox.setCurrentIndex(ra_index) color = self.prefs.get('find_annotations_color_comboBox', self.GENERIC_STYLE) color_index = self.find_annotations_color_comboBox.findText(color) self.find_annotations_color_comboBox.setCurrentIndex(color_index) text = self.prefs.get('find_annotations_text_lineEdit', '') self.find_annotations_text_lineEdit.setText(text) note = self.prefs.get('find_annotations_note_lineEdit', '') self.find_annotations_note_lineEdit.setText(note) if False: from_date = self.prefs.get('find_annotations_date_from_dateEdit', datetime(1970,1,1)) self.find_annotations_date_from_dateEdit.current_val = from_date to_date = self.prefs.get('find_annotations_date_to_dateEdit', datetime.today()) self.find_annotations_date_to_dateEdit.current_val = to_date self.blockSignals(False) def return_pressed(self): self.update_results("return_pressed") def save_settings(self): ra = str(self.find_annotations_reader_comboBox.currentText()) self.prefs.set('find_annotations_reader_comboBox', ra) color = str(self.find_annotations_color_comboBox.currentText()) self.prefs.set('find_annotations_color_comboBox', color) text = str(self.find_annotations_text_lineEdit.text()) self.prefs.set('find_annotations_text_lineEdit', text) note = str(self.find_annotations_note_lineEdit.text()) self.prefs.set('find_annotations_note_lineEdit', note) if False: from_date = self.find_annotations_date_from_dateEdit.current_val self.prefs.set('find_annotations_date_from_dateEdit', from_date) to_date = self.find_annotations_date_to_dateEdit.current_val self.prefs.set('find_annotations_date_to_dateEdit', to_date) def start_inventory_scan(self): self._log_location() self.annotated_books_scanner.start() def update_results(self, trigger): #self._log_location(trigger) reader_to_match = str(self.find_annotations_reader_comboBox.currentText()) color_to_match = str(self.find_annotations_color_comboBox.currentText()) text_to_match = str(self.find_annotations_text_lineEdit.text()) note_to_match = str(self.find_annotations_note_lineEdit.text()) from_date = self.find_annotations_date_from_dateEdit.dateTime().toTime_t() to_date = self.find_annotations_date_to_dateEdit.dateTime().toTime_t() annotation_map = self.annotated_books_scanner.annotation_map #field = self.prefs.get("cfg_annotations_destination_field", None) field = get_cc_mapping('annotations', 'field', None) db = self.opts.gui.current_db matched_titles = [] self.matched_ids = set() for cid in annotation_map: mi = db.get_metadata(cid, index_is_id=True) soup = None if field == 'Comments': if mi.comments: soup = BeautifulSoup(mi.comments) else: if mi.get_user_metadata(field, False)['#value#'] is not None: soup = BeautifulSoup(mi.get_user_metadata(field, False)['#value#']) if soup: uas = soup.findAll('div', 'annotation') for ua in uas: # Are we already logged? if cid in self.matched_ids: continue # Check reader if reader_to_match != self.GENERIC_READER: this_reader = ua['reader'] if this_reader != reader_to_match: continue # Check color if color_to_match != self.GENERIC_STYLE: this_color = ua.find('table')['color'] if this_color != color_to_match: continue # Check date range, allow for mangled timestamp try: timestamp = float(ua.find('td', 'timestamp')['uts']) if timestamp < from_date or timestamp > to_date: continue except: continue highlight_text = '' try: pels = ua.findAll('p', 'highlight') highlight_text = '\n'.join([p.string for p in pels]) except: pass if text_to_match > '': if not re.search(text_to_match, highlight_text, flags=re.IGNORECASE): continue note_text = '' try: nels = ua.findAll('p', 'note') note_text = '\n'.join([n.string for n in nels]) except: pass if note_to_match > '': if not re.search(note_to_match, note_text, flags=re.IGNORECASE): continue # If we made it this far, add the id to matched_ids self.matched_ids.add(cid) matched_titles.append(mi.title) # Update the results box matched_titles.sort() if len(annotation_map): if len(matched_titles): first_match = ("<i>%s</i>" % matched_titles[0]) if len(matched_titles) == 1: results = first_match else: results = first_match + (_(" and {0} more.").format(len(matched_titles) - 1)) self.result_label.setText('<p style="color:blue">{0}</p>'.format(results)) else: self.result_label.setText('<p style="color:red">{0}</p>'.format(_('no matches'))) else: self.result_label.setText('<p style="color:red">{0}</p>'.format(_('no annotated books in library'))) self.resize_dialog()
class FindAnnotationsDialog(SizePersistedDialog, Logger): GENERIC_STYLE = 'Any style' GENERIC_READER = 'Any reader' def __init__(self, opts): self.matched_ids = set() self.opts = opts self.prefs = opts.prefs super(FindAnnotationsDialog, self).__init__(self.opts.gui, 'find_annotations_dialog') self.setWindowTitle('Find Annotations') self.setWindowIcon(self.opts.icon) self.l = QVBoxLayout(self) self.setLayout(self.l) self.search_criteria_gb = QGroupBox(self) self.search_criteria_gb.setTitle("Search criteria") self.scgl = QGridLayout(self.search_criteria_gb) self.l.addWidget(self.search_criteria_gb) # addWidget(widget, row, col, rowspan, colspan) row = 0 # ~~~~~~~~ Create the Readers comboBox ~~~~~~~~ self.reader_label = QLabel('Reader') self.reader_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter) self.scgl.addWidget(self.reader_label, row, 0, 1, 1) self.find_annotations_reader_comboBox = QComboBox() self.find_annotations_reader_comboBox.setObjectName('find_annotations_reader_comboBox') self.find_annotations_reader_comboBox.setToolTip('Reader annotations to search for') self.find_annotations_reader_comboBox.addItem(self.GENERIC_READER) racs = ReaderApp.get_reader_app_classes() for ra in sorted(racs.keys()): self.find_annotations_reader_comboBox.addItem(ra) self.scgl.addWidget(self.find_annotations_reader_comboBox, row, 1, 1, 4) row += 1 # ~~~~~~~~ Create the Styles comboBox ~~~~~~~~ self.style_label = QLabel('Style') self.style_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter) self.scgl.addWidget(self.style_label, row, 0, 1, 1) self.find_annotations_color_comboBox = QComboBox() self.find_annotations_color_comboBox.setObjectName('find_annotations_color_comboBox') self.find_annotations_color_comboBox.setToolTip('Annotation style to search for') self.find_annotations_color_comboBox.addItem(self.GENERIC_STYLE) all_colors = COLOR_MAP.keys() all_colors.remove('Default') for color in sorted(all_colors): self.find_annotations_color_comboBox.addItem(color) self.scgl.addWidget(self.find_annotations_color_comboBox, row, 1, 1, 4) row += 1 # ~~~~~~~~ Create the Text LineEdit control ~~~~~~~~ self.text_label = QLabel('Text') self.text_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter) self.scgl.addWidget(self.text_label, row, 0, 1, 1) self.find_annotations_text_lineEdit = MyLineEdit() self.find_annotations_text_lineEdit.setObjectName('find_annotations_text_lineEdit') self.scgl.addWidget(self.find_annotations_text_lineEdit, row, 1, 1, 3) self.reset_text_tb = QToolButton() self.reset_text_tb.setObjectName('reset_text_tb') self.reset_text_tb.setToolTip('Clear search criteria') self.reset_text_tb.setIcon(QIcon(I('trash.png'))) self.reset_text_tb.clicked.connect(self.clear_text_field) self.scgl.addWidget(self.reset_text_tb, row, 4, 1, 1) row += 1 # ~~~~~~~~ Create the Note LineEdit control ~~~~~~~~ self.note_label = QLabel('Note') self.note_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter) self.scgl.addWidget(self.note_label, row, 0, 1, 1) self.find_annotations_note_lineEdit = MyLineEdit() self.find_annotations_note_lineEdit.setObjectName('find_annotations_note_lineEdit') self.scgl.addWidget(self.find_annotations_note_lineEdit, row, 1, 1, 3) self.reset_note_tb = QToolButton() self.reset_note_tb.setObjectName('reset_note_tb') self.reset_note_tb.setToolTip('Clear search criteria') self.reset_note_tb.setIcon(QIcon(I('trash.png'))) self.reset_note_tb.clicked.connect(self.clear_note_field) self.scgl.addWidget(self.reset_note_tb, row, 4, 1, 1) row += 1 # ~~~~~~~~ Create the Date range controls ~~~~~~~~ self.date_range_label = QLabel('Date range') self.date_range_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter) self.scgl.addWidget(self.date_range_label, row, 0, 1, 1) # Date 'From' self.find_annotations_date_from_dateEdit = MyDateEdit(self, datetime(1970,1,1)) self.find_annotations_date_from_dateEdit.setObjectName('find_annotations_date_from_dateEdit') #self.find_annotations_date_from_dateEdit.current_val = datetime(1970,1,1) self.find_annotations_date_from_dateEdit.clear_button.clicked.connect(self.find_annotations_date_from_dateEdit.reset_from_date) self.scgl.addWidget(self.find_annotations_date_from_dateEdit, row, 1, 1, 1) self.scgl.addWidget(self.find_annotations_date_from_dateEdit.clear_button, row, 2, 1, 1) # Date 'To' self.find_annotations_date_to_dateEdit = MyDateEdit(self, datetime.today()) self.find_annotations_date_to_dateEdit.setObjectName('find_annotations_date_to_dateEdit') #self.find_annotations_date_to_dateEdit.current_val = datetime.today() self.find_annotations_date_to_dateEdit.clear_button.clicked.connect(self.find_annotations_date_to_dateEdit.reset_to_date) self.scgl.addWidget(self.find_annotations_date_to_dateEdit, row, 3, 1, 1) self.scgl.addWidget(self.find_annotations_date_to_dateEdit.clear_button, row, 4, 1, 1) row += 1 # ~~~~~~~~ Create a horizontal line ~~~~~~~~ self.hl = QFrame(self) self.hl.setGeometry(QRect(0, 0, 1, 3)) self.hl.setFrameShape(QFrame.HLine) self.hl.setFrameShadow(QFrame.Raised) self.scgl.addWidget(self.hl, row, 0, 1, 5) row += 1 # ~~~~~~~~ Create the results label field ~~~~~~~~ self.result_label = QLabel('<p style="color:red">scanning…</p>') self.result_label.setAlignment(Qt.AlignCenter) self.result_label.setWordWrap(False) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Maximum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.result_label.sizePolicy().hasHeightForWidth()) self.result_label.setSizePolicy(sizePolicy) self.result_label.setMinimumSize(QtCore.QSize(250, 0)) self.scgl.addWidget(self.result_label, row, 0, 1, 5) row += 1 # ~~~~~~~~ Create the ButtonBox ~~~~~~~~ self.dialogButtonBox = QDialogButtonBox(self) self.dialogButtonBox.setOrientation(Qt.Horizontal) if False: self.update_button = QPushButton('Update results') self.update_button.setDefault(True) self.update_button.setVisible(False) self.dialogButtonBox.addButton(self.update_button, QDialogButtonBox.ActionRole) self.cancel_button = self.dialogButtonBox.addButton(self.dialogButtonBox.Cancel) self.find_button = self.dialogButtonBox.addButton(self.dialogButtonBox.Ok) self.find_button.setText('Find Matching Books') self.l.addWidget(self.dialogButtonBox) self.dialogButtonBox.clicked.connect(self.find_annotations_dialog_clicked) # ~~~~~~~~ Add a spacer ~~~~~~~~ self.spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.l.addItem(self.spacerItem) # ~~~~~~~~ Restore previously saved settings ~~~~~~~~ self.restore_settings() # ~~~~~~~~ Declare sizing ~~~~~~~~ sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth()) self.setSizePolicy(sizePolicy) self.resize_dialog() # ~~~~~~~~ Connect all signals ~~~~~~~~ self.find_annotations_reader_comboBox.currentIndexChanged.connect(partial(self.update_results, 'reader')) self.find_annotations_color_comboBox.currentIndexChanged.connect(partial(self.update_results, 'color')) self.find_annotations_text_lineEdit.editingFinished.connect(partial(self.update_results, 'text')) self.find_annotations_note_lineEdit.editingFinished.connect(partial(self.update_results, 'note')) # self.connect(self.find_annotations_text_lineEdit, pyqtSignal("return_pressed"), self.return_pressed) self.find_annotations_text_lineEdit.return_pressed.connect(self.return_pressed) # self.connect(self.find_annotations_note_lineEdit, pyqtSignal("return_pressed"), self.return_pressed) self.find_annotations_note_lineEdit.return_pressed.connect(self.return_pressed) # Date range signals connected in inventory_available() # ~~~~~~~~ Allow dialog to render before doing inventory ~~~~~~~~ #field = self.prefs.get('cfg_annotations_destination_field', None) field = get_cc_mapping('annotations', 'field', None) self.annotated_books_scanner = InventoryAnnotatedBooks(self.opts.gui, field, get_date_range=True) self.annotated_books_scanner.signal.connect(self.inventory_available) QTimer.singleShot(1, self.start_inventory_scan) def clear_note_field(self): if str(self.find_annotations_note_lineEdit.text()) > '': self.find_annotations_note_lineEdit.setText('') self.update_results('clear_note_field') def clear_text_field(self): if str(self.find_annotations_text_lineEdit.text()) > '': self.find_annotations_text_lineEdit.setText('') self.update_results('clear_text_field') def find_annotations_dialog_clicked(self, button): if self.dialogButtonBox.buttonRole(button) == QDialogButtonBox.AcceptRole: self.save_settings() self.accept() elif self.dialogButtonBox.buttonRole(button) == QDialogButtonBox.RejectRole: self.close() def inventory_available(self): ''' Update the Date range widgets with the rounded oldest, newest dates Don't connect date signals until date range available ''' self._log_location() # Reset the date range based on available annotations oldest = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.oldest_annotation)) oldest_day = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.oldest_annotation).replace(hour=0, minute=0, second=0)) newest = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.newest_annotation)) newest_day = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.newest_annotation).replace(hour=23, minute=59, second=59)) # Set 'From' date limits to inventory values self.find_annotations_date_from_dateEdit.setMinimumDateTime(oldest_day) self.find_annotations_date_from_dateEdit.current_val = oldest self.find_annotations_date_from_dateEdit.setMaximumDateTime(newest_day) # Set 'To' date limits to inventory values self.find_annotations_date_to_dateEdit.setMinimumDateTime(oldest_day) self.find_annotations_date_to_dateEdit.current_val = newest_day self.find_annotations_date_to_dateEdit.setMaximumDateTime(newest_day) # Connect the signals for date range changes self.find_annotations_date_from_dateEdit.dateTimeChanged.connect(partial(self.update_results, 'from_date')) self.find_annotations_date_to_dateEdit.dateTimeChanged.connect(partial(self.update_results, 'to_date')) self.update_results('inventory_available') def restore_settings(self): self.blockSignals(True) ra = self.prefs.get('find_annotations_reader_comboBox', self.GENERIC_READER) ra_index = self.find_annotations_reader_comboBox.findText(ra) self.find_annotations_reader_comboBox.setCurrentIndex(ra_index) color = self.prefs.get('find_annotations_color_comboBox', self.GENERIC_STYLE) color_index = self.find_annotations_color_comboBox.findText(color) self.find_annotations_color_comboBox.setCurrentIndex(color_index) text = self.prefs.get('find_annotations_text_lineEdit', '') self.find_annotations_text_lineEdit.setText(text) note = self.prefs.get('find_annotations_note_lineEdit', '') self.find_annotations_note_lineEdit.setText(note) if False: from_date = self.prefs.get('find_annotations_date_from_dateEdit', datetime(1970,1,1)) self.find_annotations_date_from_dateEdit.current_val = from_date to_date = self.prefs.get('find_annotations_date_to_dateEdit', datetime.today()) self.find_annotations_date_to_dateEdit.current_val = to_date self.blockSignals(False) def return_pressed(self): self.update_results("return_pressed") def save_settings(self): ra = str(self.find_annotations_reader_comboBox.currentText()) self.prefs.set('find_annotations_reader_comboBox', ra) color = str(self.find_annotations_color_comboBox.currentText()) self.prefs.set('find_annotations_color_comboBox', color) text = str(self.find_annotations_text_lineEdit.text()) self.prefs.set('find_annotations_text_lineEdit', text) note = str(self.find_annotations_note_lineEdit.text()) self.prefs.set('find_annotations_note_lineEdit', note) if False: from_date = self.find_annotations_date_from_dateEdit.current_val self.prefs.set('find_annotations_date_from_dateEdit', from_date) to_date = self.find_annotations_date_to_dateEdit.current_val self.prefs.set('find_annotations_date_to_dateEdit', to_date) def start_inventory_scan(self): self._log_location() self.annotated_books_scanner.start() def update_results(self, trigger): #self._log_location(trigger) reader_to_match = str(self.find_annotations_reader_comboBox.currentText()) color_to_match = str(self.find_annotations_color_comboBox.currentText()) text_to_match = str(self.find_annotations_text_lineEdit.text()) note_to_match = str(self.find_annotations_note_lineEdit.text()) from_date = self.find_annotations_date_from_dateEdit.dateTime().toTime_t() to_date = self.find_annotations_date_to_dateEdit.dateTime().toTime_t() annotation_map = self.annotated_books_scanner.annotation_map #field = self.prefs.get("cfg_annotations_destination_field", None) field = get_cc_mapping('annotations', 'field', None) db = self.opts.gui.current_db matched_titles = [] self.matched_ids = set() for cid in annotation_map: mi = db.get_metadata(cid, index_is_id=True) soup = None if field == 'Comments': if mi.comments: soup = BeautifulSoup(mi.comments) else: if mi.get_user_metadata(field, False)['#value#'] is not None: soup = BeautifulSoup(mi.get_user_metadata(field, False)['#value#']) if soup: uas = soup.findAll('div', 'annotation') for ua in uas: # Are we already logged? if cid in self.matched_ids: continue # Check reader if reader_to_match != self.GENERIC_READER: this_reader = ua['reader'] if this_reader != reader_to_match: continue # Check color if color_to_match != self.GENERIC_STYLE: this_color = ua.find('table')['color'] if this_color != color_to_match: continue # Check date range, allow for mangled timestamp try: timestamp = float(ua.find('td', 'timestamp')['uts']) if timestamp < from_date or timestamp > to_date: continue except: continue highlight_text = '' try: pels = ua.findAll('p', 'highlight') for pel in pels: highlight_text += pel.string + '\n' except: pass if text_to_match > '': if not re.search(text_to_match, highlight_text, flags=re.IGNORECASE): continue note_text = '' try: nels = ua.findAll('p', 'note') for nel in nels: note_text += nel.string + '\n' except: pass if note_to_match > '': if not re.search(note_to_match, note_text, flags=re.IGNORECASE): continue # If we made it this far, add the id to matched_ids self.matched_ids.add(cid) matched_titles.append(mi.title) # Update the results box matched_titles.sort() if len(annotation_map): if len(matched_titles): first_match = ("<i>%s</i>" % matched_titles[0]) if len(matched_titles) == 1: results = first_match else: results = first_match + (" and %d more." % (len(matched_titles) - 1)) self.result_label.setText('<p style="color:blue">{0}</p>'.format(results)) else: self.result_label.setText('<p style="color:red">no matches</p>') else: self.result_label.setText('<p style="color:red">no annotated books in library</p>') self.resize_dialog()
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 ## sort by visible Column Name (vs #name) for key, column in sorted(custom_columns.items(), key=lambda x: x[1]['name']): if column['datatype'] in permitted_values: # logger.debug("\n============== %s ===========\n"%key) 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 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 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): # 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)
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 __init__(self, extra_customization_message, extra_customization_choices, device_settings): super(ExtraCustomization, self).__init__() debug_print( "ExtraCustomization.__init__ - extra_customization_message=", extra_customization_message) debug_print( "ExtraCustomization.__init__ - extra_customization_choices=", extra_customization_choices) debug_print( "ExtraCustomization.__init__ - device_settings.extra_customization=", device_settings.extra_customization) debug_print("ExtraCustomization.__init__ - device_settings=", device_settings) self.extra_customization_message = extra_customization_message self.l = QVBoxLayout(self) self.setLayout(self.l) options_group = QGroupBox(_("Extra driver customization options"), self) self.l.addWidget(options_group) self.extra_layout = QGridLayout() self.extra_layout.setObjectName("extra_layout") options_group.setLayout(self.extra_layout) if extra_customization_message: extra_customization_choices = extra_customization_choices or {} def parse_msg(m): msg, _, tt = m.partition(':::') if m else ('', '', '') return msg.strip(), textwrap.fill(tt.strip(), 100) if isinstance(extra_customization_message, list): self.opt_extra_customization = [] if len(extra_customization_message) > 6: row_func = lambda x, y: ((x / 2) * 2) + y col_func = lambda x: x % 2 else: row_func = lambda x, y: x * 2 + y col_func = lambda x: 0 for i, m in enumerate(extra_customization_message): label_text, tt = parse_msg(m) if not label_text: self.opt_extra_customization.append(None) continue if isinstance(device_settings.extra_customization[i], bool): self.opt_extra_customization.append( QCheckBox(label_text)) self.opt_extra_customization[-1].setToolTip(tt) self.opt_extra_customization[i].setChecked( bool(device_settings.extra_customization[i])) elif i in extra_customization_choices: cb = QComboBox(self) self.opt_extra_customization.append(cb) l = QLabel(label_text) l.setToolTip(tt), cb.setToolTip(tt), l.setBuddy( cb), cb.setToolTip(tt) for li in sorted(extra_customization_choices[i]): self.opt_extra_customization[i].addItem(li) cb.setCurrentIndex( max( 0, cb.findText( device_settings.extra_customization[i]))) else: self.opt_extra_customization.append(QLineEdit(self)) l = QLabel(label_text) l.setToolTip(tt) self.opt_extra_customization[i].setToolTip(tt) l.setBuddy(self.opt_extra_customization[i]) l.setWordWrap(True) self.opt_extra_customization[i].setText( device_settings.extra_customization[i]) self.opt_extra_customization[i].setCursorPosition(0) self.extra_layout.addWidget(l, row_func(i + 2, 0), col_func(i)) self.extra_layout.addWidget( self.opt_extra_customization[i], row_func(i + 2, 1), col_func(i)) spacerItem1 = QSpacerItem(10, 10, QSizePolicy.Minimum, QSizePolicy.Expanding) self.extra_layout.addItem(spacerItem1, row_func(i + 2 + 2, 1), 0, 1, 2) self.extra_layout.setRowStretch(row_func(i + 2 + 2, 1), 2) else: self.opt_extra_customization = QLineEdit() label_text, tt = parse_msg(extra_customization_message) l = QLabel(label_text) l.setToolTip(tt) l.setBuddy(self.opt_extra_customization) l.setWordWrap(True) if device_settings.extra_customization: self.opt_extra_customization.setText( device_settings.extra_customization) self.opt_extra_customization.setCursorPosition(0) self.opt_extra_customization.setCursorPosition(0) self.extra_layout.addWidget(l, 0, 0) self.extra_layout.addWidget(self.opt_extra_customization, 1, 0)
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): # 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.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) 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.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.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) def _ok_clicked(self): 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.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['use_html_file'] = True plugin_prefs.defaults['use_entire_book'] = True 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['use_html_file'] = self.file_source_button.isChecked() self.prefs['use_entire_book'] = self.book_source_button.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 RuleEditor(QDialog): # {{{ @property def doing_multiple(self): return hasattr(self, 'multiple_icon_cb') and self.multiple_icon_cb.isChecked() def __init__(self, fm, pref_name, parent=None): QDialog.__init__(self, parent) self.fm = fm if pref_name == 'column_color_rules': self.rule_kind = 'color' rule_text = _('column coloring') elif pref_name == 'column_icon_rules': self.rule_kind = 'icon' rule_text = _('column icon') elif pref_name == 'cover_grid_icon_rules': self.rule_kind = 'emblem' rule_text = _('Cover grid emblem') self.setWindowIcon(QIcon(I('format-fill-color.png'))) self.setWindowTitle(_('Create/edit a {0} rule').format(rule_text)) self.l = l = QGridLayout(self) self.setLayout(l) self.l1 = l1 = QLabel(_('Create a {0} rule by' ' filling in the boxes below').format(rule_text)) l.addWidget(l1, 0, 0, 1, 8) self.f1 = QFrame(self) self.f1.setFrameShape(QFrame.HLine) l.addWidget(self.f1, 1, 0, 1, 8) self.l2 = l2 = QLabel(_('Add the emblem:') if self.rule_kind == 'emblem' else _('Set the')) l.addWidget(l2, 2, 0) if self.rule_kind == 'color': l.addWidget(QLabel(_('color'))) elif self.rule_kind == 'icon': self.kind_box = QComboBox(self) for tt, t in icon_rule_kinds: self.kind_box.addItem(tt, t) l.addWidget(self.kind_box, 2, 1) self.kind_box.setToolTip(textwrap.fill(_( 'If you choose composed icons and multiple rules match, then all the' ' matching icons will be combined, otherwise the icon from the' ' first rule to match will be used.'))) else: pass self.l3 = l3 = QLabel(_('of the column:')) l.addWidget(l3, 2, 2) self.column_box = QComboBox(self) l.addWidget(self.column_box, 2, 3) self.l4 = l4 = QLabel(_('to')) l.addWidget(l4, 2, 4) if self.rule_kind == 'emblem': l3.setVisible(False), self.column_box.setVisible(False), l4.setVisible(False) def create_filename_box(): self.filename_box = f = QComboBox() self.filenamebox_view = v = QListView() v.setIconSize(QSize(32, 32)) self.filename_box.setView(v) self.orig_filenamebox_view = f.view() f.setMinimumContentsLength(20), f.setSizeAdjustPolicy(f.AdjustToMinimumContentsLengthWithIcon) self.populate_icon_filenames() if self.rule_kind == 'color': self.color_box = ColorButton(parent=self) self.color_label = QLabel('Sample text Sample text') self.color_label.setTextFormat(Qt.RichText) l.addWidget(self.color_box, 2, 5) l.addWidget(self.color_label, 2, 6) l.addItem(QSpacerItem(10, 10, QSizePolicy.Expanding), 2, 7) elif self.rule_kind == 'emblem': create_filename_box() self.update_filename_box() self.filename_button = QPushButton(QIcon(I('document_open.png')), _('&Add new image')) l.addWidget(self.filename_box) l.addWidget(self.filename_button, 2, 6) l.addWidget(QLabel(_('(Images should be square-ish)')), 2, 7) l.setColumnStretch(7, 10) else: create_filename_box() vb = QVBoxLayout() self.multiple_icon_cb = QCheckBox(_('Choose &more than one icon')) vb.addWidget(self.multiple_icon_cb) self.update_filename_box() self.multiple_icon_cb.clicked.connect(self.multiple_box_clicked) vb.addWidget(self.filename_box) l.addLayout(vb, 2, 5) self.filename_button = QPushButton(QIcon(I('document_open.png')), _('&Add icon')) l.addWidget(self.filename_button, 2, 6) l.addWidget(QLabel(_('Icons should be square or landscape')), 2, 7) l.setColumnStretch(7, 10) self.l5 = l5 = QLabel( _('Only if the following conditions are all satisfied:')) l.addWidget(l5, 3, 0, 1, 7) self.scroll_area = sa = QScrollArea(self) sa.setMinimumHeight(300) sa.setMinimumWidth(950) sa.setWidgetResizable(True) l.addWidget(sa, 4, 0, 1, 8) self.add_button = b = QPushButton(QIcon(I('plus.png')), _('Add &another condition')) l.addWidget(b, 5, 0, 1, 8) b.clicked.connect(self.add_blank_condition) self.l6 = l6 = QLabel(_('You can disable a condition by' ' blanking all of its boxes')) l.addWidget(l6, 6, 0, 1, 8) self.bb = bb = QDialogButtonBox( QDialogButtonBox.Ok|QDialogButtonBox.Cancel) bb.accepted.connect(self.accept) bb.rejected.connect(self.reject) l.addWidget(bb, 7, 0, 1, 8) if self.rule_kind != 'color': self.remove_button = b = bb.addButton(_('&Remove icon'), bb.ActionRole) b.setIcon(QIcon(I('minus.png'))) b.setMenu(QMenu()) b.setToolTip('<p>' + _('Remove a previously added icon. Note that doing so will cause rules that use it to stop working.')) self.update_remove_button() self.conditions_widget = QWidget(self) sa.setWidget(self.conditions_widget) self.conditions_widget.setLayout(QVBoxLayout()) self.conditions_widget.layout().setAlignment(Qt.AlignTop) self.conditions = [] if self.rule_kind == 'color': for b in (self.column_box, ): b.setSizeAdjustPolicy(b.AdjustToMinimumContentsLengthWithIcon) b.setMinimumContentsLength(15) for key in sorted(displayable_columns(fm), key=lambda(k): sort_key(fm[k]['name']) if k != color_row_key else 0): if key == color_row_key and self.rule_kind != 'color': continue name = all_columns_string if key == color_row_key else fm[key]['name'] if name: self.column_box.addItem(name, key) self.column_box.setCurrentIndex(0) if self.rule_kind == 'color': self.color_box.color = '#000' self.update_color_label() self.color_box.color_changed.connect(self.update_color_label) else: self.rule_icon_files = [] self.filename_button.clicked.connect(self.filename_button_clicked) self.resize(self.sizeHint()) def multiple_box_clicked(self): self.update_filename_box() self.update_icon_filenames_in_box() @property def icon_folder(self): return os.path.join(config_dir, 'cc_icons') def populate_icon_filenames(self): d = self.icon_folder self.icon_file_names = [] if os.path.exists(d): for icon_file in os.listdir(d): icon_file = lower(icon_file) if os.path.exists(os.path.join(d, icon_file)) and icon_file.endswith('.png'): self.icon_file_names.append(icon_file) self.icon_file_names.sort(key=sort_key) def update_filename_box(self): doing_multiple = self.doing_multiple model = QStandardItemModel() self.filename_box.setModel(model) self.icon_file_names.sort(key=sort_key) if doing_multiple: item = QStandardItem(_('Open to see checkboxes')) item.setIcon(QIcon(I('blank.png'))) else: item = QStandardItem('') item.setFlags(Qt.ItemFlag(0)) model.appendRow(item) for i,filename in enumerate(self.icon_file_names): item = QStandardItem(filename) if doing_multiple: item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) item.setData(Qt.Unchecked, Qt.CheckStateRole) else: item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) icon = QIcon(os.path.join(self.icon_folder, filename)) item.setIcon(icon) model.appendRow(item) def update_color_label(self): pal = QApplication.palette() bg1 = unicode(pal.color(pal.Base).name()) bg2 = unicode(pal.color(pal.AlternateBase).name()) c = self.color_box.color self.color_label.setText(''' <span style="color: {c}; background-color: {bg1}"> {st} </span> <span style="color: {c}; background-color: {bg2}"> {st} </span> '''.format(c=c, bg1=bg1, bg2=bg2, st=_('Sample text'))) def sanitize_icon_file_name(self, icon_path): n = lower(sanitize_file_name_unicode( os.path.splitext( os.path.basename(icon_path))[0]+'.png')) return n.replace("'", '_') def filename_button_clicked(self): try: path = choose_files(self, 'choose_category_icon', _('Select Icon'), filters=[ (_('Images'), ['png', 'gif', 'jpg', 'jpeg'])], all_files=False, select_only_single_file=True) if path: icon_path = path[0] icon_name = self.sanitize_icon_file_name(icon_path) if icon_name not in self.icon_file_names: self.icon_file_names.append(icon_name) try: p = QIcon(icon_path).pixmap(QSize(128, 128)) d = self.icon_folder if not os.path.exists(os.path.join(d, icon_name)): if not os.path.exists(d): os.makedirs(d) with open(os.path.join(d, icon_name), 'wb') as f: f.write(pixmap_to_data(p, format='PNG')) except: import traceback traceback.print_exc() self.update_filename_box() self.update_remove_button() if self.doing_multiple: if icon_name not in self.rule_icon_files: self.rule_icon_files.append(icon_name) self.update_icon_filenames_in_box() else: self.filename_box.setCurrentIndex(self.filename_box.findText(icon_name)) self.filename_box.adjustSize() except: import traceback traceback.print_exc() return def get_filenames_from_box(self): if self.doing_multiple: model = self.filename_box.model() fnames = [] for i in range(1, model.rowCount()): item = model.item(i, 0) if item.checkState() == Qt.Checked: fnames.append(lower(unicode(item.text()))) fname = ' : '.join(fnames) else: fname = lower(unicode(self.filename_box.currentText())) return fname def update_icon_filenames_in_box(self): if self.rule_icon_files: if not self.doing_multiple: idx = self.filename_box.findText(self.rule_icon_files[0]) if idx >= 0: self.filename_box.setCurrentIndex(idx) else: self.filename_box.setCurrentIndex(0) else: model = self.filename_box.model() for icon in self.rule_icon_files: idx = self.filename_box.findText(icon) if idx >= 0: item = model.item(idx) item.setCheckState(Qt.Checked) def update_remove_button(self): m = self.remove_button.menu() m.clear() for name in self.icon_file_names: m.addAction(QIcon(os.path.join(self.icon_folder, name)), name).triggered.connect(partial( self.remove_image, name)) def remove_image(self, name): try: os.remove(os.path.join(self.icon_folder, name)) except EnvironmentError: pass else: self.populate_icon_filenames() self.update_remove_button() self.update_filename_box() self.update_icon_filenames_in_box() def add_blank_condition(self): c = ConditionEditor(self.fm, parent=self.conditions_widget) self.conditions.append(c) self.conditions_widget.layout().addWidget(c) def apply_rule(self, kind, col, rule): if kind == 'color': if rule.color: self.color_box.color = rule.color else: if self.rule_kind == 'icon': for i, tup in enumerate(icon_rule_kinds): if kind == tup[1]: self.kind_box.setCurrentIndex(i) break self.rule_icon_files = [ic.strip() for ic in rule.color.split(':')] if len(self.rule_icon_files) > 1: self.multiple_icon_cb.setChecked(True) self.update_filename_box() self.update_icon_filenames_in_box() for i in range(self.column_box.count()): c = unicode(self.column_box.itemData(i) or '') if col == c: self.column_box.setCurrentIndex(i) break for c in rule.conditions: ce = ConditionEditor(self.fm, parent=self.conditions_widget) self.conditions.append(ce) self.conditions_widget.layout().addWidget(ce) try: ce.condition = c except: import traceback traceback.print_exc() def accept(self): if self.rule_kind != 'color': fname = self.get_filenames_from_box() if not fname: error_dialog(self, _('No icon selected'), _('You must choose an icon for this rule'), show=True) return if self.validate(): QDialog.accept(self) def validate(self): r = Rule(self.fm) for c in self.conditions: condition = c.condition if condition is not None: try: r.add_condition(*condition) except Exception as e: import traceback error_dialog(self, _('Invalid condition'), _('One of the conditions for this rule is' ' invalid: <b>%s</b>')%e, det_msg=traceback.format_exc(), show=True) return False if len(r.conditions) < 1: error_dialog(self, _('No conditions'), _('You must specify at least one non-empty condition' ' for this rule'), show=True) return False return True @property def rule(self): r = Rule(self.fm) if self.rule_kind != 'color': r.color = self.get_filenames_from_box() else: r.color = self.color_box.color idx = self.column_box.currentIndex() col = unicode(self.column_box.itemData(idx) or '') for c in self.conditions: condition = c.condition if condition is not None: r.add_condition(*condition) if self.rule_kind == 'icon': kind = unicode(self.kind_box.itemData( self.kind_box.currentIndex()) or '') else: kind = self.rule_kind return kind, col, r
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 CustomColumnsTab(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( _("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 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 RuleEditor(QDialog): # {{{ @property def doing_multiple(self): return hasattr(self, 'multiple_icon_cb') and self.multiple_icon_cb.isChecked() def __init__(self, fm, pref_name, parent=None): QDialog.__init__(self, parent) self.fm = fm if pref_name == 'column_color_rules': self.rule_kind = 'color' rule_text = _('column coloring') elif pref_name == 'column_icon_rules': self.rule_kind = 'icon' rule_text = _('column icon') elif pref_name == 'cover_grid_icon_rules': self.rule_kind = 'emblem' rule_text = _('Cover grid emblem') self.setWindowIcon(QIcon(I('format-fill-color.png'))) self.setWindowTitle(_('Create/edit a {0} rule').format(rule_text)) self.l = l = QGridLayout(self) self.setLayout(l) self.l1 = l1 = QLabel(_('Create a {0} rule by' ' filling in the boxes below').format(rule_text)) l.addWidget(l1, 0, 0, 1, 8) self.f1 = QFrame(self) self.f1.setFrameShape(QFrame.HLine) l.addWidget(self.f1, 1, 0, 1, 8) self.l2 = l2 = QLabel(_('Add the emblem:') if self.rule_kind == 'emblem' else _('Set the')) l.addWidget(l2, 2, 0) if self.rule_kind == 'color': l.addWidget(QLabel(_('color'))) elif self.rule_kind == 'icon': self.kind_box = QComboBox(self) for tt, t in icon_rule_kinds: self.kind_box.addItem(tt, t) l.addWidget(self.kind_box, 2, 1) self.kind_box.setToolTip(textwrap.fill(_( 'If you choose composed icons and multiple rules match, then all the' ' matching icons will be combined, otherwise the icon from the' ' first rule to match will be used.'))) else: pass self.l3 = l3 = QLabel(_('of the column:')) l.addWidget(l3, 2, 2) self.column_box = QComboBox(self) l.addWidget(self.column_box, 2, 3) self.l4 = l4 = QLabel(_('to')) l.addWidget(l4, 2, 4) if self.rule_kind == 'emblem': l3.setVisible(False), self.column_box.setVisible(False), l4.setVisible(False) def create_filename_box(): self.filename_box = f = QComboBox() self.filenamebox_view = v = QListView() v.setIconSize(QSize(32, 32)) self.filename_box.setView(v) self.orig_filenamebox_view = f.view() f.setMinimumContentsLength(20), f.setSizeAdjustPolicy(f.AdjustToMinimumContentsLengthWithIcon) self.populate_icon_filenames() if self.rule_kind == 'color': self.color_box = ColorButton(parent=self) self.color_label = QLabel('Sample text Sample text') self.color_label.setTextFormat(Qt.RichText) l.addWidget(self.color_box, 2, 5) l.addWidget(self.color_label, 2, 6) l.addItem(QSpacerItem(10, 10, QSizePolicy.Expanding), 2, 7) elif self.rule_kind == 'emblem': create_filename_box() self.update_filename_box() self.filename_button = QPushButton(QIcon(I('document_open.png')), _('&Add new image')) l.addWidget(self.filename_box) l.addWidget(self.filename_button, 2, 6) l.addWidget(QLabel(_('(Images should be square-ish)')), 2, 7) l.setColumnStretch(7, 10) else: create_filename_box() vb = QVBoxLayout() self.multiple_icon_cb = QCheckBox(_('Choose &more than one icon')) vb.addWidget(self.multiple_icon_cb) self.update_filename_box() self.multiple_icon_cb.clicked.connect(self.multiple_box_clicked) vb.addWidget(self.filename_box) l.addLayout(vb, 2, 5) self.filename_button = QPushButton(QIcon(I('document_open.png')), _('&Add icon')) l.addWidget(self.filename_button, 2, 6) l.addWidget(QLabel(_('Icons should be square or landscape')), 2, 7) l.setColumnStretch(7, 10) self.l5 = l5 = QLabel( _('Only if the following conditions are all satisfied:')) l.addWidget(l5, 3, 0, 1, 7) self.scroll_area = sa = QScrollArea(self) sa.setMinimumHeight(300) sa.setMinimumWidth(950) sa.setWidgetResizable(True) l.addWidget(sa, 4, 0, 1, 8) self.add_button = b = QPushButton(QIcon(I('plus.png')), _('Add &another condition')) l.addWidget(b, 5, 0, 1, 8) b.clicked.connect(self.add_blank_condition) self.l6 = l6 = QLabel(_('You can disable a condition by' ' blanking all of its boxes')) l.addWidget(l6, 6, 0, 1, 8) self.bb = bb = QDialogButtonBox( QDialogButtonBox.Ok|QDialogButtonBox.Cancel) bb.accepted.connect(self.accept) bb.rejected.connect(self.reject) l.addWidget(bb, 7, 0, 1, 8) if self.rule_kind != 'color': self.remove_button = b = bb.addButton(_('&Remove icon'), bb.ActionRole) b.setIcon(QIcon(I('minus.png'))) b.setMenu(QMenu()) b.setToolTip('<p>' + _('Remove a previously added icon. Note that doing so will cause rules that use it to stop working.')) self.update_remove_button() self.conditions_widget = QWidget(self) sa.setWidget(self.conditions_widget) self.conditions_widget.setLayout(QVBoxLayout()) self.conditions_widget.layout().setAlignment(Qt.AlignTop) self.conditions = [] if self.rule_kind == 'color': for b in (self.column_box, ): b.setSizeAdjustPolicy(b.AdjustToMinimumContentsLengthWithIcon) b.setMinimumContentsLength(15) for key in sorted(displayable_columns(fm), key=lambda k: sort_key(fm[k]['name']) if k != color_row_key else 0): if key == color_row_key and self.rule_kind != 'color': continue name = all_columns_string if key == color_row_key else fm[key]['name'] if name: self.column_box.addItem(name, key) self.column_box.setCurrentIndex(0) if self.rule_kind == 'color': self.color_box.color = '#000' self.update_color_label() self.color_box.color_changed.connect(self.update_color_label) else: self.rule_icon_files = [] self.filename_button.clicked.connect(self.filename_button_clicked) self.resize(self.sizeHint()) def multiple_box_clicked(self): self.update_filename_box() self.update_icon_filenames_in_box() @property def icon_folder(self): return os.path.join(config_dir, 'cc_icons') def populate_icon_filenames(self): d = self.icon_folder self.icon_file_names = [] if os.path.exists(d): for icon_file in os.listdir(d): icon_file = lower(icon_file) if os.path.exists(os.path.join(d, icon_file)) and icon_file.endswith('.png'): self.icon_file_names.append(icon_file) self.icon_file_names.sort(key=sort_key) def update_filename_box(self): doing_multiple = self.doing_multiple model = QStandardItemModel() self.filename_box.setModel(model) self.icon_file_names.sort(key=sort_key) if doing_multiple: item = QStandardItem(_('Open to see checkboxes')) item.setIcon(QIcon(I('blank.png'))) else: item = QStandardItem('') item.setFlags(Qt.ItemFlag(0)) model.appendRow(item) for i,filename in enumerate(self.icon_file_names): item = QStandardItem(filename) if doing_multiple: item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) item.setData(Qt.Unchecked, Qt.CheckStateRole) else: item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) icon = QIcon(os.path.join(self.icon_folder, filename)) item.setIcon(icon) model.appendRow(item) def update_color_label(self): pal = QApplication.palette() bg1 = unicode_type(pal.color(pal.Base).name()) bg2 = unicode_type(pal.color(pal.AlternateBase).name()) c = self.color_box.color self.color_label.setText(''' <span style="color: {c}; background-color: {bg1}"> {st} </span> <span style="color: {c}; background-color: {bg2}"> {st} </span> '''.format(c=c, bg1=bg1, bg2=bg2, st=_('Sample text'))) def sanitize_icon_file_name(self, icon_path): n = lower(sanitize_file_name_unicode( os.path.splitext( os.path.basename(icon_path))[0]+'.png')) return n.replace("'", '_') def filename_button_clicked(self): try: path = choose_files(self, 'choose_category_icon', _('Select Icon'), filters=[ (_('Images'), ['png', 'gif', 'jpg', 'jpeg'])], all_files=False, select_only_single_file=True) if path: icon_path = path[0] icon_name = self.sanitize_icon_file_name(icon_path) if icon_name not in self.icon_file_names: self.icon_file_names.append(icon_name) try: p = QIcon(icon_path).pixmap(QSize(128, 128)) d = self.icon_folder if not os.path.exists(os.path.join(d, icon_name)): if not os.path.exists(d): os.makedirs(d) with open(os.path.join(d, icon_name), 'wb') as f: f.write(pixmap_to_data(p, format='PNG')) except: import traceback traceback.print_exc() self.update_filename_box() self.update_remove_button() if self.doing_multiple: if icon_name not in self.rule_icon_files: self.rule_icon_files.append(icon_name) self.update_icon_filenames_in_box() else: self.filename_box.setCurrentIndex(self.filename_box.findText(icon_name)) self.filename_box.adjustSize() except: import traceback traceback.print_exc() return def get_filenames_from_box(self): if self.doing_multiple: model = self.filename_box.model() fnames = [] for i in range(1, model.rowCount()): item = model.item(i, 0) if item.checkState() == Qt.Checked: fnames.append(lower(unicode_type(item.text()))) fname = ' : '.join(fnames) else: fname = lower(unicode_type(self.filename_box.currentText())) return fname def update_icon_filenames_in_box(self): if self.rule_icon_files: if not self.doing_multiple: idx = self.filename_box.findText(self.rule_icon_files[0]) if idx >= 0: self.filename_box.setCurrentIndex(idx) else: self.filename_box.setCurrentIndex(0) else: model = self.filename_box.model() for icon in self.rule_icon_files: idx = self.filename_box.findText(icon) if idx >= 0: item = model.item(idx) item.setCheckState(Qt.Checked) def update_remove_button(self): m = self.remove_button.menu() m.clear() for name in self.icon_file_names: ac = m.addAction(QIcon(os.path.join(self.icon_folder, name)), name) connect_lambda(ac.triggered, self, lambda self: self.remove_image(self.sender().text())) def remove_image(self, name): try: os.remove(os.path.join(self.icon_folder, name)) except EnvironmentError: pass else: self.populate_icon_filenames() self.update_remove_button() self.update_filename_box() self.update_icon_filenames_in_box() def add_blank_condition(self): c = ConditionEditor(self.fm, parent=self.conditions_widget) self.conditions.append(c) self.conditions_widget.layout().addWidget(c) def apply_rule(self, kind, col, rule): if kind == 'color': if rule.color: self.color_box.color = rule.color else: if self.rule_kind == 'icon': for i, tup in enumerate(icon_rule_kinds): if kind == tup[1]: self.kind_box.setCurrentIndex(i) break self.rule_icon_files = [ic.strip() for ic in rule.color.split(':')] if len(self.rule_icon_files) > 1: self.multiple_icon_cb.setChecked(True) self.update_filename_box() self.update_icon_filenames_in_box() for i in range(self.column_box.count()): c = unicode_type(self.column_box.itemData(i) or '') if col == c: self.column_box.setCurrentIndex(i) break for c in rule.conditions: ce = ConditionEditor(self.fm, parent=self.conditions_widget) self.conditions.append(ce) self.conditions_widget.layout().addWidget(ce) try: ce.condition = c except: import traceback traceback.print_exc() def accept(self): if self.rule_kind != 'color': fname = self.get_filenames_from_box() if not fname: error_dialog(self, _('No icon selected'), _('You must choose an icon for this rule'), show=True) return if self.validate(): QDialog.accept(self) def validate(self): r = Rule(self.fm) for c in self.conditions: condition = c.condition if condition is not None: try: r.add_condition(*condition) except Exception as e: import traceback error_dialog(self, _('Invalid condition'), _('One of the conditions for this rule is' ' invalid: <b>%s</b>')%e, det_msg=traceback.format_exc(), show=True) return False if len(r.conditions) < 1: error_dialog(self, _('No conditions'), _('You must specify at least one non-empty condition' ' for this rule'), show=True) return False return True @property def rule(self): r = Rule(self.fm) if self.rule_kind != 'color': r.color = self.get_filenames_from_box() else: r.color = self.color_box.color idx = self.column_box.currentIndex() col = unicode_type(self.column_box.itemData(idx) or '') for c in self.conditions: condition = c.condition if condition is not None: r.add_condition(*condition) if self.rule_kind == 'icon': kind = unicode_type(self.kind_box.itemData( self.kind_box.currentIndex()) or '') else: kind = self.rule_kind return kind, col, r
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()