예제 #1
0
class SettingsUI(QtWidgets.QDialog, settingswindow.Ui_Dialog):
    def __init__(self, parent=None):
        super(SettingsUI, self).__init__()
        self.setupUi(self)
        self.verticalLayout_4.setContentsMargins(0, 0, 0, 0)
        self._translate = QtCore.QCoreApplication.translate

        self.main_window = parent
        self.database_path = self.main_window.database_path
        self.image_factory = self.main_window.QImageFactory

        # The annotation dialog will use the settings dialog as its parent
        self.annotationsDialog = AnnotationsUI(self)

        self.resize(self.main_window.settings['settings_dialog_size'])
        self.move(self.main_window.settings['settings_dialog_position'])

        install_dir = os.path.realpath(__file__)
        install_dir = pathlib.Path(install_dir).parents[1]
        aboutfile_path = os.path.join(install_dir, 'lector', 'resources',
                                      'about.html')
        with open(aboutfile_path) as about_html:
            html = about_html.readlines()
            html.insert(8,
                        f'<h3 style="text-align: center;">v{VERSION}</h3>\n')
            self.aboutBox.setHtml(''.join(html))

        self.paths = None
        self.thread = None
        self.filesystemModel = None
        self.tag_data_copy = None

        english_string = self._translate('SettingsUI', 'English')
        spanish_string = self._translate('SettingsUI', 'Spanish')
        hindi_string = self._translate('SettingsUI', 'Hindi')
        languages = [english_string, spanish_string, hindi_string]

        self.languageBox.addItems(languages)
        current_language = self.main_window.settings['dictionary_language']
        if current_language == 'en':
            self.languageBox.setCurrentIndex(0)
        elif current_language == 'es':
            self.languageBox.setCurrentIndex(1)
        else:
            self.languageBox.setCurrentIndex(2)
        self.languageBox.activated.connect(self.change_dictionary_language)

        self.okButton.setToolTip(
            self._translate('SettingsUI',
                            'Save changes and start library scan'))
        self.okButton.clicked.connect(self.start_library_scan)
        self.cancelButton.clicked.connect(self.cancel_pressed)

        # Radio buttons
        if self.main_window.settings['icon_theme'] == 'DarkIcons':
            self.darkIconsRadio.setChecked(True)
        else:
            self.lightIconsRadio.setChecked(True)
        self.darkIconsRadio.clicked.connect(self.change_icon_theme)
        self.lightIconsRadio.clicked.connect(self.change_icon_theme)

        # Check boxes
        self.autoTags.setChecked(self.main_window.settings['auto_tags'])
        self.coverShadows.setChecked(
            self.main_window.settings['cover_shadows'])
        self.refreshLibrary.setChecked(
            self.main_window.settings['scan_library'])
        self.fileRemember.setChecked(
            self.main_window.settings['remember_files'])
        self.performCulling.setChecked(
            self.main_window.settings['perform_culling'])
        self.cachingEnabled.setChecked(
            self.main_window.settings['caching_enabled'])
        self.hideScrollBars.setChecked(
            self.main_window.settings['hide_scrollbars'])
        self.attenuateTitles.setChecked(
            self.main_window.settings['attenuate_titles'])
        self.navBarVisible.setChecked(self.main_window.settings['nav_bar'])
        self.autoCover.setChecked(self.main_window.settings['auto_cover'])
        self.scrollSpeedSlider.setValue(
            self.main_window.settings['scroll_speed'])
        self.readAtPercent.setValue(
            self.main_window.settings['consider_read_at'])
        self.smallIncrementBox.setValue(
            self.main_window.settings['small_increment'])
        self.largeIncrementBox.setValue(
            self.main_window.settings['large_increment'])

        self.autoTags.clicked.connect(self.manage_checkboxes)
        self.coverShadows.clicked.connect(self.manage_checkboxes)
        self.refreshLibrary.clicked.connect(self.manage_checkboxes)
        self.fileRemember.clicked.connect(self.manage_checkboxes)
        self.performCulling.clicked.connect(self.manage_checkboxes)
        self.cachingEnabled.clicked.connect(self.manage_checkboxes)
        self.hideScrollBars.clicked.connect(self.manage_checkboxes)
        self.attenuateTitles.clicked.connect(self.manage_checkboxes)
        self.navBarVisible.clicked.connect(self.manage_checkboxes)
        self.autoCover.clicked.connect(self.manage_checkboxes)
        self.scrollSpeedSlider.valueChanged.connect(self.change_scroll_speed)
        self.readAtPercent.valueChanged.connect(self.change_read_at)
        self.smallIncrementBox.valueChanged.connect(self.change_increment)
        self.largeIncrementBox.valueChanged.connect(self.change_increment)

        # Generate the QStandardItemModel for the listView
        self.listModel = QtGui.QStandardItemModel(self.listView)

        library_string = self._translate('SettingsUI', 'Library')
        switches_string = self._translate('SettingsUI', 'Switches')
        annotations_string = self._translate('SettingsUI', 'Annotations')
        about_string = self._translate('SettingsUI', 'About')
        list_options = [
            library_string, switches_string, annotations_string, about_string
        ]

        icon_dict = {
            0: 'view-readermode',
            1: 'switches',
            2: 'annotate',
            3: 'about'
        }

        for count, i in enumerate(list_options):
            item = QtGui.QStandardItem()
            item.setText(i)
            this_icon = icon_dict[count]
            item.setIcon(self.main_window.QImageFactory.get_image(this_icon))
            self.listModel.appendRow(item)
        self.listView.setModel(self.listModel)

        # Custom signal to account for page changes
        self.listView.newIndexSignal.connect(self.list_index_changed)

        # Annotation related buttons
        # Icon names
        self.newAnnotation.setIcon(self.image_factory.get_image('add'))
        self.deleteAnnotation.setIcon(self.image_factory.get_image('remove'))
        self.editAnnotation.setIcon(
            self.image_factory.get_image('edit-rename'))
        self.moveUp.setIcon(self.image_factory.get_image('arrow-up'))
        self.moveDown.setIcon(self.image_factory.get_image('arrow-down'))

        # Icon sizes
        self.newAnnotation.setIconSize(QtCore.QSize(24, 24))
        self.deleteAnnotation.setIconSize(QtCore.QSize(24, 24))
        self.editAnnotation.setIconSize(QtCore.QSize(24, 24))
        self.moveUp.setIconSize(QtCore.QSize(24, 24))
        self.moveDown.setIconSize(QtCore.QSize(24, 24))

        self.annotationsList.clicked.connect(self.load_annotation)
        self.annotationsList.doubleClicked.connect(self.editAnnotation.click)
        self.newAnnotation.clicked.connect(self.add_annotation)
        self.deleteAnnotation.clicked.connect(self.delete_annotation)
        self.editAnnotation.clicked.connect(self.load_annotation)
        self.moveUp.clicked.connect(self.move_annotation)
        self.moveDown.clicked.connect(self.move_annotation)

        # Generate annotation settings
        self.annotationModel = QtGui.QStandardItemModel()
        self.generate_annotations()

        # Generate the filesystem treeView
        self.generate_tree()

        # About... About
        self.aboutTabWidget.setDocumentMode(True)
        self.aboutTabWidget.setContentsMargins(0, 0, 0, 0)
        self.logBox.setReadOnly(True)

        # About buttons
        self.resetButton.clicked.connect(self.delete_database)
        self.clearLogButton.clicked.connect(self.clear_log)

        # Hide the image annotation tab
        # TODO
        # Maybe get off your lazy ass and write something for this
        self.tabWidget.setContentsMargins(0, 0, 0, 0)
        self.tabWidget.tabBar().setVisible(False)

    def list_index_changed(self, index):
        switch_to = index.row()
        self.stackedWidget.setCurrentIndex(switch_to)

        valid_buttons = {
            0: (self.okButton, ),
            3: (self.resetButton, self.clearLogButton),
        }

        for i in valid_buttons:
            if i == switch_to:
                for j in valid_buttons[i]:
                    j.setVisible(True)
            else:
                for j in valid_buttons[i]:
                    j.setVisible(False)

    def generate_tree(self):
        # Fetch all directories in the database
        paths = database.DatabaseFunctions(self.database_path).fetch_data(
            ('Path', 'Name', 'Tags', 'CheckState'), 'directories',
            {'Path': ''}, 'LIKE')

        self.main_window.generate_library_filter_menu(paths)
        directory_data = {}
        if not paths:
            logger.warning('No book paths saved')
        else:
            # Convert to the dictionary format that is
            # to be fed into the QFileSystemModel
            for i in paths:
                directory_data[i[0]] = {
                    'name': i[1],
                    'tags': i[2],
                    'check_state': i[3]
                }

        self.filesystemModel = MostExcellentFileSystemModel(directory_data)
        self.filesystemModel.setFilter(QtCore.QDir.NoDotAndDotDot
                                       | QtCore.QDir.Dirs)
        self.treeView.setModel(self.filesystemModel)

        # TODO
        # This here might break on them pestilent non unixy OSes
        # Check and see

        root_directory = QtCore.QDir().rootPath()
        self.treeView.setRootIndex(
            self.filesystemModel.setRootPath(root_directory))

        # Set the treeView and QFileSystemModel to its desired state
        selected_paths = [
            i for i in directory_data
            if directory_data[i]['check_state'] == QtCore.Qt.Checked
        ]
        expand_paths = set()
        for i in selected_paths:

            # Recursively grind down parent paths for expansion
            this_path = i
            while True:
                parent_path = os.path.dirname(this_path)
                if parent_path == this_path:
                    break
                expand_paths.add(parent_path)
                this_path = parent_path

        # Expand all the parent paths derived from the selected path
        if root_directory in expand_paths:
            expand_paths.remove(root_directory)

        for i in expand_paths:
            this_index = self.filesystemModel.index(i)
            self.treeView.expand(this_index)

        header_sizes = self.main_window.settings['settings_dialog_headers']
        if header_sizes:
            for count, i in enumerate((0, 4)):
                self.treeView.setColumnWidth(i, int(header_sizes[count]))

        # TODO
        # Set a QSortFilterProxy model on top of the existing QFileSystem model
        # self.filesystem_proxy_model = FileSystemProxyModel()
        # self.filesystem_proxy_model.setSourceModel(self.filesystem_model)
        # self.treeView.setModel(self.filesystem_proxy_model)

        for i in range(1, 4):
            self.treeView.hideColumn(i)

    def start_library_scan(self):
        self.hide()

        data_pairs = []
        for i in self.filesystemModel.tag_data.items():
            data_pairs.append(
                [i[0], i[1]['name'], i[1]['tags'], i[1]['check_state']])

        database.DatabaseFunctions(
            self.database_path).set_library_paths(data_pairs)

        if not data_pairs:
            logger.error('Can\'t scan - No book paths saved')
            try:
                if self.sender().objectName() == 'reloadLibrary':
                    self.show()
                    treeViewIndex = self.listModel.index(0, 0)
                    self.listView.setCurrentIndex(treeViewIndex)
                    return
            except AttributeError:
                pass

            database.DatabaseFunctions(
                self.database_path).delete_from_database('*', '*')

            self.main_window.lib_ref.generate_model('build')
            self.main_window.lib_ref.generate_proxymodels()
            self.main_window.generate_library_filter_menu()

            return

        # Update the main window library filter menu
        self.main_window.generate_library_filter_menu(data_pairs)
        self.main_window.set_library_filter()

        # Disallow rechecking until the first check completes
        self.okButton.setEnabled(False)
        self.main_window.libraryToolBar.reloadLibraryButton.setEnabled(False)
        self.okButton.setToolTip(
            self._translate('SettingsUI', 'Library scan in progress...'))

        # Traverse directories looking for files
        self.main_window.statusMessage.setText(
            self._translate('SettingsUI', 'Checking library folders'))
        self.thread = BackGroundBookSearch(data_pairs)
        self.thread.finished.connect(self.finished_iterating)
        self.thread.start()

    def finished_iterating(self):
        # The books the search thread has found
        # are now in self.thread.valid_files
        if not self.thread.valid_files:
            self.main_window.move_on()
            return

        # Hey, messaging is important, okay?
        self.main_window.statusBar.setVisible(True)
        self.main_window.sorterProgress.setVisible(True)
        self.main_window.statusMessage.setText(
            self._translate('SettingsUI', 'Parsing files'))

        # We now create a new thread to put those files into the database
        self.thread = BackGroundBookAddition(self.thread.valid_files,
                                             self.database_path, 'automatic',
                                             self.main_window)
        self.thread.finished.connect(
            lambda: self.main_window.move_on(self.thread.errors))
        self.thread.start()

    def cancel_pressed(self):
        self.filesystemModel.tag_data = copy.deepcopy(self.tag_data_copy)
        self.hide()

    def hideEvent(self, event):
        self.no_more_settings()
        event.accept()

    def showEvent(self, event):
        # Load log into the plainTextEdit
        with open(logger_filename) as infile:
            log_text = infile.read()
            self.logBox.setPlainText(log_text)
        # Annotation preview
        self.format_preview()
        # Make copy of tags in case of a nope.jpg
        self.tag_data_copy = copy.deepcopy(self.filesystemModel.tag_data)

        event.accept()

    def no_more_settings(self):
        self.main_window.libraryToolBar.settingsButton.setChecked(False)
        self.gather_annotations()
        Settings(self.main_window).save_settings()
        Settings(self.main_window).read_settings()
        self.main_window.settings[
            'last_open_tab'] = None  # Needed to allow focus change
        # to newly opened book
        self.resizeEvent()

    def resizeEvent(self, event=None):
        self.main_window.settings['settings_dialog_size'] = self.size()
        self.main_window.settings['settings_dialog_position'] = self.pos()
        table_headers = []
        for i in [0, 4]:
            table_headers.append(self.treeView.columnWidth(i))
        self.main_window.settings['settings_dialog_headers'] = table_headers

    def change_icon_theme(self):
        if self.sender() == self.darkIconsRadio:
            self.main_window.settings['icon_theme'] = 'DarkIcons'
        else:
            self.main_window.settings['icon_theme'] = 'LightIcons'

    def change_dictionary_language(self, event):
        language_dict = {0: 'en', 1: 'es', 2: 'hi'}
        self.main_window.settings['dictionary_language'] = language_dict[
            self.languageBox.currentIndex()]

    def change_scroll_speed(self, event=None):
        self.main_window.settings[
            'scroll_speed'] = self.scrollSpeedSlider.value()

    def change_read_at(self, event=None):
        self.main_window.settings[
            'consider_read_at'] = self.readAtPercent.value()

    def change_increment(self, event=None):
        self.main_window.settings[
            'small_increment'] = self.smallIncrementBox.value()
        self.main_window.settings[
            'large_increment'] = self.largeIncrementBox.value()

    def manage_checkboxes(self, event=None):
        sender = self.sender().objectName()

        sender_dict = {
            'coverShadows': 'cover_shadows',
            'autoTags': 'auto_tags',
            'refreshLibrary': 'scan_library',
            'fileRemember': 'remember_files',
            'performCulling': 'perform_culling',
            'cachingEnabled': 'caching_enabled',
            'hideScrollBars': 'hide_scrollbars',
            'attenuateTitles': 'attenuate_titles',
            'navBarVisible': 'nav_bar',
            'autoCover': 'auto_cover'
        }

        self.main_window.settings[sender_dict[
            sender]] = not self.main_window.settings[sender_dict[sender]]

        if not self.performCulling.isChecked():
            self.main_window.cover_functions.load_all_covers()

    def generate_annotations(self):
        saved_annotations = self.main_window.settings['annotations']

        for i in saved_annotations:
            item = QtGui.QStandardItem()
            item.setText(i['name'])
            item.setData(i, QtCore.Qt.UserRole)
            self.annotationModel.appendRow(item)

        self.annotationsList.setModel(self.annotationModel)

    def format_preview(self):
        # Needed to clear the preview of annotation ickiness
        cursor = QtGui.QTextCursor()
        self.previewView.setTextCursor(cursor)

        self.previewView.setText('Vidistine nuper imagines moventes bonas?')
        profile_index = self.main_window.bookToolBar.profileBox.currentIndex()
        current_profile = self.main_window.bookToolBar.profileBox.itemData(
            profile_index, QtCore.Qt.UserRole)

        if not current_profile:
            return

        font = current_profile['font']
        self.foreground = current_profile['foreground']
        background = current_profile['background']
        font_size = current_profile['font_size']

        self.previewView.setStyleSheet(
            "QTextEdit {{font-family: {0}; font-size: {1}px; color: {2}; background-color: {3}}}"
            .format(font, font_size, self.foreground.name(),
                    background.name()))

        block_format = QtGui.QTextBlockFormat()
        block_format.setAlignment(QtCore.Qt.AlignVCenter
                                  | QtCore.Qt.AlignHCenter)

        cursor = self.previewView.textCursor()
        while True:
            old_position = cursor.position()
            cursor.mergeBlockFormat(block_format)
            cursor.movePosition(QtGui.QTextCursor.NextBlock, 0, 1)
            new_position = cursor.position()
            if old_position == new_position:
                break

    def add_annotation(self):
        self.annotationsDialog.show_dialog('add')

    def delete_annotation(self):
        selected_index = self.annotationsList.currentIndex()
        if not selected_index.isValid():
            return

        self.annotationModel.removeRow(
            self.annotationsList.currentIndex().row())
        self.format_preview()
        self.annotationsList.clearSelection()

    def load_annotation(self):
        selected_index = self.annotationsList.currentIndex()
        if not selected_index.isValid():
            return

        if self.sender() == self.annotationsList:
            self.annotationsDialog.show_dialog('preview', selected_index)

        elif self.sender() == self.editAnnotation:
            self.annotationsDialog.show_dialog('edit', selected_index)

    def move_annotation(self):
        current_row = self.annotationsList.currentIndex().row()

        if self.sender() == self.moveUp:
            new_row = current_row - 1
            if new_row < 0:
                return

        elif self.sender() == self.moveDown:
            new_row = current_row + 1
            if new_row == self.annotationModel.rowCount():
                return

        row_out = self.annotationModel.takeRow(current_row)
        self.annotationModel.insertRow(new_row, row_out)
        new_index = self.annotationModel.index(new_row, 0)

        self.annotationsList.setCurrentIndex(new_index)

    def gather_annotations(self):
        annotations_out = []
        for i in range(self.annotationModel.rowCount()):
            annotation_item = self.annotationModel.item(i, 0)
            annotation_data = annotation_item.data(QtCore.Qt.UserRole)
            annotations_out.append(annotation_data)

        self.main_window.settings['annotations'] = annotations_out

    def delete_database(self):
        def ifcontinue(box_button):
            if box_button.text() != '&Yes':
                return

            database_filename = os.path.join(self.main_window.database_path,
                                             'Lector.db')
            os.remove(database_filename)
            QtWidgets.qApp.exit()

        # Generate a message box to confirm deletion
        confirm_deletion = QtWidgets.QMessageBox()
        deletion_prompt = self._translate('SettingsUI',
                                          f'Delete database and exit?')
        confirm_deletion.setText(deletion_prompt)
        confirm_deletion.setIcon(QtWidgets.QMessageBox.Critical)
        confirm_deletion.setWindowTitle(
            self._translate('SettingsUI', 'Confirm'))
        confirm_deletion.setStandardButtons(QtWidgets.QMessageBox.Yes
                                            | QtWidgets.QMessageBox.No)
        confirm_deletion.buttonClicked.connect(ifcontinue)
        confirm_deletion.show()
        confirm_deletion.exec_()

    def clear_log(self):
        self.logBox.clear()
        open(logger_filename, 'w').close()
예제 #2
0
class SettingsUI(QtWidgets.QDialog, settingswindow.Ui_Dialog):
    def __init__(self, parent=None):
        super(SettingsUI, self).__init__()
        self.setupUi(self)
        self.verticalLayout_4.setContentsMargins(0, 0, 0, 0)
        self._translate = QtCore.QCoreApplication.translate

        self.parent = parent
        self.database_path = self.parent.database_path

        self.resize(self.parent.settings['settings_dialog_size'])
        self.move(self.parent.settings['settings_dialog_position'])

        install_dir = os.path.realpath(__file__)
        install_dir = pathlib.Path(install_dir).parents[1]
        aboutfile_path = os.path.join(install_dir, 'lector', 'resources', 'about.html')
        with open(aboutfile_path) as about_html:
            self.aboutBox.setHtml(about_html.read())

        self.paths = None
        self.thread = None
        self.filesystem_model = None
        self.tag_data_copy = None

        english_string = self._translate('SettingsUI', 'English')
        spanish_string = self._translate('SettingsUI', 'Spanish')
        hindi_string = self._translate('SettingsUI', 'Hindi')
        languages = [english_string, spanish_string, hindi_string]

        self.languageBox.addItems(languages)
        current_language = self.parent.settings['dictionary_language']
        if current_language == 'en':
            self.languageBox.setCurrentIndex(0)
        elif current_language == 'es':
            self.languageBox.setCurrentIndex(1)
        else:
            self.languageBox.setCurrentIndex(2)
        self.languageBox.activated.connect(self.change_dictionary_language)

        self.okButton.setToolTip(
            self._translate('SettingsUI', 'Save changes and start library scan'))
        self.okButton.clicked.connect(self.start_library_scan)
        self.cancelButton.clicked.connect(self.cancel_pressed)

        # Radio buttons
        if self.parent.settings['icon_theme'] == 'DarkIcons':
            self.darkIconsRadio.setChecked(True)
        else:
            self.lightIconsRadio.setChecked(True)
        self.darkIconsRadio.clicked.connect(self.change_icon_theme)
        self.lightIconsRadio.clicked.connect(self.change_icon_theme)

        # Check boxes
        self.autoTags.setChecked(self.parent.settings['auto_tags'])
        self.coverShadows.setChecked(self.parent.settings['cover_shadows'])
        self.refreshLibrary.setChecked(self.parent.settings['scan_library'])
        self.fileRemember.setChecked(self.parent.settings['remember_files'])
        self.performCulling.setChecked(self.parent.settings['perform_culling'])
        self.cachingEnabled.setChecked(self.parent.settings['caching_enabled'])
        self.hideScrollBars.setChecked(self.parent.settings['hide_scrollbars'])
        self.scrollSpeedSlider.setValue(self.parent.settings['scroll_speed'])

        self.autoTags.clicked.connect(self.manage_checkboxes)
        self.coverShadows.clicked.connect(self.manage_checkboxes)
        self.refreshLibrary.clicked.connect(self.manage_checkboxes)
        self.fileRemember.clicked.connect(self.manage_checkboxes)
        self.performCulling.clicked.connect(self.manage_checkboxes)
        self.cachingEnabled.clicked.connect(self.manage_checkboxes)
        self.hideScrollBars.clicked.connect(self.manage_checkboxes)
        self.scrollSpeedSlider.valueChanged.connect(self.change_scroll_speed)

        # Generate the QStandardItemModel for the listView
        self.listModel = QtGui.QStandardItemModel()

        library_string = self._translate('SettingsUI', 'Library')
        switches_string = self._translate('SettingsUI', 'Switches')
        annotations_string = self._translate('SettingsUI', 'Annotations')
        about_string = self._translate('SettingsUI', 'About')
        list_options = [
            library_string, switches_string, annotations_string, about_string]

        for i in list_options:
            item = QtGui.QStandardItem()
            item.setText(i)
            self.listModel.appendRow(item)
        self.listView.setModel(self.listModel)
        self.listView.clicked.connect(self.page_switch)

        # Generate the filesystem treeView
        self.generate_tree()

    def generate_tree(self):
        # Fetch all directories in the database
        paths = database.DatabaseFunctions(
            self.database_path).fetch_data(
                ('Path', 'Name', 'Tags', 'CheckState'),
                'directories',
                {'Path': ''},
                'LIKE')

        self.parent.generate_library_filter_menu(paths)
        directory_data = {}
        if not paths:
            print('Database: No paths for settings...')
        else:
            # Convert to the dictionary format that is
            # to be fed into the QFileSystemModel
            for i in paths:
                directory_data[i[0]] = {
                    'name': i[1],
                    'tags': i[2],
                    'check_state': i[3]}

        self.filesystem_model = MostExcellentFileSystemModel(directory_data)
        self.filesystem_model.setFilter(QtCore.QDir.NoDotAndDotDot | QtCore.QDir.Dirs)
        self.treeView.setModel(self.filesystem_model)

        # TODO
        # This here might break on them pestilent non unixy OSes
        # Check and see

        root_directory = QtCore.QDir().rootPath()
        self.treeView.setRootIndex(
            self.filesystem_model.setRootPath(root_directory))

        # Set the treeView and QFileSystemModel to its desired state
        selected_paths = [
            i for i in directory_data if directory_data[i]['check_state'] == QtCore.Qt.Checked]
        expand_paths = set()
        for i in selected_paths:

            # Recursively grind down parent paths for expansion
            this_path = i
            while True:
                parent_path = os.path.dirname(this_path)
                if parent_path == this_path:
                    break
                expand_paths.add(parent_path)
                this_path = parent_path

        # Expand all the parent paths derived from the selected path
        if root_directory in expand_paths:
            expand_paths.remove(root_directory)

        for i in expand_paths:
            this_index = self.filesystem_model.index(i)
            self.treeView.expand(this_index)

        header_sizes = self.parent.settings['settings_dialog_headers']
        if header_sizes:
            for count, i in enumerate((0, 4)):
                self.treeView.setColumnWidth(i, int(header_sizes[count]))

        # TODO
        # Set a QSortFilterProxy model on top of the existing QFileSystem model
        # self.filesystem_proxy_model = FileSystemProxyModel()
        # self.filesystem_proxy_model.setSourceModel(self.filesystem_model)
        # self.treeView.setModel(self.filesystem_proxy_model)

        for i in range(1, 4):
            self.treeView.hideColumn(i)

    def start_library_scan(self):
        self.hide()

        data_pairs = []
        for i in self.filesystem_model.tag_data.items():
            data_pairs.append([
                i[0], i[1]['name'], i[1]['tags'], i[1]['check_state']
            ])

        database.DatabaseFunctions(
            self.database_path).set_library_paths(data_pairs)

        if not data_pairs:
            try:
                if self.sender().objectName() == 'reloadLibrary':
                    self.show()
            except AttributeError:
                pass

            database.DatabaseFunctions(
                self.database_path).delete_from_database('*', '*')

            self.parent.lib_ref.generate_model('build')
            self.parent.lib_ref.generate_proxymodels()
            self.parent.generate_library_filter_menu()

            return

        # Update the main window library filter menu
        self.parent.generate_library_filter_menu(data_pairs)
        self.parent.set_library_filter()

        # Disallow rechecking until the first check completes
        self.okButton.setEnabled(False)
        self.parent.libraryToolBar.reloadLibraryButton.setEnabled(False)
        self.okButton.setToolTip(
            self._translate('SettingsUI', 'Library scan in progress...'))

        # Traverse directories looking for files
        self.parent.statusMessage.setText(
            self._translate('SettingsUI', 'Checking library folders'))
        self.thread = BackGroundBookSearch(data_pairs)
        self.thread.finished.connect(self.finished_iterating)
        self.thread.start()

    def finished_iterating(self):
        # The books the search thread has found
        # are now in self.thread.valid_files
        if not self.thread.valid_files:
            self.parent.move_on()
            return

        # Hey, messaging is important, okay?
        self.parent.statusBar.setVisible(True)
        self.parent.sorterProgress.setVisible(True)
        self.parent.statusMessage.setText(
            self._translate('SettingsUI', 'Parsing files'))

        # We now create a new thread to put those files into the database
        self.thread = BackGroundBookAddition(
            self.thread.valid_files, self.database_path, 'automatic', self.parent)
        self.thread.finished.connect(self.parent.move_on)
        self.thread.start()

    def page_switch(self, index):
        self.stackedWidget.setCurrentIndex(index.row())
        if index.row() == 0:
            self.okButton.setVisible(True)
        else:
            self.okButton.setVisible(False)

    def cancel_pressed(self):
        self.filesystem_model.tag_data = copy.deepcopy(self.tag_data_copy)
        self.hide()

    def hideEvent(self, event):
        self.no_more_settings()
        event.accept()

    def showEvent(self, event):
        self.tag_data_copy = copy.deepcopy(self.filesystem_model.tag_data)
        event.accept()

    def no_more_settings(self):
        self.parent.libraryToolBar.settingsButton.setChecked(False)
        self.resizeEvent()

    def resizeEvent(self, event=None):
        self.parent.settings['settings_dialog_size'] = self.size()
        self.parent.settings['settings_dialog_position'] = self.pos()
        table_headers = []
        for i in [0, 4]:
            table_headers.append(self.treeView.columnWidth(i))
        self.parent.settings['settings_dialog_headers'] = table_headers

    def change_icon_theme(self):
        if self.sender() == self.darkIconsRadio:
            self.parent.settings['icon_theme'] = 'DarkIcons'
        else:
            self.parent.settings['icon_theme'] = 'LightIcons'

    def change_dictionary_language(self, event):
        language_dict = {
            0: 'en',
            1: 'es',
            2: 'hi'}
        self.parent.settings['dictionary_language'] = language_dict[self.languageBox.currentIndex()]

    def change_scroll_speed(self, event):
        self.parent.settings['scroll_speed'] = self.scrollSpeedSlider.value()

    def manage_checkboxes(self, event=None):
        sender = self.sender().objectName()

        sender_dict = {
            'coverShadows': 'cover_shadows',
            'autoTags': 'auto_tags',
            'refreshLibrary': 'scan_library',
            'fileRemember': 'remember_files',
            'performCulling': 'perform_culling',
            'cachingEnabled': 'caching_enabled',
            'hideScrollBars': 'hide_scrollbars'}

        self.parent.settings[sender_dict[sender]] = not self.parent.settings[sender_dict[sender]]

        if not self.performCulling.isChecked():
            self.parent.cover_functions.load_all_covers()
예제 #3
0
class SettingsUI(QtWidgets.QDialog, settingswindow.Ui_Dialog):
    def __init__(self, parent=None):
        super(SettingsUI, self).__init__()
        self.setupUi(self)
        self.verticalLayout_4.setContentsMargins(0, 0, 0, 0)
        self._translate = QtCore.QCoreApplication.translate

        self.main_window = parent
        self.database_path = self.main_window.database_path
        self.image_factory = self.main_window.QImageFactory

        # The annotation dialog will use the settings dialog as its parent
        self.annotationsDialog = AnnotationsUI(self)

        self.resize(self.main_window.settings['settings_dialog_size'])
        self.move(self.main_window.settings['settings_dialog_position'])

        install_dir = os.path.realpath(__file__)
        install_dir = pathlib.Path(install_dir).parents[1]
        aboutfile_path = os.path.join(install_dir, 'lector', 'resources', 'about.html')
        with open(aboutfile_path) as about_html:
            html = about_html.readlines()
            html.insert(
                8, f'<h3 style="text-align: center;">v{VERSION}</h3>\n')
            self.aboutBox.setHtml(''.join(html))

        self.paths = None
        self.thread = None
        self.filesystemModel = None
        self.tag_data_copy = None

        english_string = self._translate('SettingsUI', 'English')
        spanish_string = self._translate('SettingsUI', 'Spanish')
        hindi_string = self._translate('SettingsUI', 'Hindi')
        languages = [english_string, spanish_string, hindi_string]

        self.languageBox.addItems(languages)
        current_language = self.main_window.settings['dictionary_language']
        if current_language == 'en':
            self.languageBox.setCurrentIndex(0)
        elif current_language == 'es':
            self.languageBox.setCurrentIndex(1)
        else:
            self.languageBox.setCurrentIndex(2)
        self.languageBox.activated.connect(self.change_dictionary_language)

        self.okButton.setToolTip(
            self._translate('SettingsUI', 'Save changes and start library scan'))
        self.okButton.clicked.connect(self.start_library_scan)
        self.cancelButton.clicked.connect(self.cancel_pressed)

        # Radio buttons
        if self.main_window.settings['icon_theme'] == 'DarkIcons':
            self.darkIconsRadio.setChecked(True)
        else:
            self.lightIconsRadio.setChecked(True)
        self.darkIconsRadio.clicked.connect(self.change_icon_theme)
        self.lightIconsRadio.clicked.connect(self.change_icon_theme)

        # Check boxes
        self.autoTags.setChecked(self.main_window.settings['auto_tags'])
        self.coverShadows.setChecked(self.main_window.settings['cover_shadows'])
        self.refreshLibrary.setChecked(self.main_window.settings['scan_library'])
        self.fileRemember.setChecked(self.main_window.settings['remember_files'])
        self.performCulling.setChecked(self.main_window.settings['perform_culling'])
        self.cachingEnabled.setChecked(self.main_window.settings['caching_enabled'])
        self.hideScrollBars.setChecked(self.main_window.settings['hide_scrollbars'])
        self.attenuateTitles.setChecked(self.main_window.settings['attenuate_titles'])
        self.autoCover.setChecked(self.main_window.settings['auto_cover'])
        self.scrollSpeedSlider.setValue(self.main_window.settings['scroll_speed'])
        self.readAtPercent.setValue(self.main_window.settings['consider_read_at'])
        self.smallIncrementBox.setValue(self.main_window.settings['small_increment'])
        self.largeIncrementBox.setValue(self.main_window.settings['large_increment'])

        self.autoTags.clicked.connect(self.manage_checkboxes)
        self.coverShadows.clicked.connect(self.manage_checkboxes)
        self.refreshLibrary.clicked.connect(self.manage_checkboxes)
        self.fileRemember.clicked.connect(self.manage_checkboxes)
        self.performCulling.clicked.connect(self.manage_checkboxes)
        self.cachingEnabled.clicked.connect(self.manage_checkboxes)
        self.hideScrollBars.clicked.connect(self.manage_checkboxes)
        self.attenuateTitles.clicked.connect(self.manage_checkboxes)
        self.autoCover.clicked.connect(self.manage_checkboxes)
        self.scrollSpeedSlider.valueChanged.connect(self.change_scroll_speed)
        self.readAtPercent.valueChanged.connect(self.change_read_at)
        self.smallIncrementBox.valueChanged.connect(self.change_increment)
        self.largeIncrementBox.valueChanged.connect(self.change_increment)

        # Generate the QStandardItemModel for the listView
        self.listModel = QtGui.QStandardItemModel(self.listView)

        library_string = self._translate('SettingsUI', 'Library')
        switches_string = self._translate('SettingsUI', 'Switches')
        annotations_string = self._translate('SettingsUI', 'Annotations')
        about_string = self._translate('SettingsUI', 'About')
        list_options = [
            library_string, switches_string, annotations_string, about_string]

        icon_dict = {
            0: 'view-readermode',
            1: 'switches',
            2: 'annotate',
            3: 'about'}

        for count, i in enumerate(list_options):
            item = QtGui.QStandardItem()
            item.setText(i)
            this_icon = icon_dict[count]
            item.setIcon(
                self.main_window.QImageFactory.get_image(this_icon))
            self.listModel.appendRow(item)
        self.listView.setModel(self.listModel)

        # Custom signal to account for page changes
        self.listView.newIndexSignal.connect(self.list_index_changed)

        # Annotation related buttons
        # Icon names
        self.newAnnotation.setIcon(self.image_factory.get_image('add'))
        self.deleteAnnotation.setIcon(self.image_factory.get_image('remove'))
        self.editAnnotation.setIcon(self.image_factory.get_image('edit-rename'))
        self.moveUp.setIcon(self.image_factory.get_image('arrow-up'))
        self.moveDown.setIcon(self.image_factory.get_image('arrow-down'))

        # Icon sizes
        self.newAnnotation.setIconSize(QtCore.QSize(24, 24))
        self.deleteAnnotation.setIconSize(QtCore.QSize(24, 24))
        self.editAnnotation.setIconSize(QtCore.QSize(24, 24))
        self.moveUp.setIconSize(QtCore.QSize(24, 24))
        self.moveDown.setIconSize(QtCore.QSize(24, 24))

        self.annotationsList.clicked.connect(self.load_annotation)
        self.annotationsList.doubleClicked.connect(self.editAnnotation.click)
        self.newAnnotation.clicked.connect(self.add_annotation)
        self.deleteAnnotation.clicked.connect(self.delete_annotation)
        self.editAnnotation.clicked.connect(self.load_annotation)
        self.moveUp.clicked.connect(self.move_annotation)
        self.moveDown.clicked.connect(self.move_annotation)

        # Generate annotation settings
        self.annotationModel = QtGui.QStandardItemModel()
        self.generate_annotations()

        # Generate the filesystem treeView
        self.generate_tree()

        # About... About
        self.aboutTabWidget.setDocumentMode(True)
        self.aboutTabWidget.setContentsMargins(0, 0, 0, 0)
        self.logBox.setReadOnly(True)

        # About buttons
        self.resetButton.clicked.connect(self.delete_database)
        self.clearLogButton.clicked.connect(self.clear_log)

        # Hide the image annotation tab
        # TODO
        # Maybe get off your lazy ass and write something for this
        self.tabWidget.setContentsMargins(0, 0, 0, 0)
        self.tabWidget.tabBar().setVisible(False)

    def list_index_changed(self, index):
        switch_to = index.row()
        self.stackedWidget.setCurrentIndex(switch_to)

        valid_buttons = {
            0: (self.okButton,),
            3: (self.resetButton, self.clearLogButton),}

        for i in valid_buttons:
            if i == switch_to:
                for j in valid_buttons[i]:
                    j.setVisible(True)
            else:
                for j in valid_buttons[i]:
                    j.setVisible(False)

    def generate_tree(self):
        # Fetch all directories in the database
        paths = database.DatabaseFunctions(
            self.database_path).fetch_data(
                ('Path', 'Name', 'Tags', 'CheckState'),
                'directories',
                {'Path': ''},
                'LIKE')

        self.main_window.generate_library_filter_menu(paths)
        directory_data = {}
        if not paths:
            logger.warning('No book paths saved')
        else:
            # Convert to the dictionary format that is
            # to be fed into the QFileSystemModel
            for i in paths:
                directory_data[i[0]] = {
                    'name': i[1],
                    'tags': i[2],
                    'check_state': i[3]}

        self.filesystemModel = MostExcellentFileSystemModel(directory_data)
        self.filesystemModel.setFilter(
            QtCore.QDir.NoDotAndDotDot | QtCore.QDir.Dirs)
        self.treeView.setModel(self.filesystemModel)

        # TODO
        # This here might break on them pestilent non unixy OSes
        # Check and see

        root_directory = QtCore.QDir().rootPath()
        self.treeView.setRootIndex(
            self.filesystemModel.setRootPath(root_directory))

        # Set the treeView and QFileSystemModel to its desired state
        selected_paths = [
            i for i in directory_data
            if directory_data[i]['check_state'] == QtCore.Qt.Checked]
        expand_paths = set()
        for i in selected_paths:

            # Recursively grind down parent paths for expansion
            this_path = i
            while True:
                parent_path = os.path.dirname(this_path)
                if parent_path == this_path:
                    break
                expand_paths.add(parent_path)
                this_path = parent_path

        # Expand all the parent paths derived from the selected path
        if root_directory in expand_paths:
            expand_paths.remove(root_directory)

        for i in expand_paths:
            this_index = self.filesystemModel.index(i)
            self.treeView.expand(this_index)

        header_sizes = self.main_window.settings['settings_dialog_headers']
        if header_sizes:
            for count, i in enumerate((0, 4)):
                self.treeView.setColumnWidth(i, int(header_sizes[count]))

        # TODO
        # Set a QSortFilterProxy model on top of the existing QFileSystem model
        # self.filesystem_proxy_model = FileSystemProxyModel()
        # self.filesystem_proxy_model.setSourceModel(self.filesystem_model)
        # self.treeView.setModel(self.filesystem_proxy_model)

        for i in range(1, 4):
            self.treeView.hideColumn(i)

    def start_library_scan(self):
        self.hide()

        data_pairs = []
        for i in self.filesystemModel.tag_data.items():
            data_pairs.append([
                i[0], i[1]['name'], i[1]['tags'], i[1]['check_state']
            ])

        database.DatabaseFunctions(
            self.database_path).set_library_paths(data_pairs)

        if not data_pairs:
            logger.error('Can\'t scan - No book paths saved')
            try:
                if self.sender().objectName() == 'reloadLibrary':
                    self.show()
                    treeViewIndex = self.listModel.index(0, 0)
                    self.listView.setCurrentIndex(treeViewIndex)
                    return
            except AttributeError:
                pass

            database.DatabaseFunctions(
                self.database_path).delete_from_database('*', '*')

            self.main_window.lib_ref.generate_model('build')
            self.main_window.lib_ref.generate_proxymodels()
            self.main_window.generate_library_filter_menu()

            return

        # Update the main window library filter menu
        self.main_window.generate_library_filter_menu(data_pairs)
        self.main_window.set_library_filter()

        # Disallow rechecking until the first check completes
        self.okButton.setEnabled(False)
        self.main_window.libraryToolBar.reloadLibraryButton.setEnabled(False)
        self.okButton.setToolTip(
            self._translate('SettingsUI', 'Library scan in progress...'))

        # Traverse directories looking for files
        self.main_window.statusMessage.setText(
            self._translate('SettingsUI', 'Checking library folders'))
        self.thread = BackGroundBookSearch(data_pairs)
        self.thread.finished.connect(self.finished_iterating)
        self.thread.start()

    def finished_iterating(self):
        # The books the search thread has found
        # are now in self.thread.valid_files
        if not self.thread.valid_files:
            self.main_window.move_on()
            return

        # Hey, messaging is important, okay?
        self.main_window.statusBar.setVisible(True)
        self.main_window.sorterProgress.setVisible(True)
        self.main_window.statusMessage.setText(
            self._translate('SettingsUI', 'Parsing files'))

        # We now create a new thread to put those files into the database
        self.thread = BackGroundBookAddition(
            self.thread.valid_files, self.database_path, 'automatic', self.main_window)
        self.thread.finished.connect(
            lambda: self.main_window.move_on(self.thread.errors))
        self.thread.start()

    def cancel_pressed(self):
        self.filesystemModel.tag_data = copy.deepcopy(self.tag_data_copy)
        self.hide()

    def hideEvent(self, event):
        self.no_more_settings()
        event.accept()

    def showEvent(self, event):
        # Load log into the plainTextEdit
        with open(logger_filename) as infile:
            log_text = infile.read()
            self.logBox.setPlainText(log_text)
        # Annotation preview
        self.format_preview()
        # Make copy of tags in case of a nope.jpg
        self.tag_data_copy = copy.deepcopy(self.filesystemModel.tag_data)

        event.accept()

    def no_more_settings(self):
        self.main_window.libraryToolBar.settingsButton.setChecked(False)
        self.gather_annotations()
        Settings(self.main_window).save_settings()
        Settings(self.main_window).read_settings()
        self.main_window.settings['last_open_tab'] = None  # Needed to allow focus change
                                                           # to newly opened book
        self.resizeEvent()

    def resizeEvent(self, event=None):
        self.main_window.settings['settings_dialog_size'] = self.size()
        self.main_window.settings['settings_dialog_position'] = self.pos()
        table_headers = []
        for i in [0, 4]:
            table_headers.append(self.treeView.columnWidth(i))
        self.main_window.settings['settings_dialog_headers'] = table_headers

    def change_icon_theme(self):
        if self.sender() == self.darkIconsRadio:
            self.main_window.settings['icon_theme'] = 'DarkIcons'
        else:
            self.main_window.settings['icon_theme'] = 'LightIcons'

    def change_dictionary_language(self, event):
        language_dict = {
            0: 'en',
            1: 'es',
            2: 'hi'}
        self.main_window.settings[
            'dictionary_language'] = language_dict[self.languageBox.currentIndex()]

    def change_scroll_speed(self, event=None):
        self.main_window.settings['scroll_speed'] = self.scrollSpeedSlider.value()

    def change_read_at(self, event=None):
        self.main_window.settings['consider_read_at'] = self.readAtPercent.value()

    def change_increment(self, event=None):
        self.main_window.settings['small_increment'] = self.smallIncrementBox.value()
        self.main_window.settings['large_increment'] = self.largeIncrementBox.value()

    def manage_checkboxes(self, event=None):
        sender = self.sender().objectName()

        sender_dict = {
            'coverShadows': 'cover_shadows',
            'autoTags': 'auto_tags',
            'refreshLibrary': 'scan_library',
            'fileRemember': 'remember_files',
            'performCulling': 'perform_culling',
            'cachingEnabled': 'caching_enabled',
            'hideScrollBars': 'hide_scrollbars',
            'attenuateTitles': 'attenuate_titles',
            'autoCover': 'auto_cover'}

        self.main_window.settings[
            sender_dict[sender]] = not self.main_window.settings[sender_dict[sender]]

        if not self.performCulling.isChecked():
            self.main_window.cover_functions.load_all_covers()

    def generate_annotations(self):
        saved_annotations = self.main_window.settings['annotations']

        for i in saved_annotations:
            item = QtGui.QStandardItem()
            item.setText(i['name'])
            item.setData(i, QtCore.Qt.UserRole)
            self.annotationModel.appendRow(item)

        self.annotationsList.setModel(self.annotationModel)

    def format_preview(self):
        # Needed to clear the preview of annotation ickiness
        cursor = QtGui.QTextCursor()
        self.previewView.setTextCursor(cursor)

        self.previewView.setText('Vidistine nuper imagines moventes bonas?')
        profile_index = self.main_window.bookToolBar.profileBox.currentIndex()
        current_profile = self.main_window.bookToolBar.profileBox.itemData(
            profile_index, QtCore.Qt.UserRole)

        if not current_profile:
            return

        font = current_profile['font']
        self.foreground = current_profile['foreground']
        background = current_profile['background']
        font_size = current_profile['font_size']

        self.previewView.setStyleSheet(
            "QTextEdit {{font-family: {0}; font-size: {1}px; color: {2}; background-color: {3}}}".format(
                font, font_size, self.foreground.name(), background.name()))

        block_format = QtGui.QTextBlockFormat()
        block_format.setAlignment(QtCore.Qt.AlignVCenter | QtCore.Qt.AlignHCenter)

        cursor = self.previewView.textCursor()
        while True:
            old_position = cursor.position()
            cursor.mergeBlockFormat(block_format)
            cursor.movePosition(QtGui.QTextCursor.NextBlock, 0, 1)
            new_position = cursor.position()
            if old_position == new_position:
                break

    def add_annotation(self):
        self.annotationsDialog.show_dialog('add')

    def delete_annotation(self):
        selected_index = self.annotationsList.currentIndex()
        if not selected_index.isValid():
            return

        self.annotationModel.removeRow(
            self.annotationsList.currentIndex().row())
        self.format_preview()
        self.annotationsList.clearSelection()

    def load_annotation(self):
        selected_index = self.annotationsList.currentIndex()
        if not selected_index.isValid():
            return

        if self.sender() == self.annotationsList:
            self.annotationsDialog.show_dialog('preview', selected_index)

        elif self.sender() == self.editAnnotation:
            self.annotationsDialog.show_dialog('edit', selected_index)

    def move_annotation(self):
        current_row = self.annotationsList.currentIndex().row()

        if self.sender() == self.moveUp:
            new_row = current_row - 1
            if new_row < 0:
                return

        elif self.sender() == self.moveDown:
            new_row = current_row + 1
            if new_row == self.annotationModel.rowCount():
                return

        row_out = self.annotationModel.takeRow(current_row)
        self.annotationModel.insertRow(new_row, row_out)
        new_index = self.annotationModel.index(new_row, 0)

        self.annotationsList.setCurrentIndex(new_index)

    def gather_annotations(self):
        annotations_out = []
        for i in range(self.annotationModel.rowCount()):
            annotation_item = self.annotationModel.item(i, 0)
            annotation_data = annotation_item.data(QtCore.Qt.UserRole)
            annotations_out.append(annotation_data)

        self.main_window.settings['annotations'] = annotations_out

    def delete_database(self):
        def ifcontinue(box_button):
            if box_button.text() != '&Yes':
                return

            database_filename = os.path.join(
                self.main_window.database_path, 'Lector.db')
            os.remove(database_filename)
            QtWidgets.qApp.exit()

        # Generate a message box to confirm deletion
        confirm_deletion = QtWidgets.QMessageBox()
        deletion_prompt = self._translate(
            'SettingsUI', f'Delete database and exit?')
        confirm_deletion.setText(deletion_prompt)
        confirm_deletion.setIcon(QtWidgets.QMessageBox.Critical)
        confirm_deletion.setWindowTitle(self._translate('SettingsUI', 'Confirm'))
        confirm_deletion.setStandardButtons(
            QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
        confirm_deletion.buttonClicked.connect(ifcontinue)
        confirm_deletion.show()
        confirm_deletion.exec_()

    def clear_log(self):
        self.logBox.clear()
        open(logger_filename, 'w').close()