Beispiel #1
0
class PlayerInfoField(QWidget): #Widget for inputting player info. 
    names = ['Alex', 'Clifford', 'Tyrone', 'Ava', 'Ralph', 'Emily', 'Falcon', 'Giselle', 'Jaeger', 'Sally', 'Quentin', 'Lara']
    
    def __init__(self, parent, index):
        super(PlayerInfoField, self).__init__(parent)
        self.index = index
        
        self.layout = QHBoxLayout()
        
        self.auto_button = Button(self, 'Auto')
        self.auto_button.setFixedWidth(60)
        self.auto_button.clicked.connect(self.generate_name)
        self.layout.addWidget(self.auto_button)
        
        self.name_field = QLineEdit()
        self.name_field.setPalette(QPalette(Qt.white))
        self.name_field.setPlaceholderText('Name')
        self.name_field.setClearButtonEnabled(True)
        self.name_field.setFixedWidth(250)
        self.layout.addWidget(self.name_field)
        
        self.AItoggle = QCheckBox()
        self.AItoggle.setText('Computer')
        self.AItoggle.setFixedWidth(100)
        self.layout.addWidget(self.AItoggle)
        self.AItoggle.stateChanged.connect(self.AIToggled)
        
        self.AIdifficulty = QComboBox()
        self.AIdifficulty.setPalette(QPalette(Qt.white))
        self.AIdifficulty.setFixedWidth(100)
        self.AIdifficulty.addItems(['Braindead', 'Easy', 'Normal', 'Hard', 'HAL-9000'])
        self.AIdifficulty.setCurrentIndex(2)
        self.AIdifficulty.setDisabled(True)
        self.layout.addWidget(self.AIdifficulty)
        
        self.spacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
        self.layout.addItem(self.spacer)
        self.layout.setContentsMargins(0, 0, 0, 0)
        
        self.setLayout(self.layout)
        
        
    def generate_name(self):
        self.name_field.setText(PlayerInfoField.names[self.index])
     
    def AIToggled(self):
        if self.AItoggle.checkState() == Qt.Checked:
            self.AIdifficulty.setEnabled(True)
            
        else:
            self.AIdifficulty.setDisabled(True)
Beispiel #2
0
class ConfigWidget(DefaultConfigWidget):
    def __init__(self, plugin):
        DefaultConfigWidget.__init__(self, plugin)
        c = plugin_prefs[STORE_NAME]

        other_group_box = QGroupBox('Other options', self)
        self.l.addWidget(other_group_box, self.l.rowCount(), 0, 1, 2)
        other_group_box_layout = QGridLayout()
        other_group_box.setLayout(other_group_box_layout)

        # Maximum # of title/author searches to review.
        max_label = QLabel(
            'Maximum title/author search matches to evaluate (1 = fastest):',
            self)
        max_label.setToolTip(
            'ISFDB doesn\'t always have links to large covers for every ISBN\n'
            'of the same book. Increasing this value will take effect when doing\n'
            'title/author searches to consider more ISBN editions.\n\n'
            'This will increase the potential likelihood of getting a larger cover,\n'
            'though does not guarantee it.')
        other_group_box_layout.addWidget(max_label, 0, 0, 1, 1)
        self.max_downloads_spin = QtGui.QSpinBox(self)
        self.max_downloads_spin.setMinimum(1)
        self.max_downloads_spin.setMaximum(5)
        self.max_downloads_spin.setProperty(
            'value',
            c.get(KEY_MAX_DOWNLOADS, DEFAULT_STORE_VALUES[KEY_MAX_DOWNLOADS]))
        other_group_box_layout.addWidget(self.max_downloads_spin, 0, 1, 1, 1)
        other_group_box_layout.setColumnStretch(2, 1)

        # Contents field, if possible.
        self.contents_checkbox = QCheckBox(
            'Append Contents if available to comments', self)
        self.contents_checkbox.setToolTip(
            'Choosing this option will write the Contents section to the comments\n'
            'field, if such a section exists.')
        self.contents_checkbox.setChecked(
            c.get(KEY_APPEND_CONTENTS,
                  DEFAULT_STORE_VALUES[KEY_APPEND_CONTENTS]))
        other_group_box_layout.addWidget(self.contents_checkbox, 2, 0, 1, 3)

    def commit(self):
        DefaultConfigWidget.commit(self)
        new_prefs = {}
        new_prefs[KEY_MAX_DOWNLOADS] = int(
            unicode(self.max_downloads_spin.value()))
        new_prefs[KEY_APPEND_CONTENTS] = self.contents_checkbox.checkState(
        ) == Qt.Checked
        plugin_prefs[STORE_NAME] = new_prefs
class ConfigWidget(QWidget):
    def __init__(self, plugin_action):
        QWidget.__init__(self)
        self.plugin_action = plugin_action
        self.gui = plugin_action.gui
        self.library = get_library_config(self.gui.current_db)
        self.views = self.library[KEY_VIEWS]
        self.all_columns = self.get_current_columns()
        self.view_name = None

        self.has_pin_view = hasattr(self.gui.library_view, 'pin_view')

        toplayout = QVBoxLayout(self)
        self.setLayout(toplayout)
        ## wrap config in a scrollable area for smaller displays.
        scrollable = QScrollArea()
        scrollcontent = QWidget()
        scrollable.setWidget(scrollcontent)
        scrollable.setWidgetResizable(True)
        toplayout.addWidget(scrollable)

        layout = QVBoxLayout()
        scrollcontent.setLayout(layout)

        select_view_layout = QHBoxLayout()
        layout.addLayout(select_view_layout)
        select_view_label = QLabel('Select view to customize:', self)
        select_view_layout.addWidget(select_view_label)
        self.select_view_combo = ViewComboBox(self, self.views)
        self.select_view_combo.setMinimumSize(150, 20)
        select_view_layout.addWidget(self.select_view_combo)
        self.add_view_button = QtGui.QToolButton(self)
        self.add_view_button.setToolTip('Add view')
        self.add_view_button.setIcon(QIcon(I('plus.png')))
        self.add_view_button.clicked.connect(self.add_view)
        select_view_layout.addWidget(self.add_view_button)
        self.delete_view_button = QtGui.QToolButton(self)
        self.delete_view_button.setToolTip('Delete view')
        self.delete_view_button.setIcon(QIcon(I('minus.png')))
        self.delete_view_button.clicked.connect(self.delete_view)
        select_view_layout.addWidget(self.delete_view_button)
        self.rename_view_button = QtGui.QToolButton(self)
        self.rename_view_button.setToolTip('Rename view')
        self.rename_view_button.setIcon(QIcon(I('edit-undo.png')))
        self.rename_view_button.clicked.connect(self.rename_view)
        select_view_layout.addWidget(self.rename_view_button)
        select_view_layout.insertStretch(-1)

        view_group_box = QGroupBox('Column Options', self)
        layout.addWidget(view_group_box)
        view_group_box_layout = QVBoxLayout()
        view_group_box.setLayout(view_group_box_layout)

        customise_layout = QGridLayout()
        view_group_box_layout.addLayout(customise_layout, 1)

        if self.has_pin_view:
            columns_label = 'Columns in Default (Left) pane'
        else:
            columns_label = 'Columns in view'
        self.columns_label = QLabel(columns_label, self)
        self.columns_list = ColumnListWidget(self, self.gui)
        self.move_column_up_button = QtGui.QToolButton(self)
        self.move_column_up_button.setToolTip('Move column up')
        self.move_column_up_button.setIcon(QIcon(I('arrow-up.png')))
        self.move_column_down_button = QtGui.QToolButton(self)
        self.move_column_down_button.setToolTip('Move column down')
        self.move_column_down_button.setIcon(QIcon(I('arrow-down.png')))
        self.move_column_up_button.clicked.connect(
            self.columns_list.move_column_up)
        self.move_column_down_button.clicked.connect(
            self.columns_list.move_column_down)

        if self.has_pin_view:

            self.apply_pin_columns_checkbox = QCheckBox(
                'Columns in Split (Right) Pane', self)
            self.apply_pin_columns_checkbox.setToolTip(
                'Split Book List will <i>only</i> be shown if this is checked.  This will be checked if you save a Split View.'
            )
            self.pin_columns_list = ColumnListWidget(self, self.gui)
            self.move_pin_column_up_button = QtGui.QToolButton(self)
            self.move_pin_column_up_button.setToolTip('Move column up')
            self.move_pin_column_up_button.setIcon(QIcon(I('arrow-up.png')))
            self.move_pin_column_down_button = QtGui.QToolButton(self)
            self.move_pin_column_down_button.setToolTip('Move column down')
            self.move_pin_column_down_button.setIcon(QIcon(
                I('arrow-down.png')))
            self.move_pin_column_up_button.clicked.connect(
                self.pin_columns_list.move_column_up)
            self.move_pin_column_down_button.clicked.connect(
                self.pin_columns_list.move_column_down)

            def group_abled(elems, cb):
                for el in elems:
                    el.setEnabled(cb.isChecked())

            pin_abled = partial(group_abled, [
                self.pin_columns_list, self.move_pin_column_up_button,
                self.move_pin_column_down_button
            ], self.apply_pin_columns_checkbox)
            pin_abled()
            self.apply_pin_columns_checkbox.stateChanged.connect(pin_abled)

        self.sort_label = QLabel('Sort order', self)
        self.sort_list = SortColumnListWidget(self, self.gui)
        self.move_sort_up_button = QtGui.QToolButton(self)
        self.move_sort_up_button.setToolTip('Move sort column up')
        self.move_sort_up_button.setIcon(QIcon(I('arrow-up.png')))
        self.move_sort_down_button = QtGui.QToolButton(self)
        self.move_sort_down_button.setToolTip('Move sort down')
        self.move_sort_down_button.setIcon(QIcon(I('arrow-down.png')))
        self.move_sort_up_button.clicked.connect(self.sort_list.move_column_up)
        self.move_sort_down_button.clicked.connect(
            self.sort_list.move_column_down)

        layout_col = 0  # calculate layout because pin column only shown if available.
        customise_layout.addWidget(self.columns_label, 0, layout_col, 1, 1)
        customise_layout.addWidget(self.columns_list, 1, layout_col, 3, 1)
        layout_col = layout_col + 1
        customise_layout.addWidget(self.move_column_up_button, 1, layout_col,
                                   1, 1)
        customise_layout.addWidget(self.move_column_down_button, 3, layout_col,
                                   1, 1)
        layout_col = layout_col + 1

        if self.has_pin_view:
            customise_layout.addWidget(self.apply_pin_columns_checkbox, 0,
                                       layout_col, 1, 1)
            customise_layout.addWidget(self.pin_columns_list, 1, layout_col, 3,
                                       1)
            layout_col = layout_col + 1
            customise_layout.addWidget(self.move_pin_column_up_button, 1,
                                       layout_col, 1, 1)
            customise_layout.addWidget(self.move_pin_column_down_button, 3,
                                       layout_col, 1, 1)
            layout_col = layout_col + 1

        customise_layout.addWidget(self.sort_label, 0, layout_col, 1, 1)
        customise_layout.addWidget(self.sort_list, 1, layout_col, 3, 1)
        layout_col = layout_col + 1

        customise_layout.addWidget(self.move_sort_up_button, 1, layout_col, 1,
                                   1)
        customise_layout.addWidget(self.move_sort_down_button, 3, layout_col,
                                   1, 1)
        layout_col = layout_col + 1

        search_group_box = QGroupBox("Search and Virtual Library Options",
                                     self)
        layout.addWidget(search_group_box)
        search_group_box_layout = QVBoxLayout()
        search_group_box.setLayout(search_group_box_layout)

        other_layout = QGridLayout()
        search_group_box_layout.addLayout(other_layout)

        self.apply_search_checkbox = QCheckBox('Apply saved &search', self)
        self.apply_search_checkbox.setToolTip(
            "Apply the selected saved search when the View is activated.")
        self.saved_search_combo = SearchComboBox(
            self, entries=saved_searches().names(), empty="(Clear Search)")
        self.saved_search_combo.setToolTip("Saved search to apply.")
        # enable/disable combo based on check.
        self.saved_search_combo.setEnabled(
            self.apply_search_checkbox.isChecked())
        self.apply_search_checkbox.stateChanged.connect(
            lambda x: self.saved_search_combo.setEnabled(
                self.apply_search_checkbox.isChecked()))

        self.apply_virtlib_checkbox = QCheckBox('Switch to &Virtual library',
                                                self)
        self.apply_virtlib_checkbox.setToolTip(
            "Switch to the selected Virtual library when the View is activated."
        )
        self.virtlib_combo = SearchComboBox(
            self,
            entries=self.gui.library_view.model().db.prefs.get(
                'virtual_libraries', {}),
            empty="(No Virtual library)")
        self.virtlib_combo.setToolTip("Virtual library to switch to.")
        # enable/disable combo based on check.
        self.virtlib_combo.setEnabled(self.apply_virtlib_checkbox.isChecked())
        self.apply_virtlib_checkbox.stateChanged.connect(
            lambda x: self.virtlib_combo.setEnabled(self.apply_virtlib_checkbox
                                                    .isChecked()))

        self.apply_restriction_checkbox = QCheckBox(
            'Apply VL additional search &restriction', self)
        self.apply_restriction_checkbox.setToolTip(
            "Apply the selected saved search as a Virtual library additional restriction when the View is activated."
        )
        self.search_restriction_combo = SearchComboBox(
            self,
            entries=saved_searches().names(),
            empty="(Clear VL restriction search)")
        self.search_restriction_combo.setToolTip(
            "Saved search to apply as VL additional search restriction.")
        # enable/disable combo based on check.
        self.search_restriction_combo.setEnabled(
            self.apply_restriction_checkbox.isChecked())
        self.apply_restriction_checkbox.stateChanged.connect(
            lambda x: self.search_restriction_combo.setEnabled(
                self.apply_restriction_checkbox.isChecked()))

        other_layout.addWidget(self.apply_search_checkbox, 0, 0, 1, 1)
        other_layout.addWidget(self.saved_search_combo, 0, 1, 1, 1)
        other_layout.addWidget(self.apply_virtlib_checkbox, 1, 0, 1, 1)
        other_layout.addWidget(self.virtlib_combo, 1, 1, 1, 1)
        other_layout.addWidget(self.apply_restriction_checkbox, 2, 0, 1, 1)
        other_layout.addWidget(self.search_restriction_combo, 2, 1, 1, 1)
        # other_layout.setRowStretch(4, 1)

        #layout.addSpacing(10)
        other_group_box = QGroupBox('General Options', self)
        layout.addWidget(other_group_box)
        other_group_box_layout = QGridLayout()
        other_group_box.setLayout(other_group_box_layout)

        self.jump_to_top_checkbox = QCheckBox(
            'Jump to the top when applying this View', self)
        jump_to_top = self.library.get(KEY_JUMP_TO_TOP, False)
        self.jump_to_top_checkbox.setCheckState(
            Qt.Checked if jump_to_top else Qt.Unchecked)

        restart_label = QLabel(
            'When restarting Calibre or switching to this library...')
        self.auto_apply_checkbox = QCheckBox('&Automatically apply view:',
                                             self)
        auto_apply = self.library.get(KEY_AUTO_APPLY_VIEW, False)
        self.auto_apply_checkbox.setCheckState(
            Qt.Checked if auto_apply else Qt.Unchecked)
        self.auto_view_combo = ViewComboBox(self,
                                            self.views,
                                            special=LAST_VIEW_ITEM)
        self.auto_view_combo.select_view(
            self.library.get(KEY_VIEW_TO_APPLY, LAST_VIEW_ITEM))
        self.auto_view_combo.setMinimumSize(150, 20)
        info_apply_label = QLabel(
            'Enabling this option may override any startup search restriction or '
            'title sort set in Preferences -> Behaviour/Tweaks.')
        info_apply_label.setWordWrap(True)
        other_group_box_layout.addWidget(self.jump_to_top_checkbox, 0, 0, 1, 2)
        other_group_box_layout.addWidget(restart_label, 1, 0, 1, 2)
        other_group_box_layout.addWidget(self.auto_apply_checkbox, 2, 0, 1, 1)
        other_group_box_layout.addWidget(self.auto_view_combo, 2, 1, 1, 1)
        other_group_box_layout.addWidget(info_apply_label, 3, 0, 1, 2)
        #other_group_box.setMaximumHeight(other_group_box.sizeHint().height())

        keyboard_layout = QHBoxLayout()
        layout.addLayout(keyboard_layout)
        keyboard_shortcuts_button = QPushButton('Keyboard shortcuts...', self)
        keyboard_shortcuts_button.setToolTip(
            _('Edit the keyboard shortcuts associated with this plugin'))
        keyboard_shortcuts_button.clicked.connect(self.edit_shortcuts)
        view_prefs_button = QPushButton('&View library preferences...', self)
        view_prefs_button.setToolTip(
            _('View data stored in the library database for this plugin'))
        view_prefs_button.clicked.connect(self.view_prefs)
        keyboard_layout.addWidget(keyboard_shortcuts_button)
        keyboard_layout.addWidget(view_prefs_button)
        keyboard_layout.addStretch(1)

        # Force an initial display of view information
        if KEY_LAST_VIEW in list(self.library.keys()):
            last_view = self.library[KEY_LAST_VIEW]
            if last_view in self.views:
                self.select_view_combo.select_view(self.library[KEY_LAST_VIEW])
        self.select_view_combo_index_changed(save_previous=False)
        self.select_view_combo.currentIndexChanged.connect(
            partial(self.select_view_combo_index_changed, save_previous=True))

    def save_settings(self):
        # We only need to update the store for the current view, as switching views
        # will have updated the other stores
        self.persist_view_config()

        library_config = get_library_config(self.gui.current_db)
        library_config[KEY_VIEWS] = self.views
        library_config[
            KEY_AUTO_APPLY_VIEW] = self.auto_apply_checkbox.checkState(
            ) == Qt.Checked
        library_config[KEY_VIEW_TO_APPLY] = unicode(
            self.auto_view_combo.currentText())
        set_library_config(self.gui.current_db, library_config)

    def persist_view_config(self):
        if not self.view_name:
            return
        # Update all of the current user information in the store
        view_info = self.views[self.view_name]
        view_info[KEY_COLUMNS] = self.columns_list.get_data()
        view_info[KEY_SORT] = self.sort_list.get_data()
        if self.has_pin_view:
            view_info[
                KEY_APPLY_PIN_COLUMNS] = self.apply_pin_columns_checkbox.checkState(
                ) == Qt.Checked
            view_info[KEY_PIN_COLUMNS] = self.pin_columns_list.get_data()
        view_info[
            KEY_APPLY_RESTRICTION] = self.apply_restriction_checkbox.checkState(
            ) == Qt.Checked
        if view_info[KEY_APPLY_RESTRICTION]:
            view_info[KEY_RESTRICTION] = unicode(
                self.search_restriction_combo.currentText()).strip()
        else:
            view_info[KEY_RESTRICTION] = ''
        view_info[KEY_APPLY_SEARCH] = self.apply_search_checkbox.checkState(
        ) == Qt.Checked
        if view_info[KEY_APPLY_SEARCH]:
            view_info[KEY_SEARCH] = unicode(
                self.saved_search_combo.currentText()).strip()
        else:
            view_info[KEY_SEARCH] = ''
        view_info[KEY_APPLY_VIRTLIB] = self.apply_virtlib_checkbox.checkState(
        ) == Qt.Checked
        if view_info[KEY_APPLY_VIRTLIB]:
            view_info[KEY_VIRTLIB] = unicode(
                self.virtlib_combo.currentText()).strip()
        else:
            view_info[KEY_VIRTLIB] = ''
        view_info[KEY_JUMP_TO_TOP] = self.jump_to_top_checkbox.checkState(
        ) == Qt.Checked

        self.views[self.view_name] = view_info

    def select_view_combo_index_changed(self, save_previous=True):
        # Update the dialog contents with data for the selected view
        if save_previous:
            # Switching views, persist changes made to the other view
            self.persist_view_config()
        if self.select_view_combo.count() == 0:
            self.view_name = None
        else:
            self.view_name = unicode(
                self.select_view_combo.currentText()).strip()
        columns = []
        sort_columns = []
        all_columns = []
        pin_columns = []
        apply_columns = True
        apply_pin_columns = False
        apply_sort = True
        apply_restriction = False
        restriction_to_apply = ''
        apply_search = False
        search_to_apply = ''
        apply_virtlib = False
        virtlib_to_apply = ''
        jump_to_top = False
        if self.view_name != None:
            view_info = self.views[self.view_name]
            columns = copy.deepcopy(view_info[KEY_COLUMNS])
            pin_columns = copy.deepcopy(view_info.get(KEY_PIN_COLUMNS, {}))
            sort_columns = copy.deepcopy(view_info[KEY_SORT])
            all_columns = self.all_columns
            apply_pin_columns = view_info.get(KEY_APPLY_PIN_COLUMNS, False)
            apply_restriction = view_info[KEY_APPLY_RESTRICTION]
            restriction_to_apply = view_info[KEY_RESTRICTION]
            apply_search = view_info[KEY_APPLY_SEARCH]
            search_to_apply = view_info[KEY_SEARCH]
            apply_virtlib = view_info.get(KEY_APPLY_VIRTLIB, False)
            virtlib_to_apply = view_info.get(KEY_VIRTLIB, '')
            jump_to_top = view_info.get(KEY_JUMP_TO_TOP, False)

        self.columns_list.populate(columns, all_columns)
        self.sort_list.populate(sort_columns, all_columns)
        if self.has_pin_view:
            self.pin_columns_list.populate(pin_columns, all_columns)
            self.apply_pin_columns_checkbox.setCheckState(
                Qt.Checked if apply_pin_columns else Qt.Unchecked)
        self.apply_restriction_checkbox.setCheckState(
            Qt.Checked if apply_restriction else Qt.Unchecked)
        self.search_restriction_combo.select_value(restriction_to_apply)
        self.apply_search_checkbox.setCheckState(
            Qt.Checked if apply_search else Qt.Unchecked)
        self.saved_search_combo.select_value(search_to_apply)
        self.apply_virtlib_checkbox.setCheckState(
            Qt.Checked if apply_virtlib else Qt.Unchecked)
        self.virtlib_combo.select_value(virtlib_to_apply)
        self.jump_to_top_checkbox.setCheckState(
            Qt.Checked if jump_to_top else Qt.Unchecked)

    def add_view(self):
        # Display a prompt allowing user to specify a new view
        new_view_name, ok = QInputDialog.getText(
            self,
            'Add new view',
            'Enter a unique display name for this view:',
            text='Default')
        if not ok:
            # Operation cancelled
            return
        new_view_name = unicode(new_view_name).strip()
        # Verify it does not clash with any other views in the list
        for view_name in self.views.keys():
            if view_name.lower() == new_view_name.lower():
                return error_dialog(self,
                                    'Add Failed',
                                    'A view with the same name already exists',
                                    show=True)

        self.persist_view_config()
        view_info = get_empty_view()

        if self.view_name != None:
            # We will copy values from the currently selected view
            old_view_info = self.views[self.view_name]
            view_info[KEY_COLUMNS] = copy.deepcopy(old_view_info[KEY_COLUMNS])
            view_info[KEY_APPLY_PIN_COLUMNS] = copy.deepcopy(
                old_view_info.get(KEY_APPLY_PIN_COLUMNS, False))
            view_info[KEY_PIN_COLUMNS] = copy.deepcopy(
                old_view_info.get(KEY_PIN_COLUMNS, {}))
            view_info[KEY_SORT] = copy.deepcopy(old_view_info[KEY_SORT])
            view_info[KEY_APPLY_RESTRICTION] = copy.deepcopy(
                old_view_info[KEY_APPLY_RESTRICTION])
            view_info[KEY_RESTRICTION] = copy.deepcopy(
                old_view_info[KEY_RESTRICTION])
            view_info[KEY_APPLY_SEARCH] = copy.deepcopy(
                old_view_info[KEY_APPLY_SEARCH])
            view_info[KEY_SEARCH] = copy.deepcopy(old_view_info[KEY_SEARCH])
            view_info[KEY_APPLY_VIRTLIB] = copy.deepcopy(
                old_view_info.get(KEY_APPLY_VIRTLIB, False))
            view_info[KEY_VIRTLIB] = copy.deepcopy(
                old_view_info.get(KEY_VIRTLIB, ''))
            view_info[KEY_JUMP_TO_TOP] = copy.deepcopy(
                old_view_info[KEY_JUMP_TO_TOP])
        else:
            # We will copy values from the current library view
            view_info[KEY_COLUMNS] = self.get_current_columns(
                visible_only=True)

        self.view_name = new_view_name
        self.views[new_view_name] = view_info
        # Now update the views combobox
        self.select_view_combo.populate_combo(self.views, new_view_name)
        self.select_view_combo_index_changed(save_previous=False)
        self.auto_view_combo.populate_combo(
            self.views, unicode(self.auto_view_combo.currentText()))

    def rename_view(self):
        if not self.view_name != None:
            return
        # Display a prompt allowing user to specify a rename view
        old_view_name = self.view_name
        new_view_name, ok = QInputDialog.getText(
            self,
            'Rename view',
            'Enter a new display name for this view:',
            text=old_view_name)
        if not ok:
            # Operation cancelled
            return
        new_view_name = unicode(new_view_name).strip()
        if new_view_name == old_view_name:
            return
        # Verify it does not clash with any other views in the list
        for view_name in self.views.keys():
            if view_name == old_view_name:
                continue
            if view_name.lower() == new_view_name.lower():
                return error_dialog(self,
                                    'Add Failed',
                                    'A view with the same name already exists',
                                    show=True)

        # Ensure any changes are persisted
        self.persist_view_config()
        view_info = self.views[old_view_name]
        del self.views[old_view_name]
        self.view_name = new_view_name
        self.views[new_view_name] = view_info
        # Now update the views combobox
        self.select_view_combo.populate_combo(self.views, new_view_name)
        self.select_view_combo_index_changed(save_previous=False)
        if unicode(self.auto_view_combo.currentText()) == old_view_name:
            self.auto_view_combo.populate_combo(self.views, new_view_name)
        else:
            self.auto_view_combo.populate_combo(self.views)

    def delete_view(self):
        if self.view_name == None:
            return
        if not question_dialog(
                self,
                _('Are you sure?'),
                '<p>' +
                'Do you want to delete the view named \'%s\'' % self.view_name,
                show_copy_button=False):
            return
        del self.views[self.view_name]
        # Now update the views combobox
        self.select_view_combo.populate_combo(self.views)
        self.select_view_combo_index_changed(save_previous=False)
        self.auto_view_combo.populate_combo(self.views)

    def get_current_columns(self, defaults=False, visible_only=False):
        model = self.gui.library_view.model()
        colmap = list(model.column_map)
        state = self.columns_state(defaults)
        positions = state['column_positions']
        colmap.sort(key=lambda x: positions[x])
        hidden_cols = state['hidden_columns']
        if visible_only:
            colmap = [
                col for col in colmap
                if col not in hidden_cols or col == 'ondevice'
            ]
        # Convert our list of column names into a list of tuples with column widths
        colsizemap = []
        for col in colmap:
            if col in hidden_cols:
                colsizemap.append((col, -1))
            else:
                colsizemap.append((col, state['column_sizes'].get(col, -1)))
        return colsizemap

    def columns_state(self, defaults=False):
        if defaults:
            return self.gui.library_view.get_default_state()
        return self.gui.library_view.get_state()

    def edit_shortcuts(self):
        self.save_settings()
        # Force the menus to be rebuilt immediately, so we have all our actions registered
        self.plugin_action.rebuild_menus()
        d = KeyboardConfigDialog(self.plugin_action.gui,
                                 self.plugin_action.action_spec[0])
        if d.exec_() == d.Accepted:
            self.plugin_action.gui.keyboard.finalize()

    def view_prefs(self):
        d = PrefsViewerDialog(self.plugin_action.gui, PREFS_NAMESPACE)
        d.exec_()
Beispiel #4
0
class BulkSeries(BulkBase):

    def setup_ui(self, parent):
        self.make_widgets(parent, EditWithComplete)
        values = self.all_values = list(self.db.all_custom(num=self.col_id))
        values.sort(key=sort_key)
        self.main_widget.setSizeAdjustPolicy(self.main_widget.AdjustToMinimumContentsLengthWithIcon)
        self.main_widget.setMinimumContentsLength(25)
        self.widgets.append(QLabel('', parent))
        w = QWidget(parent)
        layout = QHBoxLayout(w)
        layout.setContentsMargins(0, 0, 0, 0)
        self.remove_series = QCheckBox(parent)
        self.remove_series.setText(_('Clear series'))
        layout.addWidget(self.remove_series)
        self.idx_widget = QCheckBox(parent)
        self.idx_widget.setText(_('Automatically number books'))
        self.idx_widget.setToolTip('<p>' + _(
            'If not checked, the series number for the books will be set to 1. '
            'If checked, selected books will be automatically numbered, '
            'in the order you selected them. So if you selected '
            'Book A and then Book B, Book A will have series number 1 '
            'and Book B series number 2.') + '</p>')
        layout.addWidget(self.idx_widget)
        self.force_number = QCheckBox(parent)
        self.force_number.setText(_('Force numbers to start with '))
        self.force_number.setToolTip('<p>' + _(
            'Series will normally be renumbered from the highest '
            'number in the database for that series. Checking this '
            'box will tell calibre to start numbering from the value '
            'in the box') + '</p>')
        layout.addWidget(self.force_number)
        self.series_start_number = QDoubleSpinBox(parent)
        self.series_start_number.setMinimum(0.0)
        self.series_start_number.setMaximum(9999999.0)
        self.series_start_number.setProperty("value", 1.0)
        layout.addWidget(self.series_start_number)
        self.series_increment = QDoubleSpinBox(parent)
        self.series_increment.setMinimum(0.00)
        self.series_increment.setMaximum(99999.0)
        self.series_increment.setProperty("value", 1.0)
        self.series_increment.setToolTip('<p>' + _(
            'The amount by which to increment the series number '
            'for successive books. Only applicable when using '
            'force series numbers.') + '</p>')
        self.series_increment.setPrefix('+')
        layout.addWidget(self.series_increment)
        layout.addItem(QSpacerItem(20, 10, QSizePolicy.Expanding, QSizePolicy.Minimum))
        self.widgets.append(w)
        self.idx_widget.stateChanged.connect(self.a_c_checkbox_changed)
        self.force_number.stateChanged.connect(self.a_c_checkbox_changed)
        self.series_start_number.valueChanged.connect(self.a_c_checkbox_changed)
        self.series_increment.valueChanged.connect(self.a_c_checkbox_changed)
        self.remove_series.stateChanged.connect(self.a_c_checkbox_changed)
        self.main_widget
        self.ignore_change_signals = False

    def a_c_checkbox_changed(self):
        def disable_numbering_checkboxes(idx_widget_enable):
            if idx_widget_enable:
                self.idx_widget.setEnabled(True)
            else:
                self.idx_widget.setChecked(False)
                self.idx_widget.setEnabled(False)
            self.force_number.setChecked(False)
            self.force_number.setEnabled(False)
            self.series_start_number.setEnabled(False)
            self.series_increment.setEnabled(False)

        if self.ignore_change_signals:
            return
        self.ignore_change_signals = True
        apply_changes = False
        if self.remove_series.isChecked():
            self.main_widget.setText('')
            self.main_widget.setEnabled(False)
            disable_numbering_checkboxes(idx_widget_enable=False)
            apply_changes = True
        elif self.main_widget.text():
            self.remove_series.setEnabled(False)
            self.idx_widget.setEnabled(True)
            apply_changes = True
        else:  # no text, no clear. Basically reinitialize
            self.main_widget.setEnabled(True)
            self.remove_series.setEnabled(True)
            disable_numbering_checkboxes(idx_widget_enable=False)
            apply_changes = False

        self.force_number.setEnabled(self.idx_widget.isChecked())
        self.series_start_number.setEnabled(self.force_number.isChecked())
        self.series_increment.setEnabled(self.force_number.isChecked())

        self.ignore_change_signals = False
        self.a_c_checkbox.setChecked(apply_changes)

    def initialize(self, book_id):
        self.idx_widget.setChecked(False)
        self.main_widget.set_separator(None)
        self.main_widget.update_items_cache(self.all_values)
        self.main_widget.setEditText('')
        self.a_c_checkbox.setChecked(False)

    def getter(self):
        n = unicode(self.main_widget.currentText()).strip()
        autonumber = self.idx_widget.checkState()
        force = self.force_number.checkState()
        start = self.series_start_number.value()
        remove = self.remove_series.checkState()
        increment = self.series_increment.value()
        return n, autonumber, force, start, remove, increment

    def commit(self, book_ids, notify=False):
        if not self.a_c_checkbox.isChecked():
            return
        val, update_indices, force_start, at_value, clear, increment = self.gui_val
        val = None if clear else self.normalize_ui_val(val)
        if clear or val != '':
            extras = []
            for book_id in book_ids:
                if clear:
                    extras.append(None)
                    continue
                if update_indices:
                    if force_start:
                        s_index = at_value
                        at_value += increment
                    elif tweaks['series_index_auto_increment'] != 'const':
                        s_index = self.db.get_next_cc_series_num_for(val, num=self.col_id)
                    else:
                        s_index = 1.0
                else:
                    s_index = self.db.get_custom_extra(book_id, num=self.col_id,
                                                       index_is_id=True)
                extras.append(s_index)
            self.db.set_custom_bulk(book_ids, val, extras=extras,
                                   num=self.col_id, notify=notify)
class BulkSeries(BulkBase):

    def setup_ui(self, parent):
        self.make_widgets(parent, EditWithComplete)
        values = self.all_values = list(self.db.all_custom(num=self.col_id))
        values.sort(key=sort_key)
        self.main_widget.setSizeAdjustPolicy(self.main_widget.AdjustToMinimumContentsLengthWithIcon)
        self.main_widget.setMinimumContentsLength(25)
        self.widgets.append(QLabel('', parent))
        w = QWidget(parent)
        layout = QHBoxLayout(w)
        layout.setContentsMargins(0, 0, 0, 0)
        self.remove_series = QCheckBox(parent)
        self.remove_series.setText(_('Remove series'))
        layout.addWidget(self.remove_series)
        self.idx_widget = QCheckBox(parent)
        self.idx_widget.setText(_('Automatically number books'))
        layout.addWidget(self.idx_widget)
        self.force_number = QCheckBox(parent)
        self.force_number.setText(_('Force numbers to start with '))
        layout.addWidget(self.force_number)
        self.series_start_number = QSpinBox(parent)
        self.series_start_number.setMinimum(1)
        self.series_start_number.setMaximum(9999999)
        self.series_start_number.setProperty("value", 1)
        layout.addWidget(self.series_start_number)
        layout.addItem(QSpacerItem(20, 10, QSizePolicy.Expanding, QSizePolicy.Minimum))
        self.widgets.append(w)
        self.idx_widget.stateChanged.connect(self.check_changed_checkbox)
        self.force_number.stateChanged.connect(self.check_changed_checkbox)
        self.series_start_number.valueChanged.connect(self.check_changed_checkbox)
        self.remove_series.stateChanged.connect(self.check_changed_checkbox)
        self.ignore_change_signals = False

    def check_changed_checkbox(self):
        self.a_c_checkbox.setChecked(True)

    def initialize(self, book_id):
        self.idx_widget.setChecked(False)
        self.main_widget.set_separator(None)
        self.main_widget.update_items_cache(self.all_values)
        self.main_widget.setEditText('')
        self.a_c_checkbox.setChecked(False)

    def getter(self):
        n = unicode(self.main_widget.currentText()).strip()
        i = self.idx_widget.checkState()
        f = self.force_number.checkState()
        s = self.series_start_number.value()
        r = self.remove_series.checkState()
        return n, i, f, s, r

    def commit(self, book_ids, notify=False):
        if not self.a_c_checkbox.isChecked():
            return
        val, update_indices, force_start, at_value, clear = self.gui_val
        val = None if clear else self.normalize_ui_val(val)
        if clear or val != '':
            extras = []
            for book_id in book_ids:
                if clear:
                    extras.append(None)
                    continue
                if update_indices:
                    if force_start:
                        s_index = at_value
                        at_value += 1
                    elif tweaks['series_index_auto_increment'] != 'const':
                        s_index = self.db.get_next_cc_series_num_for(val, num=self.col_id)
                    else:
                        s_index = 1.0
                else:
                    s_index = self.db.get_custom_extra(book_id, num=self.col_id,
                                                       index_is_id=True)
                extras.append(s_index)
            self.db.set_custom_bulk(book_ids, val, extras=extras,
                                   num=self.col_id, notify=notify)
Beispiel #6
0
class BulkSeries(BulkBase):
    def setup_ui(self, parent):
        self.make_widgets(parent, EditWithComplete)
        values = self.all_values = list(self.db.all_custom(num=self.col_id))
        values.sort(key=sort_key)
        self.main_widget.setSizeAdjustPolicy(
            self.main_widget.AdjustToMinimumContentsLengthWithIcon)
        self.main_widget.setMinimumContentsLength(25)
        self.widgets.append(QLabel('', parent))
        w = QWidget(parent)
        layout = QHBoxLayout(w)
        layout.setContentsMargins(0, 0, 0, 0)
        self.remove_series = QCheckBox(parent)
        self.remove_series.setText(_('Remove series'))
        layout.addWidget(self.remove_series)
        self.idx_widget = QCheckBox(parent)
        self.idx_widget.setText(_('Automatically number books'))
        layout.addWidget(self.idx_widget)
        self.force_number = QCheckBox(parent)
        self.force_number.setText(_('Force numbers to start with '))
        layout.addWidget(self.force_number)
        self.series_start_number = QSpinBox(parent)
        self.series_start_number.setMinimum(1)
        self.series_start_number.setMaximum(9999999)
        self.series_start_number.setProperty("value", 1)
        layout.addWidget(self.series_start_number)
        layout.addItem(
            QSpacerItem(20, 10, QSizePolicy.Expanding, QSizePolicy.Minimum))
        self.widgets.append(w)
        self.idx_widget.stateChanged.connect(self.check_changed_checkbox)
        self.force_number.stateChanged.connect(self.check_changed_checkbox)
        self.series_start_number.valueChanged.connect(
            self.check_changed_checkbox)
        self.remove_series.stateChanged.connect(self.check_changed_checkbox)
        self.ignore_change_signals = False

    def check_changed_checkbox(self):
        self.a_c_checkbox.setChecked(True)

    def initialize(self, book_id):
        self.idx_widget.setChecked(False)
        self.main_widget.set_separator(None)
        self.main_widget.update_items_cache(self.all_values)
        self.main_widget.setEditText('')
        self.a_c_checkbox.setChecked(False)

    def getter(self):
        n = unicode(self.main_widget.currentText()).strip()
        i = self.idx_widget.checkState()
        f = self.force_number.checkState()
        s = self.series_start_number.value()
        r = self.remove_series.checkState()
        return n, i, f, s, r

    def commit(self, book_ids, notify=False):
        if not self.a_c_checkbox.isChecked():
            return
        val, update_indices, force_start, at_value, clear = self.gui_val
        val = None if clear else self.normalize_ui_val(val)
        if clear or val != '':
            extras = []
            for book_id in book_ids:
                if clear:
                    extras.append(None)
                    continue
                if update_indices:
                    if force_start:
                        s_index = at_value
                        at_value += 1
                    elif tweaks['series_index_auto_increment'] != 'const':
                        s_index = self.db.get_next_cc_series_num_for(
                            val, num=self.col_id)
                    else:
                        s_index = 1.0
                else:
                    s_index = self.db.get_custom_extra(book_id,
                                                       num=self.col_id,
                                                       index_is_id=True)
                extras.append(s_index)
            self.db.set_custom_bulk(book_ids,
                                    val,
                                    extras=extras,
                                    num=self.col_id,
                                    notify=notify)
class ConfigWidget(DefaultConfigWidget):

    def __init__(self, plugin):
        DefaultConfigWidget.__init__(self, plugin)
        c = plugin_prefs[STORE_NAME]
        all_tags = get_current_db().all_tags()

        self.gb.setMaximumHeight(80)
        genre_group_box = QGroupBox(_('Ridibooks genre to calibre tag mappings'), self)
        self.l.addWidget(genre_group_box, self.l.rowCount(), 0, 1, 2)
        genre_group_box_layout = QVBoxLayout()
        genre_group_box.setLayout(genre_group_box_layout)

        tags_layout = QHBoxLayout()
        genre_group_box_layout.addLayout(tags_layout)

        self.edit_table = GenreTagMappingsTableWidget(self, all_tags)
        tags_layout.addWidget(self.edit_table)
        button_layout = QVBoxLayout()
        tags_layout.addLayout(button_layout)
        add_mapping_button = QtGui.QToolButton(self)
        add_mapping_button.setToolTip(_('Add genre mapping'))
        add_mapping_button.setIcon(QIcon(I('plus.png')))
        add_mapping_button.clicked.connect(self.add_mapping)
        button_layout.addWidget(add_mapping_button)
        spacerItem1 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
        button_layout.addItem(spacerItem1)
        remove_mapping_button = QtGui.QToolButton(self)
        remove_mapping_button.setToolTip(_('Delete genre mapping'))
        remove_mapping_button.setIcon(QIcon(I('minus.png')))
        remove_mapping_button.clicked.connect(self.delete_mapping)
        button_layout.addWidget(remove_mapping_button)
        spacerItem3 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
        button_layout.addItem(spacerItem3)
        rename_genre_button = QtGui.QToolButton(self)
        rename_genre_button.setToolTip(_('Rename Goodreads genre'))
        rename_genre_button.setIcon(QIcon(I('edit-undo.png')))
        rename_genre_button.clicked.connect(self.rename_genre)
        button_layout.addWidget(rename_genre_button)
        spacerItem2 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
        button_layout.addItem(spacerItem2)
        reset_defaults_button = QtGui.QToolButton(self)
        reset_defaults_button.setToolTip(_('Reset to plugin default mappings'))
        reset_defaults_button.setIcon(QIcon(I('clear_left.png')))
        reset_defaults_button.clicked.connect(self.reset_to_defaults)
        button_layout.addWidget(reset_defaults_button)
        self.l.setRowStretch(self.l.rowCount()-1, 2)

        other_group_box = QGroupBox(_('Other options'), self)
        self.l.addWidget(other_group_box, self.l.rowCount(), 0, 1, 2)
        other_group_box_layout = QVBoxLayout()
        other_group_box.setLayout(other_group_box_layout)

        self.get_editions_checkbox = QCheckBox('Scan multiple editions for title/author searches (slower)', self)
        self.get_editions_checkbox.setToolTip('When checked will perform an additional search to scan the top ranked\n'
                                              'Goodreads editions (if available) to exclude audiobook editions.\n'
                                              'Without this enabled you will get a faster search, using the "best".\n'
                                              'edition ranked by Goodreads which can in some cases be an audiobook.')
        self.get_editions_checkbox.setChecked(c[KEY_GET_EDITIONS])
        other_group_box_layout.addWidget(self.get_editions_checkbox)
        self.all_authors_checkbox = QCheckBox('Get all contributing authors (e.g. illustrators, series editors etc)', self)
        self.all_authors_checkbox.setToolTip('Goodreads for some books will list all of the contributing authors and\n'
                                              'the type of contribution like (Editor), (Illustrator) etc.\n\n'
                                              'When this option is checked, all contributing authors are retrieved.\n\n'
                                              'When unchecked (default) only the primary author(s) are returned which\n'
                                              'are those that either have no contribution type specified, or have the\n'
                                              'value of (Goodreads Author).\n\n'
                                              'If there is no primary author then only those with the same contribution\n'
                                              'type as the first author are returned.\n'
                                              'e.g. "A, B (Illustrator)" will return author A\n'
                                              'e.g. "A (Goodreads Author)" will return author A\n'
                                              'e.g. "A (Editor), B (Editor), C (Illustrator)" will return authors A & B\n'
                                              'e.g. "A (Editor), B (Series Editor)" will return author A\n')
        self.all_authors_checkbox.setChecked(c[KEY_GET_ALL_AUTHORS])
        other_group_box_layout.addWidget(self.all_authors_checkbox)

        self.edit_table.populate_table(c[KEY_GENRE_MAPPINGS])

    def commit(self):
        DefaultConfigWidget.commit(self)
        new_prefs = {}
        new_prefs[KEY_GET_EDITIONS] = self.get_editions_checkbox.checkState() == Qt.Checked
        new_prefs[KEY_GET_ALL_AUTHORS] = self.all_authors_checkbox.checkState() == Qt.Checked
        new_prefs[KEY_GENRE_MAPPINGS] = self.edit_table.get_data()
        plugin_prefs[STORE_NAME] = new_prefs

    def add_mapping(self):
        new_genre_name, ok = QInputDialog.getText(self, 'Add new mapping',
                    'Enter a Goodreads genre name to create a mapping for:', text='')
        if not ok:
            # Operation cancelled
            return
        new_genre_name = unicode(new_genre_name).strip()
        if not new_genre_name:
            return
        # Verify it does not clash with any other mappings in the list
        data = self.edit_table.get_data()
        for genre_name in data.keys():
            if genre_name.lower() == new_genre_name.lower():
                return error_dialog(self, 'Add Failed', 'A genre with the same name already exists', show=True)
        data[new_genre_name] = []
        self.edit_table.populate_table(data)
        self.edit_table.select_genre(new_genre_name)

    def delete_mapping(self):
        if not self.edit_table.selectionModel().hasSelection():
            return
        if not question_dialog(self, _('Are you sure?'), '<p>'+
                'Are you sure you want to delete the selected genre mappings?',
                show_copy_button=False):
            return
        for row in reversed(sorted(self.edit_table.selectionModel().selectedRows())):
            self.edit_table.removeRow(row.row())

    def rename_genre(self):
        selected_genre = self.edit_table.get_selected_genre()
        if not selected_genre:
            return
        new_genre_name, ok = QInputDialog.getText(self, 'Add new mapping',
                    'Enter a Goodreads genre name to create a mapping for:', text=selected_genre)
        if not ok:
            # Operation cancelled
            return
        new_genre_name = unicode(new_genre_name).strip()
        if not new_genre_name or new_genre_name == selected_genre:
            return
        data = self.edit_table.get_data()
        if new_genre_name.lower() != selected_genre.lower():
            # Verify it does not clash with any other mappings in the list
            for genre_name in data.keys():
                if genre_name.lower() == new_genre_name.lower():
                    return error_dialog(self, 'Rename Failed', 'A genre with the same name already exists', show=True)
        data[new_genre_name] = data[selected_genre]
        del data[selected_genre]
        self.edit_table.populate_table(data)
        self.edit_table.select_genre(new_genre_name)

    def reset_to_defaults(self):
        if not question_dialog(self, _('Are you sure?'), '<p>'+
                'Are you sure you want to reset to the plugin default genre mappings?',
                show_copy_button=False):
            return
        self.edit_table.populate_table(DEFAULT_GENRE_MAPPINGS)
Beispiel #8
0
class ConfigWidget(QWidget):

    def __init__(self, plugin_action):
        QWidget.__init__(self)
        self.plugin_action = plugin_action
        layout = QVBoxLayout(self)
        self.setLayout(layout)
        self.help_anchor = "configuration"
        
        title_layout = ImageTitleLayout(self, 'images/icon.png', 'Sony Utilities Options')
        layout.addLayout(title_layout)

#        c = plugin_prefs[STORE_NAME]
        library_config = get_library_config(self.plugin_action.gui.current_db)

        custom_column_group = QGroupBox(_('Custom Columns'), self)
        layout.addWidget(custom_column_group )
        options_layout = QGridLayout()
        custom_column_group.setLayout(options_layout)

        avail_text_columns   = self.get_text_custom_columns()
        avail_number_columns = self.get_number_custom_columns()
        avail_rating_columns = self.get_rating_custom_columns()
        avail_date_columns   = self.get_date_custom_columns()
#        debug_print("avail_rating_columns=", avail_rating_columns)
#        debug_print("default columns=", self.plugin_action.gui.library_view.model().orig_headers)
        current_Location_column  = library_config.get(KEY_CURRENT_LOCATION_CUSTOM_COLUMN, DEFAULT_LIBRARY_VALUES[KEY_CURRENT_LOCATION_CUSTOM_COLUMN])
        precent_read_column      = library_config.get(KEY_PERCENT_READ_CUSTOM_COLUMN, DEFAULT_LIBRARY_VALUES[KEY_PERCENT_READ_CUSTOM_COLUMN])
        rating_column            = library_config.get(KEY_RATING_CUSTOM_COLUMN, DEFAULT_LIBRARY_VALUES[KEY_RATING_CUSTOM_COLUMN])
        last_read_column         = library_config.get(KEY_LAST_READ_CUSTOM_COLUMN, DEFAULT_LIBRARY_VALUES[KEY_LAST_READ_CUSTOM_COLUMN])

        store_on_connect         = get_plugin_pref(COMMON_OPTIONS_STORE_NAME, KEY_STORE_ON_CONNECT)
        prompt_to_store          = get_plugin_pref(COMMON_OPTIONS_STORE_NAME, KEY_PROMPT_TO_STORE)
        store_if_more_recent     = get_plugin_pref(COMMON_OPTIONS_STORE_NAME, KEY_STORE_IF_MORE_RECENT)
        do_not_store_if_reopened = get_plugin_pref(COMMON_OPTIONS_STORE_NAME, KEY_DO_NOT_STORE_IF_REOPENED)

#         do_check_for_firmware_updates = get_plugin_pref(UPDATE_OPTIONS_STORE_NAME, KEY_DO_UPDATE_CHECK)
#         do_early_firmware_updates     = get_plugin_pref(UPDATE_OPTIONS_STORE_NAME, KEY_DO_EARLY_FIRMWARE_CHECK)
#         self.update_check_last_time   = get_plugin_pref(UPDATE_OPTIONS_STORE_NAME, KEY_LAST_FIRMWARE_CHECK_TIME)

        do_daily_backup          = get_plugin_pref(BACKUP_OPTIONS_STORE_NAME, KEY_DO_DAILY_BACKUP)
        dest_directory           = get_plugin_pref(BACKUP_OPTIONS_STORE_NAME, KEY_BACKUP_DEST_DIRECTORY)
        copies_to_keep           = get_plugin_pref(BACKUP_OPTIONS_STORE_NAME, KEY_BACKUP_COPIES_TO_KEEP)
#        debug_print("current_Location_column=%s, precent_read_column=%s, rating_column=%s" % (current_Location_column, precent_read_column, rating_column))

        current_Location_label = QLabel(_('Current Reading Location Column:'), self)
        current_Location_label.setToolTip(_("Select a custom column to store the current reading location. The column type must be 'text'. Leave this blank if you do not want to store or restore the current reading location."))
        self.current_Location_combo = CustomColumnComboBox(self, avail_text_columns, current_Location_column)
        current_Location_label.setBuddy(self.current_Location_combo)
        options_layout.addWidget(current_Location_label, 0, 0, 1, 1)
        options_layout.addWidget(self.current_Location_combo, 0, 1, 1, 1)
        
        percent_read_label = QLabel(_('Percent Read Column:'), self)
        percent_read_label.setToolTip(_("Column used to store the current percent read. The column type must be a 'integer'. Leave this blank if you do not want to store or restore the percentage read."))
        self.percent_read_combo = CustomColumnComboBox(self, avail_number_columns, precent_read_column)
        percent_read_label.setBuddy(self.percent_read_combo)
        options_layout.addWidget(percent_read_label, 2, 0, 1, 1)
        options_layout.addWidget(self.percent_read_combo, 2, 1, 1, 1)

        rating_label = QLabel(_('Rating Column:'), self)
        rating_label.setToolTip(_("Column used to store the rating. The column type must be a 'integer'. Leave this blank if you do not want to store or restore the rating."))
        self.rating_combo = CustomColumnComboBox(self, avail_rating_columns, rating_column)
        rating_label.setBuddy(self.rating_combo)
        options_layout.addWidget(rating_label, 3, 0, 1, 1)
        options_layout.addWidget(self.rating_combo, 3, 1, 1, 1)

        last_read_label = QLabel(_('Last Read Column:'), self)
        last_read_label.setToolTip(_("Column used to store when the book was last read. The column type must be a 'Date'. Leave this blank if you do not want to store the last read timestamp."))
        self.last_read_combo = CustomColumnComboBox(self, avail_date_columns, last_read_column)
        last_read_label.setBuddy(self.last_read_combo)
        options_layout.addWidget(last_read_label, 4, 0, 1, 1)
        options_layout.addWidget(self.last_read_combo, 4, 1, 1, 1)

        auto_store_group = QGroupBox(_('Store on connect'), self)
        layout.addWidget(auto_store_group )
        options_layout = QGridLayout()
        auto_store_group.setLayout(options_layout)

        self.store_on_connect_checkbox = QCheckBox(_("Store current bookmarks on connect"), self)
        self.store_on_connect_checkbox.setToolTip(_("When this is checked, the library will be updated with the current bookmark for all books on the device."))
        self.store_on_connect_checkbox.setCheckState(Qt.Checked if store_on_connect else Qt.Unchecked)
        self.store_on_connect_checkbox.clicked.connect(self.store_on_connect_checkbox_clicked)
        options_layout.addWidget(self.store_on_connect_checkbox, 0, 0, 1, 3)

        self.prompt_to_store_checkbox = QCheckBox(_("Prompt to store any changes"), self)
        self.prompt_to_store_checkbox.setToolTip(_("Enable this to be prompted to save the changed bookmarks after an automatic store is done."))
        self.prompt_to_store_checkbox.setCheckState(Qt.Checked if prompt_to_store else Qt.Unchecked)
        self.prompt_to_store_checkbox.setEnabled(store_on_connect)
        options_layout.addWidget(self.prompt_to_store_checkbox, 1, 0, 1, 1)

        self.store_if_more_recent_checkbox = QCheckBox(_("Only if more recent"), self)
        self.store_if_more_recent_checkbox.setToolTip(_("Only store the reading position if the last read timestamp on the device is more recent than in the library."))
        self.store_if_more_recent_checkbox.setCheckState(Qt.Checked if store_if_more_recent else Qt.Unchecked)
        self.store_if_more_recent_checkbox.setEnabled(store_on_connect)
        options_layout.addWidget(self.store_if_more_recent_checkbox, 1, 1, 1, 1)

        self.do_not_store_if_reopened_checkbox = QCheckBox(_("Not if finished in library"), self)
        self.do_not_store_if_reopened_checkbox.setToolTip(_("Do not store the reading position if the library has the book as finished. This is if the percent read is 100%."))
        self.do_not_store_if_reopened_checkbox.setCheckState(Qt.Checked if do_not_store_if_reopened else Qt.Unchecked)
        self.do_not_store_if_reopened_checkbox.setEnabled(store_on_connect)
        options_layout.addWidget(self.do_not_store_if_reopened_checkbox, 1, 2, 1, 1)

#         update_options_group = QGroupBox(_('Firmware Update Options'), self)
#         layout.addWidget(update_options_group)
#         options_layout = QGridLayout()
#         update_options_group.setLayout(options_layout)
# 
#         self.do_update_check = QCheckBox(_('Check for Sony firmware updates daily?'), self)
#         self.do_update_check.setToolTip(_('If this is selected the plugin will check for Sony firmware updates when your Sony device is plugged in, once per 24-hour period.'))
#         self.do_update_check.setCheckState(Qt.Checked if do_check_for_firmware_updates else Qt.Unchecked)
#         options_layout.addWidget(self.do_update_check, 0, 0, 1, 1)
# 
#         self.do_early_firmware_check = QCheckBox(_('Use early firmware adopter affiliate?'), self)
#         self.do_early_firmware_check.setToolTip(_('WARNING: THIS OPTION RISKS DOWNLOADING THE WRONG FIRMWARE FOR YOUR DEVICE! YOUR DEVICE MAY NOT FUNCTION PROPERLY IF THIS HAPPENS! Choose this option to attempt to download Sony firmware updates before they are officially available for your device.'))
#         self.do_early_firmware_check.setCheckState(Qt.Checked if do_early_firmware_updates else Qt.Unchecked)
#         options_layout.addWidget(self.do_early_firmware_check, 0, 1, 1, 1)

        backup_options_group = QGroupBox(_('Device Database Backup'), self)
        layout.addWidget(backup_options_group)
        options_layout = QGridLayout()
        backup_options_group.setLayout(options_layout)

        self.do_daily_backp_checkbox = QCheckBox(_('Backup the device database daily'), self)
        self.do_daily_backp_checkbox.setToolTip(_('If this is selected the plugin will backup the device database the first time it is connected each day.'))
        self.do_daily_backp_checkbox.setCheckState(Qt.Checked if do_daily_backup else Qt.Unchecked)
        self.do_daily_backp_checkbox.clicked.connect(self.do_daily_backp_checkbox_clicked)
        options_layout.addWidget(self.do_daily_backp_checkbox, 0, 0, 1, 3)

        self.dest_directory_label = QLabel(_("Destination:"), self)
        self.dest_directory_label.setToolTip(_("Select the destination the annotations files are to be backed up in."))
        self.dest_directory_edit = QLineEdit(self)
        self.dest_directory_edit.setMinimumSize(150, 0)
        self.dest_directory_edit.setText(dest_directory)
        self.dest_directory_label.setBuddy(self.dest_directory_edit)
        self.dest_pick_button = QPushButton(_("..."), self)
        self.dest_pick_button.setMaximumSize(24, 20)
        self.dest_pick_button.clicked.connect(self._get_dest_directory_name)
        options_layout.addWidget(self.dest_directory_label, 1, 0, 1, 1)
        options_layout.addWidget(self.dest_directory_edit, 1, 1, 1, 1)
        options_layout.addWidget(self.dest_pick_button, 1, 2, 1, 1)

        self.copies_to_keep_checkbox = QCheckBox(_('Copies to keep'), self)
        self.copies_to_keep_checkbox.setToolTip(_("Select this to limit the number of backup kept. If not set, the backup files must be manually deleted."))
        self.copies_to_keep_spin = QSpinBox(self)
        self.copies_to_keep_spin.setMinimum(2)
        self.copies_to_keep_spin.setToolTip(_("The number of backup copies of the database to keep. The minimum is 2."))
        options_layout.addWidget(self.copies_to_keep_checkbox, 1, 3, 1, 1)
        options_layout.addWidget(self.copies_to_keep_spin, 1, 4, 1, 1)
        self.copies_to_keep_checkbox.clicked.connect(self.copies_to_keep_checkbox_clicked)
        if copies_to_keep == -1:
            self.copies_to_keep_checkbox.setCheckState(not Qt.Checked)
        else:
            self.copies_to_keep_checkbox.setCheckState(Qt.Checked)
            self.copies_to_keep_spin.setProperty('value', copies_to_keep)

        self.do_daily_backp_checkbox_clicked(do_daily_backup)

        other_options_group = QGroupBox(_('Other Options'), self)
        layout.addWidget(other_options_group )
        options_layout = QGridLayout()
        other_options_group.setLayout(options_layout)

        library_default_label = QLabel(_('&Library Button default:'), self)
        library_default_label.setToolTip(_('If plugin is placed as a toolbar button, choose a default action when clicked on'))
        self.library_default_combo = KeyComboBox(self, self.plugin_action.library_actions_map, unicode(get_plugin_pref(COMMON_OPTIONS_STORE_NAME, KEY_BUTTON_ACTION_LIBRARY)))
        library_default_label.setBuddy(self.library_default_combo)
        options_layout.addWidget(library_default_label, 0, 0, 1, 1)
        options_layout.addWidget(self.library_default_combo, 0, 1, 1, 2)

        device_default_label = QLabel(_('&Device Button default:'), self)
        device_default_label.setToolTip(_('If plugin is placed as a toolbar button, choose a default action when clicked on'))
        self.device_default_combo = KeyComboBox(self, self.plugin_action.device_actions_map, unicode(get_plugin_pref(COMMON_OPTIONS_STORE_NAME, KEY_BUTTON_ACTION_DEVICE)))
        device_default_label.setBuddy(self.device_default_combo)
        options_layout.addWidget(device_default_label, 1, 0, 1, 1)
        options_layout.addWidget(self.device_default_combo, 1, 1, 1, 2)

        keyboard_shortcuts_button = QPushButton(_('Keyboard shortcuts...'), self)
        keyboard_shortcuts_button.setToolTip(_('Edit the keyboard shortcuts associated with this plugin'))
        keyboard_shortcuts_button.clicked.connect(self.edit_shortcuts)
        layout.addWidget(keyboard_shortcuts_button)
        layout.addStretch(1)

    def store_on_connect_checkbox_clicked(self, checked):
        self.prompt_to_store_checkbox.setEnabled(checked)
        self.store_if_more_recent_checkbox.setEnabled(checked)
        self.do_not_store_if_reopened_checkbox.setEnabled(checked)

    def do_daily_backp_checkbox_clicked(self, checked):
        self.dest_directory_edit.setEnabled(checked)
        self.dest_pick_button.setEnabled(checked)
        self.dest_directory_label.setEnabled(checked)
        self.copies_to_keep_checkbox.setEnabled(checked)
        self.copies_to_keep_checkbox_clicked(checked and self.copies_to_keep_checkbox.checkState() == Qt.Checked)

    def copies_to_keep_checkbox_clicked(self, checked):
        self.copies_to_keep_spin.setEnabled(checked)

    # Called by Calibre before save_settings 
    def validate(self):
#        import traceback
#        traceback.print_stack()
        
        debug_print('BEGIN Validate')
        valid = True
        # Only save if we were able to get data to avoid corrupting stored data
#        if self.do_daily_backp_checkbox.checkState() == Qt.Checked and not len(self.dest_directory_edit.text()):
#            error_dialog(self, 'No destination directory',
#                            'If the automatic device backup is set, there must be a destination directory.',
#                            show=True, show_copy_button=False)
#            valid = False

        debug_print('END Validate, status = %s' % valid)
        return valid

    def save_settings(self):

        new_prefs = {}
        new_prefs[KEY_BUTTON_ACTION_DEVICE]     = unicode(self.device_default_combo.currentText())
        new_prefs[KEY_BUTTON_ACTION_LIBRARY]    = unicode(self.library_default_combo.currentText())
        new_prefs[KEY_STORE_ON_CONNECT]         = self.store_on_connect_checkbox.checkState() == Qt.Checked
        new_prefs[KEY_PROMPT_TO_STORE]          = self.prompt_to_store_checkbox.checkState() == Qt.Checked
        new_prefs[KEY_STORE_IF_MORE_RECENT]     = self.store_if_more_recent_checkbox.checkState() == Qt.Checked
        new_prefs[KEY_DO_NOT_STORE_IF_REOPENED] = self.do_not_store_if_reopened_checkbox.checkState() == Qt.Checked
        plugin_prefs[COMMON_OPTIONS_STORE_NAME] = new_prefs

        new_update_prefs = {}
#         new_update_prefs[KEY_DO_UPDATE_CHECK]          = self.do_update_check.checkState() == Qt.Checked
#         new_update_prefs[KEY_DO_EARLY_FIRMWARE_CHECK]  = self.do_early_firmware_check.checkState() == Qt.Checked
#         new_update_prefs[KEY_LAST_FIRMWARE_CHECK_TIME] = self.update_check_last_time
        plugin_prefs[UPDATE_OPTIONS_STORE_NAME]        = new_update_prefs

        backup_prefs = {}
        backup_prefs[KEY_DO_DAILY_BACKUP]       = self.do_daily_backp_checkbox.checkState() == Qt.Checked
        backup_prefs[KEY_BACKUP_DEST_DIRECTORY] = unicode(self.dest_directory_edit.text())
        backup_prefs[KEY_BACKUP_COPIES_TO_KEEP] = int(unicode(self.copies_to_keep_spin.value())) if self.copies_to_keep_checkbox.checkState() == Qt.Checked else -1 
        plugin_prefs[BACKUP_OPTIONS_STORE_NAME] = backup_prefs

        db = self.plugin_action.gui.current_db
        library_config = get_library_config(db)
        library_config[KEY_CURRENT_LOCATION_CUSTOM_COLUMN] = self.current_Location_combo.get_selected_column()
        library_config[KEY_PERCENT_READ_CUSTOM_COLUMN]     = self.percent_read_combo.get_selected_column()
        library_config[KEY_RATING_CUSTOM_COLUMN]           = self.rating_combo.get_selected_column()
        library_config[KEY_LAST_READ_CUSTOM_COLUMN]        = self.last_read_combo.get_selected_column()
        set_library_config(db, library_config)

    def get_number_custom_columns(self):
        column_types = ['float','int']
        return self.get_custom_columns(column_types)

    def get_rating_custom_columns(self):
        column_types = ['rating','int']
        custom_columns = self.get_custom_columns(column_types)
        ratings_column_name = self.plugin_action.gui.library_view.model().orig_headers['rating']
        custom_columns['rating'] = {'name': ratings_column_name}
        return custom_columns

    def get_text_custom_columns(self):
        column_types = ['text']
        return self.get_custom_columns(column_types)

    def get_date_custom_columns(self):
        column_types = ['datetime']
        return self.get_custom_columns(column_types)

    def get_custom_columns(self, column_types):
        custom_columns = self.plugin_action.gui.library_view.model().custom_columns
        available_columns = {}
        for key, column in custom_columns.iteritems():
            typ = column['datatype']
            if typ in column_types and not column['is_multiple']:
                available_columns[key] = column
        return available_columns

    def help_link_activated(self, url):
        self.plugin_action.show_help(anchor="configuration")

    def edit_shortcuts(self):
        d = KeyboardConfigDialog(self.plugin_action.gui, self.plugin_action.action_spec[0])
        if d.exec_() == d.Accepted:
            self.plugin_action.gui.keyboard.finalize()

    def _get_dest_directory_name(self):
        path = choose_dir(self, 'backup annotations destination dialog','Choose destination directory')
        if path:
            self.dest_directory_edit.setText(path)
class ConfigWidget(DefaultConfigWidget):
    def __init__(self, plugin):
        DefaultConfigWidget.__init__(self, plugin)
        c = plugin_prefs[STORE_NAME]
        all_tags = get_current_db().all_tags()

        self.gb.setMaximumHeight(80)
        genre_group_box = QGroupBox(
            _('Ridibooks genre to calibre tag mappings'), self)
        self.l.addWidget(genre_group_box, self.l.rowCount(), 0, 1, 2)
        genre_group_box_layout = QVBoxLayout()
        genre_group_box.setLayout(genre_group_box_layout)

        tags_layout = QHBoxLayout()
        genre_group_box_layout.addLayout(tags_layout)

        self.edit_table = GenreTagMappingsTableWidget(self, all_tags)
        tags_layout.addWidget(self.edit_table)
        button_layout = QVBoxLayout()
        tags_layout.addLayout(button_layout)
        add_mapping_button = QtGui.QToolButton(self)
        add_mapping_button.setToolTip(_('Add genre mapping'))
        add_mapping_button.setIcon(QIcon(I('plus.png')))
        add_mapping_button.clicked.connect(self.add_mapping)
        button_layout.addWidget(add_mapping_button)
        spacerItem1 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum,
                                        QtGui.QSizePolicy.Expanding)
        button_layout.addItem(spacerItem1)
        remove_mapping_button = QtGui.QToolButton(self)
        remove_mapping_button.setToolTip(_('Delete genre mapping'))
        remove_mapping_button.setIcon(QIcon(I('minus.png')))
        remove_mapping_button.clicked.connect(self.delete_mapping)
        button_layout.addWidget(remove_mapping_button)
        spacerItem3 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum,
                                        QtGui.QSizePolicy.Expanding)
        button_layout.addItem(spacerItem3)
        rename_genre_button = QtGui.QToolButton(self)
        rename_genre_button.setToolTip(_('Rename Goodreads genre'))
        rename_genre_button.setIcon(QIcon(I('edit-undo.png')))
        rename_genre_button.clicked.connect(self.rename_genre)
        button_layout.addWidget(rename_genre_button)
        spacerItem2 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum,
                                        QtGui.QSizePolicy.Expanding)
        button_layout.addItem(spacerItem2)
        reset_defaults_button = QtGui.QToolButton(self)
        reset_defaults_button.setToolTip(_('Reset to plugin default mappings'))
        reset_defaults_button.setIcon(QIcon(I('clear_left.png')))
        reset_defaults_button.clicked.connect(self.reset_to_defaults)
        button_layout.addWidget(reset_defaults_button)
        self.l.setRowStretch(self.l.rowCount() - 1, 2)

        other_group_box = QGroupBox(_('Other options'), self)
        self.l.addWidget(other_group_box, self.l.rowCount(), 0, 1, 2)
        other_group_box_layout = QVBoxLayout()
        other_group_box.setLayout(other_group_box_layout)

        self.get_editions_checkbox = QCheckBox(
            'Scan multiple editions for title/author searches (slower)', self)
        self.get_editions_checkbox.setToolTip(
            'When checked will perform an additional search to scan the top ranked\n'
            'Goodreads editions (if available) to exclude audiobook editions.\n'
            'Without this enabled you will get a faster search, using the "best".\n'
            'edition ranked by Goodreads which can in some cases be an audiobook.'
        )
        self.get_editions_checkbox.setChecked(c[KEY_GET_EDITIONS])
        other_group_box_layout.addWidget(self.get_editions_checkbox)
        self.all_authors_checkbox = QCheckBox(
            'Get all contributing authors (e.g. illustrators, series editors etc)',
            self)
        self.all_authors_checkbox.setToolTip(
            'Goodreads for some books will list all of the contributing authors and\n'
            'the type of contribution like (Editor), (Illustrator) etc.\n\n'
            'When this option is checked, all contributing authors are retrieved.\n\n'
            'When unchecked (default) only the primary author(s) are returned which\n'
            'are those that either have no contribution type specified, or have the\n'
            'value of (Goodreads Author).\n\n'
            'If there is no primary author then only those with the same contribution\n'
            'type as the first author are returned.\n'
            'e.g. "A, B (Illustrator)" will return author A\n'
            'e.g. "A (Goodreads Author)" will return author A\n'
            'e.g. "A (Editor), B (Editor), C (Illustrator)" will return authors A & B\n'
            'e.g. "A (Editor), B (Series Editor)" will return author A\n')
        self.all_authors_checkbox.setChecked(c[KEY_GET_ALL_AUTHORS])
        other_group_box_layout.addWidget(self.all_authors_checkbox)

        self.edit_table.populate_table(c[KEY_GENRE_MAPPINGS])

    def commit(self):
        DefaultConfigWidget.commit(self)
        new_prefs = {}
        new_prefs[KEY_GET_EDITIONS] = self.get_editions_checkbox.checkState(
        ) == Qt.Checked
        new_prefs[KEY_GET_ALL_AUTHORS] = self.all_authors_checkbox.checkState(
        ) == Qt.Checked
        new_prefs[KEY_GENRE_MAPPINGS] = self.edit_table.get_data()
        plugin_prefs[STORE_NAME] = new_prefs

    def add_mapping(self):
        new_genre_name, ok = QInputDialog.getText(
            self,
            'Add new mapping',
            'Enter a Goodreads genre name to create a mapping for:',
            text='')
        if not ok:
            # Operation cancelled
            return
        new_genre_name = unicode(new_genre_name).strip()
        if not new_genre_name:
            return
        # Verify it does not clash with any other mappings in the list
        data = self.edit_table.get_data()
        for genre_name in data.keys():
            if genre_name.lower() == new_genre_name.lower():
                return error_dialog(
                    self,
                    'Add Failed',
                    'A genre with the same name already exists',
                    show=True)
        data[new_genre_name] = []
        self.edit_table.populate_table(data)
        self.edit_table.select_genre(new_genre_name)

    def delete_mapping(self):
        if not self.edit_table.selectionModel().hasSelection():
            return
        if not question_dialog(
                self,
                _('Are you sure?'),
                '<p>' +
                'Are you sure you want to delete the selected genre mappings?',
                show_copy_button=False):
            return
        for row in reversed(
                sorted(self.edit_table.selectionModel().selectedRows())):
            self.edit_table.removeRow(row.row())

    def rename_genre(self):
        selected_genre = self.edit_table.get_selected_genre()
        if not selected_genre:
            return
        new_genre_name, ok = QInputDialog.getText(
            self,
            'Add new mapping',
            'Enter a Goodreads genre name to create a mapping for:',
            text=selected_genre)
        if not ok:
            # Operation cancelled
            return
        new_genre_name = unicode(new_genre_name).strip()
        if not new_genre_name or new_genre_name == selected_genre:
            return
        data = self.edit_table.get_data()
        if new_genre_name.lower() != selected_genre.lower():
            # Verify it does not clash with any other mappings in the list
            for genre_name in data.keys():
                if genre_name.lower() == new_genre_name.lower():
                    return error_dialog(
                        self,
                        'Rename Failed',
                        'A genre with the same name already exists',
                        show=True)
        data[new_genre_name] = data[selected_genre]
        del data[selected_genre]
        self.edit_table.populate_table(data)
        self.edit_table.select_genre(new_genre_name)

    def reset_to_defaults(self):
        if not question_dialog(
                self,
                _('Are you sure?'),
                '<p>' +
                'Are you sure you want to reset to the plugin default genre mappings?',
                show_copy_button=False):
            return
        self.edit_table.populate_table(DEFAULT_GENRE_MAPPINGS)
Beispiel #10
0
class ConfigWidget(QWidget):

    plugin = None

    def __init__(self, plugin):
        QWidget.__init__(self)

        self.plugin = plugin

        self.layout = QVBoxLayout()
        self.setLayout(self.layout)

        self.engine_location_label = QLabel('ElasticSearch engine location:')
        self.layout.addWidget(self.engine_location_label)

        self.elasticsearch_url_textbox = QLineEdit(self)
        self.elasticsearch_url_textbox.setText(prefs['elasticsearch_url'])
        self.layout.addWidget(self.elasticsearch_url_textbox)
        self.engine_location_label.setBuddy(self.elasticsearch_url_textbox)

        self.layout.addSpacing(10)

        self.pdftotext_path_label = QLabel('Path to pdftotext tool:')
        self.layout.addWidget(self.pdftotext_path_label)

        self.pdftotext_path_textbox = QLineEdit(self)
        self.pdftotext_path_textbox.setText(prefs['pdftotext_path'])
        self.layout.addWidget(self.pdftotext_path_textbox)
        self.pdftotext_path_label.setBuddy(self.pdftotext_path_textbox)

        self.layout.addSpacing(10)

        self.concurrency_label = QLabel(
            'Number of parallel processes for text extraction:')
        self.layout.addWidget(self.concurrency_label)

        self.concurrency_textbox = QLineEdit(self)
        self.concurrency_textbox.setText(str(prefs['concurrency']))
        self.layout.addWidget(self.concurrency_textbox)
        self.concurrency_label.setBuddy(self.concurrency_textbox)

        self.layout.addSpacing(10)

        self.formats_label = QLabel('Index book formats:')
        self.layout.addWidget(self.formats_label)

        file_formats = prefs['file_formats'].split(',')

        self.formats_list = QListWidget(self)
        for fmt in SUPPORTED_FORMATS:
            item = QListWidgetItem(fmt)
            item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
            item.setCheckState(Qt.Checked if fmt in
                               file_formats else Qt.Unchecked)
            self.formats_list.addItem(item)
        self.layout.addWidget(self.formats_list)
        self.formats_label.setBuddy(self.formats_list)

        self.layout.addSpacing(10)

        self.autoindex_checkbox = QCheckBox(
            "Automatically index new books on search", self)
        self.autoindex_checkbox.setCheckState(
            Qt.Checked if prefs['autoindex'] else Qt.Unchecked)
        self.layout.addWidget(self.autoindex_checkbox)

        self.layout.addSpacing(10)

        self.privacy_label = QLabel('Privacy:')
        self.layout.addWidget(self.privacy_label)

        self.clear_search_history_button = QPushButton('Clear search &history',
                                                       self)
        self.clear_search_history_button.clicked.connect(self.on_clear_history)
        self.layout.addWidget(self.clear_search_history_button)

        self.clear_search_index_buttin = QPushButton('Clear search &index',
                                                     self)
        self.clear_search_index_buttin.clicked.connect(self.on_clear_index)
        self.layout.addWidget(self.clear_search_index_buttin)

    def save_settings(self):
        prefs['elasticsearch_url'] = self.elasticsearch_url_textbox.text()
        prefs['pdftotext_path'] = self.pdftotext_path_textbox.text()
        try:
            prefs['concurrency'] = int(self.concurrency_textbox.text())
        except Exception:
            pass
        file_formats = []
        for i in range(len(SUPPORTED_FORMATS)):
            if self.formats_list.item(i).checkState() == Qt.Checked:
                file_formats.append(self.formats_list.item(i).text())
        prefs['file_formats'] = ','.join(file_formats)
        prefs['autoindex'] = True if self.autoindex_checkbox.checkState(
        ) == Qt.Checked else False

    def on_clear_history(self):
        from calibre.gui2 import info_dialog

        if 'search_lru' in prefs:
            del prefs['search_lru']

        if self.plugin.search_dialog:
            self.plugin.search_dialog.clear_lru()

        info_dialog(self, TITLE, 'History cleared', show=True)

    def on_clear_index(self):
        from calibre.gui2 import question_dialog, error_dialog

        if question_dialog(
                self,
                TITLE,
                'You are about to clear all fulltext search index. Rebuilding it might take a while. Are you sure?',
                default_yes=False):

            elastic_search_client = Elasticsearch([prefs['elasticsearch_url']],
                                                  timeout=20.0)

            if not elastic_search_client.ping():

                error_dialog(
                    self,
                    TITLE,
                    'Could not connect to ElasticSearch cluster. Please make sure that it\'s running.',
                    show=True)
                return

            elastic_search_client.indices.delete(index='library',
                                                 ignore=[400, 404])

            prefs[self.plugin.gui.current_db.new_api.library_id] = {
                'index_state': {}
            }
Beispiel #11
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()