Ejemplo n.º 1
0
class ConfigWidget(QWidget, Logger):
    '''
    Config dialog for Marvin Manager
    '''

    WIZARD_PROFILES = {
        'Annotations': {
            'label': 'mm_annotations',
            'datatype': 'comments',
            'display': {},
            'is_multiple': False
        },
        'Collections': {
            'label': 'mm_collections',
            'datatype': 'text',
            'display': {u'is_names': False},
            'is_multiple': True
        },
        'Last read': {
            'label': 'mm_date_read',
            'datatype': 'datetime',
            'display': {},
            'is_multiple': False
        },
        'Locked': {
            'label': 'mm_locked',
            'datatype': 'bool',
            'display': {},
            'is_multiple': False
        },
        'Progress': {
            'label': 'mm_progress',
            'datatype': 'float',
            'display': {u'number_format': u'{0:.0f}%'},
            'is_multiple': False
        },
        'Read': {
            'label': 'mm_read',
            'datatype': 'bool',
            'display': {},
            'is_multiple': False
        },
        'Reading list': {
            'label': 'mm_reading_list',
            'datatype': 'bool',
            'display': {},
            'is_multiple': False
        },
        'Word count': {
            'label': 'mm_word_count',
            'datatype': 'int',
            'display': {u'number_format': u'{0:n}'},
            'is_multiple': False
        }
    }

    def __init__(self, plugin_action):
        QWidget.__init__(self)
        self.parent = plugin_action

        self.gui = get_gui()
        self.icon = plugin_action.icon
        self.opts = plugin_action.opts
        self.prefs = plugin_prefs
        self.resources_path = plugin_action.resources_path
        self.verbose = plugin_action.verbose

        self.restart_required = False

        self._log_location()

        self.l = QGridLayout()
        self.setLayout(self.l)
        self.column1_layout = QVBoxLayout()
        self.l.addLayout(self.column1_layout, 0, 0)
        self.column2_layout = QVBoxLayout()
        self.l.addLayout(self.column2_layout, 0, 1)

        # ----------------------------- Column 1 -----------------------------
        # ~~~~~~~~ Create the Custom fields options group box ~~~~~~~~
        self.cfg_custom_fields_gb = QGroupBox(self)
        self.cfg_custom_fields_gb.setTitle('Custom column assignments')
        self.column1_layout.addWidget(self.cfg_custom_fields_gb)

        self.cfg_custom_fields_qgl = QGridLayout(self.cfg_custom_fields_gb)
        current_row = 0

        # ++++++++ Labels + HLine ++++++++
        self.marvin_source_label = QLabel("Marvin source")
        self.cfg_custom_fields_qgl.addWidget(self.marvin_source_label, current_row, 0)
        self.calibre_destination_label = QLabel("calibre destination")
        self.cfg_custom_fields_qgl.addWidget(self.calibre_destination_label, current_row, 1)
        current_row += 1
        self.sd_hl = QFrame(self.cfg_custom_fields_gb)
        self.sd_hl.setFrameShape(QFrame.HLine)
        self.sd_hl.setFrameShadow(QFrame.Raised)
        self.cfg_custom_fields_qgl.addWidget(self.sd_hl, current_row, 0, 1, 3)
        current_row += 1

        # ++++++++ Annotations ++++++++
        self.cfg_annotations_label = QLabel('Annotations')
        self.cfg_annotations_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_annotations_label, current_row, 0)

        self.annotations_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.annotations_field_comboBox.setObjectName('annotations_field_comboBox')
        self.annotations_field_comboBox.setToolTip('Select a custom column to store Marvin annotations')
        self.cfg_custom_fields_qgl.addWidget(self.annotations_field_comboBox, current_row, 1)

        self.cfg_highlights_wizard = QToolButton()
        self.cfg_highlights_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_highlights_wizard.setToolTip("Create a custom column to store Marvin annotations")
        self.cfg_highlights_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Annotations'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_highlights_wizard, current_row, 2)
        current_row += 1

        # ++++++++ Collections ++++++++
        self.cfg_collections_label = QLabel('Collections')
        self.cfg_collections_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_collections_label, current_row, 0)

        self.collection_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.collection_field_comboBox.setObjectName('collection_field_comboBox')
        self.collection_field_comboBox.setToolTip('Select a custom column to store Marvin collection assignments')
        self.cfg_custom_fields_qgl.addWidget(self.collection_field_comboBox, current_row, 1)

        self.cfg_collections_wizard = QToolButton()
        self.cfg_collections_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_collections_wizard.setToolTip("Create a custom column for Marvin collection assignments")
        self.cfg_collections_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Collections'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_collections_wizard, current_row, 2)
        current_row += 1

        # ++++++++ Last read ++++++++
        self.cfg_date_read_label = QLabel("Last read")
        self.cfg_date_read_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_date_read_label, current_row, 0)

        self.date_read_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.date_read_field_comboBox.setObjectName('date_read_field_comboBox')
        self.date_read_field_comboBox.setToolTip('Select a custom column to store Last read date')
        self.cfg_custom_fields_qgl.addWidget(self.date_read_field_comboBox, current_row, 1)

        self.cfg_collections_wizard = QToolButton()
        self.cfg_collections_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_collections_wizard.setToolTip("Create a custom column to store Last read date")
        self.cfg_collections_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Last read'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_collections_wizard, current_row, 2)
        current_row += 1

        # ++++++++ Locked ++++++++
        self.cfg_locked_label = QLabel("Locked")
        self.cfg_locked_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_locked_label, current_row, 0)

        self.locked_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.locked_field_comboBox.setObjectName('locked_field_comboBox')
        self.locked_field_comboBox.setToolTip('Select a custom column to store Locked status')
        self.cfg_custom_fields_qgl.addWidget(self.locked_field_comboBox, current_row, 1)

        self.cfg_locked_wizard = QToolButton()
        self.cfg_locked_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_locked_wizard.setToolTip("Create a custom column to store Locked status")
        self.cfg_locked_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Locked'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_locked_wizard, current_row, 2)
        current_row += 1

        # ++++++++ Progress ++++++++
        self.cfg_progress_label = QLabel('Progress')
        self.cfg_progress_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_progress_label, current_row, 0)

        self.progress_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.progress_field_comboBox.setObjectName('progress_field_comboBox')
        self.progress_field_comboBox.setToolTip('Select a custom column to store Marvin reading progress')
        self.cfg_custom_fields_qgl.addWidget(self.progress_field_comboBox, current_row, 1)

        self.cfg_progress_wizard = QToolButton()
        self.cfg_progress_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_progress_wizard.setToolTip("Create a custom column to store Marvin reading progress")
        self.cfg_progress_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Progress'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_progress_wizard, current_row, 2)
        current_row += 1

        # ++++++++ Read flag ++++++++
        self.cfg_read_label = QLabel('Read')
        self.cfg_read_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_read_label, current_row, 0)

        self.read_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.read_field_comboBox.setObjectName('read_field_comboBox')
        self.read_field_comboBox.setToolTip('Select a custom column to store Marvin Read status')
        self.cfg_custom_fields_qgl.addWidget(self.read_field_comboBox, current_row, 1)

        self.cfg_read_wizard = QToolButton()
        self.cfg_read_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_read_wizard.setToolTip("Create a custom column to store Marvin Read status")
        self.cfg_read_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Read'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_read_wizard, current_row, 2)
        current_row += 1

        # ++++++++ Reading list flag ++++++++
        self.cfg_reading_list_label = QLabel('Reading list')
        self.cfg_reading_list_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_reading_list_label, current_row, 0)

        self.reading_list_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.reading_list_field_comboBox.setObjectName('reading_list_field_comboBox')
        self.reading_list_field_comboBox.setToolTip('Select a custom column to store Marvin Reading list status')
        self.cfg_custom_fields_qgl.addWidget(self.reading_list_field_comboBox, current_row, 1)

        self.cfg_reading_list_wizard = QToolButton()
        self.cfg_reading_list_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_reading_list_wizard.setToolTip("Create a custom column to store Marvin Reading list status")
        self.cfg_reading_list_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Reading list'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_reading_list_wizard, current_row, 2)
        current_row += 1

        # ++++++++ Word count ++++++++
        self.cfg_word_count_label = QLabel('Word count')
        self.cfg_word_count_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_word_count_label, current_row, 0)

        self.word_count_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.word_count_field_comboBox.setObjectName('word_count_field_comboBox')
        self.word_count_field_comboBox.setToolTip('Select a custom column to store Marvin word counts')
        self.cfg_custom_fields_qgl.addWidget(self.word_count_field_comboBox, current_row, 1)

        self.cfg_word_count_wizard = QToolButton()
        self.cfg_word_count_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_word_count_wizard.setToolTip("Create a custom column to store Marvin word counts")
        self.cfg_word_count_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Word count'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_word_count_wizard, current_row, 2)
        current_row += 1

        self.spacerItem1 = QSpacerItem(20, 20, QSizePolicy.Minimum, QSizePolicy.Expanding)
        self.column1_layout.addItem(self.spacerItem1)

        # ----------------------------- Column 2 -----------------------------
        # ~~~~~~~~ Create the CSS group box ~~~~~~~~
        self.cfg_css_options_gb = QGroupBox(self)
        self.cfg_css_options_gb.setTitle('CSS')
        self.column2_layout.addWidget(self.cfg_css_options_gb)
        self.cfg_css_options_qgl = QGridLayout(self.cfg_css_options_gb)

        current_row = 0

        # ++++++++ Annotations appearance ++++++++
        self.annotations_icon = QIcon(os.path.join(self.resources_path, 'icons', 'annotations_hiliter.png'))
        self.cfg_annotations_appearance_toolbutton = QToolButton()
        self.cfg_annotations_appearance_toolbutton.setIcon(self.annotations_icon)
        self.cfg_annotations_appearance_toolbutton.clicked.connect(self.configure_appearance)
        self.cfg_css_options_qgl.addWidget(self.cfg_annotations_appearance_toolbutton, current_row, 0)
        self.cfg_annotations_label = ClickableQLabel("Book notes, Bookmark notes and Annotations")
        self.connect(self.cfg_annotations_label, SIGNAL('clicked()'), self.configure_appearance)
        self.cfg_css_options_qgl.addWidget(self.cfg_annotations_label, current_row, 1)
        current_row += 1

        # ++++++++ Injected CSS ++++++++
        self.css_editor_icon = QIcon(I('format-text-heading.png'))
        self.cfg_css_editor_toolbutton = QToolButton()
        self.cfg_css_editor_toolbutton.setIcon(self.css_editor_icon)
        self.cfg_css_editor_toolbutton.clicked.connect(self.edit_css)
        self.cfg_css_options_qgl.addWidget(self.cfg_css_editor_toolbutton, current_row, 0)
        self.cfg_css_editor_label = ClickableQLabel("Articles, Vocabulary")
        self.connect(self.cfg_css_editor_label, SIGNAL('clicked()'), self.edit_css)
        self.cfg_css_options_qgl.addWidget(self.cfg_css_editor_label, current_row, 1)


        """
        # ~~~~~~~~ Create the Dropbox syncing group box ~~~~~~~~
        self.cfg_dropbox_syncing_gb = QGroupBox(self)
        self.cfg_dropbox_syncing_gb.setTitle('Dropbox')
        self.column2_layout.addWidget(self.cfg_dropbox_syncing_gb)
        self.cfg_dropbox_syncing_qgl = QGridLayout(self.cfg_dropbox_syncing_gb)
        current_row = 0

        # ++++++++ Syncing enabled checkbox ++++++++
        self.dropbox_syncing_checkbox = QCheckBox('Enable Dropbox updates')
        self.dropbox_syncing_checkbox.setObjectName('dropbox_syncing')
        self.dropbox_syncing_checkbox.setToolTip('Refresh custom column content from Marvin metadata')
        self.cfg_dropbox_syncing_qgl.addWidget(self.dropbox_syncing_checkbox,
            current_row, 0, 1, 3)
        current_row += 1

        # ++++++++ Dropbox folder picker ++++++++
        self.dropbox_folder_icon = QIcon(os.path.join(self.resources_path, 'icons', 'dropbox.png'))
        self.cfg_dropbox_folder_toolbutton = QToolButton()
        self.cfg_dropbox_folder_toolbutton.setIcon(self.dropbox_folder_icon)
        self.cfg_dropbox_folder_toolbutton.setToolTip("Specify Dropbox folder location on your computer")
        self.cfg_dropbox_folder_toolbutton.clicked.connect(self.select_dropbox_folder)
        self.cfg_dropbox_syncing_qgl.addWidget(self.cfg_dropbox_folder_toolbutton,
            current_row, 1)

        # ++++++++ Dropbox location lineedit ++++++++
        self.dropbox_location_lineedit = QLineEdit()
        self.dropbox_location_lineedit.setPlaceholderText("Dropbox folder location")
        self.cfg_dropbox_syncing_qgl.addWidget(self.dropbox_location_lineedit,
            current_row, 2)
        """

        # ~~~~~~~~ Create the General options group box ~~~~~~~~
        self.cfg_runtime_options_gb = QGroupBox(self)
        self.cfg_runtime_options_gb.setTitle('General options')
        self.column2_layout.addWidget(self.cfg_runtime_options_gb)
        self.cfg_runtime_options_qvl = QVBoxLayout(self.cfg_runtime_options_gb)

        # ++++++++ Temporary markers: Duplicates ++++++++
        self.duplicate_markers_checkbox = QCheckBox('Apply temporary markers to duplicate books')
        self.duplicate_markers_checkbox.setObjectName('apply_markers_to_duplicates')
        self.duplicate_markers_checkbox.setToolTip('Books with identical content will be flagged in the Library window')
        self.cfg_runtime_options_qvl.addWidget(self.duplicate_markers_checkbox)

        # ++++++++ Temporary markers: Updated ++++++++
        self.updated_markers_checkbox = QCheckBox('Apply temporary markers to books with updated content')
        self.updated_markers_checkbox.setObjectName('apply_markers_to_updated')
        self.updated_markers_checkbox.setToolTip('Books with updated content will be flagged in the Library window')
        self.cfg_runtime_options_qvl.addWidget(self.updated_markers_checkbox)

        # ++++++++ Auto refresh checkbox ++++++++
        self.auto_refresh_checkbox = QCheckBox('Automatically refresh custom column content')
        self.auto_refresh_checkbox.setObjectName('auto_refresh_at_startup')
        self.auto_refresh_checkbox.setToolTip('Update calibre custom column when Marvin XD is opened')
        self.cfg_runtime_options_qvl.addWidget(self.auto_refresh_checkbox)

        # ++++++++ Progress as percentage checkbox ++++++++
        self.reading_progress_checkbox = QCheckBox('Show reading progress as percentage')
        self.reading_progress_checkbox.setObjectName('show_progress_as_percentage')
        self.reading_progress_checkbox.setToolTip('Display percentage in Progress column')
        self.cfg_runtime_options_qvl.addWidget(self.reading_progress_checkbox)

        # ~~~~~~~~ Create the Debug options group box ~~~~~~~~
        self.cfg_debug_options_gb = QGroupBox(self)
        self.cfg_debug_options_gb.setTitle('Debug options')
        self.column2_layout.addWidget(self.cfg_debug_options_gb)
        self.cfg_debug_options_qvl = QVBoxLayout(self.cfg_debug_options_gb)

        # ++++++++ Debug logging checkboxes ++++++++
        self.debug_plugin_checkbox = QCheckBox('Enable debug logging for Marvin XD')
        self.debug_plugin_checkbox.setObjectName('debug_plugin_checkbox')
        self.debug_plugin_checkbox.setToolTip('Print plugin diagnostic messages to console')
        self.cfg_debug_options_qvl.addWidget(self.debug_plugin_checkbox)

        self.debug_libimobiledevice_checkbox = QCheckBox('Enable debug logging for libiMobileDevice')
        self.debug_libimobiledevice_checkbox.setObjectName('debug_libimobiledevice_checkbox')
        self.debug_libimobiledevice_checkbox.setToolTip('Print libiMobileDevice diagnostic messages to console')
        self.cfg_debug_options_qvl.addWidget(self.debug_libimobiledevice_checkbox)

        self.spacerItem2 = QSpacerItem(20, 20, QSizePolicy.Minimum, QSizePolicy.Expanding)
        self.column2_layout.addItem(self.spacerItem2)

        # ~~~~~~~~ End of construction zone ~~~~~~~~
        self.resize(self.sizeHint())

        # ~~~~~~~~ Populate/restore config options ~~~~~~~~
        #  Annotations comboBox
        self.populate_annotations()
        self.populate_collections()
        self.populate_date_read()
        self.populate_locked()
        self.populate_progress()
        self.populate_read()
        self.populate_reading_list()
        self.populate_word_count()

        """
        # Restore Dropbox settings, hook changes
        dropbox_syncing = self.prefs.get('dropbox_syncing', False)
        self.dropbox_syncing_checkbox.setChecked(dropbox_syncing)
        self.set_dropbox_syncing(dropbox_syncing)
        self.dropbox_syncing_checkbox.clicked.connect(partial(self.set_dropbox_syncing))
        self.dropbox_location_lineedit.setText(self.prefs.get('dropbox_folder', ''))
        """

        # Restore general settings
        self.duplicate_markers_checkbox.setChecked(self.prefs.get('apply_markers_to_duplicates', True))
        self.updated_markers_checkbox.setChecked(self.prefs.get('apply_markers_to_updated', True))
        self.auto_refresh_checkbox.setChecked(self.prefs.get('auto_refresh_at_startup', False))
        self.reading_progress_checkbox.setChecked(self.prefs.get('show_progress_as_percentage', False))

        # Restore debug settings, hook changes
        self.debug_plugin_checkbox.setChecked(self.prefs.get('debug_plugin', False))
        self.debug_plugin_checkbox.stateChanged.connect(self.set_restart_required)
        self.debug_libimobiledevice_checkbox.setChecked(self.prefs.get('debug_libimobiledevice', False))
        self.debug_libimobiledevice_checkbox.stateChanged.connect(self.set_restart_required)

        # Hook changes to Annotations comboBox
#         self.annotations_field_comboBox.currentIndexChanged.connect(
#             partial(self.save_combobox_setting, 'annotations_field_comboBox'))
        self.connect(self.annotations_field_comboBox,
                     SIGNAL('currentIndexChanged(const QString &)'),
                     self.annotations_destination_changed)

        # Launch the annotated_books_scanner
        field = get_cc_mapping('annotations', 'field', None)
        self.annotated_books_scanner = InventoryAnnotatedBooks(self.gui, field)
        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(str(qs_new_destination_name))
        #self._log("self.eligible_annotations_fields: %s" % self.eligible_annotations_fields)

        old_destination_field = get_cc_mapping('annotations', 'field', None)
        old_destination_name = get_cc_mapping('annotations', 'combobox', None)

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

        new_destination_name = unicode(qs_new_destination_name)
        self._log("new_destination_name: %s" % repr(new_destination_name))

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

        if new_destination_name == '':
            self._log_location("annotations storage disabled")
            set_cc_mapping('annotations', field=None, combobox=new_destination_name)
            return

        new_destination_field = self.eligible_annotations_fields[new_destination_name]

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

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

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

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

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

        else:
            # No existing annotations, just update prefs
            self._log("no existing annotations, updating destination to '{0}'".format(new_destination_name))
            set_cc_mapping('annotations', field=new_destination_field, combobox=new_destination_name)

    def configure_appearance(self):
        '''
        '''
        self._log_location()
        appearance_settings = {
                                'appearance_css': default_elements,
                                'appearance_hr_checkbox': False,
                                'appearance_timestamp_format': default_timestamp
                              }

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

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

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

                # Wait for indexing to complete
                while not self.annotated_books_scanner.isFinished():
                    Application.processEvents()

                move_annotations(self, self.annotated_books_scanner.annotation_map,
                    field, field, window_title="Updating appearance")

    def edit_css(self):
        '''
        '''
        self._log_location()
        from calibre_plugins.marvin_manager.book_status import dialog_resources_path
        klass = os.path.join(dialog_resources_path, 'css_editor.py')
        if os.path.exists(klass):
            sys.path.insert(0, dialog_resources_path)
            this_dc = importlib.import_module('css_editor')
            sys.path.remove(dialog_resources_path)
            dlg = this_dc.CSSEditorDialog(self, 'css_editor')
            dlg.initialize(self)
            dlg.exec_()

    def get_eligible_custom_fields(self, eligible_types=[], is_multiple=None):
        '''
        Discover qualifying custom fields for eligible_types[]
        '''
        #self._log_location(eligible_types)

        eligible_custom_fields = {}
        for cf in self.gui.current_db.custom_field_keys():
            cft = self.gui.current_db.metadata_for_field(cf)['datatype']
            cfn = self.gui.current_db.metadata_for_field(cf)['name']
            cfim = self.gui.current_db.metadata_for_field(cf)['is_multiple']
            #self._log("cf: %s  cft: %s  cfn: %s cfim: %s" % (cf, cft, cfn, cfim))
            if cft in eligible_types:
                if is_multiple is not None:
                    if bool(cfim) == is_multiple:
                        eligible_custom_fields[cfn] = cf
                else:
                    eligible_custom_fields[cfn] = cf
        return eligible_custom_fields

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

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

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

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

            cb.blockSignals(False)

        from calibre_plugins.marvin_manager.book_status import dialog_resources_path

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

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

                self.restart_required = True

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

                if source == "Annotations":
                    _update_combo_box("annotations_field_comboBox", destination, previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_annotations_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('annotations', combobox=destination, field=label)

                elif source == 'Collections':
                    _update_combo_box("collection_field_comboBox", destination, previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_collection_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('collections', combobox=destination, field=label)

                elif source == 'Last read':
                    _update_combo_box("date_read_field_comboBox", destination, previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_date_read_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('date_read', combobox=destination, field=label)

                elif source == 'Locked':
                    _update_combo_box("locked_field_comboBox", destination, previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_locked_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('locked', combobox=destination, field=label)

                elif source == "Progress":
                    _update_combo_box("progress_field_comboBox", destination, previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_progress_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('progress', combobox=destination, field=label)

                elif source == "Read":
                    _update_combo_box("read_field_comboBox", destination, previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_read_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('read', combobox=destination, field=label)

                elif source == "Reading list":
                    _update_combo_box("reading_list_field_comboBox", destination, previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_reading_list_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('reading_list', combobox=destination, field=label)

                elif source == "Word count":
                    _update_combo_box("word_count_field_comboBox", destination, previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_word_count_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('word_count', combobox=destination, field=label)
        else:
            self._log("ERROR: Can't import from '%s'" % klass)

    def launch_new_destination_dialog(self, old, new):
        '''
        Return 'move', 'change' or 'cancel'
        '''
        from calibre_plugins.marvin_manager.book_status import dialog_resources_path
        self._log_location()
        klass = os.path.join(dialog_resources_path, 'new_destination.py')
        if os.path.exists(klass):
            self._log("importing new destination dialog from '%s'" % klass)
            sys.path.insert(0, dialog_resources_path)
            this_dc = importlib.import_module('new_destination')
            sys.path.remove(dialog_resources_path)
            dlg = this_dc.NewDestinationDialog(self, old, new)
            dlg.exec_()
            return dlg.command

    def populate_annotations(self):
        datatype = self.WIZARD_PROFILES['Annotations']['datatype']
        self.eligible_annotations_fields = self.get_eligible_custom_fields([datatype])
        self.annotations_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_annotations_fields.keys(), key=lambda s: s.lower())
        self.annotations_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('annotations', 'combobox')
        if existing:
            ci = self.annotations_field_comboBox.findText(existing)
            self.annotations_field_comboBox.setCurrentIndex(ci)

    def populate_collections(self):
        datatype = self.WIZARD_PROFILES['Collections']['datatype']
        self.eligible_collection_fields = self.get_eligible_custom_fields([datatype],
                                                                          is_multiple=True)
        self.collection_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_collection_fields.keys(), key=lambda s: s.lower())
        self.collection_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('collections', 'combobox')
        if existing:
            ci = self.collection_field_comboBox.findText(existing)
            self.collection_field_comboBox.setCurrentIndex(ci)

    def populate_date_read(self):
        #self.eligible_date_read_fields = self.get_eligible_custom_fields(['datetime'])
        datatype = self.WIZARD_PROFILES['Last read']['datatype']
        self.eligible_date_read_fields = self.get_eligible_custom_fields([datatype])
        self.date_read_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_date_read_fields.keys(), key=lambda s: s.lower())
        self.date_read_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('date_read', 'combobox')
        if existing:
            ci = self.date_read_field_comboBox.findText(existing)
            self.date_read_field_comboBox.setCurrentIndex(ci)

    def populate_locked(self):
        datatype = self.WIZARD_PROFILES['Locked']['datatype']
        self.eligible_locked_fields = self.get_eligible_custom_fields([datatype])
        self.locked_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_locked_fields.keys(), key=lambda s: s.lower())
        self.locked_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('locked', 'combobox')
        if existing:
            ci = self.locked_field_comboBox.findText(existing)
            self.locked_field_comboBox.setCurrentIndex(ci)

    def populate_progress(self):
        #self.eligible_progress_fields = self.get_eligible_custom_fields(['float'])
        datatype = self.WIZARD_PROFILES['Progress']['datatype']
        self.eligible_progress_fields = self.get_eligible_custom_fields([datatype])
        self.progress_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_progress_fields.keys(), key=lambda s: s.lower())
        self.progress_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('progress', 'combobox')
        if existing:
            ci = self.progress_field_comboBox.findText(existing)
            self.progress_field_comboBox.setCurrentIndex(ci)

    def populate_read(self):
        datatype = self.WIZARD_PROFILES['Read']['datatype']
        self.eligible_read_fields = self.get_eligible_custom_fields([datatype])
        self.read_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_read_fields.keys(), key=lambda s: s.lower())
        self.read_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('read', 'combobox')
        if existing:
            ci = self.read_field_comboBox.findText(existing)
            self.read_field_comboBox.setCurrentIndex(ci)

    def populate_reading_list(self):
        datatype = self.WIZARD_PROFILES['Reading list']['datatype']
        self.eligible_reading_list_fields = self.get_eligible_custom_fields([datatype])
        self.reading_list_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_reading_list_fields.keys(), key=lambda s: s.lower())
        self.reading_list_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('reading_list', 'combobox')
        if existing:
            ci = self.reading_list_field_comboBox.findText(existing)
            self.reading_list_field_comboBox.setCurrentIndex(ci)

    def populate_word_count(self):
        #self.eligible_word_count_fields = self.get_eligible_custom_fields(['int'])
        datatype = self.WIZARD_PROFILES['Word count']['datatype']
        self.eligible_word_count_fields = self.get_eligible_custom_fields([datatype])
        self.word_count_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_word_count_fields.keys(), key=lambda s: s.lower())
        self.word_count_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('word_count', 'combobox')
        if existing:
            ci = self.word_count_field_comboBox.findText(existing)
            self.word_count_field_comboBox.setCurrentIndex(ci)

    """
    def select_dropbox_folder(self):
        '''
        '''
        self._log_location()
        dropbox_location = QFileDialog.getExistingDirectory(
            self,
            "Dropbox folder",
            os.path.expanduser("~"),
            QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks)
        self.dropbox_location_lineedit.setText(unicode(dropbox_location))

    def set_dropbox_syncing(self, state):
        '''
        Called when checkbox changes state, or when restoring state
        Set enabled state of Dropbox folder picker to match
        '''
        self.cfg_dropbox_folder_toolbutton.setEnabled(state)
        self.dropbox_location_lineedit.setEnabled(state)
    """

    def set_restart_required(self, state):
        '''
        Set restart_required flag to show show dialog when closing dialog
        '''
        self.restart_required = True

    """
    def save_combobox_setting(self, cb, index):
        '''
        Apply changes immediately
        '''
        cf = str(getattr(self, cb).currentText())
        self._log_location("%s => %s" % (cb, repr(cf)))

        if cb == 'annotations_field_comboBox':
            field = None
            if cf:
                field = self.eligible_annotations_fields[cf]
            set_cc_mapping('annotations', combobox=cf, field=field)
    """

    def save_settings(self):
        self._log_location()

        # Annotations field
        cf = unicode(self.annotations_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_annotations_fields[cf]
        set_cc_mapping('annotations', combobox=cf, field=field)

        # Collections field
        cf = unicode(self.collection_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_collection_fields[cf]
        set_cc_mapping('collections', combobox=cf, field=field)

        # Date read field
        cf = unicode(self.date_read_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_date_read_fields[cf]
        set_cc_mapping('date_read', combobox=cf, field=field)

        # Locked field
        cf = unicode(self.locked_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_locked_fields[cf]
        set_cc_mapping('locked', combobox=cf, field=field)

        # Progress field
        cf = unicode(self.progress_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_progress_fields[cf]
        set_cc_mapping('progress', combobox=cf, field=field)

        # Read field
        cf = unicode(self.read_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_read_fields[cf]
        set_cc_mapping('read', combobox=cf, field=field)

        # Reading list field
        cf = unicode(self.reading_list_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_reading_list_fields[cf]
        set_cc_mapping('reading_list', combobox=cf, field=field)

        # Word count field
        cf = unicode(self.word_count_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_word_count_fields[cf]
        set_cc_mapping('word_count', combobox=cf, field=field)

        '''
        # Save Dropbox settings
        self.prefs.set('dropbox_syncing', self.dropbox_syncing_checkbox.isChecked())
        self.prefs.set('dropbox_folder', unicode(self.dropbox_location_lineedit.text()))
        '''

        # Save general settings
        self.prefs.set('apply_markers_to_duplicates', self.duplicate_markers_checkbox.isChecked())
        self.prefs.set('apply_markers_to_updated', self.updated_markers_checkbox.isChecked())
        self.prefs.set('auto_refresh_at_startup', self.auto_refresh_checkbox.isChecked())
        self.prefs.set('show_progress_as_percentage', self.reading_progress_checkbox.isChecked())

        # Save debug settings
        self.prefs.set('debug_plugin', self.debug_plugin_checkbox.isChecked())
        self.prefs.set('debug_libimobiledevice', self.debug_libimobiledevice_checkbox.isChecked())

        # If restart needed, inform user
        if self.restart_required:
            do_restart = show_restart_warning('Restart calibre for the changes to be applied.',
                                              parent=self.gui)
            if do_restart:
                self.gui.quit(restart=True)

    def start_inventory(self):
        self._log_location()
        self.annotated_books_scanner.start()
Ejemplo n.º 2
0
class CreateVirtualLibrary(QDialog):  # {{{

    def __init__(self, gui, existing_names, editing=None):
        QDialog.__init__(self, gui)

        self.gui = gui
        self.existing_names = existing_names

        if editing:
            self.setWindowTitle(_('Edit virtual library'))
        else:
            self.setWindowTitle(_('Create virtual library'))
        self.setWindowIcon(QIcon(I('lt.png')))

        gl = QGridLayout()
        self.setLayout(gl)
        self.la1 = la1 = QLabel(_('Virtual library &name:'))
        gl.addWidget(la1, 0, 0)
        self.vl_name = QComboBox()
        self.vl_name.setEditable(True)
        self.vl_name.lineEdit().setMaxLength(MAX_VIRTUAL_LIBRARY_NAME_LENGTH)
        la1.setBuddy(self.vl_name)
        gl.addWidget(self.vl_name, 0, 1)
        self.editing = editing

        self.saved_searches_label = QLabel('')
        self.saved_searches_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
        gl.addWidget(self.saved_searches_label, 2, 0, 1, 2)

        self.la2 = la2 = QLabel(_('&Search expression:'))
        gl.addWidget(la2, 1, 0)
        self.vl_text = QLineEdit()
        self.vl_text.textChanged.connect(self.search_text_changed)
        la2.setBuddy(self.vl_text)
        gl.addWidget(self.vl_text, 1, 1)
        self.vl_text.setText(_build_full_search_string(self.gui))

        self.sl = sl = QLabel('<p>'+_('Create a virtual library based on: ')+
            ('<a href="author.{0}">{0}</a>, '
            '<a href="tag.{1}">{1}</a>, '
            '<a href="publisher.{2}">{2}</a>, '
            '<a href="series.{3}">{3}</a>, '
            '<a href="search.{4}">{4}</a>.').format(_('Authors'), _('Tags'),
                                            _('Publishers'), _('Series'), _('Saved Searches')))
        sl.setWordWrap(True)
        sl.setTextInteractionFlags(Qt.LinksAccessibleByMouse)
        sl.linkActivated.connect(self.link_activated)
        gl.addWidget(sl, 3, 0, 1, 2)
        gl.setRowStretch(3,10)

        self.hl = hl = QLabel(_('''
            <h2>Virtual Libraries</h2>

            <p>Using <i>virtual libraries</i> you can restrict calibre to only show
            you books that match a search. When a virtual library is in effect, calibre
            behaves as though the library contains only the matched books. The Tag Browser
            display only the tags/authors/series/etc. that belong to the matched books and any searches
            you do will only search within the books in the virtual library. This
            is a good way to partition your large library into smaller and easier to work with subsets.</p>

            <p>For example you can use a Virtual Library to only show you books with the Tag <i>"Unread"</i>
            or only books by <i>"My Favorite Author"</i> or only books in a particular series.</p>

            <p>More information and examples are available in the
            <a href="http://manual.calibre-ebook.com/virtual_libraries.html">User Manual</a>.</p>
            '''))
        hl.setWordWrap(True)
        hl.setOpenExternalLinks(True)
        hl.setFrameStyle(hl.StyledPanel)
        gl.addWidget(hl, 0, 3, 4, 1)

        bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        bb.accepted.connect(self.accept)
        bb.rejected.connect(self.reject)
        gl.addWidget(bb, 4, 0, 1, 0)

        if editing:
            db = self.gui.current_db
            virt_libs = db.prefs.get('virtual_libraries', {})
            for dex,vl in enumerate(sorted(virt_libs.keys(), key=sort_key)):
                self.vl_name.addItem(vl, virt_libs.get(vl, ''))
                if vl == editing:
                    self.vl_name.setCurrentIndex(dex)
                    self.original_index = dex
            self.original_search = virt_libs.get(editing, '')
            self.vl_text.setText(self.original_search)
            self.new_name = editing
            self.vl_name.currentIndexChanged[int].connect(self.name_index_changed)
            self.vl_name.lineEdit().textEdited.connect(self.name_text_edited)

        self.resize(self.sizeHint()+QSize(150, 25))

    def search_text_changed(self, txt):
        db = self.gui.current_db
        searches = [_('Saved searches recognized in the expression:')]
        txt = unicode(txt)
        while txt:
            p = txt.partition('search:')
            if p[1]:  # found 'search:'
                possible_search = p[2]
                if possible_search:  # something follows the 'search:'
                    if possible_search[0] == '"':  # strip any quotes
                        possible_search = possible_search[1:].partition('"')
                    else:  # find end of the search name. Is EOL, space, rparen
                        sp = possible_search.find(' ')
                        pp = possible_search.find(')')
                        if pp < 0 or (sp > 0 and sp <= pp):
                            # space in string before rparen, or neither found
                            possible_search = possible_search.partition(' ')
                        else:
                            # rparen in string before space
                            possible_search = possible_search.partition(')')
                    txt = possible_search[2]  # grab remainder of the string
                    search_name = possible_search[0]
                    if search_name.startswith('='):
                        search_name = search_name[1:]
                    if search_name in db.saved_search_names():
                        searches.append(search_name + '=' +
                                        db.saved_search_lookup(search_name))
                else:
                    txt = ''
            else:
                txt = ''
        if len(searches) > 1:
            self.saved_searches_label.setText('\n'.join(searches))
        else:
            self.saved_searches_label.setText('')

    def name_text_edited(self, new_name):
        self.new_name = unicode(new_name)

    def name_index_changed(self, dex):
        if self.editing and (self.vl_text.text() != self.original_search or
                             self.new_name != self.editing):
            if not question_dialog(self.gui, _('Search text changed'),
                         _('The virtual library name or the search text has changed. '
                           'Do you want to discard these changes?'),
                         default_yes=False):
                self.vl_name.blockSignals(True)
                self.vl_name.setCurrentIndex(self.original_index)
                self.vl_name.lineEdit().setText(self.new_name)
                self.vl_name.blockSignals(False)
                return
        self.new_name = self.editing = self.vl_name.currentText()
        self.original_index = dex
        self.original_search = unicode(self.vl_name.itemData(dex).toString())
        self.vl_text.setText(self.original_search)

    def link_activated(self, url):
        db = self.gui.current_db
        f, txt = unicode(url).partition('.')[0::2]
        if f == 'search':
            names = db.saved_search_names()
        else:
            names = getattr(db, 'all_%s_names'%f)()
        d = SelectNames(names, txt, parent=self)
        if d.exec_() == d.Accepted:
            prefix = f+'s' if f in {'tag', 'author'} else f
            if f == 'search':
                search = ['(%s)'%(db.saved_search_lookup(x)) for x in d.names]
            else:
                search = ['%s:"=%s"'%(prefix, x.replace('"', '\\"')) for x in d.names]
            if search:
                if not self.editing:
                    self.vl_name.lineEdit().setText(d.names.next())
                    self.vl_name.lineEdit().setCursorPosition(0)
                self.vl_text.setText(d.match_type.join(search))
                self.vl_text.setCursorPosition(0)

    def accept(self):
        n = unicode(self.vl_name.currentText()).strip()
        if not n:
            error_dialog(self.gui, _('No name'),
                         _('You must provide a name for the new virtual library'),
                         show=True)
            return

        if n.startswith('*'):
            error_dialog(self.gui, _('Invalid name'),
                         _('A virtual library name cannot begin with "*"'),
                         show=True)
            return

        if n in self.existing_names and n != self.editing:
            if not question_dialog(self.gui, _('Name already in use'),
                         _('That name is already in use. Do you want to replace it '
                           'with the new search?'),
                            default_yes=False):
                return

        v = unicode(self.vl_text.text()).strip()
        if not v:
            error_dialog(self.gui, _('No search string'),
                         _('You must provide a search to define the new virtual library'),
                         show=True)
            return

        try:
            db = self.gui.library_view.model().db
            recs = db.data.search_getting_ids('', v, use_virtual_library=False)
        except ParseException as e:
            error_dialog(self.gui, _('Invalid search'),
                         _('The search in the search box is not valid'),
                         det_msg=e.msg, show=True)
            return

        if not recs and not question_dialog(
                self.gui, _('Search found no books'),
                _('The search found no books, so the virtual library '
                'will be empty. Do you really want to use that search?'),
                default_yes=False):
                return

        self.library_name = n
        self.library_search = v
        QDialog.accept(self)
Ejemplo n.º 3
0
 def currentText(self):
     if self.currentIndex() == 0:
         return ''
     return QComboBox.currentText(self)
Ejemplo n.º 4
0
class CreateVirtualLibrary(QDialog):  # {{{
    def __init__(self, gui, existing_names, editing=None):
        QDialog.__init__(self, gui)

        self.gui = gui
        self.existing_names = existing_names

        if editing:
            self.setWindowTitle(_('Edit virtual library'))
        else:
            self.setWindowTitle(_('Create virtual library'))
        self.setWindowIcon(QIcon(I('lt.png')))

        gl = QGridLayout()
        self.setLayout(gl)
        self.la1 = la1 = QLabel(_('Virtual library &name:'))
        gl.addWidget(la1, 0, 0)
        self.vl_name = QComboBox()
        self.vl_name.setEditable(True)
        self.vl_name.lineEdit().setMaxLength(MAX_VIRTUAL_LIBRARY_NAME_LENGTH)
        la1.setBuddy(self.vl_name)
        gl.addWidget(self.vl_name, 0, 1)
        self.editing = editing

        self.saved_searches_label = QLabel('')
        self.saved_searches_label.setTextInteractionFlags(
            Qt.TextSelectableByMouse)
        gl.addWidget(self.saved_searches_label, 2, 0, 1, 2)

        self.la2 = la2 = QLabel(_('&Search expression:'))
        gl.addWidget(la2, 1, 0)
        self.vl_text = QLineEdit()
        self.vl_text.textChanged.connect(self.search_text_changed)
        la2.setBuddy(self.vl_text)
        gl.addWidget(self.vl_text, 1, 1)
        self.vl_text.setText(_build_full_search_string(self.gui))

        self.sl = sl = QLabel(
            '<p>' + _('Create a virtual library based on: ') +
            ('<a href="author.{0}">{0}</a>, '
             '<a href="tag.{1}">{1}</a>, '
             '<a href="publisher.{2}">{2}</a>, '
             '<a href="series.{3}">{3}</a>, '
             '<a href="search.{4}">{4}</a>.').format(_('Authors'), _(
                 'Tags'), _('Publishers'), _('Series'), _('Saved Searches')))
        sl.setWordWrap(True)
        sl.setTextInteractionFlags(Qt.LinksAccessibleByMouse)
        sl.linkActivated.connect(self.link_activated)
        gl.addWidget(sl, 3, 0, 1, 2)
        gl.setRowStretch(3, 10)

        self.hl = hl = QLabel(
            _('''
            <h2>Virtual Libraries</h2>

            <p>Using <i>virtual libraries</i> you can restrict calibre to only show
            you books that match a search. When a virtual library is in effect, calibre
            behaves as though the library contains only the matched books. The Tag Browser
            display only the tags/authors/series/etc. that belong to the matched books and any searches
            you do will only search within the books in the virtual library. This
            is a good way to partition your large library into smaller and easier to work with subsets.</p>

            <p>For example you can use a Virtual Library to only show you books with the Tag <i>"Unread"</i>
            or only books by <i>"My Favorite Author"</i> or only books in a particular series.</p>

            <p>More information and examples are available in the
            <a href="http://manual.calibre-ebook.com/virtual_libraries.html">User Manual</a>.</p>
            '''))
        hl.setWordWrap(True)
        hl.setOpenExternalLinks(True)
        hl.setFrameStyle(hl.StyledPanel)
        gl.addWidget(hl, 0, 3, 4, 1)

        bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        bb.accepted.connect(self.accept)
        bb.rejected.connect(self.reject)
        gl.addWidget(bb, 4, 0, 1, 0)

        if editing:
            db = self.gui.current_db
            virt_libs = db.prefs.get('virtual_libraries', {})
            for dex, vl in enumerate(sorted(virt_libs.keys(), key=sort_key)):
                self.vl_name.addItem(vl, virt_libs.get(vl, ''))
                if vl == editing:
                    self.vl_name.setCurrentIndex(dex)
                    self.original_index = dex
            self.original_search = virt_libs.get(editing, '')
            self.vl_text.setText(self.original_search)
            self.new_name = editing
            self.vl_name.currentIndexChanged[int].connect(
                self.name_index_changed)
            self.vl_name.lineEdit().textEdited.connect(self.name_text_edited)

        self.resize(self.sizeHint() + QSize(150, 25))

    def search_text_changed(self, txt):
        searches = [_('Saved searches recognized in the expression:')]
        txt = unicode(txt)
        while txt:
            p = txt.partition('search:')
            if p[1]:  # found 'search:'
                possible_search = p[2]
                if possible_search:  # something follows the 'search:'
                    if possible_search[0] == '"':  # strip any quotes
                        possible_search = possible_search[1:].partition('"')
                    else:  # find end of the search name. Is EOL, space, rparen
                        sp = possible_search.find(' ')
                        pp = possible_search.find(')')
                        if pp < 0 or (sp > 0 and sp <= pp):
                            # space in string before rparen, or neither found
                            possible_search = possible_search.partition(' ')
                        else:
                            # rparen in string before space
                            possible_search = possible_search.partition(')')
                    txt = possible_search[2]  # grab remainder of the string
                    search_name = possible_search[0]
                    if search_name.startswith('='):
                        search_name = search_name[1:]
                    if search_name in saved_searches().names():
                        searches.append(search_name + '=' +
                                        saved_searches().lookup(search_name))
                else:
                    txt = ''
            else:
                txt = ''
        if len(searches) > 1:
            self.saved_searches_label.setText('\n'.join(searches))
        else:
            self.saved_searches_label.setText('')

    def name_text_edited(self, new_name):
        self.new_name = unicode(new_name)

    def name_index_changed(self, dex):
        if self.editing and (self.vl_text.text() != self.original_search
                             or self.new_name != self.editing):
            if not question_dialog(
                    self.gui,
                    _('Search text changed'),
                    _('The virtual library name or the search text has changed. '
                      'Do you want to discard these changes?'),
                    default_yes=False):
                self.vl_name.blockSignals(True)
                self.vl_name.setCurrentIndex(self.original_index)
                self.vl_name.lineEdit().setText(self.new_name)
                self.vl_name.blockSignals(False)
                return
        self.new_name = self.editing = self.vl_name.currentText()
        self.original_index = dex
        self.original_search = unicode(self.vl_name.itemData(dex).toString())
        self.vl_text.setText(self.original_search)

    def link_activated(self, url):
        db = self.gui.current_db
        f, txt = unicode(url).partition('.')[0::2]
        if f == 'search':
            names = saved_searches().names()
        else:
            names = getattr(db, 'all_%s_names' % f)()
        d = SelectNames(names, txt, parent=self)
        if d.exec_() == d.Accepted:
            prefix = f + 's' if f in {'tag', 'author'} else f
            if f == 'search':
                search = [
                    '(%s)' % (saved_searches().lookup(x)) for x in d.names
                ]
            else:
                search = [
                    '%s:"=%s"' % (prefix, x.replace('"', '\\"'))
                    for x in d.names
                ]
            if search:
                if not self.editing:
                    self.vl_name.lineEdit().setText(d.names.next())
                    self.vl_name.lineEdit().setCursorPosition(0)
                self.vl_text.setText(d.match_type.join(search))
                self.vl_text.setCursorPosition(0)

    def accept(self):
        n = unicode(self.vl_name.currentText()).strip()
        if not n:
            error_dialog(
                self.gui,
                _('No name'),
                _('You must provide a name for the new virtual library'),
                show=True)
            return

        if n.startswith('*'):
            error_dialog(self.gui,
                         _('Invalid name'),
                         _('A virtual library name cannot begin with "*"'),
                         show=True)
            return

        if n in self.existing_names and n != self.editing:
            if not question_dialog(
                    self.gui,
                    _('Name already in use'),
                    _('That name is already in use. Do you want to replace it '
                      'with the new search?'),
                    default_yes=False):
                return

        v = unicode(self.vl_text.text()).strip()
        if not v:
            error_dialog(
                self.gui,
                _('No search string'),
                _('You must provide a search to define the new virtual library'
                  ),
                show=True)
            return

        try:
            db = self.gui.library_view.model().db
            recs = db.data.search_getting_ids('', v, use_virtual_library=False)
        except ParseException as e:
            error_dialog(self.gui,
                         _('Invalid search'),
                         _('The search in the search box is not valid'),
                         det_msg=e.msg,
                         show=True)
            return

        if not recs and not question_dialog(
                self.gui,
                _('Search found no books'),
                _('The search found no books, so the virtual library '
                  'will be empty. Do you really want to use that search?'),
                default_yes=False):
            return

        self.library_name = n
        self.library_search = v
        QDialog.accept(self)
Ejemplo n.º 5
0
class RuleEditor(QDialog): # {{{

    def __init__(self, fm, parent=None):
        QDialog.__init__(self, parent)
        self.fm = fm

        self.setWindowIcon(QIcon(I('format-fill-color.png')))
        self.setWindowTitle(_('Create/edit a column coloring rule'))

        self.l = l = QGridLayout(self)
        self.setLayout(l)

        self.l1 = l1 = QLabel(_('Create a coloring rule by'
            ' filling in the boxes below'))
        l.addWidget(l1, 0, 0, 1, 5)

        self.f1 = QFrame(self)
        self.f1.setFrameShape(QFrame.HLine)
        l.addWidget(self.f1, 1, 0, 1, 5)

        self.l2 = l2 = QLabel(_('Set the color of the column:'))
        l.addWidget(l2, 2, 0)

        self.column_box = QComboBox(self)
        l.addWidget(self.column_box, 2, 1)

        self.l3 = l3 = QLabel(_('to'))
        l.addWidget(l3, 2, 2)

        self.color_box = QComboBox(self)
        self.color_label = QLabel('Sample text Sample text')
        self.color_label.setTextFormat(Qt.RichText)
        l.addWidget(self.color_box, 2, 3)
        l.addWidget(self.color_label, 2, 4)
        l.addItem(QSpacerItem(10, 10, QSizePolicy.Expanding), 2, 5)

        self.l4 = l4 = QLabel(
            _('Only if the following conditions are all satisfied:'))
        l.addWidget(l4, 3, 0, 1, 6)

        self.scroll_area = sa = QScrollArea(self)
        sa.setMinimumHeight(300)
        sa.setMinimumWidth(950)
        sa.setWidgetResizable(True)
        l.addWidget(sa, 4, 0, 1, 6)

        self.add_button = b = QPushButton(QIcon(I('plus.png')),
                _('Add another condition'))
        l.addWidget(b, 5, 0, 1, 6)
        b.clicked.connect(self.add_blank_condition)

        self.l5 = l5 = QLabel(_('You can disable a condition by'
            ' blanking all of its boxes'))
        l.addWidget(l5, 6, 0, 1, 6)

        self.bb = bb = QDialogButtonBox(
                QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
        bb.accepted.connect(self.accept)
        bb.rejected.connect(self.reject)
        l.addWidget(bb, 7, 0, 1, 6)

        self.conditions_widget = QWidget(self)
        sa.setWidget(self.conditions_widget)
        self.conditions_widget.setLayout(QVBoxLayout())
        self.conditions_widget.layout().setAlignment(Qt.AlignTop)
        self.conditions = []

        for b in (self.column_box, self.color_box):
            b.setSizeAdjustPolicy(b.AdjustToMinimumContentsLengthWithIcon)
            b.setMinimumContentsLength(15)

        for key in sorted(
                displayable_columns(fm),
                key=sort_key):
            name = fm[key]['name']
            if name:
                self.column_box.addItem(key, key)
        self.column_box.setCurrentIndex(0)

        self.color_box.addItems(QColor.colorNames())
        self.color_box.setCurrentIndex(0)

        self.update_color_label()
        self.color_box.currentIndexChanged.connect(self.update_color_label)
        self.resize(self.sizeHint())

    def update_color_label(self):
        pal = QApplication.palette()
        bg1 = unicode(pal.color(pal.Base).name())
        bg2 = unicode(pal.color(pal.AlternateBase).name())
        c = unicode(self.color_box.currentText())
        self.color_label.setText('''
            <span style="color: {c}; background-color: {bg1}">&nbsp;{st}&nbsp;</span>
            <span style="color: {c}; background-color: {bg2}">&nbsp;{st}&nbsp;</span>
            '''.format(c=c, bg1=bg1, bg2=bg2, st=_('Sample Text')))


    def add_blank_condition(self):
        c = ConditionEditor(self.fm, parent=self.conditions_widget)
        self.conditions.append(c)
        self.conditions_widget.layout().addWidget(c)

    def apply_rule(self, col, rule):
        for i in range(self.column_box.count()):
            c = unicode(self.column_box.itemData(i).toString())
            if col == c:
                self.column_box.setCurrentIndex(i)
                break
        if rule.color:
            idx = self.color_box.findText(rule.color)
            if idx >= 0:
                self.color_box.setCurrentIndex(idx)
        for c in rule.conditions:
            ce = ConditionEditor(self.fm, parent=self.conditions_widget)
            self.conditions.append(ce)
            self.conditions_widget.layout().addWidget(ce)
            try:
                ce.condition = c
            except:
                import traceback
                traceback.print_exc()


    def accept(self):
        if self.validate():
            QDialog.accept(self)

    def validate(self):
        r = Rule(self.fm)
        for c in self.conditions:
            condition = c.condition
            if condition is not None:
                try:
                    r.add_condition(*condition)
                except Exception as e:
                    import traceback
                    error_dialog(self, _('Invalid condition'),
                            _('One of the conditions for this rule is'
                                ' invalid: <b>%s</b>')%e,
                            det_msg=traceback.format_exc(), show=True)
                    return False
        if len(r.conditions) < 1:
            error_dialog(self, _('No conditions'),
                    _('You must specify at least one non-empty condition'
                        ' for this rule'), show=True)
            return False
        return True

    @property
    def rule(self):
        r = Rule(self.fm)
        r.color = unicode(self.color_box.currentText())
        idx = self.column_box.currentIndex()
        col = unicode(self.column_box.itemData(idx).toString())
        for c in self.conditions:
            condition = c.condition
            if condition is not None:
                r.add_condition(*condition)

        return col, r
class DlgEnterSegment(ArmoryDialog):

   #############################################################################
   def __init__(self, parent, main, isUnknownOrder=False):
      super(DlgEnterSegment, self).__init__(parent, main)
      self.setWindowTitle('Enter Segment')
      self.setWindowIcon(QIcon(self.main.iconfile))
      buttonbox = QDialogButtonBox(QDialogButtonBox.Ok | \
                                   QDialogButtonBox.Cancel)
      self.connect(buttonbox, SIGNAL('accepted()'), self.accept)
      self.connect(buttonbox, SIGNAL('rejected()'), self.reject)

      layout = QGridLayout()
      lbl =  QLabel('Segment Text:')
      self.editSegment = QLineEdit()
      h, w = relaxedSizeNChar(self, 50)
      self.editSegment.setMinimumSize(h, w)
      self.editSegment.setMaxLength(MAX_SEGMENT_LENGTH)
      editSegPanel = makeHorizFrame([self.editSegment, 'stretch'])
      layout.addWidget(lbl, 0, 0)
      layout.addWidget(editSegPanel, 0, 1)
   
      minSelectorLabel = QLabel('Min Length: ')
      maxSelectorLabel = QLabel('Max Length: ')
      self.minSelector = QComboBox()
      self.maxSelector = QComboBox()      
      if isUnknownOrder:
         self.minSelector.setFont(GETFONT('Var', 10, bold=True))
         self.maxSelector.setFont(GETFONT('Var', 10, bold=True))
         for i in range(1,MAX_UNKNOWN_SEGMENT_LENGTH):
            self.minSelector.addItem(str(i))
            self.maxSelector.addItem(str(i))
         # default to 1 to 4
         self.minSelector.setCurrentIndex(0)
         self.maxSelector.setCurrentIndex(0)
         
         # fix the inversion of min and max when user sets min
         def updateMaxSelector():
            minLen = int(str(self.minSelector.currentText()))
            maxLen = int(str(self.maxSelector.currentText()))
            if minLen > maxLen:
               self.maxSelector.setCurrentIndex(minLen - 1)
         
         # fix the inversion of min and max when user sets max
         def updateMinSelector():
            minLen = int(str(self.minSelector.currentText()))
            maxLen = int(str(self.maxSelector.currentText()))
            if minLen > maxLen:
               self.minSelector.setCurrentIndex(maxLen - 1)
               
         main.connect(self.minSelector, SIGNAL('activated(int)'), \
                                             updateMaxSelector)
         main.connect(self.maxSelector, SIGNAL('activated(int)'), \
                                             updateMinSelector)
            
         layout.addWidget(minSelectorLabel, 1, 0)
         minSelectorPanel = makeHorizFrame([self.minSelector,'stretch'])
         layout.addWidget(minSelectorPanel, 1, 1)
         layout.addWidget(maxSelectorLabel, 2, 0)
         maxSelectorPanel = makeHorizFrame([self.maxSelector,'stretch'])
         layout.addWidget(maxSelectorPanel, 2, 1)
         layout.addWidget(buttonbox, 3, 0)
      else:
         layout.addWidget(buttonbox, 1, 0)
      
      self.setLayout(layout)

   #############################################################################
   def accept(self):
      if not isASCII(unicode(self.editSegment.text())):
         UnicodeErrorBox(self)
         return
      else:
         super(DlgEnterSegment, self).accept()  
Ejemplo n.º 7
0
class RuleEditor(QDialog):  # {{{
    def __init__(self, fm, pref_name, parent=None):
        QDialog.__init__(self, parent)
        self.fm = fm

        if pref_name == 'column_color_rules':
            self.rule_kind = 'color'
            rule_text = _('coloring')
        else:
            self.rule_kind = 'icon'
            rule_text = _('icon')

        self.setWindowIcon(QIcon(I('format-fill-color.png')))
        self.setWindowTitle(
            _('Create/edit a column {0} rule').format(rule_text))

        self.l = l = QGridLayout(self)
        self.setLayout(l)

        self.l1 = l1 = QLabel(
            _('Create a column {0} rule by'
              ' filling in the boxes below'.format(rule_text)))
        l.addWidget(l1, 0, 0, 1, 8)

        self.f1 = QFrame(self)
        self.f1.setFrameShape(QFrame.HLine)
        l.addWidget(self.f1, 1, 0, 1, 8)

        self.l2 = l2 = QLabel(_('Set the'))
        l.addWidget(l2, 2, 0)

        if self.rule_kind == 'color':
            l.addWidget(QLabel(_('color')))
        else:
            self.kind_box = QComboBox(self)
            for tt, t in icon_rule_kinds:
                self.kind_box.addItem(tt, t)
            l.addWidget(self.kind_box, 2, 1)

        self.l3 = l3 = QLabel(_('of the column:'))
        l.addWidget(l3, 2, 2)

        self.column_box = QComboBox(self)
        l.addWidget(self.column_box, 2, 3)

        self.l4 = l4 = QLabel(_('to'))
        l.addWidget(l4, 2, 4)

        if self.rule_kind == 'color':
            self.color_box = QComboBox(self)
            self.color_label = QLabel('Sample text Sample text')
            self.color_label.setTextFormat(Qt.RichText)
            l.addWidget(self.color_box, 2, 5)
            l.addWidget(self.color_label, 2, 6)
            l.addItem(QSpacerItem(10, 10, QSizePolicy.Expanding), 2, 7)
        else:
            self.filename_box = QComboBox()
            self.filename_box.setInsertPolicy(
                self.filename_box.InsertAlphabetically)
            d = os.path.join(config_dir, 'cc_icons')
            self.icon_file_names = []
            if os.path.exists(d):
                for icon_file in os.listdir(d):
                    icon_file = lower(icon_file)
                    if os.path.exists(os.path.join(d, icon_file)):
                        if icon_file.endswith('.png'):
                            self.icon_file_names.append(icon_file)
            self.icon_file_names.sort(key=sort_key)
            self.update_filename_box()

            l.addWidget(self.filename_box, 2, 5)
            self.filename_button = QPushButton(QIcon(I('document_open.png')),
                                               _('&Add icon'))
            l.addWidget(self.filename_button, 2, 6)
            l.addWidget(QLabel(_('Icons should be square or landscape')), 2, 7)
            l.setColumnStretch(7, 10)

        self.l5 = l5 = QLabel(
            _('Only if the following conditions are all satisfied:'))
        l.addWidget(l5, 3, 0, 1, 7)

        self.scroll_area = sa = QScrollArea(self)
        sa.setMinimumHeight(300)
        sa.setMinimumWidth(950)
        sa.setWidgetResizable(True)
        l.addWidget(sa, 4, 0, 1, 8)

        self.add_button = b = QPushButton(QIcon(I('plus.png')),
                                          _('Add another condition'))
        l.addWidget(b, 5, 0, 1, 8)
        b.clicked.connect(self.add_blank_condition)

        self.l6 = l6 = QLabel(
            _('You can disable a condition by'
              ' blanking all of its boxes'))
        l.addWidget(l6, 6, 0, 1, 8)

        self.bb = bb = QDialogButtonBox(QDialogButtonBox.Ok
                                        | QDialogButtonBox.Cancel)
        bb.accepted.connect(self.accept)
        bb.rejected.connect(self.reject)
        l.addWidget(bb, 7, 0, 1, 8)

        self.conditions_widget = QWidget(self)
        sa.setWidget(self.conditions_widget)
        self.conditions_widget.setLayout(QVBoxLayout())
        self.conditions_widget.layout().setAlignment(Qt.AlignTop)
        self.conditions = []

        if self.rule_kind == 'color':
            for b in (self.column_box, self.color_box):
                b.setSizeAdjustPolicy(b.AdjustToMinimumContentsLengthWithIcon)
                b.setMinimumContentsLength(15)

        for key in sorted(displayable_columns(fm),
                          key=lambda (k): sort_key(fm[k]['name'])
                          if k != color_row_key else 0):
            if key == color_row_key and self.rule_kind != 'color':
                continue
            name = all_columns_string if key == color_row_key else fm[key][
                'name']
            if name:
                self.column_box.addItem(name, key)
        self.column_box.setCurrentIndex(0)

        if self.rule_kind == 'color':
            self.color_box.addItems(QColor.colorNames())
            self.color_box.setCurrentIndex(0)
            self.update_color_label()
            self.color_box.currentIndexChanged.connect(self.update_color_label)
        else:
            self.filename_button.clicked.connect(self.filename_button_clicked)

        self.resize(self.sizeHint())

    def update_filename_box(self):
        self.filename_box.clear()
        self.icon_file_names.sort(key=sort_key)
        self.filename_box.addItem('')
        self.filename_box.addItems(self.icon_file_names)
        for i, filename in enumerate(self.icon_file_names):
            icon = QIcon(os.path.join(config_dir, 'cc_icons', filename))
            self.filename_box.setItemIcon(i + 1, icon)

    def update_color_label(self):
        pal = QApplication.palette()
        bg1 = unicode(pal.color(pal.Base).name())
        bg2 = unicode(pal.color(pal.AlternateBase).name())
        c = unicode(self.color_box.currentText())
        self.color_label.setText('''
            <span style="color: {c}; background-color: {bg1}">&nbsp;{st}&nbsp;</span>
            <span style="color: {c}; background-color: {bg2}">&nbsp;{st}&nbsp;</span>
            '''.format(c=c, bg1=bg1, bg2=bg2, st=_('Sample Text')))

    def filename_button_clicked(self):
        try:
            path = choose_files(self,
                                'choose_category_icon',
                                _('Select Icon'),
                                filters=[('Images',
                                          ['png', 'gif', 'jpg', 'jpeg'])],
                                all_files=False,
                                select_only_single_file=True)
            if path:
                icon_path = path[0]
                icon_name = sanitize_file_name_unicode(
                    os.path.splitext(os.path.basename(icon_path))[0] + '.png')
                if icon_name not in self.icon_file_names:
                    self.icon_file_names.append(icon_name)
                    self.update_filename_box()
                    try:
                        p = QIcon(icon_path).pixmap(QSize(128, 128))
                        d = os.path.join(config_dir, 'cc_icons')
                        if not os.path.exists(os.path.join(d, icon_name)):
                            if not os.path.exists(d):
                                os.makedirs(d)
                            with open(os.path.join(d, icon_name), 'wb') as f:
                                f.write(pixmap_to_data(p, format='PNG'))
                    except:
                        import traceback
                        traceback.print_exc()
                self.filename_box.setCurrentIndex(
                    self.filename_box.findText(icon_name))
                self.filename_box.adjustSize()
        except:
            import traceback
            traceback.print_exc()
        return

    def add_blank_condition(self):
        c = ConditionEditor(self.fm, parent=self.conditions_widget)
        self.conditions.append(c)
        self.conditions_widget.layout().addWidget(c)

    def apply_rule(self, kind, col, rule):
        if kind == 'color':
            if rule.color:
                idx = self.color_box.findText(rule.color)
                if idx >= 0:
                    self.color_box.setCurrentIndex(idx)
        else:
            self.kind_box.setCurrentIndex(0 if kind == 'icon' else 1)
            if rule.color:
                idx = self.filename_box.findText(rule.color)
                if idx >= 0:
                    self.filename_box.setCurrentIndex(idx)
                else:
                    self.filename_box.setCurrentIndex(0)

        for i in range(self.column_box.count()):
            c = unicode(self.column_box.itemData(i).toString())
            if col == c:
                self.column_box.setCurrentIndex(i)
                break

        for c in rule.conditions:
            ce = ConditionEditor(self.fm, parent=self.conditions_widget)
            self.conditions.append(ce)
            self.conditions_widget.layout().addWidget(ce)
            try:
                ce.condition = c
            except:
                import traceback
                traceback.print_exc()

    def accept(self):
        if self.rule_kind != 'color':
            fname = lower(unicode(self.filename_box.currentText()))
            if not fname:
                error_dialog(self,
                             _('No icon selected'),
                             _('You must choose an icon for this rule'),
                             show=True)
                return
        if self.validate():
            QDialog.accept(self)

    def validate(self):
        r = Rule(self.fm)
        for c in self.conditions:
            condition = c.condition
            if condition is not None:
                try:
                    r.add_condition(*condition)
                except Exception as e:
                    import traceback
                    error_dialog(self,
                                 _('Invalid condition'),
                                 _('One of the conditions for this rule is'
                                   ' invalid: <b>%s</b>') % e,
                                 det_msg=traceback.format_exc(),
                                 show=True)
                    return False
        if len(r.conditions) < 1:
            error_dialog(self,
                         _('No conditions'),
                         _('You must specify at least one non-empty condition'
                           ' for this rule'),
                         show=True)
            return False
        return True

    @property
    def rule(self):
        r = Rule(self.fm)
        if self.rule_kind != 'color':
            r.color = unicode(self.filename_box.currentText())
        else:
            r.color = unicode(self.color_box.currentText())
        idx = self.column_box.currentIndex()
        col = unicode(self.column_box.itemData(idx).toString())
        for c in self.conditions:
            condition = c.condition
            if condition is not None:
                r.add_condition(*condition)
        if self.rule_kind == 'icon':
            kind = unicode(
                self.kind_box.itemData(
                    self.kind_box.currentIndex()).toString())
        else:
            kind = 'color'

        return kind, col, r
Ejemplo n.º 8
0
class RuleEditor(QDialog): # {{{

    def __init__(self, fm, pref_name, parent=None):
        QDialog.__init__(self, parent)
        self.fm = fm

        if pref_name == 'column_color_rules':
            self.rule_kind = 'color'
            rule_text = _('coloring')
        else:
            self.rule_kind = 'icon'
            rule_text = _('icon')

        self.setWindowIcon(QIcon(I('format-fill-color.png')))
        self.setWindowTitle(_('Create/edit a column {0} rule').format(rule_text))

        self.l = l = QGridLayout(self)
        self.setLayout(l)

        self.l1 = l1 = QLabel(_('Create a column {0} rule by'
            ' filling in the boxes below'.format(rule_text)))
        l.addWidget(l1, 0, 0, 1, 8)

        self.f1 = QFrame(self)
        self.f1.setFrameShape(QFrame.HLine)
        l.addWidget(self.f1, 1, 0, 1, 8)

        self.l2 = l2 = QLabel(_('Set the'))
        l.addWidget(l2, 2, 0)

        if self.rule_kind == 'color':
            l.addWidget(QLabel(_('color')))
        else:
            self.kind_box = QComboBox(self)
            for tt, t in icon_rule_kinds:
                self.kind_box.addItem(tt, t)
            l.addWidget(self.kind_box, 2, 1)

        self.l3 = l3 = QLabel(_('of the column:'))
        l.addWidget(l3, 2, 2)

        self.column_box = QComboBox(self)
        l.addWidget(self.column_box, 2, 3)

        self.l4 = l4 = QLabel(_('to'))
        l.addWidget(l4, 2, 4)

        if self.rule_kind == 'color':
            self.color_box = QComboBox(self)
            self.color_label = QLabel('Sample text Sample text')
            self.color_label.setTextFormat(Qt.RichText)
            l.addWidget(self.color_box, 2, 5)
            l.addWidget(self.color_label, 2, 6)
            l.addItem(QSpacerItem(10, 10, QSizePolicy.Expanding), 2, 7)
        else:
            self.filename_box = QComboBox()
            self.filename_box.setInsertPolicy(self.filename_box.InsertAlphabetically)
            d = os.path.join(config_dir, 'cc_icons')
            self.icon_file_names = []
            if os.path.exists(d):
                for icon_file in os.listdir(d):
                    icon_file = lower(icon_file)
                    if os.path.exists(os.path.join(d, icon_file)):
                        if icon_file.endswith('.png'):
                            self.icon_file_names.append(icon_file)
            self.icon_file_names.sort(key=sort_key)
            self.update_filename_box()

            l.addWidget(self.filename_box, 2, 5)
            self.filename_button = QPushButton(QIcon(I('document_open.png')),
                                               _('&Add icon'))
            l.addWidget(self.filename_button, 2, 6)
            l.addWidget(QLabel(_('Icons should be square or landscape')), 2, 7)
            l.setColumnStretch(7, 10)

        self.l5 = l5 = QLabel(
            _('Only if the following conditions are all satisfied:'))
        l.addWidget(l5, 3, 0, 1, 7)

        self.scroll_area = sa = QScrollArea(self)
        sa.setMinimumHeight(300)
        sa.setMinimumWidth(950)
        sa.setWidgetResizable(True)
        l.addWidget(sa, 4, 0, 1, 8)

        self.add_button = b = QPushButton(QIcon(I('plus.png')),
                _('Add another condition'))
        l.addWidget(b, 5, 0, 1, 8)
        b.clicked.connect(self.add_blank_condition)

        self.l6 = l6 = QLabel(_('You can disable a condition by'
            ' blanking all of its boxes'))
        l.addWidget(l6, 6, 0, 1, 8)

        self.bb = bb = QDialogButtonBox(
                QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
        bb.accepted.connect(self.accept)
        bb.rejected.connect(self.reject)
        l.addWidget(bb, 7, 0, 1, 8)

        self.conditions_widget = QWidget(self)
        sa.setWidget(self.conditions_widget)
        self.conditions_widget.setLayout(QVBoxLayout())
        self.conditions_widget.layout().setAlignment(Qt.AlignTop)
        self.conditions = []

        if self.rule_kind == 'color':
            for b in (self.column_box, self.color_box):
                b.setSizeAdjustPolicy(b.AdjustToMinimumContentsLengthWithIcon)
                b.setMinimumContentsLength(15)

        for key in sorted(displayable_columns(fm),
                          key=lambda(k): sort_key(fm[k]['name']) if k != color_row_key else 0):
            if key == color_row_key and self.rule_kind != 'color':
                continue
            name = all_columns_string if key == color_row_key else fm[key]['name']
            if name:
                self.column_box.addItem(name, key)
        self.column_box.setCurrentIndex(0)

        if self.rule_kind == 'color':
            self.color_box.addItems(QColor.colorNames())
            self.color_box.setCurrentIndex(0)
            self.update_color_label()
            self.color_box.currentIndexChanged.connect(self.update_color_label)
        else:
            self.filename_button.clicked.connect(self.filename_button_clicked)

        self.resize(self.sizeHint())

    def update_filename_box(self):
        self.filename_box.clear()
        self.icon_file_names.sort(key=sort_key)
        self.filename_box.addItem('')
        self.filename_box.addItems(self.icon_file_names)
        for i,filename in enumerate(self.icon_file_names):
            icon = QIcon(os.path.join(config_dir, 'cc_icons', filename))
            self.filename_box.setItemIcon(i+1, icon)

    def update_color_label(self):
        pal = QApplication.palette()
        bg1 = unicode(pal.color(pal.Base).name())
        bg2 = unicode(pal.color(pal.AlternateBase).name())
        c = unicode(self.color_box.currentText())
        self.color_label.setText('''
            <span style="color: {c}; background-color: {bg1}">&nbsp;{st}&nbsp;</span>
            <span style="color: {c}; background-color: {bg2}">&nbsp;{st}&nbsp;</span>
            '''.format(c=c, bg1=bg1, bg2=bg2, st=_('Sample Text')))

    def filename_button_clicked(self):
        try:
            path = choose_files(self, 'choose_category_icon',
                        _('Select Icon'), filters=[
                        ('Images', ['png', 'gif', 'jpg', 'jpeg'])],
                    all_files=False, select_only_single_file=True)
            if path:
                icon_path = path[0]
                icon_name = sanitize_file_name_unicode(
                             os.path.splitext(
                                   os.path.basename(icon_path))[0]+'.png')
                if icon_name not in self.icon_file_names:
                    self.icon_file_names.append(icon_name)
                    self.update_filename_box()
                    try:
                        p = QIcon(icon_path).pixmap(QSize(128, 128))
                        d = os.path.join(config_dir, 'cc_icons')
                        if not os.path.exists(os.path.join(d, icon_name)):
                            if not os.path.exists(d):
                                os.makedirs(d)
                            with open(os.path.join(d, icon_name), 'wb') as f:
                                f.write(pixmap_to_data(p, format='PNG'))
                    except:
                        import traceback
                        traceback.print_exc()
                self.filename_box.setCurrentIndex(self.filename_box.findText(icon_name))
                self.filename_box.adjustSize()
        except:
            import traceback
            traceback.print_exc()
        return

    def add_blank_condition(self):
        c = ConditionEditor(self.fm, parent=self.conditions_widget)
        self.conditions.append(c)
        self.conditions_widget.layout().addWidget(c)

    def apply_rule(self, kind, col, rule):
        if kind == 'color':
            if rule.color:
                idx = self.color_box.findText(rule.color)
                if idx >= 0:
                    self.color_box.setCurrentIndex(idx)
        else:
            self.kind_box.setCurrentIndex(0 if kind == 'icon' else 1)
            if rule.color:
                idx = self.filename_box.findText(rule.color)
                if idx >= 0:
                    self.filename_box.setCurrentIndex(idx)
                else:
                    self.filename_box.setCurrentIndex(0)

        for i in range(self.column_box.count()):
            c = unicode(self.column_box.itemData(i).toString())
            if col == c:
                self.column_box.setCurrentIndex(i)
                break

        for c in rule.conditions:
            ce = ConditionEditor(self.fm, parent=self.conditions_widget)
            self.conditions.append(ce)
            self.conditions_widget.layout().addWidget(ce)
            try:
                ce.condition = c
            except:
                import traceback
                traceback.print_exc()


    def accept(self):
        if self.rule_kind != 'color':
            fname = lower(unicode(self.filename_box.currentText()))
            if not fname:
                error_dialog(self, _('No icon selected'),
                        _('You must choose an icon for this rule'), show=True)
                return
        if self.validate():
            QDialog.accept(self)

    def validate(self):
        r = Rule(self.fm)
        for c in self.conditions:
            condition = c.condition
            if condition is not None:
                try:
                    r.add_condition(*condition)
                except Exception as e:
                    import traceback
                    error_dialog(self, _('Invalid condition'),
                            _('One of the conditions for this rule is'
                                ' invalid: <b>%s</b>')%e,
                            det_msg=traceback.format_exc(), show=True)
                    return False
        if len(r.conditions) < 1:
            error_dialog(self, _('No conditions'),
                    _('You must specify at least one non-empty condition'
                        ' for this rule'), show=True)
            return False
        return True

    @property
    def rule(self):
        r = Rule(self.fm)
        if self.rule_kind != 'color':
            r.color = unicode(self.filename_box.currentText())
        else:
            r.color = unicode(self.color_box.currentText())
        idx = self.column_box.currentIndex()
        col = unicode(self.column_box.itemData(idx).toString())
        for c in self.conditions:
            condition = c.condition
            if condition is not None:
                r.add_condition(*condition)
        if self.rule_kind == 'icon':
            kind = unicode(self.kind_box.itemData(
                                    self.kind_box.currentIndex()).toString())
        else:
            kind = 'color'

        return kind, col, r
Ejemplo n.º 9
0
class SourceSelectorDialog(QDialog):
    def __init__(self, parent, flags=Qt.WindowFlags()):
        QDialog.__init__(self, parent, flags)
        self.setModal(False)
        self.setWindowTitle("Select sources by...")
        lo = QVBoxLayout(self)
        lo.setMargin(10)
        lo.setSpacing(5)
        # select by
        lo1 = QHBoxLayout()
        lo.addLayout(lo1)
        lo1.setContentsMargins(0, 0, 0, 0)
        #    lab = QLabel("Select:")
        #   lo1.addWidget(lab)
        self.wselby = QComboBox(self)
        lo1.addWidget(self.wselby, 0)
        QObject.connect(self.wselby, SIGNAL("activated(const QString &)"), self._setup_selection_by)
        # under/over
        self.wgele = QComboBox(self)
        lo1.addWidget(self.wgele, 0)
        self.wgele.addItems([">", ">=", "<=", "<", "sum<=", "sum>"])
        QObject.connect(self.wgele, SIGNAL("activated(const QString &)"), self._select_threshold)
        # threshold value
        self.wthreshold = QLineEdit(self)
        QObject.connect(self.wthreshold, SIGNAL("editingFinished()"), self._select_threshold)
        lo1.addWidget(self.wthreshold, 1)
        # min and max label
        self.wminmax = QLabel(self)
        lo.addWidget(self.wminmax)
        # selection slider
        lo1 = QHBoxLayout()
        lo.addLayout(lo1)
        self.wpercent = QSlider(self)
        self.wpercent.setTracking(False)
        QObject.connect(self.wpercent, SIGNAL("valueChanged(int)"), self._select_percentile)
        QObject.connect(self.wpercent, SIGNAL("sliderMoved(int)"), self._select_percentile_threshold)
        self.wpercent.setRange(0, 100)
        self.wpercent.setOrientation(Qt.Horizontal)
        lo1.addWidget(self.wpercent)
        self.wpercent_lbl = QLabel("0%", self)
        self.wpercent_lbl.setMinimumWidth(64)
        lo1.addWidget(self.wpercent_lbl)
        #    # hide button
        #    lo.addSpacing(10)
        #    lo2 = QHBoxLayout()
        #    lo.addLayout(lo2)
        #    lo2.setContentsMargins(0,0,0,0)
        #    hidebtn = QPushButton("Close",self)
        #    hidebtn.setMinimumWidth(128)
        #    QObject.connect(hidebtn,SIGNAL("clicked()"),self.hide)
        #    lo2.addStretch(1)
        #    lo2.addWidget(hidebtn)
        #    lo2.addStretch(1)
        #    self.setMinimumWidth(384)
        self._in_select_threshold = False
        self._sort_index = None
        self.qerrmsg = QErrorMessage(self)

    def resetModel(self):
        """Resets dialog based on current model."""
        if not self.model:
            return
        # getset of model tags, and remove the non-sorting tags
        alltags = set(self.model.tagnames)
        alltags -= NonSortingTags
        # make list of tags from StandardTags that are present in model
        self.sorttags = [tag for tag in StandardTags if tag in alltags or tag in TagAccessors]
        # append model tags that were not in StandardTags
        self.sorttags += list(alltags - set(self.sorttags))
        # set selector
        self.wselby.clear()
        self.wselby.addItems(self.sorttags)
        for tag in "Iapp", "I":
            if tag in self.sorttags:
                self.wselby.setCurrentIndex(self.sorttags.index(tag))
                break
        self._setup_selection_by(self.wselby.currentText())

    def _reset_percentile(self):
        self.wthreshold.setText("")
        self.wpercent.setValue(50)
        self.wpercent_lbl.setText("--%")

    def _setup_selection_by(self, tag):
        tag = str(tag);  # may be QString
        # clear threshold value and percentiles
        self._reset_percentile()
        # get min/max values, and sort indices
        # _sort_index will be an array of (value,src,cumsum) tuples, sorted by tag value (high to low),
        # where src is the source, and cumsum is the sum of all values in the list from 0 up to and including the current one
        self._sort_index = []
        minval = maxval = None
        for isrc, src in enumerate(self.model.sources):
            try:
                if hasattr(src, tag):
                    value = float(getattr(src, tag))
                else:
                    value = float(TagAccessors[tag](src))
            # skip source if failed to access this tag as a float
            except:
                traceback.print_exc()
                continue
            self._sort_index.append([value, src, 0])
            minval = min(minval, value) if minval is not None else value
            maxval = max(maxval, value) if maxval is not None else value
        # add label
        if minval is None:
            self._range = None
            self.wminmax.setText("<font color=red>'%s' is not a numeric attribute</font>" % tag)
            for w in self.wgele, self.wthreshold, self.wpercent, self.wpercent_lbl:
                w.setEnabled(False)
        else:
            self._range = (minval, maxval)
            self.wminmax.setText("min: %g max: %g" % self._range)
            for w in self.wgele, self.wthreshold, self.wpercent, self.wpercent_lbl:
                w.setEnabled(True)
        # sort index by descending values
        self._sort_index.sort(reverse=True)
        # generate cumulative sums
        cumsum = 0.
        for entry in self._sort_index:
            cumsum += entry[0]
            entry[2] = cumsum

    # Maps comparison operators to callables. Used in _select_threshold.
    # Each callable takes two arguments: e is a tuple of (value,src,cumsum) (see _sort_index above), and x is a threshold
    # Second argument is a flag: if False, selection is inverted w.r.t. operator
    Operators = {
        "<": ((lambda e, x: e[0] >= x), False),
        "<=": ((lambda e, x: e[0] > x), False),
        ">": ((lambda e, x: e[0] > x), True),
        ">=": ((lambda e, x: e[0] >= x), True),
        "sum<=": ((lambda e, x: e[2] <= x), True),
        "sum>": ((lambda e, x: e[2] <= x), False)
    }

    def _select_threshold(self, *dum):
        dprint(1, "select_threshold", dum)
        self._in_select_threshold = True
        busy = BusyIndicator()
        try:
            # get threshold, ignore if not set
            threshold = str(self.wthreshold.text())
            if not threshold:
                self._reset_percentile()
                return
            # try to parse threshold, ignore if invalid
            try:
                threshold = float(threshold)
            except:
                self._reset_percentile()
                return
            # get comparison operator
            op, select = self.Operators[str(self.wgele.currentText())]
            # apply to initial segment (that matches operator)
            for num, entry in enumerate(self._sort_index):
                if not op(entry, threshold):
                    break
                entry[1].selected = select
            else:
                num = len(self._sort_index)
            # apply to remaining segment
            for val, src, cumsum in self._sort_index[num:]:
                src.selected = not select
            # set percentile
            percent = round(float(num * 100) / len(self._sort_index))
            if not select:
                percent = 100 - percent
            self.wpercent.setValue(percent)
            self.wpercent_lbl.setText("%3d%%" % percent)
            # emit signal
            self.model.emitSelection(self)
        finally:
            self._in_select_threshold = False
            busy = None

    def _select_percentile(self, percent):
        self._select_percentile_threshold(percent, do_select=True)

    def _select_percentile_threshold(self, percent, do_select=False):
        # ignore if no sort index set up, or if _select_threshold() is being called
        if self._sort_index is None or self._in_select_threshold:
            return
        dprint(1, "select_precentile_threshold", percent)
        busy = BusyIndicator()
        # number of objects to select
        nsrc = len(self._sort_index)
        nsel = int(math.ceil(nsrc * float(percent) / 100))
        # get comparison operator
        opstr = str(self.wgele.currentText())
        op, select = self.Operators[opstr]
        # select head or tail of list, depending on direction of operator
        if select:
            thr = self._sort_index[min(nsel, nsrc - 1)]
            slc1 = slice(0, nsel)
            slc2 = slice(nsel, None)
        else:
            thr = self._sort_index[-min(nsel + 1, nsrc)]
            slc1 = slice(nsrc - nsel, None)
            slc2 = slice(0, nsrc - nsel)
        if do_select:
            for val, src, cumsum in self._sort_index[slc1]:
                src.selected = True
            for val, src, cumsum in self._sort_index[slc2]:
                src.selected = False
            self.model.emitSelection(self)
        self.wpercent_lbl.setText("%3d%%" % percent)
        self.wthreshold.setText("%g" % (thr[2] if opstr.startswith("sum") else thr[0]))
        return nsel

    def setModel(self, model):
        """Sets the current model. If dialog is visible, applies the changes"""
        self.model = model
        if self.isVisible():
            self.resetModel()
        if not model:
            self.hide()

    def show(self):
        """Shows dialog, resetting the model if it was invisible."""
        if not self.isVisible():
            self.resetModel()
        QDialog.show(self)
Ejemplo n.º 10
0
class AddTagDialog(QDialog):
    def __init__(self, parent, modal=True, flags=Qt.WindowFlags()):
        QDialog.__init__(self, parent, flags)
        self.setModal(modal)
        self.setWindowTitle("Add Tag")
        lo = QVBoxLayout(self)
        lo.setMargin(10)
        lo.setSpacing(5)
        # tag selector
        lo1 = QHBoxLayout()
        lo.addLayout(lo1)
        lo1.setSpacing(5)
        self.wtagsel = QComboBox(self)
        self.wtagsel.setEditable(True)
        wtagsel_lbl = QLabel("&Tag:", self)
        wtagsel_lbl.setBuddy(self.wtagsel)
        lo1.addWidget(wtagsel_lbl, 0)
        lo1.addWidget(self.wtagsel, 1)
        QObject.connect(self.wtagsel, SIGNAL("activated(int)"),
                        self._check_tag)
        QObject.connect(self.wtagsel,
                        SIGNAL("editTextChanged(const QString &)"),
                        self._check_tag_text)
        # value editor
        self.valedit = ValueTypeEditor(self)
        lo.addWidget(self.valedit)
        # buttons
        lo.addSpacing(10)
        lo2 = QHBoxLayout()
        lo.addLayout(lo2)
        lo2.setContentsMargins(0, 0, 0, 0)
        lo2.setMargin(5)
        self.wokbtn = QPushButton("OK", self)
        self.wokbtn.setMinimumWidth(128)
        QObject.connect(self.wokbtn, SIGNAL("clicked()"), self.accept)
        self.wokbtn.setEnabled(False)
        cancelbtn = QPushButton("Cancel", self)
        cancelbtn.setMinimumWidth(128)
        QObject.connect(cancelbtn, SIGNAL("clicked()"), self.reject)
        lo2.addWidget(self.wokbtn)
        lo2.addStretch(1)
        lo2.addWidget(cancelbtn)
        self.setMinimumWidth(384)

    def setTags(self, tagnames):
        self.wtagsel.clear()
        self.wtagsel.addItems(list(tagnames))
        self.wtagsel.addItem("")
        self.wtagsel.setCurrentIndex(len(tagnames))

    def setValue(self, value):
        self.valedit.setValue(value)

    def _check_tag(self, tag):
        self.wokbtn.setEnabled(True)

    def _check_tag_text(self, text):
        self.wokbtn.setEnabled(bool(str(text) != ""))

    def accept(self):
        """When dialog is accepted with a default (bool) tag type,
        check if the user hasn't entered a name=value entry in the tag name field.
        This is a common mistake, and should be treated as a shortcut for setting string tags."""
        if isinstance(self.valedit.getValue(), bool):
            tagval = str(self.wtagsel.currentText()).split("=", 1)
            if len(tagval) > 1:
                #        print tagval
                if QMessageBox.warning(
                        self, "Set a string tag instead?",
                        """<P>You have included an "=" sign in the tag name. 
            Perhaps you actually mean to set tag "%s" to the string value "%s"?</P>"""
                        % tuple(tagval), QMessageBox.Yes | QMessageBox.No,
                        QMessageBox.Yes) == QMessageBox.No:
                    return
                self.wtagsel.setEditText(tagval[0])
                self.valedit.setValue(tagval[1])
        return QDialog.accept(self)

    def getTag(self):
        return str(self.wtagsel.currentText()), self.valedit.getValue()
Ejemplo n.º 11
0
class StepEditDialog(QDialog):
    def __init__(self, parent, step):
        super(StepEditDialog, self).__init__(parent)
        self._step = step
        self._initGui()

    #--------------------------------------------------------------------#

    def _initGui(self):
        layout = QVBoxLayout()
        self.setLayout(layout)

        self.cb_step_type = QComboBox()
        self.widgets = QStackedWidget()
        layout.addWidget(self.cb_step_type)
        layout.addWidget(self.widgets)

        if self._step is None:
            for step_class in Step.REGISTERED_STEPS.values():
                self.cb_step_type.addItem(step_class.NAME)
                w = step_class.GET_WIDGET()
                self.widgets.addWidget(w)
            self.cb_step_type.currentIndexChanged.connect(
                self._stepTypeChanged)
        else:
            self.cb_step_type.addItems(Step.REGISTERED_STEPS.keys())
            self.cb_step_type.setEnabled(False)
            index = self.cb_step_type.findText(QString(str(self._step.NAME)))
            self.cb_step_type.setCurrentIndex(index)
            w = self._step.getWidget()
            self.widgets.addWidget(w)

        self._showStepProperties()

        ###################
        # Action Buttons: #
        ###################
        buttons = QDialogButtonBox(
            QDialogButtonBox.Ok | QDialogButtonBox.Cancel, Qt.Horizontal, self)
        buttons.accepted.connect(self.accept)
        buttons.rejected.connect(self.reject)
        layout.addWidget(buttons)

    #--------------------------------------------------------------------#

    def _showStepProperties(self):
        index = self.cb_step_type.currentIndex()
        self.widgets.setCurrentIndex(index)

    #--------------------------------------------------------------------#

    def _stepTypeChanged(self):
        self._showStepProperties()

    #--------------------------------------------------------------------#

    def sizeHint(self):
        hint = QDialog.sizeHint(self)
        hint.setWidth(500)
        return hint

    #--------------------------------------------------------------------#

    def step(self):
        return self._step

    #--------------------------------------------------------------------#

    def accept(self, *args, **kwargs):
        if self._step is None:
            name = str(self.cb_step_type.currentText())
            step_class = Step.REGISTERED_STEPS[name]
            self._step = step_class()
            self.widgets.currentWidget().save(self._step.attributes())
        else:
            self._selected_widget.save()
        return QDialog.accept(self, *args, **kwargs)
Ejemplo n.º 12
0
class SourceSelectorDialog(QDialog):
    def __init__(self, parent, flags=Qt.WindowFlags()):
        QDialog.__init__(self, parent, flags)
        self.setModal(False)
        self.setWindowTitle("Select sources by...")
        lo = QVBoxLayout(self)
        lo.setMargin(10)
        lo.setSpacing(5)
        # select by
        lo1 = QHBoxLayout()
        lo.addLayout(lo1)
        lo1.setContentsMargins(0, 0, 0, 0)
        #    lab = QLabel("Select:")
        #   lo1.addWidget(lab)
        self.wselby = QComboBox(self)
        lo1.addWidget(self.wselby, 0)
        QObject.connect(self.wselby, SIGNAL("activated(const QString &)"),
                        self._setup_selection_by)
        # under/over
        self.wgele = QComboBox(self)
        lo1.addWidget(self.wgele, 0)
        self.wgele.addItems([">", ">=", "<=", "<", "sum<=", "sum>"])
        QObject.connect(self.wgele, SIGNAL("activated(const QString &)"),
                        self._select_threshold)
        # threshold value
        self.wthreshold = QLineEdit(self)
        QObject.connect(self.wthreshold, SIGNAL("editingFinished()"),
                        self._select_threshold)
        lo1.addWidget(self.wthreshold, 1)
        # min and max label
        self.wminmax = QLabel(self)
        lo.addWidget(self.wminmax)
        # selection slider
        lo1 = QHBoxLayout()
        lo.addLayout(lo1)
        self.wpercent = QSlider(self)
        self.wpercent.setTracking(False)
        QObject.connect(self.wpercent, SIGNAL("valueChanged(int)"),
                        self._select_percentile)
        QObject.connect(self.wpercent, SIGNAL("sliderMoved(int)"),
                        self._select_percentile_threshold)
        self.wpercent.setRange(0, 100)
        self.wpercent.setOrientation(Qt.Horizontal)
        lo1.addWidget(self.wpercent)
        self.wpercent_lbl = QLabel("0%", self)
        self.wpercent_lbl.setMinimumWidth(64)
        lo1.addWidget(self.wpercent_lbl)
        #    # hide button
        #    lo.addSpacing(10)
        #    lo2 = QHBoxLayout()
        #    lo.addLayout(lo2)
        #    lo2.setContentsMargins(0,0,0,0)
        #    hidebtn = QPushButton("Close",self)
        #    hidebtn.setMinimumWidth(128)
        #    QObject.connect(hidebtn,SIGNAL("clicked()"),self.hide)
        #    lo2.addStretch(1)
        #    lo2.addWidget(hidebtn)
        #    lo2.addStretch(1)
        #    self.setMinimumWidth(384)
        self._in_select_threshold = False
        self._sort_index = None
        self.qerrmsg = QErrorMessage(self)

    def resetModel(self):
        """Resets dialog based on current model."""
        if not self.model:
            return
        # getset of model tags, and remove the non-sorting tags
        alltags = set(self.model.tagnames)
        alltags -= NonSortingTags
        # make list of tags from StandardTags that are present in model
        self.sorttags = [
            tag for tag in StandardTags
            if tag in alltags or tag in TagAccessors
        ]
        # append model tags that were not in StandardTags
        self.sorttags += list(alltags - set(self.sorttags))
        # set selector
        self.wselby.clear()
        self.wselby.addItems(self.sorttags)
        for tag in "Iapp", "I":
            if tag in self.sorttags:
                self.wselby.setCurrentIndex(self.sorttags.index(tag))
                break
        self._setup_selection_by(self.wselby.currentText())

    def _reset_percentile(self):
        self.wthreshold.setText("")
        self.wpercent.setValue(50)
        self.wpercent_lbl.setText("--%")

    def _setup_selection_by(self, tag):
        tag = str(tag)
        # may be QString
        # clear threshold value and percentiles
        self._reset_percentile()
        # get min/max values, and sort indices
        # _sort_index will be an array of (value,src,cumsum) tuples, sorted by tag value (high to low),
        # where src is the source, and cumsum is the sum of all values in the list from 0 up to and including the current one
        self._sort_index = []
        minval = maxval = None
        for isrc, src in enumerate(self.model.sources):
            try:
                if hasattr(src, tag):
                    value = float(getattr(src, tag))
                else:
                    value = float(TagAccessors[tag](src))
            # skip source if failed to access this tag as a float
            except:
                traceback.print_exc()
                continue
            self._sort_index.append([value, src, 0])
            minval = min(minval, value) if minval is not None else value
            maxval = max(maxval, value) if maxval is not None else value
        # add label
        if minval is None:
            self._range = None
            self.wminmax.setText(
                "<font color=red>'%s' is not a numeric attribute</font>" % tag)
            for w in self.wgele, self.wthreshold, self.wpercent, self.wpercent_lbl:
                w.setEnabled(False)
        else:
            self._range = (minval, maxval)
            self.wminmax.setText("min: %g max: %g" % self._range)
            for w in self.wgele, self.wthreshold, self.wpercent, self.wpercent_lbl:
                w.setEnabled(True)
        # sort index by descending values
        self._sort_index.sort(reverse=True)
        # generate cumulative sums
        cumsum = 0.
        for entry in self._sort_index:
            cumsum += entry[0]
            entry[2] = cumsum

    # Maps comparison operators to callables. Used in _select_threshold.
    # Each callable takes two arguments: e is a tuple of (value,src,cumsum) (see _sort_index above), and x is a threshold
    # Second argument is a flag: if False, selection is inverted w.r.t. operator
    Operators = {
        "<": ((lambda e, x: e[0] >= x), False),
        "<=": ((lambda e, x: e[0] > x), False),
        ">": ((lambda e, x: e[0] > x), True),
        ">=": ((lambda e, x: e[0] >= x), True),
        "sum<=": ((lambda e, x: e[2] <= x), True),
        "sum>": ((lambda e, x: e[2] <= x), False)
    }

    def _select_threshold(self, *dum):
        dprint(1, "select_threshold", dum)
        self._in_select_threshold = True
        busy = BusyIndicator()
        try:
            # get threshold, ignore if not set
            threshold = str(self.wthreshold.text())
            if not threshold:
                self._reset_percentile()
                return
            # try to parse threshold, ignore if invalid
            try:
                threshold = float(threshold)
            except:
                self._reset_percentile()
                return
            # get comparison operator
            op, select = self.Operators[str(self.wgele.currentText())]
            # apply to initial segment (that matches operator)
            for num, entry in enumerate(self._sort_index):
                if not op(entry, threshold):
                    break
                entry[1].selected = select
            else:
                num = len(self._sort_index)
            # apply to remaining segment
            for val, src, cumsum in self._sort_index[num:]:
                src.selected = not select
            # set percentile
            percent = round(float(num * 100) / len(self._sort_index))
            if not select:
                percent = 100 - percent
            self.wpercent.setValue(percent)
            self.wpercent_lbl.setText("%3d%%" % percent)
            # emit signal
            self.model.emitSelection(self)
        finally:
            self._in_select_threshold = False
            busy = None

    def _select_percentile(self, percent):
        self._select_percentile_threshold(percent, do_select=True)

    def _select_percentile_threshold(self, percent, do_select=False):
        # ignore if no sort index set up, or if _select_threshold() is being called
        if self._sort_index is None or self._in_select_threshold:
            return
        dprint(1, "select_precentile_threshold", percent)
        busy = BusyIndicator()
        # number of objects to select
        nsrc = len(self._sort_index)
        nsel = int(math.ceil(nsrc * float(percent) / 100))
        # get comparison operator
        opstr = str(self.wgele.currentText())
        op, select = self.Operators[opstr]
        # select head or tail of list, depending on direction of operator
        if select:
            thr = self._sort_index[min(nsel, nsrc - 1)]
            slc1 = slice(0, nsel)
            slc2 = slice(nsel, None)
        else:
            thr = self._sort_index[-min(nsel + 1, nsrc)]
            slc1 = slice(nsrc - nsel, None)
            slc2 = slice(0, nsrc - nsel)
        if do_select:
            for val, src, cumsum in self._sort_index[slc1]:
                src.selected = True
            for val, src, cumsum in self._sort_index[slc2]:
                src.selected = False
            self.model.emitSelection(self)
        self.wpercent_lbl.setText("%3d%%" % percent)
        self.wthreshold.setText(
            "%g" % (thr[2] if opstr.startswith("sum") else thr[0]))
        return nsel

    def setModel(self, model):
        """Sets the current model. If dialog is visible, applies the changes"""
        self.model = model
        if self.isVisible():
            self.resetModel()
        if not model:
            self.hide()

    def show(self):
        """Shows dialog, resetting the model if it was invisible."""
        if not self.isVisible():
            self.resetModel()
        QDialog.show(self)
Ejemplo n.º 13
0
class ConfigWidget(QWidget, Logger):
    '''
    Config dialog for Marvin Manager
    '''

    WIZARD_PROFILES = {
        'Annotations': {
            'label': 'mm_annotations',
            'datatype': 'comments',
            'display': {},
            'is_multiple': False
        },
        'Collections': {
            'label': 'mm_collections',
            'datatype': 'text',
            'display': {
                u'is_names': False
            },
            'is_multiple': True
        },
        'Last read': {
            'label': 'mm_date_read',
            'datatype': 'datetime',
            'display': {},
            'is_multiple': False
        },
        'Progress': {
            'label': 'mm_progress',
            'datatype': 'float',
            'display': {
                u'number_format': u'{0:.0f}%'
            },
            'is_multiple': False
        },
        'Read': {
            'label': 'mm_read',
            'datatype': 'bool',
            'display': {},
            'is_multiple': False
        },
        'Reading list': {
            'label': 'mm_reading_list',
            'datatype': 'bool',
            'display': {},
            'is_multiple': False
        },
        'Word count': {
            'label': 'mm_word_count',
            'datatype': 'int',
            'display': {
                u'number_format': u'{0:n}'
            },
            'is_multiple': False
        }
    }

    def __init__(self, plugin_action):
        QWidget.__init__(self)
        self.parent = plugin_action

        self.gui = get_gui()
        self.icon = plugin_action.icon
        self.opts = plugin_action.opts
        self.prefs = plugin_prefs
        self.resources_path = plugin_action.resources_path
        self.verbose = plugin_action.verbose

        self.restart_required = False

        self._log_location()

        self.l = QGridLayout()
        self.setLayout(self.l)
        self.column1_layout = QVBoxLayout()
        self.l.addLayout(self.column1_layout, 0, 0)
        self.column2_layout = QVBoxLayout()
        self.l.addLayout(self.column2_layout, 0, 1)

        # ----------------------------- Column 1 -----------------------------
        # ~~~~~~~~ Create the Custom fields options group box ~~~~~~~~
        self.cfg_custom_fields_gb = QGroupBox(self)
        self.cfg_custom_fields_gb.setTitle('Custom column assignments')
        self.column1_layout.addWidget(self.cfg_custom_fields_gb)

        self.cfg_custom_fields_qgl = QGridLayout(self.cfg_custom_fields_gb)
        current_row = 0

        # ++++++++ Labels + HLine ++++++++
        self.marvin_source_label = QLabel("Marvin source")
        self.cfg_custom_fields_qgl.addWidget(self.marvin_source_label,
                                             current_row, 0)
        self.calibre_destination_label = QLabel("calibre destination")
        self.cfg_custom_fields_qgl.addWidget(self.calibre_destination_label,
                                             current_row, 1)
        current_row += 1
        self.sd_hl = QFrame(self.cfg_custom_fields_gb)
        self.sd_hl.setFrameShape(QFrame.HLine)
        self.sd_hl.setFrameShadow(QFrame.Raised)
        self.cfg_custom_fields_qgl.addWidget(self.sd_hl, current_row, 0, 1, 3)
        current_row += 1

        # ++++++++ Annotations ++++++++
        self.cfg_annotations_label = QLabel('Annotations')
        self.cfg_annotations_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_annotations_label,
                                             current_row, 0)

        self.annotations_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.annotations_field_comboBox.setObjectName(
            'annotations_field_comboBox')
        self.annotations_field_comboBox.setToolTip(
            'Select a custom column to store Marvin annotations')
        self.cfg_custom_fields_qgl.addWidget(self.annotations_field_comboBox,
                                             current_row, 1)

        self.cfg_highlights_wizard = QToolButton()
        self.cfg_highlights_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_highlights_wizard.setToolTip(
            "Create a custom column to store Marvin annotations")
        self.cfg_highlights_wizard.clicked.connect(
            partial(self.launch_cc_wizard, 'Annotations'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_highlights_wizard,
                                             current_row, 2)
        current_row += 1

        # ++++++++ Collections ++++++++
        self.cfg_collections_label = QLabel('Collections')
        self.cfg_collections_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_collections_label,
                                             current_row, 0)

        self.collection_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.collection_field_comboBox.setObjectName(
            'collection_field_comboBox')
        self.collection_field_comboBox.setToolTip(
            'Select a custom column to store Marvin collection assignments')
        self.cfg_custom_fields_qgl.addWidget(self.collection_field_comboBox,
                                             current_row, 1)

        self.cfg_collections_wizard = QToolButton()
        self.cfg_collections_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_collections_wizard.setToolTip(
            "Create a custom column for Marvin collection assignments")
        self.cfg_collections_wizard.clicked.connect(
            partial(self.launch_cc_wizard, 'Collections'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_collections_wizard,
                                             current_row, 2)
        current_row += 1

        # ++++++++ Last read ++++++++
        self.cfg_date_read_label = QLabel("Last read")
        self.cfg_date_read_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_date_read_label,
                                             current_row, 0)

        self.date_read_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.date_read_field_comboBox.setObjectName('date_read_field_comboBox')
        self.date_read_field_comboBox.setToolTip(
            'Select a custom column to store Last read date')
        self.cfg_custom_fields_qgl.addWidget(self.date_read_field_comboBox,
                                             current_row, 1)

        self.cfg_collections_wizard = QToolButton()
        self.cfg_collections_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_collections_wizard.setToolTip(
            "Create a custom column to store Last read date")
        self.cfg_collections_wizard.clicked.connect(
            partial(self.launch_cc_wizard, 'Last read'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_collections_wizard,
                                             current_row, 2)
        current_row += 1

        # ++++++++ Progress ++++++++
        self.cfg_progress_label = QLabel('Progress')
        self.cfg_progress_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_progress_label,
                                             current_row, 0)

        self.progress_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.progress_field_comboBox.setObjectName('progress_field_comboBox')
        self.progress_field_comboBox.setToolTip(
            'Select a custom column to store Marvin reading progress')
        self.cfg_custom_fields_qgl.addWidget(self.progress_field_comboBox,
                                             current_row, 1)

        self.cfg_progress_wizard = QToolButton()
        self.cfg_progress_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_progress_wizard.setToolTip(
            "Create a custom column to store Marvin reading progress")
        self.cfg_progress_wizard.clicked.connect(
            partial(self.launch_cc_wizard, 'Progress'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_progress_wizard,
                                             current_row, 2)
        current_row += 1

        # ++++++++ Read flag ++++++++
        self.cfg_read_label = QLabel('Read')
        self.cfg_read_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_read_label, current_row,
                                             0)

        self.read_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.read_field_comboBox.setObjectName('read_field_comboBox')
        self.read_field_comboBox.setToolTip(
            'Select a custom column to store Marvin Read status')
        self.cfg_custom_fields_qgl.addWidget(self.read_field_comboBox,
                                             current_row, 1)

        self.cfg_read_wizard = QToolButton()
        self.cfg_read_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_read_wizard.setToolTip(
            "Create a custom column to store Marvin Read status")
        self.cfg_read_wizard.clicked.connect(
            partial(self.launch_cc_wizard, 'Read'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_read_wizard, current_row,
                                             2)
        current_row += 1

        # ++++++++ Reading list flag ++++++++
        self.cfg_reading_list_label = QLabel('Reading list')
        self.cfg_reading_list_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_reading_list_label,
                                             current_row, 0)

        self.reading_list_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.reading_list_field_comboBox.setObjectName(
            'reading_list_field_comboBox')
        self.reading_list_field_comboBox.setToolTip(
            'Select a custom column to store Marvin Reading list status')
        self.cfg_custom_fields_qgl.addWidget(self.reading_list_field_comboBox,
                                             current_row, 1)

        self.cfg_reading_list_wizard = QToolButton()
        self.cfg_reading_list_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_reading_list_wizard.setToolTip(
            "Create a custom column to store Marvin Reading list status")
        self.cfg_reading_list_wizard.clicked.connect(
            partial(self.launch_cc_wizard, 'Reading list'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_reading_list_wizard,
                                             current_row, 2)
        current_row += 1

        # ++++++++ Word count ++++++++
        self.cfg_word_count_label = QLabel('Word count')
        self.cfg_word_count_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_word_count_label,
                                             current_row, 0)

        self.word_count_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.word_count_field_comboBox.setObjectName(
            'word_count_field_comboBox')
        self.word_count_field_comboBox.setToolTip(
            'Select a custom column to store Marvin word counts')
        self.cfg_custom_fields_qgl.addWidget(self.word_count_field_comboBox,
                                             current_row, 1)

        self.cfg_word_count_wizard = QToolButton()
        self.cfg_word_count_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_word_count_wizard.setToolTip(
            "Create a custom column to store Marvin word counts")
        self.cfg_word_count_wizard.clicked.connect(
            partial(self.launch_cc_wizard, 'Word count'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_word_count_wizard,
                                             current_row, 2)
        current_row += 1

        self.spacerItem1 = QSpacerItem(20, 20, QSizePolicy.Minimum,
                                       QSizePolicy.Expanding)
        self.column1_layout.addItem(self.spacerItem1)

        # ----------------------------- Column 2 -----------------------------
        # ~~~~~~~~ Create the CSS group box ~~~~~~~~
        self.cfg_css_options_gb = QGroupBox(self)
        self.cfg_css_options_gb.setTitle('CSS')
        self.column2_layout.addWidget(self.cfg_css_options_gb)
        self.cfg_css_options_qgl = QGridLayout(self.cfg_css_options_gb)

        current_row = 0

        # ++++++++ Annotations appearance ++++++++
        self.annotations_icon = QIcon(
            os.path.join(self.resources_path, 'icons',
                         'annotations_hiliter.png'))
        self.cfg_annotations_appearance_toolbutton = QToolButton()
        self.cfg_annotations_appearance_toolbutton.setIcon(
            self.annotations_icon)
        self.cfg_annotations_appearance_toolbutton.clicked.connect(
            self.configure_appearance)
        self.cfg_css_options_qgl.addWidget(
            self.cfg_annotations_appearance_toolbutton, current_row, 0)
        self.cfg_annotations_label = ClickableQLabel("Annotations")
        self.connect(self.cfg_annotations_label, SIGNAL('clicked()'),
                     self.configure_appearance)
        self.cfg_css_options_qgl.addWidget(self.cfg_annotations_label,
                                           current_row, 1)
        current_row += 1

        # ++++++++ Injected CSS ++++++++
        self.css_editor_icon = QIcon(I('format-text-heading.png'))
        self.cfg_css_editor_toolbutton = QToolButton()
        self.cfg_css_editor_toolbutton.setIcon(self.css_editor_icon)
        self.cfg_css_editor_toolbutton.clicked.connect(self.edit_css)
        self.cfg_css_options_qgl.addWidget(self.cfg_css_editor_toolbutton,
                                           current_row, 0)
        self.cfg_css_editor_label = ClickableQLabel("Articles, Vocabulary")
        self.connect(self.cfg_css_editor_label, SIGNAL('clicked()'),
                     self.edit_css)
        self.cfg_css_options_qgl.addWidget(self.cfg_css_editor_label,
                                           current_row, 1)
        """
        # ~~~~~~~~ Create the Dropbox syncing group box ~~~~~~~~
        self.cfg_dropbox_syncing_gb = QGroupBox(self)
        self.cfg_dropbox_syncing_gb.setTitle('Dropbox')
        self.column2_layout.addWidget(self.cfg_dropbox_syncing_gb)
        self.cfg_dropbox_syncing_qgl = QGridLayout(self.cfg_dropbox_syncing_gb)
        current_row = 0

        # ++++++++ Syncing enabled checkbox ++++++++
        self.dropbox_syncing_checkbox = QCheckBox('Enable Dropbox updates')
        self.dropbox_syncing_checkbox.setObjectName('dropbox_syncing')
        self.dropbox_syncing_checkbox.setToolTip('Refresh custom column content from Marvin metadata')
        self.cfg_dropbox_syncing_qgl.addWidget(self.dropbox_syncing_checkbox,
            current_row, 0, 1, 3)
        current_row += 1

        # ++++++++ Dropbox folder picker ++++++++
        self.dropbox_folder_icon = QIcon(os.path.join(self.resources_path, 'icons', 'dropbox.png'))
        self.cfg_dropbox_folder_toolbutton = QToolButton()
        self.cfg_dropbox_folder_toolbutton.setIcon(self.dropbox_folder_icon)
        self.cfg_dropbox_folder_toolbutton.setToolTip("Specify Dropbox folder location on your computer")
        self.cfg_dropbox_folder_toolbutton.clicked.connect(self.select_dropbox_folder)
        self.cfg_dropbox_syncing_qgl.addWidget(self.cfg_dropbox_folder_toolbutton,
            current_row, 1)

        # ++++++++ Dropbox location lineedit ++++++++
        self.dropbox_location_lineedit = QLineEdit()
        self.dropbox_location_lineedit.setPlaceholderText("Dropbox folder location")
        self.cfg_dropbox_syncing_qgl.addWidget(self.dropbox_location_lineedit,
            current_row, 2)
        """

        # ~~~~~~~~ Create the General options group box ~~~~~~~~
        self.cfg_runtime_options_gb = QGroupBox(self)
        self.cfg_runtime_options_gb.setTitle('General options')
        self.column2_layout.addWidget(self.cfg_runtime_options_gb)
        self.cfg_runtime_options_qvl = QVBoxLayout(self.cfg_runtime_options_gb)

        # ++++++++ Auto refresh checkbox ++++++++
        self.auto_refresh_checkbox = QCheckBox(
            'Automatically refresh custom column content')
        self.auto_refresh_checkbox.setObjectName('auto_refresh_at_startup')
        self.auto_refresh_checkbox.setToolTip(
            'Update calibre custom column when Marvin XD is opened')
        self.cfg_runtime_options_qvl.addWidget(self.auto_refresh_checkbox)

        # ++++++++ Progress as percentage checkbox ++++++++
        self.reading_progress_checkbox = QCheckBox(
            'Show reading progress as percentage')
        self.reading_progress_checkbox.setObjectName(
            'show_progress_as_percentage')
        self.reading_progress_checkbox.setToolTip(
            'Display percentage in Progress column')
        self.cfg_runtime_options_qvl.addWidget(self.reading_progress_checkbox)

        # ~~~~~~~~ Create the Debug options group box ~~~~~~~~
        self.cfg_debug_options_gb = QGroupBox(self)
        self.cfg_debug_options_gb.setTitle('Debug options')
        self.column2_layout.addWidget(self.cfg_debug_options_gb)
        self.cfg_debug_options_qvl = QVBoxLayout(self.cfg_debug_options_gb)

        # ++++++++ Debug logging checkboxes ++++++++
        self.debug_plugin_checkbox = QCheckBox(
            'Enable debug logging for Marvin XD')
        self.debug_plugin_checkbox.setObjectName('debug_plugin_checkbox')
        self.debug_plugin_checkbox.setToolTip(
            'Print plugin diagnostic messages to console')
        self.cfg_debug_options_qvl.addWidget(self.debug_plugin_checkbox)

        self.debug_libimobiledevice_checkbox = QCheckBox(
            'Enable debug logging for libiMobileDevice')
        self.debug_libimobiledevice_checkbox.setObjectName(
            'debug_libimobiledevice_checkbox')
        self.debug_libimobiledevice_checkbox.setToolTip(
            'Print libiMobileDevice diagnostic messages to console')
        self.cfg_debug_options_qvl.addWidget(
            self.debug_libimobiledevice_checkbox)

        self.spacerItem2 = QSpacerItem(20, 20, QSizePolicy.Minimum,
                                       QSizePolicy.Expanding)
        self.column2_layout.addItem(self.spacerItem2)

        # ~~~~~~~~ End of construction zone ~~~~~~~~
        self.resize(self.sizeHint())

        # ~~~~~~~~ Populate/restore config options ~~~~~~~~
        #  Annotations comboBox
        self.populate_annotations()
        self.populate_collections()
        self.populate_date_read()
        self.populate_progress()
        self.populate_read()
        self.populate_reading_list()
        self.populate_word_count()
        """
        # Restore Dropbox settings, hook changes
        dropbox_syncing = self.prefs.get('dropbox_syncing', False)
        self.dropbox_syncing_checkbox.setChecked(dropbox_syncing)
        self.set_dropbox_syncing(dropbox_syncing)
        self.dropbox_syncing_checkbox.clicked.connect(partial(self.set_dropbox_syncing))
        self.dropbox_location_lineedit.setText(self.prefs.get('dropbox_folder', ''))
        """

        # Restore general settings
        self.auto_refresh_checkbox.setChecked(
            self.prefs.get('auto_refresh_at_startup', False))
        self.reading_progress_checkbox.setChecked(
            self.prefs.get('show_progress_as_percentage', False))

        # Restore debug settings, hook changes
        self.debug_plugin_checkbox.setChecked(
            self.prefs.get('debug_plugin', False))
        self.debug_plugin_checkbox.stateChanged.connect(
            self.set_restart_required)
        self.debug_libimobiledevice_checkbox.setChecked(
            self.prefs.get('debug_libimobiledevice', False))
        self.debug_libimobiledevice_checkbox.stateChanged.connect(
            self.set_restart_required)

        # Hook changes to Annotations comboBox
        #         self.annotations_field_comboBox.currentIndexChanged.connect(
        #             partial(self.save_combobox_setting, 'annotations_field_comboBox'))
        self.connect(self.annotations_field_comboBox,
                     SIGNAL('currentIndexChanged(const QString &)'),
                     self.annotations_destination_changed)

        # Launch the annotated_books_scanner
        field = get_cc_mapping('annotations', 'field', None)
        self.annotated_books_scanner = InventoryAnnotatedBooks(self.gui, field)
        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(str(qs_new_destination_name))
        #self._log("self.eligible_annotations_fields: %s" % self.eligible_annotations_fields)

        old_destination_field = get_cc_mapping('annotations', 'field', None)
        old_destination_name = get_cc_mapping('annotations', 'combobox', None)

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

        new_destination_name = unicode(qs_new_destination_name)
        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 = self.eligible_annotations_fields[
            new_destination_name]

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

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

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

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

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

        else:
            # No existing annotations, just update prefs
            self._log("no existing annotations, updating destination to '{0}'".
                      format(new_destination_name))
            set_cc_mapping('annotations',
                           field=new_destination_field,
                           combobox=new_destination_name)

    def configure_appearance(self):
        '''
        '''
        self._log_location()
        appearance_settings = {
            'appearance_css': default_elements,
            'appearance_hr_checkbox': False,
            'appearance_timestamp_format': default_timestamp
        }

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

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

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

                # Wait for indexing to complete
                while not self.annotated_books_scanner.isFinished():
                    Application.processEvents()

                move_annotations(self,
                                 self.annotated_books_scanner.annotation_map,
                                 field,
                                 field,
                                 window_title="Updating appearance")

    def edit_css(self):
        '''
        '''
        self._log_location()
        from calibre_plugins.marvin_manager.book_status import dialog_resources_path
        klass = os.path.join(dialog_resources_path, 'css_editor.py')
        if os.path.exists(klass):
            sys.path.insert(0, dialog_resources_path)
            this_dc = importlib.import_module('css_editor')
            sys.path.remove(dialog_resources_path)
            dlg = this_dc.CSSEditorDialog(self, 'css_editor')
            dlg.initialize(self)
            dlg.exec_()

    def get_eligible_custom_fields(self, eligible_types=[], is_multiple=None):
        '''
        Discover qualifying custom fields for eligible_types[]
        '''
        #self._log_location(eligible_types)

        eligible_custom_fields = {}
        for cf in self.gui.current_db.custom_field_keys():
            cft = self.gui.current_db.metadata_for_field(cf)['datatype']
            cfn = self.gui.current_db.metadata_for_field(cf)['name']
            cfim = self.gui.current_db.metadata_for_field(cf)['is_multiple']
            #self._log("cf: %s  cft: %s  cfn: %s cfim: %s" % (cf, cft, cfn, cfim))
            if cft in eligible_types:
                if is_multiple is not None:
                    if bool(cfim) == is_multiple:
                        eligible_custom_fields[cfn] = cf
                else:
                    eligible_custom_fields[cfn] = cf
        return eligible_custom_fields

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

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

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

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

            cb.blockSignals(False)

        from calibre_plugins.marvin_manager.book_status import dialog_resources_path

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

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

                self.restart_required = True

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

                if source == "Annotations":
                    _update_combo_box("annotations_field_comboBox",
                                      destination, previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_annotations_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('annotations',
                                   combobox=destination,
                                   field=label)

                elif source == 'Collections':
                    _update_combo_box("collection_field_comboBox", destination,
                                      previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_collection_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('collections',
                                   combobox=destination,
                                   field=label)

                elif source == 'Last read':
                    _update_combo_box("date_read_field_comboBox", destination,
                                      previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_date_read_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('date_read',
                                   combobox=destination,
                                   field=label)

                elif source == "Progress":
                    _update_combo_box("progress_field_comboBox", destination,
                                      previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_progress_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('progress',
                                   combobox=destination,
                                   field=label)

                elif source == "Read":
                    _update_combo_box("read_field_comboBox", destination,
                                      previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_read_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('read', combobox=destination, field=label)

                elif source == "Reading list":
                    _update_combo_box("reading_list_field_comboBox",
                                      destination, previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_reading_list_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('reading_list',
                                   combobox=destination,
                                   field=label)

                elif source == "Word count":
                    _update_combo_box("word_count_field_comboBox", destination,
                                      previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_word_count_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('word_count',
                                   combobox=destination,
                                   field=label)
        else:
            self._log("ERROR: Can't import from '%s'" % klass)

    def launch_new_destination_dialog(self, old, new):
        '''
        Return 'move', 'change' or 'cancel'
        '''
        from calibre_plugins.marvin_manager.book_status import dialog_resources_path
        self._log_location()
        klass = os.path.join(dialog_resources_path, 'new_destination.py')
        if os.path.exists(klass):
            self._log("importing new destination dialog from '%s'" % klass)
            sys.path.insert(0, dialog_resources_path)
            this_dc = importlib.import_module('new_destination')
            sys.path.remove(dialog_resources_path)
            dlg = this_dc.NewDestinationDialog(self, old, new)
            dlg.exec_()
            return dlg.command

    def populate_annotations(self):
        datatype = self.WIZARD_PROFILES['Annotations']['datatype']
        self.eligible_annotations_fields = self.get_eligible_custom_fields(
            [datatype])
        self.annotations_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_annotations_fields.keys(),
                     key=lambda s: s.lower())
        self.annotations_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('annotations', 'combobox')
        if existing:
            ci = self.annotations_field_comboBox.findText(existing)
            self.annotations_field_comboBox.setCurrentIndex(ci)

    def populate_collections(self):
        datatype = self.WIZARD_PROFILES['Collections']['datatype']
        self.eligible_collection_fields = self.get_eligible_custom_fields(
            [datatype], is_multiple=True)
        self.collection_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_collection_fields.keys(),
                     key=lambda s: s.lower())
        self.collection_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('collections', 'combobox')
        if existing:
            ci = self.collection_field_comboBox.findText(existing)
            self.collection_field_comboBox.setCurrentIndex(ci)

    def populate_date_read(self):
        #self.eligible_date_read_fields = self.get_eligible_custom_fields(['datetime'])
        datatype = self.WIZARD_PROFILES['Last read']['datatype']
        self.eligible_date_read_fields = self.get_eligible_custom_fields(
            [datatype])
        self.date_read_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_date_read_fields.keys(),
                     key=lambda s: s.lower())
        self.date_read_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('date_read', 'combobox')
        if existing:
            ci = self.date_read_field_comboBox.findText(existing)
            self.date_read_field_comboBox.setCurrentIndex(ci)

    def populate_progress(self):
        #self.eligible_progress_fields = self.get_eligible_custom_fields(['float'])
        datatype = self.WIZARD_PROFILES['Progress']['datatype']
        self.eligible_progress_fields = self.get_eligible_custom_fields(
            [datatype])
        self.progress_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_progress_fields.keys(),
                     key=lambda s: s.lower())
        self.progress_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('progress', 'combobox')
        if existing:
            ci = self.progress_field_comboBox.findText(existing)
            self.progress_field_comboBox.setCurrentIndex(ci)

    def populate_read(self):
        datatype = self.WIZARD_PROFILES['Read']['datatype']
        self.eligible_read_fields = self.get_eligible_custom_fields([datatype])
        self.read_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_read_fields.keys(), key=lambda s: s.lower())
        self.read_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('read', 'combobox')
        if existing:
            ci = self.read_field_comboBox.findText(existing)
            self.read_field_comboBox.setCurrentIndex(ci)

    def populate_reading_list(self):
        datatype = self.WIZARD_PROFILES['Reading list']['datatype']
        self.eligible_reading_list_fields = self.get_eligible_custom_fields(
            [datatype])
        self.reading_list_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_reading_list_fields.keys(),
                     key=lambda s: s.lower())
        self.reading_list_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('reading_list', 'combobox')
        if existing:
            ci = self.reading_list_field_comboBox.findText(existing)
            self.reading_list_field_comboBox.setCurrentIndex(ci)

    def populate_word_count(self):
        #self.eligible_word_count_fields = self.get_eligible_custom_fields(['int'])
        datatype = self.WIZARD_PROFILES['Word count']['datatype']
        self.eligible_word_count_fields = self.get_eligible_custom_fields(
            [datatype])
        self.word_count_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_word_count_fields.keys(),
                     key=lambda s: s.lower())
        self.word_count_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('word_count', 'combobox')
        if existing:
            ci = self.word_count_field_comboBox.findText(existing)
            self.word_count_field_comboBox.setCurrentIndex(ci)

    """
    def select_dropbox_folder(self):
        '''
        '''
        self._log_location()
        dropbox_location = QFileDialog.getExistingDirectory(
            self,
            "Dropbox folder",
            os.path.expanduser("~"),
            QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks)
        self.dropbox_location_lineedit.setText(unicode(dropbox_location))

    def set_dropbox_syncing(self, state):
        '''
        Called when checkbox changes state, or when restoring state
        Set enabled state of Dropbox folder picker to match
        '''
        self.cfg_dropbox_folder_toolbutton.setEnabled(state)
        self.dropbox_location_lineedit.setEnabled(state)
    """

    def set_restart_required(self, state):
        '''
        Set restart_required flag to show show dialog when closing dialog
        '''
        self.restart_required = True

    """
    def save_combobox_setting(self, cb, index):
        '''
        Apply changes immediately
        '''
        cf = str(getattr(self, cb).currentText())
        self._log_location("%s => %s" % (cb, repr(cf)))

        if cb == 'annotations_field_comboBox':
            field = None
            if cf:
                field = self.eligible_annotations_fields[cf]
            set_cc_mapping('annotations', combobox=cf, field=field)
    """

    def save_settings(self):
        self._log_location()

        # Annotations
        cf = unicode(self.annotations_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_annotations_fields[cf]
        set_cc_mapping('annotations', combobox=cf, field=field)

        # Collections
        cf = unicode(self.collection_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_collection_fields[cf]
        set_cc_mapping('collections', combobox=cf, field=field)

        # Save Date read field
        cf = unicode(self.date_read_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_date_read_fields[cf]
        set_cc_mapping('date_read', combobox=cf, field=field)

        # Save Progress field
        cf = unicode(self.progress_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_progress_fields[cf]
        set_cc_mapping('progress', combobox=cf, field=field)

        # Save Read field
        cf = unicode(self.read_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_read_fields[cf]
        set_cc_mapping('read', combobox=cf, field=field)

        # Save Reading list field
        cf = unicode(self.reading_list_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_reading_list_fields[cf]
        set_cc_mapping('reading_list', combobox=cf, field=field)

        # Save Word count field
        cf = unicode(self.word_count_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_word_count_fields[cf]
        set_cc_mapping('word_count', combobox=cf, field=field)
        '''
        # Save Dropbox settings
        self.prefs.set('dropbox_syncing', self.dropbox_syncing_checkbox.isChecked())
        self.prefs.set('dropbox_folder', unicode(self.dropbox_location_lineedit.text()))
        '''

        # Save general settings
        self.prefs.set('auto_refresh_at_startup',
                       self.auto_refresh_checkbox.isChecked())
        self.prefs.set('show_progress_as_percentage',
                       self.reading_progress_checkbox.isChecked())

        # Save debug settings
        self.prefs.set('debug_plugin', self.debug_plugin_checkbox.isChecked())
        self.prefs.set('debug_libimobiledevice',
                       self.debug_libimobiledevice_checkbox.isChecked())

        # If restart needed, inform user
        if self.restart_required:
            do_restart = show_restart_warning(
                'Restart calibre for the changes to be applied.',
                parent=self.gui)
            if do_restart:
                self.gui.quit(restart=True)

    def start_inventory(self):
        self._log_location()
        self.annotated_books_scanner.start()
Ejemplo n.º 14
0
class AddTagDialog(QDialog):
    def __init__(self, parent, modal=True, flags=Qt.WindowFlags()):
        QDialog.__init__(self, parent, flags)
        self.setModal(modal)
        self.setWindowTitle("Add Tag")
        lo = QVBoxLayout(self)
        lo.setMargin(10)
        lo.setSpacing(5)
        # tag selector
        lo1 = QHBoxLayout()
        lo.addLayout(lo1)
        lo1.setSpacing(5)
        self.wtagsel = QComboBox(self)
        self.wtagsel.setEditable(True)
        wtagsel_lbl = QLabel("&Tag:", self)
        wtagsel_lbl.setBuddy(self.wtagsel)
        lo1.addWidget(wtagsel_lbl, 0)
        lo1.addWidget(self.wtagsel, 1)
        QObject.connect(self.wtagsel, SIGNAL("activated(int)"), self._check_tag)
        QObject.connect(self.wtagsel, SIGNAL("editTextChanged(const QString &)"), self._check_tag_text)
        # value editor
        self.valedit = ValueTypeEditor(self)
        lo.addWidget(self.valedit)
        # buttons
        lo.addSpacing(10)
        lo2 = QHBoxLayout()
        lo.addLayout(lo2)
        lo2.setContentsMargins(0, 0, 0, 0)
        lo2.setMargin(5)
        self.wokbtn = QPushButton("OK", self)
        self.wokbtn.setMinimumWidth(128)
        QObject.connect(self.wokbtn, SIGNAL("clicked()"), self.accept)
        self.wokbtn.setEnabled(False)
        cancelbtn = QPushButton("Cancel", self)
        cancelbtn.setMinimumWidth(128)
        QObject.connect(cancelbtn, SIGNAL("clicked()"), self.reject)
        lo2.addWidget(self.wokbtn)
        lo2.addStretch(1)
        lo2.addWidget(cancelbtn)
        self.setMinimumWidth(384)

    def setTags(self, tagnames):
        self.wtagsel.clear()
        self.wtagsel.addItems(list(tagnames))
        self.wtagsel.addItem("")
        self.wtagsel.setCurrentIndex(len(tagnames))

    def setValue(self, value):
        self.valedit.setValue(value)

    def _check_tag(self, tag):
        self.wokbtn.setEnabled(True)

    def _check_tag_text(self, text):
        self.wokbtn.setEnabled(bool(str(text) != ""))

    def accept(self):
        """When dialog is accepted with a default (bool) tag type,
        check if the user hasn't entered a name=value entry in the tag name field.
        This is a common mistake, and should be treated as a shortcut for setting string tags."""
        if isinstance(self.valedit.getValue(), bool):
            tagval = str(self.wtagsel.currentText()).split("=", 1)
            if len(tagval) > 1:
                #        print tagval
                if QMessageBox.warning(self,
                                       "Set a string tag instead?", """<P>You have included an "=" sign in the tag name. 
            Perhaps you actually mean to set tag "%s" to the string value "%s"?</P>""" % tuple(tagval),
                                       QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) == QMessageBox.No:
                    return
                self.wtagsel.setEditText(tagval[0])
                self.valedit.setValue(tagval[1])
        return QDialog.accept(self)

    def getTag(self):
        return str(self.wtagsel.currentText()), self.valedit.getValue()
Ejemplo n.º 15
0
class TwitterGui(QWidget):    
    URL_REGEX = re.compile(r'''((?:mailto:|ftp://|http://|https://)[^ <>'"{}|\\^`[\]]*)''')
    
    def __init__(self, parent, logger, db_conn, update_func, safe_conn):
        super(TwitterGui, self).__init__(parent)
        self._db_conn = db_conn
        self.logger = logger
        self._reply_to_id = 0
        self._update_func = update_func
        
        self._list = None
        
        if get_settings().get_proxy():
            u = urlparse.urlsplit(get_settings().get_proxy())
            proxy = QNetworkProxy()
            
            proxy.setType(QNetworkProxy.HttpProxy)
            proxy.setHostName(u.hostname);
            proxy.setPort(u.port)
            QNetworkProxy.setApplicationProxy(proxy);
        
        self.msgview = QWebView(self)
        self.msgview.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks)
        self.msgview.linkClicked.connect(self.link_clicked)
        
        self.userCombo = QComboBox(self)
        self.userCombo.setEditable(True)
        self.userCombo.activated.connect(self.toggle_user_in_list)
        
        self.showButton = QPushButton(chr(94), self)  
        self.showButton.setMaximumHeight(13)
        self.showButton.clicked.connect(self.show_hide_animation)
        
        self.post_field = QTextEdit(self)
        self.post_field.setMaximumHeight(50)
        self.post_field.textChanged.connect(self.text_changed)
        self.send_button = QPushButton("Post", self)
        self.send_button.clicked.connect(self.post_status_clicked)
        self.refresh_button = QPushButton("Refresh", self)
        self.refresh_button.clicked.connect(self._update_func)
        self.attach_button = QPushButton("Attach", self)
        self.attach_button.clicked.connect(lambda _ : self.set_status("Attach something"))
        self.lists_box = QComboBox(self)
        self.lists_box.currentIndexChanged.connect(self.list_changed)
        self.lists_box.setEditable(False)
        self.lists_box.addItems([u"Home"] + self._db_conn.get_lists())
        self.statusLabel = QLabel("Status", self)
        self.charCounter = QLabel("0", self)
        
        self.gridw = QWidget(self)
        self.gridw.setContentsMargins(0, 0, 0, 0)
        gridlay = QGridLayout(self.gridw)
        gridlay.setContentsMargins(0, 0, 0, 0)
        gridlay.addWidget(self.post_field, 0, 0, 2, 1)
        gridlay.addWidget(self.attach_button, 0, 1, 1, 1)
        gridlay.addWidget(self.send_button, 1, 1, 1, 1)
        gridlay.addWidget(self.lists_box, 0, 2, 1, 1)
        gridlay.addWidget(self.refresh_button, 1, 2, 1, 1)
        gridlay.addWidget(self.statusLabel, 2, 0, 1, 1)
        gridlay.addWidget(self.charCounter, 2, 1, 1, 2)
        
        hlay = QVBoxLayout(self)
        hlay.addWidget(self.msgview)
        hlay.addWidget(self.userCombo)
        hlay.addWidget(self.showButton)
        hlay.addWidget(self.gridw)
        
        safe_conn.connect_home_timeline_updated(self.update_view)
        safe_conn.connect_twitter_loop_started(self.start_refresh_animation)
        safe_conn.connect_twitter_loop_stopped(self.stop_refresh_animation)
        safe_conn.connect_update_posted(self.enable_posting)
        safe_conn.connect_range_limit_exceeded(lambda _ : self.set_status("Range limit exceeded"))
        safe_conn.connect_not_authenticated(lambda _ : self.set_status("Authentication failed"))
        
        self.gridw.hide()
        self.update_view()
        self.set_status("Twitter plugin initialized")
        
    def enable_posting(self, q_id, m_id):
        if m_id>1:
            self.post_field.setText("")
            self.set_status("Tweet posted")
        else:
            self.set_status("Failed to post tweet, Error: " + str(abs(m_id)))
        self.post_field.setEnabled(True)
        
    def link_clicked(self, url):
        if not url.host():
            if url.hasQueryItem("reply-to") and url.hasQueryItem("screen-name"):
                self._reply_to_id = long(convert_string(url.queryItemValue("reply-to")))
                self.post_field.setPlainText("@"+convert_string(url.queryItemValue("screen-name"))+" ")
                self.set_status("Reply to @"+convert_string(url.queryItemValue("screen-name")))
            else:
                self.logger.error("Unknown command from link: "+str(url.toString()))
        else:
            webbrowser.open(str(url.toString()))
            
    def list_changed(self, list_idx):
        if list_idx:
            self._list = convert_string(self.lists_box.currentText())
            self.userCombo.clear()
            self.userCombo.addItems(self._db_conn.get_known_users())            
            self.userCombo.completer().setCompletionMode(QCompleter.PopupCompletion)
            self.userCombo.show()
            self.set_status(self._list)
        else:
            self.userCombo.hide()
            self._list = None
        self.update_view()
        
    def post_status_clicked(self):
        msg = unicode(self.post_field.toPlainText().toUtf8(), encoding="UTF-8")
        if msg:
            self._db_conn.insert_post_queue(msg, self._reply_to_id)
            self._reply_to_id = 0
            self._update_func()
            self.post_field.setDisabled(True)
    
    def start_refresh_animation(self):
        self.refresh_button.setDisabled(True)
    
    def stop_refresh_animation(self):
        self.refresh_button.setEnabled(True)
        
    def show_hide_animation(self):
        if self.gridw.isHidden():
            self.gridw.show()            
            self.showButton.setText("v")
        else:
            self.gridw.hide()
            self.showButton.setText(chr(94))
            
    def text_changed(self):
        count = len(self.post_field.toPlainText())
        if count==0:
            self._reply_to_id = 0
        if self._reply_to_id:
            self.charCounter.setText(str(count) + " - reply to ")
        else:
            self.charCounter.setText(str(count))
            
    def toggle_user_in_list(self, _):
        user = convert_string(self.userCombo.currentText())
        if user in self._db_conn.get_users_from_list(self._list):
            self._db_conn.delete_user_from_list(user, self._list)
            self.set_status("Removed user %s from list %s"%(user, self._list))
        else:
            self._db_conn.add_user_to_list(user, self._list)
            self.set_status("Added user %s to list %s"%(user, self._list))
        self.update_view()
        
        
    @pyqtSlot(object)
    def update_view(self, _ = None):
        template_file_path = os.path.join(get_settings().get_main_config_dir(),"tweet.thtml")
        
        tweets = self._db_conn.get_last_tweets(user_list = self._list)
        if len(tweets)==0:
            return 0
        
        templ_text = '<div>\
                    <a href="http://twitter.com/$NAME$/status/$ID$">\
                            <img src="$IMAGE$" style="float: left; margin-right: 2px" alt="$NAME$" title="$NAME$"/>\
                    </a>\
                    <p>$TEXT$</p>\
                    <span><a href="http://twitter.com/$RT_USER$">$RT_USER$</a></span>\
                    <span style="float: right">$CREATE_TIME$ <a href="?retweet=$ID$">retweet</a> <a href="?reply-to=$ID$&screen-name=$NAME$">reply</a></span>\
                    </div>\
                    <hr style="clear: both" />\
                    '
        if os.path.exists(template_file_path):
            t_file = open(template_file_path, "r")
            templ_text = t_file.read()
            t_file.close()
        
        txt = ""
        for t in tweets:
            """m_id, screen_name, user_image, create_time, message_text, retweeted_by"""
            text = self.URL_REGEX.sub(r'<a href="\1">\1</a>', t[4])
            t_txt = templ_text.replace("$IMAGE$", t[2]).replace("$NAME$", t[1])
            t_txt = t_txt.replace("$ID$", str(t[0])).replace("$TEXT$", text)
            t_txt = t_txt.replace("$CREATE_TIME$", self.humanReadableTime(t[3]))
            t_txt = t_txt.replace("$RT_USER$", t[5] if t[5] else "")
            txt += t_txt
        txt += "<p style=\"float:right\">Updated: %s</p>"%time.strftime("%H:%M")

        self.msgview.setHtml(txt)
        
            
    """helper:"""
    def set_status(self, status_text):
        self.statusLabel.setText(status_text)
        self.showButton.setToolTip(status_text)
    
    def humanReadableTime(self, post_time):
        fudge = 1.25
        delta = long(time.time()) - long(post_time)
    
        if delta < (1 * fudge):
            return 'about a second ago' 
        elif delta < (60 * (1 / fudge)):
            return 'about %d seconds ago' % (delta)
        elif delta < (60 * fudge):
            return 'about a minute ago'
        elif delta < (60 * 60 * (1 / fudge)):
            return 'about %d minutes ago' % (delta / 60)
        elif delta < (60 * 60 * fudge) or delta / (60 * 60) == 1:
            return 'about an hour ago'
        elif delta < (60 * 60 * 24 * (1 / fudge)):
            return 'about %d hours ago' % (delta / (60 * 60))
        elif delta < (60 * 60 * 24 * fudge) or delta / (60 * 60 * 24) == 1:
            return 'about a day ago'
        else:
            return 'about %d days ago' % (delta / (60 * 60 * 24))
Ejemplo n.º 16
0
class RuleEditor(QDialog): # {{{

    def __init__(self, fm, parent=None):
        QDialog.__init__(self, parent)
        self.fm = fm

        self.setWindowIcon(QIcon(I('format-fill-color.png')))
        self.setWindowTitle(_('Create/edit a column coloring rule'))

        self.l = l = QGridLayout(self)
        self.setLayout(l)

        self.l1 = l1 = QLabel(_('Create a coloring rule by'
            ' filling in the boxes below'))
        l.addWidget(l1, 0, 0, 1, 5)

        self.f1 = QFrame(self)
        self.f1.setFrameShape(QFrame.HLine)
        l.addWidget(self.f1, 1, 0, 1, 5)

        self.l2 = l2 = QLabel(_('Set the color of the column:'))
        l.addWidget(l2, 2, 0)

        self.column_box = QComboBox(self)
        l.addWidget(self.column_box, 2, 1)

        self.l3 = l3 = QLabel(_('to'))
        l.addWidget(l3, 2, 2)

        self.color_box = QComboBox(self)
        self.color_label = QLabel('Sample text Sample text')
        self.color_label.setTextFormat(Qt.RichText)
        l.addWidget(self.color_box, 2, 3)
        l.addWidget(self.color_label, 2, 4)
        l.addItem(QSpacerItem(10, 10, QSizePolicy.Expanding), 2, 5)

        self.l4 = l4 = QLabel(
            _('Only if the following conditions are all satisfied:'))
        l.addWidget(l4, 3, 0, 1, 6)

        self.scroll_area = sa = QScrollArea(self)
        sa.setMinimumHeight(300)
        sa.setMinimumWidth(950)
        sa.setWidgetResizable(True)
        l.addWidget(sa, 4, 0, 1, 6)

        self.add_button = b = QPushButton(QIcon(I('plus.png')),
                _('Add another condition'))
        l.addWidget(b, 5, 0, 1, 6)
        b.clicked.connect(self.add_blank_condition)

        self.l5 = l5 = QLabel(_('You can disable a condition by'
            ' blanking all of its boxes'))
        l.addWidget(l5, 6, 0, 1, 6)

        self.bb = bb = QDialogButtonBox(
                QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
        bb.accepted.connect(self.accept)
        bb.rejected.connect(self.reject)
        l.addWidget(bb, 7, 0, 1, 6)

        self.conditions_widget = QWidget(self)
        sa.setWidget(self.conditions_widget)
        self.conditions_widget.setLayout(QVBoxLayout())
        self.conditions_widget.layout().setAlignment(Qt.AlignTop)
        self.conditions = []

        for b in (self.column_box, self.color_box):
            b.setSizeAdjustPolicy(b.AdjustToMinimumContentsLengthWithIcon)
            b.setMinimumContentsLength(15)

        for key in sorted(
                displayable_columns(fm),
                key=sort_key):
            name = fm[key]['name']
            if name:
                self.column_box.addItem(key, key)
        self.column_box.setCurrentIndex(0)

        self.color_box.addItems(QColor.colorNames())
        self.color_box.setCurrentIndex(0)

        self.update_color_label()
        self.color_box.currentIndexChanged.connect(self.update_color_label)
        self.resize(self.sizeHint())

    def update_color_label(self):
        pal = QApplication.palette()
        bg1 = unicode(pal.color(pal.Base).name())
        bg2 = unicode(pal.color(pal.AlternateBase).name())
        c = unicode(self.color_box.currentText())
        self.color_label.setText('''
            <span style="color: {c}; background-color: {bg1}">&nbsp;{st}&nbsp;</span>
            <span style="color: {c}; background-color: {bg2}">&nbsp;{st}&nbsp;</span>
            '''.format(c=c, bg1=bg1, bg2=bg2, st=_('Sample Text')))


    def add_blank_condition(self):
        c = ConditionEditor(self.fm, parent=self.conditions_widget)
        self.conditions.append(c)
        self.conditions_widget.layout().addWidget(c)

    def apply_rule(self, col, rule):
        for i in range(self.column_box.count()):
            c = unicode(self.column_box.itemData(i).toString())
            if col == c:
                self.column_box.setCurrentIndex(i)
                break
        if rule.color:
            idx = self.color_box.findText(rule.color)
            if idx >= 0:
                self.color_box.setCurrentIndex(idx)
        for c in rule.conditions:
            ce = ConditionEditor(self.fm, parent=self.conditions_widget)
            self.conditions.append(ce)
            self.conditions_widget.layout().addWidget(ce)
            try:
                ce.condition = c
            except:
                import traceback
                traceback.print_exc()


    def accept(self):
        if self.validate():
            QDialog.accept(self)

    def validate(self):
        r = Rule(self.fm)
        for c in self.conditions:
            condition = c.condition
            if condition is not None:
                try:
                    r.add_condition(*condition)
                except Exception as e:
                    import traceback
                    error_dialog(self, _('Invalid condition'),
                            _('One of the conditions for this rule is'
                                ' invalid: <b>%s</b>')%e,
                            det_msg=traceback.format_exc(), show=True)
                    return False
        if len(r.conditions) < 1:
            error_dialog(self, _('No conditions'),
                    _('You must specify at least one non-empty condition'
                        ' for this rule'), show=True)
            return False
        return True

    @property
    def rule(self):
        r = Rule(self.fm)
        r.color = unicode(self.color_box.currentText())
        idx = self.column_box.currentIndex()
        col = unicode(self.column_box.itemData(idx).toString())
        for c in self.conditions:
            condition = c.condition
            if condition is not None:
                r.add_condition(*condition)

        return col, r