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
Пример #2
0
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
Пример #3
0
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)
Пример #4
0
    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)
Пример #5
0
    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)
Пример #6
0
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())
Пример #7
0
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())
Пример #8
0
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)
Пример #9
0
    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)
Пример #10
0
class ConfigWidget(QWidget, Logger):
    # Manually managed controls when saving/restoring
    EXCLUDED_CONTROLS = [
        'cfg_annotations_destination_comboBox'
        ]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        current_row += 1

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

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

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

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

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

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

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

        self.resize(self.sizeHint())

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    def configure_appearance(self):
        '''
        '''
        from calibre_plugins.annotations.appearance import default_elements
        from calibre_plugins.annotations.appearance import default_timestamp
        appearance_settings = {
                                'appearance_css': default_elements,
                                'appearance_hr_checkbox': False,
                                'appearance_timestamp_format': default_timestamp
                              }

        # Save, hash the original settings
        original_settings = {}
        osh = hashlib.md5()
        for setting in appearance_settings:
            original_settings[setting] = plugin_prefs.get(setting, appearance_settings[setting])
            osh.update(repr(plugin_prefs.get(setting, appearance_settings[setting])))

        # Display the appearance dialog
        aa = AnnotationsAppearance(self, get_icon('images/annotations.png'), plugin_prefs)
        cancelled = False
        if aa.exec_():
            # appearance_hr_checkbox and appearance_timestamp_format changed live to prefs during previews
            plugin_prefs.set('appearance_css', aa.elements_table.get_data())
            # Generate a new hash
            nsh = hashlib.md5()
            for setting in appearance_settings:
                nsh.update(repr(plugin_prefs.get(setting, appearance_settings[setting])))
        else:
            for setting in appearance_settings:
                plugin_prefs.set(setting, original_settings[setting])
            nsh = osh

        # If there were changes, and there are existing annotations, offer to re-render
        field = get_cc_mapping('annotations', 'field', None)
        if osh.digest() != nsh.digest() and existing_annotations(self.opts.parent,field):
            title = _('Update annotations?')
            msg = _('<p>Update existing annotations to new appearance settings?</p>')
            d = MessageBox(MessageBox.QUESTION,
                           title, msg,
                           show_copy_button=False)
            self._log_location("QUESTION: %s" % msg)
            if d.exec_():
                self._log_location("Updating existing annotations to modified appearance")
                if self.annotated_books_scanner.isRunning():
                    self.annotated_books_scanner.wait()
                move_annotations(self, self.annotated_books_scanner.annotation_map,
                    field, field, window_title=_("Updating appearance"))

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

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

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

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

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

            # Process the changed destination
            self.annotations_destination_changed(destination)

            cb.blockSignals(False)


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

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

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

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

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

                    _update_combo_box('cfg_annotations_destination_comboBox', destination, previous)

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

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

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

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

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

    def news_clippings_destination_changed(self):
        qs_new_destination_name = self.cfg_news_clippings_lineEdit.text()
        if not re.match(r'^\S+[A-Za-z0-9 ]+$', qs_new_destination_name):
            # Complain about News clippings title
            title = _('Invalid title for News clippings')
            msg = _("Supply a valid title for News clippings, for example 'My News Clippings'.")
            d = MessageBox(MessageBox.WARNING,
                           title, msg,
                           show_copy_button=False)
            self._log_location("WARNING: %s" % msg)
            d.exec_()

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

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

    def restart_required(self, state):
        title = _('Restart required')
        msg = _('To apply changes, restart calibre.')
        d = MessageBox(MessageBox.WARNING,
                       title, msg,
                       show_copy_button=False)
        self._log_location("WARNING: %s" % (msg))
        d.exec_()

    def save_settings(self):
        save_state(self)

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

    def start_inventory(self):
        self.annotated_books_scanner.start()
Пример #11
0
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()
Пример #13
0
    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)
Пример #14
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
Пример #15
0
 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
Пример #16
0
class FindAnnotationsDialog(SizePersistedDialog, Logger):

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        # Date range signals connected in inventory_available()

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

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

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

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

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

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

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

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

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

        self.update_results('inventory_available')

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

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

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

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

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

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

        self.blockSignals(False)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        self.resize_dialog()
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()
Пример #18
0
    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)
Пример #19
0
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()
Пример #20
0
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()
Пример #21
0
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)
Пример #22
0
    def __init__(self, parent, rc, imgman):
        """An ImageControlDialog is initialized with a parent widget, a RenderControl object,
        and an ImageManager object"""
        QDialog.__init__(self, parent)
        image = rc.image
        self.setWindowTitle("%s: Colour Controls" % image.name)
        self.setWindowIcon(pixmaps.colours.icon())
        self.setModal(False)
        self.image = image
        self._rc = rc
        self._imgman = imgman
        self._currier = PersistentCurrier()

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        # update widgets
        self._setupHistogramPlot()
        self._updateDataSubset(*self._rc.currentSubset())
        self._updateColorMap(image.colorMap())
        self._updateIntensityMap(rc.currentIntensityMap(), rc.currentIntensityMapNumber())
        self._updateDisplayRange(*self._rc.displayRange())
Пример #23
0
    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)
Пример #24
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()
Пример #25
0
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}">&nbsp;{st}&nbsp;</span>
            <span style="color: {c}; background-color: {bg2}">&nbsp;{st}&nbsp;</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
Пример #26
0
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)
Пример #27
0
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)
Пример #28
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):
        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(
            )
Пример #29
0
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}">&nbsp;{st}&nbsp;</span>
            <span style="color: {c}; background-color: {bg2}">&nbsp;{st}&nbsp;</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
Пример #30
0
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()