コード例 #1
0
class NodesTreeListWidget(QWidget):
    def __init__(self, main_window, session):
        super().__init__()
        self.main_window = main_window
        self.session = session
        self.details_widget = None  # set by main window

        self.view = NodesView()
        self.view.node_selected.connect(self._node_selected)
        self.model = QStandardItemModel()
        self.view.setModel(self.model)
        self.view.setHeaderHidden(True)
        self.view.setContextMenuPolicy(Qt.CustomContextMenu)
        # self.view.customContextMenuRequested.connect(self.on_context_menu_requested)
        # self.custom_node_items = []

        self.setLayout(QVBoxLayout())
        self.layout().addWidget(self.view)
        self.setMaximumWidth(500)

    def update_list(self):
        self.model.clear()

        packages = {}  # {NodePackage: [Node]}
        for node, node_package in self.main_window.node_packages.items():
            if node_package in packages.keys():
                packages[node_package].append(node)
            else:
                packages[node_package] = [node]

        for node_package, nodes in packages.items():
            package_item = QStandardItem(node_package.name)
            package_item.setEditable(False)
            for n in nodes:
                node_item = NodeItem(n)
                # node_item.setEditable(False)
                # node_item.setDragEnabled(True)
                package_item.appendRow(node_item)
            self.model.appendRow(package_item)

    def _node_selected(self, node):
        self.details_widget.set_node(node)
コード例 #2
0
class Switcher(QDialog):
    """
    A multi purpose switcher.

    Example
    -------
      SwitcherItem:      [title description    <shortcut> section]
      SwitcherItem:      [title description    <shortcut> section]
      SwitcherSeparator: [---------------------------------------]
      SwitcherItem:      [title description    <shortcut> section]
      SwitcherItem:      [title description    <shortcut> section]
    """

    # Dismissed switcher
    sig_rejected = Signal()
    # Search/Filter text changes
    sig_text_changed = Signal(TEXT_TYPES[-1])
    # Current item changed
    sig_item_changed = Signal(object)
    # List item selected, mode and cleaned search text
    sig_item_selected = Signal(object, TEXT_TYPES[-1], TEXT_TYPES[-1], )
    sig_mode_selected = Signal(TEXT_TYPES[-1])

    _MAX_NUM_ITEMS = 15
    _MIN_WIDTH = 580
    _MIN_HEIGHT = 200
    _MAX_HEIGHT = 390
    _ITEM_WIDTH = _MIN_WIDTH - 20

    def __init__(self, parent, help_text=None, item_styles=ITEM_STYLES,
                 item_separator_styles=ITEM_SEPARATOR_STYLES):
        """Multi purpose switcher."""
        super(Switcher, self).__init__(parent)
        self._modes = {}
        self._mode_on = ''
        self._item_styles = item_styles
        self._item_separator_styles = item_separator_styles

        # Widgets
        self.edit = QLineEdit(self)
        self.list = QListView(self)
        self.model = QStandardItemModel(self.list)
        self.proxy = SwitcherProxyModel(self.list)
        self.filter = KeyPressFilter()

        # Widgets setup
        self.setWindowFlags(Qt.Popup | Qt.FramelessWindowHint)
        self.setWindowOpacity(0.95)
#        self.setMinimumHeight(self._MIN_HEIGHT)
        self.setMaximumHeight(self._MAX_HEIGHT)
        self.edit.installEventFilter(self.filter)
        self.edit.setPlaceholderText(help_text if help_text else '')
        self.list.setMinimumWidth(self._MIN_WIDTH)
        self.list.setItemDelegate(SwitcherDelegate(self))
        self.list.setFocusPolicy(Qt.NoFocus)
        self.list.setSelectionBehavior(self.list.SelectItems)
        self.list.setSelectionMode(self.list.SingleSelection)
        self.list.setVerticalScrollMode(QAbstractItemView.ScrollPerItem)
        self.proxy.setSourceModel(self.model)
        self.list.setModel(self.proxy)

        # Layout
        layout = QVBoxLayout()
        layout.addWidget(self.edit)
        layout.addWidget(self.list)
        self.setLayout(layout)

        # Signals
        self.filter.sig_up_key_pressed.connect(self.previous_row)
        self.filter.sig_down_key_pressed.connect(self.next_row)
        self.filter.sig_enter_key_pressed.connect(self.enter)
        self.edit.textChanged.connect(self.setup)
        self.edit.textChanged.connect(self.sig_text_changed)
        self.edit.returnPressed.connect(self.enter)
        self.list.clicked.connect(self.enter)
        self.list.clicked.connect(self.edit.setFocus)
        self.list.selectionModel().currentChanged.connect(
            self.current_item_changed)
        self.edit.setFocus()

    # --- Helper methods
    def _add_item(self, item, last_item=True):
        """Perform common actions when adding items."""
        item.set_width(self._ITEM_WIDTH)
        self.model.appendRow(item)
        if last_item:
            # Only set the current row to the first item when the added item is
            # the last one in order to prevent performance issues when
            # adding multiple items
            self.set_current_row(0)
            self.set_height()
        self.setup_sections()

    # --- API
    def clear(self):
        """Remove all items from the list and clear the search text."""
        self.set_placeholder_text('')
        self.model.beginResetModel()
        self.model.clear()
        self.model.endResetModel()
        self.setMinimumHeight(self._MIN_HEIGHT)

    def set_placeholder_text(self, text):
        """Set the text appearing on the empty line edit."""
        self.edit.setPlaceholderText(text)

    def add_mode(self, token, description):
        """Add mode by token key and description."""
        if len(token) == 1:
            self._modes[token] = description
        else:
            raise Exception('Token must be of length 1!')

    def get_mode(self):
        """Get the current mode the switcher is in."""
        return self._mode_on

    def remove_mode(self, token):
        """Remove mode by token key."""
        if token in self._modes:
            self._modes.pop(token)

    def clear_modes(self):
        """Delete all modes spreviously defined."""
        del self._modes
        self._modes = {}

    def add_item(self, icon=None, title=None, description=None, shortcut=None,
                 section=None, data=None, tool_tip=None, action_item=False,
                 last_item=True):
        """Add switcher list item."""
        item = SwitcherItem(
            parent=self.list,
            icon=icon,
            title=title,
            description=description,
            data=data,
            shortcut=shortcut,
            section=section,
            action_item=action_item,
            tool_tip=tool_tip,
            styles=self._item_styles
        )
        self._add_item(item, last_item=last_item)

    def add_separator(self):
        """Add separator item."""
        item = SwitcherSeparatorItem(parent=self.list,
                                     styles=self._item_separator_styles)
        self._add_item(item)

    def setup(self):
        """Set-up list widget content based on the filtering."""
        # Check exited mode
        mode = self._mode_on
        if mode:
            search_text = self.search_text()[len(mode):]
        else:
            search_text = self.search_text()

        # Check exited mode
        if self.search_text() == '':
            self._mode_on = ''
            self.clear()
            self.proxy.set_filter_by_score(False)
            self.sig_mode_selected.emit(self._mode_on)
            return

        # Check entered mode
        for key in self._modes:
            if self.search_text().startswith(key) and not mode:
                self._mode_on = key
                self.sig_mode_selected.emit(key)
                return

        # Filter by text
        titles = []
        for row in range(self.model.rowCount()):
            item = self.model.item(row)
            if isinstance(item, SwitcherItem):
                title = item.get_title()
            else:
                title = ''
            titles.append(title)

        search_text = clean_string(search_text)
        scores = get_search_scores(to_text_string(search_text),
                                   titles, template=u"<b>{0}</b>")

        for idx, (title, rich_title, score_value) in enumerate(scores):
            item = self.model.item(idx)
            if not self._is_separator(item) and not item.is_action_item():
                rich_title = rich_title.replace(" ", "&nbsp;")
                item.set_rich_title(rich_title)
            item.set_score(score_value)
        self.proxy.set_filter_by_score(True)

        self.setup_sections()
        if self.count():
            self.set_current_row(0)
        else:
            self.set_current_row(-1)
        self.set_height()

    def setup_sections(self):
        """Set-up which sections appear on the item list."""
        mode = self._mode_on
        if mode:
            search_text = self.search_text()[len(mode):]
        else:
            search_text = self.search_text()

        if search_text:
            for row in range(self.model.rowCount()):
                item = self.model.item(row)
                if isinstance(item, SwitcherItem):
                    item.set_section_visible(False)
        else:
            sections = []
            for row in range(self.model.rowCount()):
                item = self.model.item(row)
                if isinstance(item, SwitcherItem):
                    sections.append(item.get_section())
                    item.set_section_visible(bool(search_text))
                else:
                    sections.append('')

                if row != 0:
                    visible = sections[row] != sections[row - 1]
                    if not self._is_separator(item):
                        item.set_section_visible(visible)
                else:
                    item.set_section_visible(True)

        self.proxy.sortBy('_score')
        self.sig_item_changed.emit(self.current_item())

    def set_height(self):
        """Set height taking into account the number of items."""
        if self.count() >= self._MAX_NUM_ITEMS:
            switcher_height = self._MAX_HEIGHT
        elif self.count() != 0 and self.current_item():
            current_item = self.current_item()
            item_height = current_item.get_height()
            list_height = item_height * (self.count() + 3)
            edit_height = self.edit.height()
            spacing_height = self.layout().spacing() * 4
            switcher_height = list_height + edit_height + spacing_height
            switcher_height = max(switcher_height, self._MIN_HEIGHT)
        else:
            switcher_height = self._MIN_HEIGHT
        self.setFixedHeight(int(switcher_height))

    def set_position(self, top):
        """Set the position of the dialog."""
        parent = self.parent()
        if parent is not None:
            geo = parent.geometry()
            width = self.list.width()  # This has been set in setup
            left = parent.geometry().width()/2 - width/2

            while parent:
                geo = parent.geometry()
                top += geo.top()
                left += geo.left()
                parent = parent.parent()

            self.move(round(left), top)

    @Slot(QModelIndex, QModelIndex)
    def current_item_changed(self, current, previous):
        """Handle item selection."""
        self.sig_item_changed.emit(self.current_item())

    # --- Qt overrides
    # ------------------------------------------------------------------------
    @Slot()
    @Slot(QListWidgetItem)
    def enter(self, itemClicked=None):
        """Override Qt method."""
        row = self.current_row()
        model_index = self.proxy.mapToSource(self.proxy.index(row, 0))
        item = self.model.item(model_index.row())
        if item:
            mode = self._mode_on
            self.sig_item_selected.emit(item, mode,
                                        self.search_text()[len(mode):])

    def accept(self):
        """Override Qt method."""
        super(Switcher, self).accept()

    def reject(self):
        """Override Qt method."""
        self.set_search_text('')
        self.sig_rejected.emit()
        super(Switcher, self).reject()

    def resizeEvent(self, event):
        """Override Qt method."""
        super(Switcher, self).resizeEvent(event)

    # --- Helper methods: Lineedit widget
    def search_text(self):
        """Get the normalized (lowecase) content of the search text."""
        return to_text_string(self.edit.text()).lower()

    def set_search_text(self, string):
        """Set the content of the search text."""
        self.edit.setText(string)

    # --- Helper methods: List widget
    def _is_separator(self, item):
        """Check if item is an separator item (SwitcherSeparatorItem)."""
        return isinstance(item, SwitcherSeparatorItem)

    def _select_row(self, steps):
        """Select row in list widget based on a number of steps with direction.

        Steps can be positive (next rows) or negative (previous rows).
        """
        row = self.current_row() + steps
        if 0 <= row < self.count():
            self.set_current_row(row)

    def count(self):
        """Get the item count in the list widget."""
        return self.proxy.rowCount()

    def current_row(self):
        """Return the current selected row in the list widget."""
        return self.list.currentIndex().row()

    def current_item(self):
        """Return the current selected item in the list widget."""
        row = self.current_row()
        model_index = self.proxy.mapToSource(self.proxy.index(row, 0))
        item = self.model.item(model_index.row())
        return item

    def set_current_row(self, row):
        """Set the current selected row in the list widget."""
        proxy_index = self.proxy.index(row, 0)
        selection_model = self.list.selectionModel()

        # https://doc.qt.io/qt-5/qitemselectionmodel.html#SelectionFlag-enum
        selection_model.setCurrentIndex(
            proxy_index, selection_model.ClearAndSelect)

        # Ensure that the selected item is visible
        self.list.scrollTo(proxy_index, QAbstractItemView.EnsureVisible)

    def previous_row(self):
        """Select previous row in list widget."""
        steps = 1
        prev_row = self.current_row() - steps

        if prev_row == -1:
            self.set_current_row(self.count() - 1)
        else:
            if prev_row >= 0:
                # Need to map the filtered list to the actual model items
                list_index = self.proxy.index(prev_row, 0)
                model_index = self.proxy.mapToSource(list_index)
                item = self.model.item(model_index.row(), 0)
                if self._is_separator(item):
                    steps += 1
            self._select_row(-steps)

    def next_row(self):
        """Select next row in list widget."""
        steps = 1
        next_row = self.current_row() + steps

        # Need to map the filtered list to the actual model items
        list_index = self.proxy.index(next_row, 0)
        model_index = self.proxy.mapToSource(list_index)
        item = self.model.item(model_index.row(), 0)

        if next_row >= self.count():
            self.set_current_row(0)
        else:
            if item:
                if self._is_separator(item):
                    steps += 1
            self._select_row(steps)
コード例 #3
0
ファイル: settings.py プロジェクト: mrakitin/Xi-cam.gui
class ConfigDialog(QDialog):
    def __init__(self):
        super(ConfigDialog, self).__init__()

        # Set size and position
        self.setGeometry(0, 0, 900, 550)
        frameGm = self.frameGeometry()
        screen = QApplication.desktop().screenNumber(QApplication.desktop().cursor().pos())
        centerPoint = QApplication.desktop().screenGeometry(screen).center()
        frameGm.moveCenter(centerPoint)
        self.move(frameGm.topLeft())

        self.contentsWidget = QListView()
        self.contentsWidget.setViewMode(QListView.IconMode)
        # self.contentsWidget.setIconSize(QSize(96, 84))
        self.contentsWidget.setMovement(QListView.Static)
        self.contentsWidget.setMaximumWidth(174)
        self.contentsWidget.setSpacing(12)
        self.contentsWidget.setSelectionMode(QAbstractItemView.SingleSelection)

        self.contentsModel = QStandardItemModel()
        self.contentsWidget.setModel(self.contentsModel)
        self.contentsWidget.selectionModel().currentChanged.connect(self.changePage)

        self.buttonboxWidget = QDialogButtonBox(
            QDialogButtonBox.Ok | QDialogButtonBox.Cancel | QDialogButtonBox.Apply# | QDialogButtonBox.Help
        )
        self.buttonboxWidget.button(QDialogButtonBox.Ok).clicked.connect(self.ok)
        self.buttonboxWidget.button(QDialogButtonBox.Apply).clicked.connect(self.apply)
        self.buttonboxWidget.button(QDialogButtonBox.Cancel).clicked.connect(self.close)

        self.pagesWidget = QStackedWidget()

        horizontalLayout = QHBoxLayout()
        horizontalLayout.addWidget(self.contentsWidget)
        horizontalLayout.addWidget(self.pagesWidget, 1)

        mainLayout = QVBoxLayout()
        mainLayout.addLayout(horizontalLayout)
        # mainLayout.addStretch(1)
        mainLayout.addSpacing(12)
        mainLayout.addWidget(self.buttonboxWidget)

        self.setLayout(mainLayout)
        self.setWindowTitle("Config Dialog")

        # Set modality
        self.setModal(True)

        self.lastwidget = None

        self.createIcons()
        self.restore()

        pluginmanager.attach(self.pluginsChanged)

    def createIcons(self):
        self.contentsModel.clear()
        for pluginInfo in pluginmanager.getPluginsOfCategory("SettingsPlugin"):
            item = QStandardItem(pluginInfo.plugin_object.icon, pluginInfo.plugin_object.name())
            item.widget = pluginInfo.plugin_object.widget
            item.setTextAlignment(Qt.AlignHCenter)
            item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
            item.setSizeHint(QSize(136, 80))
            self.contentsModel.appendRow(item)

    def show(self):
        if self.lastwidget:
            self.pagesWidget.addWidget(self.lastwidget)
            self.pagesWidget.setCurrentWidget(self.lastwidget)
        self.restore()
        super(ConfigDialog, self).show()

    def changePage(self, current, previous):
        if not current:
            current = previous
        current = self.contentsModel.itemFromIndex(current)
        self.pagesWidget.addWidget(current.widget)
        self.pagesWidget.setCurrentWidget(current.widget)
        self.lastwidget = current.widget

    def pluginsChanged(self):
        self.createIcons()

    def restore(self):
        for pluginInfo in pluginmanager.getPluginsOfCategory("SettingsPlugin"):
            pluginInfo.plugin_object.restore()

        self.apply()

    def ok(self):
        self._empty()
        self.apply()
        self.accept()

    def apply(self):
        for pluginInfo in pluginmanager.getPluginsOfCategory("SettingsPlugin"):
            pluginInfo.plugin_object.save()

    def close(self):
        self._empty()
        self.restore()
        self.reject()

    def _empty(self):
        """
        Disown all widget children (otherwise their c++ objects are force deleted when the dialog closes).
        Must be run in reverse to avoid index update errors
        """
        for i in reversed(range(self.pagesWidget.count())):
            self.pagesWidget.widget(i).setParent(None)

    def closeEvent(self, event):
        self.close()
        event.accept()

    def keyPressEvent(self, e: QKeyEvent):
        if e.key() != Qt.Key_Escape:
            super(ConfigDialog, self).keyPressEvent(e)
        else:
            self.close()
コード例 #4
0
class InterlinearDataWindow(QWidget):

    translation = (
        "Interlinear Data",
        "Enter a reference:",
        "No bible verse reference is found!",
        "Export to Spreadsheet",
        "Close",
    )

    def __init__(self, parent, initialVerse=""):
        super().__init__()
        self.parent = parent
        # set title
        self.setWindowTitle(self.translation[0])
        self.setMinimumSize(830, 500)
        # set variables
        self.setupVariables()
        # setup interface
        self.setupUI(initialVerse)

    def setupVariables(self):
        import os, sqlite3
        # connect bibles.sqlite
        self.database = os.path.join(config.marvelData, "morphology.sqlite")
        self.connection = sqlite3.connect(self.database)
        self.cursor = self.connection.cursor()
        # Spreadsheet headers
        self.headers = ("WordID", "ClauseID", "Book", "Chapter", "Verse",
                        "Word", "LexicalEntry", "MorphologyCode", "Morphology",
                        "Lexeme", "Transliteration", "Pronunciation",
                        "Interlinear", "Translation", "Gloss")

    def __del__(self):
        self.connection.close()

    def setupUI(self, initialVerse=""):
        from qtpy.QtGui import QStandardItemModel
        from qtpy.QtWidgets import (QPushButton, QLabel, QTableView,
                                    QHBoxLayout, QVBoxLayout, QLineEdit)
        #from qtpy.QtWidgets import QAbstractItemView

        mainLayout = QVBoxLayout()

        mainLayout.addWidget(QLabel(self.translation[0]))

        layout = QHBoxLayout()
        layout.addWidget(QLabel(self.translation[1]))
        self.searchEntry = QLineEdit()
        # Set initial entry
        self.searchEntry.setText(initialVerse if initialVerse else "John 3:16")
        self.searchEntry.returnPressed.connect(self.resetItems)
        layout.addWidget(self.searchEntry)
        mainLayout.addLayout(layout)

        self.dataView = QTableView()
        # Allow editing so that users can select text and copy
        #self.dataView.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.dataView.setSortingEnabled(True)
        self.dataViewModel = QStandardItemModel(self.dataView)
        self.dataView.setModel(self.dataViewModel)
        self.resetItems()
        mainLayout.addWidget(self.dataView)

        buttonLayout = QHBoxLayout()
        button = QPushButton(self.translation[4])
        button.clicked.connect(self.close)
        buttonLayout.addWidget(button)
        button = QPushButton(self.translation[3])
        button.clicked.connect(self.exportSpreadsheet)
        buttonLayout.addWidget(button)
        mainLayout.addLayout(buttonLayout)

        self.setLayout(mainLayout)

    def resetItems(self):
        from util.BibleVerseParser import BibleVerseParser
        from qtpy.QtGui import QStandardItem

        # Empty the model before reset
        self.dataViewModel.clear()
        # Reset
        # Parse entered reference
        reference = self.searchEntry.text().strip()
        verses = BibleVerseParser(
            config.parserStandarisation).extractAllReferences(
                reference, False)
        if verses:
            # Search morphology database
            bcv = verses[0][0:3]
            query = "SELECT * FROM morphology WHERE Book = ? AND Chapter = ? AND Verse = ?"
            self.cursor.execute(query, bcv)
            self.results = self.cursor.fetchall()
            # Display data
            # TABLE morphology (WordID INT, ClauseID INT, Book INT, Chapter INT, Verse INT, Word TEXT, LexicalEntry TEXT, MorphologyCode TEXT, Morphology TEXT, Lexeme TEXT, Transliteration TEXT, Pronunciation TEXT, Interlinear TEXT, Translation TEXT, Gloss TEXT)
            #for wordID, clauseID, b, c, v, textWord, lexicalEntry, morphologyCode, morphology, lexeme, transliteration, pronuciation, interlinear, translation, gloss in self.results:
            for row, result in enumerate(self.results):
                for column in range(0, len(result)):
                    text = str(
                        result[column]) if column < 5 else result[column]
                    item = QStandardItem(text)
                    self.dataViewModel.setItem(row, column, item)
            self.dataViewModel.setHorizontalHeaderLabels([
                "WordID", "ClauseID", "Book", "Chapter", "Verse", "Word",
                "LexicalEntry", "MorphologyCode", "Morphology", "Lexeme",
                "Transliteration", "Pronunciation", "Interlinear",
                "Translation", "Gloss"
            ])
        else:
            self.results = []
            self.displayMessage(self.translation[2])
        self.dataView.resizeColumnsToContents()

    def displayMessage(self, message):
        from qtpy.QtWidgets import QMessageBox
        QMessageBox.information(self, self.translation[0], message)

    def exportSpreadsheet(self):
        import sys
        from install.module import installmodule

        module = "openpyxl"

        # Check if essential module is installed
        try:
            from openpyxl import Workbook
            moduleInstalled = True
        except:
            moduleInstalled = False

        # Install essential module if it is absent
        if not moduleInstalled:
            installmodule(module)
        if not module in sys.modules:
            try:
                from openpyxl import Workbook
                self.runExportSpreadsheet()
            except:
                #self.displayMessage("Package '{0}' is required but not installed!\nRun 'pip3 install {0}' to install it first.".format(module))
                # openpyxl requires pyton version 3.6+, try alternative 'xlsxwriter'
                self.exportSpreadsheet2()
        else:
            self.runExportSpreadsheet()

    def runExportSpreadsheet(self):
        from openpyxl import Workbook
        from openpyxl.styles import Font

        # Specify excel file path
        #filePath = os.path.join(os.getcwd(), "plugins", "menu", "Interlinear_Data.xlsx")
        filePath = self.getFilePath()
        if filePath:

            # Documentation on openpyxl: https://openpyxl.readthedocs.io/en/stable/
            wb = Workbook()
            # grab the active worksheet
            ws = wb.active
            ws.title = "UniqueBible.app"
            # Append rows
            ws.append(self.headers)
            font = Font(bold=True)
            for i in range(0, len(self.headers)):
                # row and column number starts from 1 when calling ws.cell
                ws.cell(row=1, column=i + 1).font = font
            if self.results:
                for result in self.results:
                    ws.append(result)
                # Apply style
                # Documentation: https://openpyxl.readthedocs.io/en/stable/styles.html
                font = Font(
                    name="Calibri") if self.results[0][2] >= 40 else Font(
                        name="Ezra SIL")
                for column in ("F", "J"):
                    for row in range(0, len(self.results)):
                        ws["{0}{1}".format(column, row + 2)].font = font
                font = Font(name="Calibri")
                for column in ("K", "L"):
                    for row in range(0, len(self.results)):
                        ws["{0}{1}".format(column, row + 2)].font = font

            # Save and open the file
            wb.save(filePath)
            self.openFile(filePath)

    # Use 'xlsxwriter' to export excel file if 'openpyxl' is not installed.
    def exportSpreadsheet2(self):
        import sys
        from install.module import installmodule

        module = "xlsxwriter"

        # Check if essential module is installed
        try:
            import xlsxwriter
            moduleInstalled = True
        except:
            moduleInstalled = False

        # Install essential module if it is absent
        if not moduleInstalled:
            installmodule(module)
        if not module in sys.modules:
            try:
                import xlsxwriter
                self.runExportSpreadsheet2()
            except:
                #self.displayMessage("Package '{0}' is required but not installed!\nRun 'pip3 install {0}' to install it first.".format(module))
                self.exportSpreadsheet3()
        else:
            self.runExportSpreadsheet2()

    def runExportSpreadsheet2(self):
        import xlsxwriter

        # Specify excel file path
        #filePath = os.path.join(os.getcwd(), "plugins", "menu", "Interlinear_Data.xlsx")
        filePath = self.getFilePath()
        if filePath:

            # Create an new Excel file and add a worksheet.
            # Documentation on xlsxwriter: https://pypi.org/project/XlsxWriter/
            workbook = xlsxwriter.Workbook(filePath)
            worksheet = workbook.add_worksheet("UniqueBible.app")

            # Add formats to cells.
            bold = workbook.add_format({'bold': True})
            format_right_to_left = workbook.add_format({'reading_order': 2})

            # Text with formatting.
            for index, header in enumerate(self.headers):
                worksheet.write(0, index, header, bold)

            if self.results:
                for row, result in enumerate(self.results):
                    for column, item in enumerate(result):
                        if column in (5, 9) and self.results[0][2] < 40:
                            worksheet.write(row + 1, column, item,
                                            format_right_to_left)
                        else:
                            worksheet.write(row + 1, column, item)

            workbook.close()

            # Open the saved file
            self.openFile(filePath)

    # export to csv when users cannot install either openpyxl or xlsxwriter for some reasons
    def exportSpreadsheet3(self):
        # Define a file path
        #filePath = os.path.join(os.getcwd(), "plugins", "menu", "Interlinear_Data.csv")
        filePath = self.getFilePath("csv")
        if filePath:

            # Format data
            fileContent = '"{0}"'.format('","'.join(self.headers))
            if self.results:
                for result in self.results:
                    row = [
                        str(item) if index < 5 else item
                        for index, item in enumerate(result)
                    ]
                    fileContent += '\n"{0}"'.format('","'.join(row))
            # Write data into file
            with open(filePath, "w") as fileObj:
                fileObj.write(fileContent)
            self.openFile(filePath)

    def getFilePath(self, fileExtension="xlsx"):
        from qtpy.QtWidgets import QFileDialog

        defaultName = "Interlinear_Data.{0}".format(fileExtension)
        options = QFileDialog.Options()
        filePath, *_ = QFileDialog.getSaveFileName(
            self, config.thisTranslation["note_saveAs"], defaultName,
            "Spreadsheet File (*.{0})".format(fileExtension), "", options)
        if filePath:
            filePath = filePath.replace(" ", "_")
            if not filePath.endswith(".{0}".format(fileExtension)):
                filePath = "{0}.{1}".format(filePath, fileExtension)
            return filePath
        else:
            return ""

    def openFile(self, filePath):
        import platform, subprocess, os

        if platform.system() == "Linux":
            subprocess.Popen([config.open, filePath])
        elif platform.system() == "Windows":
            try:
                subprocess("excel.exe {0}".format(filePath), shell=True)
            except:
                try:
                    subprocess("start {0}".format(filePath), shell=True)
                except:
                    pass
        else:
            os.system("{0} {1}".format(config.open, filePath))
コード例 #5
0
class NotificationsWidget(QWidget, VCPWidget):
    notificationsCleared = Signal(object)

    def __init__(self, parent=None):
        super(NotificationsWidget, self).__init__(parent)
        self.notification_channel = getPlugin("notifications")

        self.main_layout = QVBoxLayout()
        self.button_layout = QHBoxLayout()

        self.all_button = QPushButton()
        self.info_button = QPushButton()
        self.warn_button = QPushButton()
        self.error_button = QPushButton()
        self.debug_button = QPushButton()
        self.clear_button = QPushButton()

        self.all_button.setText("all")
        self.info_button.setText("info")
        self.warn_button.setText("warn")
        self.error_button.setText("error")
        self.debug_button.setText("debug")
        self.clear_button.setText("clear")

        self.all_button.setCheckable(True)
        self.info_button.setCheckable(True)
        self.warn_button.setCheckable(True)
        self.error_button.setCheckable(True)
        self.debug_button.setCheckable(True)

        self.all_button.setChecked(True)
        self.info_button.setChecked(False)
        self.warn_button.setChecked(False)
        self.error_button.setChecked(False)
        self.debug_button.setChecked(False)

        self.clear_button.clicked.connect(self.clear_all_notifications)

        self.button_layout.addWidget(self.all_button)
        self.button_layout.addWidget(self.info_button)
        self.button_layout.addWidget(self.warn_button)
        self.button_layout.addWidget(self.error_button)
        self.button_layout.addWidget(self.debug_button)
        self.button_layout.addWidget(self.clear_button)

        self.notification_name = QLabel()
        self.notification_name.setAlignment(Qt.AlignCenter)
        self.notification_name.setText("All Notifications")

        self.all_notification_view = QListView()

        self.all_notification_model = QStandardItemModel(
            self.all_notification_view)
        self.all_notification_model_proxy = QSortFilterProxyModel(
            self.all_notification_view)

        self.all_notification_model_proxy.setSourceModel(
            self.all_notification_model)

        # self.all_notification_view.setModel(self.all_notification_model)
        self.all_notification_view.setModel(self.all_notification_model_proxy)

        self.all_notifications = list()

        self.main_layout.addWidget(self.notification_name)
        self.main_layout.addWidget(self.all_notification_view)
        self.main_layout.addLayout(self.button_layout)

        self.setLayout(self.main_layout)

        self.notification_channel.info_message.notify(self.on_info_message)
        self.notification_channel.warn_message.notify(self.on_warn_message)
        self.notification_channel.error_message.notify(self.on_error_message)
        self.notification_channel.debug_message.notify(self.on_debug_message)

        self.all_button.clicked.connect(self.show_all_notifications)
        self.info_button.clicked.connect(self.show_info_notifications)
        self.warn_button.clicked.connect(self.show_warn_notifications)
        self.error_button.clicked.connect(self.show_error_notifications)
        self.debug_button.clicked.connect(self.show_debug_notifications)

    @staticmethod
    def _get_time():
        timestamp = time()
        dt_object = datetime.fromtimestamp(timestamp)
        return dt_object.strftime("%d / %b / %Y  -  %H:%M:%S")

    @staticmethod
    def _strip_new_line(message):
        return message.replace('\n', ' ').replace('\r', '').replace(
            '    ', ' ').replace('   ', ' ').replace('  ', ' ')

    def on_info_message(self, message):
        msg = 'INFO - occurred at: [ {} ]\n{}'.format(
            NotificationsWidget._get_time(),
            NotificationsWidget._strip_new_line(message))
        notification_item = QStandardItem()
        notification_item.setText(msg)
        notification_item.setIcon(QIcon.fromTheme('dialog-information'))
        notification_item.setEditable(False)
        self.all_notification_model.appendRow(notification_item)

    def on_warn_message(self, message):
        msg = 'WARNING - occurred at: [ {} ]\n{}'.format(
            NotificationsWidget._get_time(),
            NotificationsWidget._strip_new_line(message))
        notification_item = QStandardItem()
        notification_item.setText(msg)
        notification_item.setIcon(QIcon.fromTheme('dialog-warning'))
        notification_item.setEditable(False)
        self.all_notification_model.appendRow(notification_item)

    def on_error_message(self, message):
        msg = 'ERROR - occurred at: [ {} ]\n{}'.format(
            NotificationsWidget._get_time(),
            NotificationsWidget._strip_new_line(message))
        LOG.debug('-----on_error_message called: {}'.format(message))
        notification_item = QStandardItem()
        notification_item.setText(msg)
        notification_item.setIcon(QIcon.fromTheme('dialog-error'))
        notification_item.setEditable(False)
        self.all_notification_model.appendRow(notification_item)

    def on_debug_message(self, message):
        msg = 'DEBUG - occurred at: [ {} ]\n{}'.format(
            NotificationsWidget._get_time(),
            NotificationsWidget._strip_new_line(message))
        notification_item = QStandardItem()
        notification_item.setText(msg)
        notification_item.setIcon(QIcon.fromTheme('dialog-question'))
        notification_item.setEditable(False)
        self.all_notification_model.appendRow(notification_item)

    def show_all_notifications(self):
        self.all_button.setChecked(True)
        self.info_button.setChecked(False)
        self.warn_button.setChecked(False)
        self.error_button.setChecked(False)
        self.debug_button.setChecked(False)

        self.notification_name.setText("All Notifications")
        self.all_notification_model_proxy.setFilterRegExp(None)

    def show_info_notifications(self):
        self.all_button.setChecked(False)
        self.info_button.setChecked(True)
        self.warn_button.setChecked(False)
        self.error_button.setChecked(False)
        self.debug_button.setChecked(False)

        self.notification_name.setText("Information Notifications")
        self.all_notification_model_proxy.setFilterRegExp(
            QRegExp("INFO", Qt.CaseSensitive, QRegExp.FixedString))

    def show_warn_notifications(self):
        self.all_button.setChecked(False)
        self.info_button.setChecked(False)
        self.warn_button.setChecked(True)
        self.error_button.setChecked(False)
        self.debug_button.setChecked(False)

        self.notification_name.setText("Warning Notifications")
        self.all_notification_model_proxy.setFilterRegExp(
            QRegExp("WANRNING", Qt.CaseSensitive, QRegExp.FixedString))

    def show_error_notifications(self):
        self.all_button.setChecked(False)
        self.info_button.setChecked(False)
        self.warn_button.setChecked(False)
        self.error_button.setChecked(True)
        self.debug_button.setChecked(False)

        self.notification_name.setText("Error Notifications")
        self.all_notification_model_proxy.setFilterRegExp(
            QRegExp("ERROR", Qt.CaseInsensitive, QRegExp.FixedString))

    def show_debug_notifications(self):
        self.all_button.setChecked(False)
        self.info_button.setChecked(False)
        self.warn_button.setChecked(False)
        self.error_button.setChecked(False)
        self.debug_button.setChecked(True)

        self.notification_name.setText("Debug Notifications")
        self.all_notification_model_proxy.setFilterRegExp(
            QRegExp("DEBUG", Qt.CaseSensitive, QRegExp.FixedString))

    def clear_all_notifications(self):
        self.all_notification_model.clear()
        self.notificationsCleared.emit(self)
コード例 #6
0
ファイル: switcher.py プロジェクト: wtheis/spyder
class Switcher(QDialog):
    """
    A multi purpose switcher.

    Example
    -------
      SwitcherItem:      [title description    <shortcut> section]
      SwitcherItem:      [title description    <shortcut> section]
      SwitcherSeparator: [---------------------------------------]
      SwitcherItem:      [title description    <shortcut> section]
      SwitcherItem:      [title description    <shortcut> section]
    """

    # Search/Filter text changes
    sig_text_changed = Signal(TEXT_TYPES[-1])
    # List item selected, mode and cleaned search text
    sig_item_selected = Signal(
        object,
        TEXT_TYPES[-1],
        TEXT_TYPES[-1],
    )
    sig_mode_selected = Signal(TEXT_TYPES[-1])

    _MIN_WIDTH = 500

    def __init__(self,
                 parent,
                 help_text=None,
                 item_styles=ITEM_STYLES,
                 item_separator_styles=ITEM_SEPARATOR_STYLES):
        """Multi purpose switcher."""
        super(Switcher, self).__init__(parent)
        self._visible_rows = 0
        self._modes = {}
        self._mode_on = ''
        self._item_styles = item_styles
        self._item_separator_styles = item_separator_styles

        # Widgets
        self.edit = QLineEdit(self)
        self.list = QListView(self)
        self.model = QStandardItemModel(self.list)
        self.proxy = SwitcherProxyModel(self.list)
        self.filter = KeyPressFilter()

        # Widgets setup
        # self.setWindowFlags(Qt.Popup | Qt.FramelessWindowHint)
        self.setWindowOpacity(0.95)
        self.edit.installEventFilter(self.filter)
        self.edit.setPlaceholderText(help_text if help_text else '')
        self.list.setMinimumWidth(self._MIN_WIDTH)
        self.list.setItemDelegate(HTMLDelegate(self))
        self.list.setFocusPolicy(Qt.NoFocus)
        self.list.setSelectionBehavior(self.list.SelectRows)
        self.list.setSelectionMode(self.list.SingleSelection)
        self.proxy.setSourceModel(self.model)
        self.list.setModel(self.proxy)

        # Layout
        layout = QVBoxLayout()
        layout.addWidget(self.edit)
        layout.addWidget(self.list)
        self.setLayout(layout)

        # Signals
        self.filter.sig_up_key_pressed.connect(self.previous_row)
        self.filter.sig_down_key_pressed.connect(self.next_row)
        self.filter.sig_enter_key_pressed.connect(self.enter)
        self.edit.textChanged.connect(self.setup)
        self.edit.textChanged.connect(self.sig_text_changed)
        self.edit.returnPressed.connect(self.enter)
        self.list.clicked.connect(self.enter)
        self.list.clicked.connect(self.edit.setFocus)

    # --- Helper methods
    def _add_item(self, item):
        """Perform common actions when adding items."""
        item.set_width(self._MIN_WIDTH)
        self.model.appendRow(item)
        self.set_current_row(0)
        self._visible_rows = self.model.rowCount()
        self.setup_sections()

    # --- API
    def clear(self):
        """Remove all items from the list and clear the search text."""
        self.set_placeholder_text('')
        self.model.beginResetModel()
        self.model.clear()
        self.model.endResetModel()

    def set_placeholder_text(self, text):
        """Set the text appearing on the empty line edit."""
        self.edit.setPlaceholderText(text)

    def add_mode(self, token, description):
        """Add mode by token key and description."""
        if len(token) == 1:
            self._modes[token] = description
        else:
            raise Exception('Token must be of length 1!')

    def remove_mode(self, token):
        """Remove mode by token key."""
        if token in self._modes:
            self._modes.pop(token)

    def clear_modes(self):
        """Delete all modes spreviously defined."""
        del self._modes
        self._modes = {}

    def add_item(self,
                 icon=None,
                 title=None,
                 description=None,
                 shortcut=None,
                 section=None,
                 data=None,
                 tool_tip=None,
                 action_item=False):
        """Add switcher list item."""
        item = SwitcherItem(parent=self.list,
                            icon=icon,
                            title=title,
                            description=description,
                            data=data,
                            shortcut=shortcut,
                            section=section,
                            action_item=action_item,
                            tool_tip=tool_tip,
                            styles=self._item_styles)
        self._add_item(item)

    def add_separator(self):
        """Add separator item."""
        item = SwitcherSeparatorItem(parent=self.list,
                                     styles=self._item_separator_styles)
        self._add_item(item)

    def setup(self):
        """Set-up list widget content based on the filtering."""
        # Check exited mode
        mode = self._mode_on
        if mode:
            search_text = self.search_text()[len(mode):]
        else:
            search_text = self.search_text()

        # Check exited mode
        if mode and self.search_text() == '':
            self._mode_on = ''
            self.sig_mode_selected.emit(self._mode_on)
            return

        # Check entered mode
        for key in self._modes:
            if self.search_text().startswith(key) and not mode:
                self._mode_on = key
                self.sig_mode_selected.emit(key)
                return

        # Filter by text
        titles = []
        for row in range(self.model.rowCount()):
            item = self.model.item(row)
            if isinstance(item, SwitcherItem):
                title = item.get_title()
            else:
                title = ''
            titles.append(title)

        search_text = clean_string(search_text)
        scores = get_search_scores(to_text_string(search_text),
                                   titles,
                                   template=u"<b>{0}</b>")

        self._visible_rows = self.model.rowCount()
        for idx, score in enumerate(scores):
            title, rich_title, score_value = score
            item = self.model.item(idx)

            if not self._is_separator(item):
                item.set_rich_title(rich_title)

            item.set_score(score_value)
            proxy_index = self.proxy.mapFromSource(self.model.index(idx, 0))

            if not item.is_action_item():
                self.list.setRowHidden(proxy_index.row(), score_value == -1)

                if score_value == -1:
                    self._visible_rows -= 1

        if self._visible_rows:
            self.set_current_row(0)
        else:
            self.set_current_row(-1)

        self.setup_sections()

    def setup_sections(self):
        """Set-up which sections appear on the item list."""
        mode = self._mode_on
        if mode:
            search_text = self.search_text()[len(mode):]
        else:
            search_text = self.search_text()

        if search_text:
            for row in range(self.model.rowCount()):
                item = self.model.item(row)
                if isinstance(item, SwitcherItem):
                    item.set_section_visible(False)
        else:
            sections = []
            for row in range(self.model.rowCount()):
                item = self.model.item(row)
                if isinstance(item, SwitcherItem):
                    sections.append(item.get_section())
                    item.set_section_visible(bool(search_text))
                else:
                    sections.append('')

                if row != 0:
                    visible = sections[row] != sections[row - 1]
                    if not self._is_separator(item):
                        item.set_section_visible(visible)
                else:
                    item.set_section_visible(True)

        self.proxy.sortBy('_score')

    # --- Qt overrides
    # ------------------------------------------------------------------------
    @Slot()
    @Slot(QListWidgetItem)
    def enter(self, itemClicked=None):
        """Override Qt method."""
        row = self.current_row()
        model_index = self.proxy.mapToSource(self.proxy.index(row, 0))
        item = self.model.item(model_index.row())
        if item:
            mode = self._mode_on
            self.sig_item_selected.emit(item, mode,
                                        self.search_text()[len(mode):])

    def accept(self):
        """Override Qt method."""
        super(Switcher, self).accept()

    def resizeEvent(self, event):
        """Override Qt method."""
        super(Switcher, self).resizeEvent(event)

    # --- Helper methods: Lineedit widget
    def search_text(self):
        """Get the normalized (lowecase) content of the search text."""
        return to_text_string(self.edit.text()).lower()

    def set_search_text(self, string):
        """Set the content of the search text."""
        self.edit.setText(string)

    # --- Helper methods: List widget
    def _is_separator(self, item):
        """Check if item is an separator item (SwitcherSeparatorItem)."""
        return isinstance(item, SwitcherSeparatorItem)

    def _select_row(self, steps):
        """Select row in list widget based on a number of steps with direction.

        Steps can be positive (next rows) or negative (previous rows).
        """
        row = self.current_row() + steps
        if 0 <= row < self.count():
            self.set_current_row(row)

    def count(self):
        """Get the item count in the list widget."""
        return self._visible_rows

    def current_row(self):
        """Return the current selected row in the list widget."""
        return self.list.currentIndex().row()

    def set_current_row(self, row):
        """Set the current selected row in the list widget."""
        index = self.model.index(row, 0)
        selection_model = self.list.selectionModel()

        # https://doc.qt.io/qt-5/qitemselectionmodel.html#SelectionFlag-enum
        selection_model.setCurrentIndex(index, selection_model.ClearAndSelect)

    def previous_row(self):
        """Select previous row in list widget."""
        steps = 1
        prev_row = self.current_row() - steps

        if prev_row == -1:
            self.set_current_row(self.count() - 1)
        else:
            if prev_row >= 0:
                # Need to map the filtered list to the actual model items
                list_index = self.proxy.index(prev_row, 0)
                model_index = self.proxy.mapToSource(list_index)
                item = self.model.item(model_index.row(), 0)
                if self._is_separator(item):
                    steps += 1
            self._select_row(-steps)

    def next_row(self):
        """Select next row in list widget."""
        steps = 1
        next_row = self.current_row() + steps

        # Need to map the filtered list to the actual model items
        list_index = self.proxy.index(next_row, 0)
        model_index = self.proxy.mapToSource(list_index)
        item = self.model.item(model_index.row(), 0)

        if next_row >= self.count():
            self.set_current_row(0)
        else:
            if item:
                if self._is_separator(item):
                    steps += 1
            self._select_row(steps)
コード例 #7
0
class DownloadBibleMp3Dialog(QDialog):
    def __init__(self, parent):
        super().__init__()

        self.bibles = {
            "BBE (British accent)":
            ("BBE", "otseng/UniqueBible_MP3_BBE_british", "british"),
            "KJV (American accent)":
            ("KJV", "otseng/UniqueBible_MP3_KJV", "default"),
            "KJV (American soft music)":
            ("KJV", "otseng/UniqueBible_MP3_KJV_soft_music", "soft-music"),
            "NHEB (Indian accent)":
            ("NHEB", "otseng/UniqueBible_MP3_NHEB_indian", "indian"),
            "WEB (American accent)": ("WEB", "otseng/UniqueBible_MP3_WEB",
                                      "default"),
            "CUV (Chinese)": ("CUV", "otseng/UniqueBible_MP3_CUV", "default"),
            "HHBD (Hindi)": ("HHBD", "otseng/UniqueBible_MP3_HHBD", "default"),
            "RVA (Spanish)": ("RVA", "otseng/UniqueBible_MP3_RVA", "default"),
            "TR (Modern Greek)": ("TR", "otseng/UniqueBible_MP3_TR", "modern"),
        }
        self.parent = parent
        self.setWindowTitle(config.thisTranslation["gitHubBibleMp3Files"])
        self.setMinimumSize(150, 450)
        self.selectedRendition = None
        self.selectedText = None
        self.selectedRepo = None
        self.selectedDirectory = None
        self.settingBibles = False
        self.thread = None
        self.setupUI()

    def setupUI(self):
        mainLayout = QVBoxLayout()

        title = QLabel(config.thisTranslation["gitHubBibleMp3Files"])
        mainLayout.addWidget(title)

        self.versionsLayout = QVBoxLayout()
        self.renditionsList = QListWidget()
        self.renditionsList.itemClicked.connect(self.selectItem)
        for rendition in self.bibles.keys():
            self.renditionsList.addItem(rendition)
        self.renditionsList.setMaximumHeight(100)
        self.versionsLayout.addWidget(self.renditionsList)
        mainLayout.addLayout(self.versionsLayout)

        self.downloadTable = QTableView()
        self.downloadTable.setEnabled(False)
        self.downloadTable.setFocusPolicy(Qt.StrongFocus)
        self.downloadTable.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.downloadTable.setSortingEnabled(True)
        self.dataViewModel = QStandardItemModel(self.downloadTable)
        self.downloadTable.setModel(self.dataViewModel)
        mainLayout.addWidget(self.downloadTable)

        buttonsLayout = QHBoxLayout()
        selectAllButton = QPushButton(config.thisTranslation["selectAll"])
        selectAllButton.setFocusPolicy(Qt.StrongFocus)
        selectAllButton.clicked.connect(self.selectAll)
        buttonsLayout.addWidget(selectAllButton)
        selectNoneButton = QPushButton(config.thisTranslation["selectNone"])
        selectNoneButton.setFocusPolicy(Qt.StrongFocus)
        selectNoneButton.clicked.connect(self.selectNone)
        buttonsLayout.addWidget(selectNoneButton)
        otButton = QPushButton("1-39")
        otButton.setFocusPolicy(Qt.StrongFocus)
        otButton.clicked.connect(self.selectOT)
        buttonsLayout.addWidget(otButton)
        ntButton = QPushButton("40-66")
        ntButton.setFocusPolicy(Qt.StrongFocus)
        ntButton.clicked.connect(self.selectNT)
        buttonsLayout.addWidget(ntButton)
        # buttonsLayout.addStretch()
        mainLayout.addLayout(buttonsLayout)

        self.downloadButton = QPushButton(config.thisTranslation["download"])
        self.downloadButton.setFocusPolicy(Qt.StrongFocus)
        self.downloadButton.setAutoDefault(True)
        self.downloadButton.setFocus()
        self.downloadButton.clicked.connect(self.download)
        mainLayout.addWidget(self.downloadButton)

        self.status = QLabel("")
        mainLayout.addWidget(self.status)

        buttonLayout = QHBoxLayout()
        self.closeButton = QPushButton(config.thisTranslation["close"])
        self.closeButton.setFocusPolicy(Qt.StrongFocus)
        self.closeButton.clicked.connect(self.closeDialog)
        buttonLayout.addWidget(self.closeButton)
        mainLayout.addLayout(buttonLayout)

        self.setLayout(mainLayout)

        self.renditionsList.item(0).setSelected(True)
        bible = self.renditionsList.item(0).text()
        self.selectRendition(bible)

        self.downloadButton.setDefault(True)
        QTimer.singleShot(0, self.downloadButton.setFocus)

    def selectItem(self, item):
        self.selectRendition(item.text())

    def selectRendition(self, rendition):
        from util.GithubUtil import GithubUtil

        self.selectedRendition = rendition
        self.downloadTable.setEnabled(True)
        self.selectedText, self.selectedRepo, self.selectedDirectory = self.bibles[
            self.selectedRendition]
        self.github = GithubUtil(self.selectedRepo)
        self.repoData = self.github.getRepoData()
        self.settingBibles = True
        self.dataViewModel.clear()
        rowCount = 0
        for file in self.repoData.keys():
            if len(str(file)) > 3:
                engFullBookName = file[3:]
            else:
                engFullBookName = BibleBooks().eng[str(int(file))][1]
            item = QStandardItem(file[:3].strip())
            folder = os.path.join("audio", "bibles", self.selectedText,
                                  self.selectedDirectory, file)
            folderWithName = os.path.join("audio", "bibles", self.selectedText,
                                          self.selectedDirectory,
                                          file + " " + engFullBookName)
            if os.path.exists(folder) or os.path.exists(folderWithName):
                item.setCheckable(False)
                item.setCheckState(Qt.Unchecked)
                item.setEnabled(False)
            else:
                item.setCheckable(True)
                item.setCheckState(Qt.Checked)
                item.setEnabled(True)
            self.dataViewModel.setItem(rowCount, 0, item)
            item = QStandardItem(engFullBookName)
            self.dataViewModel.setItem(rowCount, 1, item)
            if os.path.exists(folder) or os.path.exists(folderWithName):
                item = QStandardItem("Installed")
                self.dataViewModel.setItem(rowCount, 2, item)
            else:
                item = QStandardItem("")
                self.dataViewModel.setItem(rowCount, 2, item)
            rowCount += 1
        self.dataViewModel.setHorizontalHeaderLabels([
            config.thisTranslation["menu_book"],
            config.thisTranslation["name"], ""
        ])
        self.downloadTable.setColumnWidth(0, 90)
        self.downloadTable.setColumnWidth(1, 125)
        self.downloadTable.setColumnWidth(2, 125)
        # self.downloadTable.resizeColumnsToContents()
        self.settingBibles = False

    def selectAll(self):
        for index in range(self.dataViewModel.rowCount()):
            item = self.dataViewModel.item(index)
            if item.isEnabled():
                item.setCheckState(Qt.Checked)

    def selectNone(self):
        for index in range(self.dataViewModel.rowCount()):
            item = self.dataViewModel.item(index)
            item.setCheckState(Qt.Unchecked)

    def selectOT(self):
        for index in range(self.dataViewModel.rowCount()):
            item = self.dataViewModel.item(index)
            bookNum = int(item.text())
            if bookNum <= 39:
                if item.isEnabled():
                    item.setCheckState(Qt.Checked)
                else:
                    item.setCheckState(Qt.Unchecked)
            else:
                item.setCheckState(Qt.Unchecked)

    def selectNT(self):
        for index in range(self.dataViewModel.rowCount()):
            item = self.dataViewModel.item(index)
            bookNum = int(item.text())
            if bookNum >= 40:
                if item.isEnabled():
                    item.setCheckState(Qt.Checked)
                else:
                    item.setCheckState(Qt.Unchecked)
            else:
                item.setCheckState(Qt.Unchecked)

    def download(self):
        self.downloadButton.setEnabled(False)
        self.setStatus(config.thisTranslation["message_installing"])
        self.closeButton.setEnabled(False)
        folder = os.path.join("audio", "bibles")
        if not os.path.exists(folder):
            os.mkdir(folder)
        folder = os.path.join("audio", "bibles", self.selectedText)
        if not os.path.exists(folder):
            os.mkdir(folder)
        folder = os.path.join("audio", "bibles", self.selectedText,
                              self.selectedDirectory)
        if not os.path.exists(folder):
            os.mkdir(folder)
        self.thread = QThread()
        self.worker = DownloadFromGitHub(self.github, self.repoData,
                                         self.dataViewModel, self.selectedText,
                                         self.selectedDirectory)
        self.worker.moveToThread(self.thread)
        self.thread.started.connect(self.worker.run)
        self.worker.finished.connect(self.thread.quit)
        self.worker.finished.connect(self.worker.deleteLater)
        self.thread.finished.connect(self.worker.deleteLater)
        self.worker.finished.connect(self.finishedDownloading)
        self.worker.progress.connect(self.setStatus)
        self.thread.start()

    def finishedDownloading(self, count):
        self.selectRendition(self.selectedRendition)
        self.setStatus("")
        self.downloadButton.setEnabled(True)
        self.closeButton.setEnabled(True)
        if count > 0:
            self.parent.displayMessage(
                config.thisTranslation["message_installed"])

    def setStatus(self, message):
        self.status.setText(message)
        QApplication.processEvents()

    def closeDialog(self):
        if self.thread:
            if self.thread.isRunning():
                self.thread.quit()
        self.close()
コード例 #8
0
class FrequencySelection(QGroupBox):
    """
    frequency selection
    """

    def __init__(self, parent, show_period=True, show_frequency=True, allow_range_select=True,
                 select_multiple=True):
        QGroupBox.__init__(self, parent)
        self._mt_objs = None
        self._unique_periods = None
        self._unique_frequencies = None
        self._periods = None
        self._frequencies = None
        self._allow_range = allow_range_select
        self._select_multiple = select_multiple
        self.ui = Ui_GroupBox_frequency_select()
        self.ui.setupUi(self)

        self.ui.label_place_holder.hide()
        self.model_selected = QStandardItemModel()
        self.ui.listView_selected.setModel(self.model_selected)
        self.frequency_delegate = FrequencySelection.FrequencyDelegate(self.ui.listView_selected)
        self.ui.listView_selected.setItemDelegate(self.frequency_delegate)

        self.histogram = FrequencySelection.Histogram(self, allow_range_select=self._allow_range)
        self.histogram.set_unit(self._units[0])
        self.histogram.set_tol(self.ui.doubleSpinBox_tolerance.value())
        self.histogram.frequency_selected.connect(self._frequency_selected)
        self.histogram.frequency_range_selected.connect(self._frequency_selected)
        self.ui.widget_histgram.layout().addWidget(self.histogram)

        self.ui.radioButton_period.setChecked(show_period)
        self.ui.radioButton_frequency.setChecked(show_frequency)
        self.ui.doubleSpinBox_tolerance.setHidden(not self._allow_range)
        self.ui.checkBox_existing_only.setChecked(not self._allow_range)
        self.ui.checkBox_existing_only.setHidden(not self._allow_range)
        self.ui.label_tolerance.setHidden(not self._allow_range)
        self.ui.radioButton_period.setHidden(not (show_period and show_frequency))
        self.ui.radioButton_frequency.setHidden(not (show_period and show_frequency))
        if self.ui.radioButton_frequency.isHidden():
            self.setTitle(self._type[1])
        elif self.ui.radioButton_period.isHidden():
            self.setTitle(self._type[0])

        self.ui.radioButton_frequency.toggled.connect(self._frequency_toggled)
        self.ui.checkBox_existing_only.toggled.connect(self.histogram.select_existing)
        self.ui.checkBox_existing_only.toggled.connect(self.model_selected.clear)
        self.ui.checkBox_show_existing.toggled.connect(self.histogram.show_existing)
        self.ui.checkBox_x_log_scale.toggled.connect(self.histogram.set_x_log_scale)
        self.ui.checkBox_y_log_scale.toggled.connect(self.histogram.set_y_log_scale)
        self.ui.pushButton_clear.clicked.connect(self._clear_all)
        self.ui.pushButton_delete.clicked.connect(self._delete_selected)
        self.ui.doubleSpinBox_tolerance.valueChanged.connect(self.histogram.set_tol)

    def set_data(self, mt_objs):
        self._mt_objs = mt_objs
        self._unique_frequencies = None
        self._unique_periods = None
        self._update_frequency()

    def get_frequencies(self):
        frequencies = [self.model_selected.item(index).data(QtCore.Qt.DisplayRole)
                       for index in range(self.model_selected.rowCount())]
        if self._allow_range:
            frequencies = [(freq[0], freq[1]) if isinstance(freq, tuple) else freq
                           for freq in frequencies]
        else:
            frequencies = [freq[3] if isinstance(freq, tuple) else freq
                           for freq in frequencies
                           if (isinstance(freq, tuple) and len(freq) == 5)
                           or isinstance(freq, float)]
        # print frequencies
        if self._select_multiple:
            return frequencies
        else:
            return frequencies[0] if frequencies else self._unique_frequencies[0]  # if nothing selected, return minimal frequency

    _units = ['Hz', 's']

    _type = ['Frequency', 'Period']

    def _clear_all(self):
        self.model_selected.clear()
        self.histogram.clear_all_drawing()

    def _delete_selected(self):
        for item in [self.model_selected.item(index.row())
                     for index in self.ui.listView_selected.selectedIndexes()]:
            x = item.data(QtCore.Qt.DisplayRole)
            self.model_selected.removeRow(self.model_selected.indexFromItem(item).row())
            self.histogram.remove_marker(x)

    def _frequency_selected(self, x):
        if not self._select_multiple:
            self.histogram.clear_all_drawing()
            self.model_selected.clear()
        for item in [self.model_selected.item(index) for index in range(self.model_selected.rowCount())]:
            value = item.data(QtCore.Qt.DisplayRole)
            if value == x:
                return
            elif isinstance(value, tuple) and isinstance(x, float) and value[0] <= x <= value[1]:
                return  # x already in interval
            elif isinstance(x, tuple) and isinstance(value, float) and x[0] <= value <= x[1]:
                # existing value in new interval
                self.model_selected.removeRow(self.model_selected.indexFromItem(item).row())
                self.histogram.remove_marker(value)
            elif isinstance(x, tuple) and isinstance(value, tuple):
                if min(x[1], value[1]) - max(x[0], value[0]) >= 0:
                    # there is intersection between intervals, so marge them
                    mi = min(x[0], value[0])
                    ma = max(x[1], value[1])
                    uniques = self._unique_frequencies \
                        if self.ui.radioButton_frequency.isChecked() \
                        else self._unique_periods
                    num = len(
                        [freq for freq in uniques if mi <= freq <= ma])  # num of existing freqs in the new interval
                    x = (mi, ma, num)
                    # remove old interval
                    self.model_selected.removeRow(self.model_selected.indexFromItem(item).row())
                    self.histogram.remove_marker(value)
            else:
                prec = self.frequency_delegate.prec
                while np.all(np.isclose(value, x, pow(.1, prec))):
                    prec += 1
                self.frequency_delegate.prec = prec
        new_item = FrequencySelection.FrequencyItem()
        new_item.setData(x, QtCore.Qt.DisplayRole)
        # update graphic
        if isinstance(x, float):
            self.histogram.add_marker(x)
            # new_item.setData(x, QtCore.Qt.UserRole)
        elif isinstance(x, tuple):
            self.histogram.add_marker(x)
            # new_item.setData(x[0], QtCore.Qt.UserRole)
        # update model
        self.model_selected.appendRow(new_item)
        self.model_selected.sort(0)

    def show_period(self):
        self.ui.radioButton_period.setChecked(True)

    def show_frequency(self):
        self.ui.radioButton_frequency.setChecked(True)

    def _frequency_toggled(self, is_checked):
        self.histogram.set_unit(self._units[0] if is_checked else self._units[1])
        self._update_frequency()

    def _update_frequency(self):
        self.model_selected.clear()
        if self._mt_objs is not None:
            if self._unique_frequencies is None:
                self._frequencies = [freq for mt_obj in self._mt_objs for freq in list(mt_obj.Z.freq)]
                all_unique = set(self._frequencies)
                self._unique_frequencies = sorted(list(all_unique))
            if self.ui.radioButton_period.isChecked() and self._unique_periods is None:
                self._periods = 1. / np.array(self._frequencies)
                all_unique = set(self._periods)
                self._unique_periods = sorted(list(all_unique))
            self.histogram.set_data(
                self._periods if self.ui.radioButton_period.isChecked()
                else self._frequencies,
                self._unique_periods if self.ui.radioButton_period.isChecked()
                else self._unique_frequencies
            )
            self.frequency_delegate.freqs = self._unique_periods \
                if self.ui.radioButton_period.isChecked() \
                else self._unique_frequencies
            self.histogram.update_figure()

    class FrequencyItem(QStandardItem):
        def __lt__(self, other):
            value = self.data(QtCore.Qt.DisplayRole)
            other_value = other.data(QtCore.Qt.DisplayRole)
            if isinstance(value, tuple):
                value = value[0]
            if isinstance(other_value, tuple):
                other_value = other_value[0]
            return value < other_value

    class FrequencyDelegate(QStyledItemDelegate):
        _prec = 5  # decimal places

        def get_prec(self):
            return self._prec

        def set_prec(self, prec):
            self._prec = prec

        prec = property(get_prec, set_prec)

        def displayText(self, value, locale):
            if isinstance(value, float):
                return '{:.{prec}f}'.format(value, prec=self._prec)
            elif isinstance(value, tuple) and len(value) == 3:  # (min, max, num)
                return '{}{}, {}{} ({num} selected)'.format(
                    '(' if value[0] == -np.inf else '[',
                    '{:.{prec}f}'.format(value[0], prec=self._prec),
                    '{:.{prec}f}'.format(value[1], prec=self._prec),
                    ')' if value[1] == np.inf else ']',
                    num=value[2]
                )
            elif len(value) == 5:  # (min, max, num, freq, tol)
                return '{:.{prec}f} ±{tol}% ({num} selected)'.format(
                    value[3], prec=self._prec, tol=value[4], num=value[2])
            # elif isinstance(py_obj, set):
            #     return '{{}}'.format(','.join(['{:.{prec}f}'.format(f, prec=self._prec) for f in py_obj if isinstance(f, float)]))
            return value

    class Histogram(MPLCanvas):
        def __init__(self, parent, y_log_scale=False, x_log_scale=False, allow_range_select=True):
            self._frequencies = None
            self._unique_frequencies = None
            self._title = None
            self._unit = None
            self._press = None
            self._tol = None
            MPLCanvas.__init__(self, parent, 5, 1.5)
            self._lx = {}
            self._cursor = None
            self._select_existing_only = False
            self._show_existing = False
            self._x_log_scale = x_log_scale
            self._y_log_scale = y_log_scale
            self._select_range = allow_range_select

            if self._select_range:
                self.mpl_connect('button_press_event', self.on_press)
            self.mpl_connect('button_release_event', self.on_release)

        def add_marker(self, x):
            if isinstance(x, float):
                lx = self._lx.setdefault(x, self._draw_v_line(x))
                # self._axes.draw_artist(lx)
                self.draw_idle()
            elif isinstance(x, tuple):
                if len(x) == 3:
                    lx = self._lx.setdefault(x, self._fill_v_area(x[0], x[1]))
                elif len(x) == 5:
                    lx = self._lx.setdefault(x, (
                        self._draw_v_line(x[3]),
                        self._fill_v_area(x[0], x[1])
                    ))
            else:
                raise NotImplemented
            self.draw_idle()

        def remove_marker(self, x):
            if x in self._lx:
                marker = self._lx[x]
                if isinstance(marker, tuple):
                    for m in marker:
                        m.remove()
                else:
                    marker.remove()
                self.draw_idle()
                del self._lx[x]

        def clear_all_drawing(self):
            for key in list(self._lx.keys()):
                marker = self._lx[key]
                if isinstance(marker, tuple):
                    for m in marker:
                        m.remove()
                else:
                    marker.remove()
            self._lx.clear()
            self.draw_idle()

        def set_unit(self, unit):
            if unit != self._unit:
                self._unit = unit
                self._cursor = Cursor(self._axes,
                                      track_y=False,
                                      show_drag=self._select_range,
                                      text_format="%f" + self._unit,
                                      useblit=True)

        def select_existing(self, select_existing):
            self._select_existing_only = select_existing
            self.clear_all_drawing()

        def set_tol(self, tol):
            self._tol = tol

        def show_existing(self, show_existing):
            self._show_existing = show_existing
            self.update_figure()

        def set_data(self, frequencies, unique_frequencies=None):
            self._frequencies = frequencies
            if unique_frequencies is not None:
                self._unique_frequencies = unique_frequencies
            else:
                self._unique_frequencies = sorted(list(set(frequencies)))
            self._lx.clear()

        def set_y_log_scale(self, ischecked):
            self._y_log_scale = ischecked
            self.update_figure()

        def set_x_log_scale(self, isChecked):
            self._x_log_scale = isChecked
            self.update_figure()

        frequency_selected = Signal(float)
        frequency_range_selected = Signal(tuple)

        def _get_valid_cursor_loc(self, event):
            if not event.inaxes:
                pos = self._axes.get_position()
                if self.height() * pos.y0 < event.y < self.height() * pos.y1:
                    x = -np.inf if event.x < self.width() * pos.x0 else np.inf
                else:
                    x = None
            else:
                x = event.xdata
            return x

        def on_press(self, event):
            self._press = self._get_valid_cursor_loc(event)

        def on_release(self, event):
            x = self._get_valid_cursor_loc(event)
            if x:
                if self._press and self._press != x:  # emit (min, max, num)
                    if self._press < x:
                        self.frequency_range_selected.emit(
                            (
                                self._press,
                                x,
                                len([freq for freq in self._unique_frequencies
                                     if self._press <= freq <= x])
                            )
                        )
                    elif self._press > x:
                        self.frequency_range_selected.emit(
                            (
                                x,
                                self._press,
                                len([freq for freq in self._unique_frequencies
                                     if x <= freq <= self._press])
                            )
                        )
                elif not self._select_range or self._select_existing_only:
                    x = self._find_closest(x)
                    self.frequency_selected.emit(x)
                else:  # emit (min, max, num, freq, tol)
                    tol = x * self._tol / 100.
                    min = x - tol
                    max = x + tol
                    self.frequency_range_selected.emit(
                        (
                            min,
                            max,
                            len([freq for freq in self._unique_frequencies
                                 if min <= freq <= max]),
                            x,
                            self._tol
                        )
                    )
            self._press = None

        def _find_closest(self, x):
            return min(self._frequencies, key=lambda freq: abs(freq - x))

        def compute_initial_figure(self):
            self._axes.tick_params(axis='both', which='major', labelsize=6)
            self._axes.tick_params(axis='both', which='minor', labelsize=4)
            if self._frequencies is not None:
                bins = gen_hist_bins(self._unique_frequencies)
                self._axes.hist(self._frequencies, bins=bins)  # , 50, normed=1)
                if self._y_log_scale:
                    self._axes.set_yscale('log', nonposy='clip')
                if self._x_log_scale:
                    self._axes.set_xscale('log', nonposx='clip')
                if self._show_existing:
                    for freq in self._unique_frequencies:
                        self._axes.axvline(freq, linewidth=1, color='black', alpha=0.2)
            if self._title and self._unit:
                self._axes.set_xlabel("%s (%s)" % (self._title, self._unit), fontsize=8)
                self.figure.suptitle('%s Distribution in Selected Stations' %
                                     self._title, fontsize=8)

            self._fig.set_tight_layout(True)

        def update_figure(self):
            self._axes.cla()
            self.compute_initial_figure()
            for key in list(self._lx.keys()):
                if isinstance(key, float):
                    self._lx[key] = self._draw_v_line(key)
                elif isinstance(key, tuple):
                    if len(key) == 3:
                        self._lx[key] = self._fill_v_area(key[0], key[1])
                    elif len(key) == 5:
                        self._lx[key] = (self._draw_v_line(key[3]), self._fill_v_area(key[0], key[1]))
            self.draw()

        def _draw_v_line(self, x):
            if x == -np.inf:
                x = self._axes.get_xlim()[0]
            if x == np.inf:
                x = self._axes.get_xlim()[1]
            return self._axes.axvline(x=x, linewidth=1, color="red")

        def _fill_v_area(self, x1, x2):
            if x1 == -np.inf:
                x1 = self._axes.get_xlim()[0]
            if x2 == np.inf:
                x2 = self._axes.get_xlim()[1]
            return self._axes.axvspan(x1, x2, alpha=0.5, color='red')
コード例 #9
0
class FrequencySelectionFromFile(QGroupBox):
    """
    select frequencies/periods from the selected edi files
    """

    def __init__(self, parent):
        QGroupBox.__init__(self, parent)
        self._mt_obj_dict = {}
        self.model_stations = QStandardItemModel()

        # setup ui
        self.ui = Ui_GroupBox_select_from_files()
        self.ui.setupUi(self)
        self.ui.listView_stations.setModel(self.model_stations)

        # connect signals
        self.ui.listView_stations.selectionModel().selectionChanged.connect(self._update_selection)

    data_changed = Signal()

    def set_data(self, mt_objs):
        self._mt_obj_dict.clear()
        for mt_obj in mt_objs:
            self._mt_obj_dict[mt_obj.station] = mt_obj

        self._update_stations()

        self.data_changed.emit()

    def _update_stations(self):
        self.model_stations.clear()
        for mt_obj in list(self._mt_obj_dict.values()):
            new_item = QStandardItem()
            new_item.setData(mt_obj.station, QtCore.Qt.DisplayRole)
            new_item.setData(mt_obj.fn, QtCore.Qt.ToolTipRole)
            self.model_stations.appendRow(new_item)
        self.model_stations.sort(0)

    def _update_selection(self):
        self.ui.tableWidget_selected.clearContents()
        unique_frequencies = set()
        # combine frequencies from all selected stations
        for index in self.ui.listView_stations.selectedIndexes():
            item = self.model_stations.item(index.row())
            station = item.data(QtCore.Qt.DisplayRole)
            mt_obj = self._mt_obj_dict[station]
            # get frequencies
            freq = [freq for freq in list(mt_obj.Z.freq)]
            unique_frequencies.update(freq)
        # order !
        unique_frequencies = sorted(list(unique_frequencies))
        unique_periods = list(1. / np.array(unique_frequencies))

        # update widget
        self.ui.tableWidget_selected.setRowCount(len(unique_frequencies))
        for index, freq_period in enumerate(zip(unique_frequencies, unique_periods)):
            for i in [0, 1]:
                newItem = QTableWidgetItem(str(freq_period[i]))
                newItem.setData(QtCore.Qt.UserRole, freq_period[i])
                newItem.setFlags(QtCore.Qt.ItemIsEnabled)
                self.ui.tableWidget_selected.setItem(index, i, newItem)

    def get_selected_frequencies(self):
        return self.get_data(0)

    def get_selected_periods(self):
        return self.get_data(1)

    def get_data(self, column_index):
        data = [
            self.ui.tableWidget_selected.item(index, column_index).data(QtCore.Qt.UserRole)
            for index in range(self.ui.tableWidget_selected.rowCount())
        ]
        return data
コード例 #10
0
class BibleReadingPlan(QWidget):

    template = {
        # Source / credits of the following plan: https://www.biblica.com/resources/reading-plans/
        1: [False, "Genesis 1, Genesis 2:1-17, Matthew 1:1-25, Psalm 1:1-6"],
        2: [
            False,
            "Genesis 2:18-25, Genesis 3, Genesis 4:1-16, Matthew 2:1-18, Psalm 2:1-12"
        ],
        3: [
            False,
            "Genesis 4:17-26, Genesis 5, Genesis 6, Matthew 2:19-23, Matthew 3, Psalm 3:1-8"
        ],
        4: [
            False,
            "Genesis 7, Genesis 8, Genesis 9:1-17, Matthew 4:1-22, Proverbs 1:1-7"
        ],
        5: [
            False,
            "Genesis 9:18-29, Genesis 10, Genesis 11:1-9, Matthew 4:23-25, Matthew 5:1-20, Psalm 4:1-8"
        ],
        6: [
            False,
            "Genesis 11:10-32, Genesis 12, Genesis 13, Matthew 5:21-42, Psalm 5:1-12"
        ],
        7: [
            False,
            "Genesis 14, Genesis 15, Genesis 16, Matthew 5:43-48, Matthew 6:1-24, Psalm 6"
        ],
        8: [
            False,
            "Genesis 17, Genesis 18, Matthew 6:25-34, Matthew 7:1-23, Proverbs 1:8-19"
        ],
        9: [
            False,
            "Genesis 19, Genesis 20:1-18, Matthew 7:24-29, Matthew 8:1-22, Psalm 7:1-9"
        ],
        10: [
            False,
            "Genesis 21, Genesis 22, Genesis 23, Matthew 8:23-34, Matthew 9:1-13, Psalm 7:10-17"
        ],
        11: [False, "Genesis 24:1-67, Matthew 9:14-38, Psalm 8:1-9"],
        12:
        [False, "Genesis 25, Genesis 26, Matthew 10:1-31, Proverbs 1:20-33"],
        13: [
            False,
            "Genesis 27, Genesis 28:1-22, Matthew 10:32-42, Matthew 11:1-15, Psalm 9:1-6"
        ],
        14: [False, "Genesis 29, Genesis 30, Matthew 11:16-30, Psalm 9:7-12"],
        15: [False, "Genesis 31:1-55, Matthew 12:1-21, Psalm 9:13-20"],
        16:
        [False, "Genesis 32, Genesis 33, Matthew 12:22-45, Proverbs 2:1-11"],
        17: [
            False,
            "Genesis 34, Genesis 35, Matthew 12:46-50, Matthew 13:1-17, Psalm 10:1-11"
        ],
        18:
        [False, "Genesis 36, Genesis 37, Matthew 13:18-35, Psalm 10:12-18"],
        19: [False, "Genesis 38, Genesis 39, Matthew 13:36-58, Psalm 11:1-7"],
        20: [
            False,
            "Genesis 40, Genesis 41:1-40, Matthew 14:1-21, Proverbs 2:12-22"
        ],
        21: [
            False,
            "Genesis 41:41-57, Genesis 42, Matthew 14:22-36, Matthew 15:1-9, Psalm 12:1-8"
        ],
        22: [False, "Genesis 43, Genesis 44, Matthew 15:10-39, Psalm 13:1-6"],
        23: [
            False,
            "Genesis 45, Genesis 46, Genesis 47:1-12, Matthew 16:1-20, Psalm 14:1-7"
        ],
        24: [
            False,
            "Genesis 47:13-31, Genesis 48, Matthew 16:21-28, Matthew 17:1-13, Proverbs 3:1-10"
        ],
        25: [
            False,
            "Genesis 49, Genesis 50, Matthew 17:14-27, Matthew 18:1-9, Psalm 15:1-5"
        ],
        26: [False, "Job 1, Job 2, Job 3, Matthew 18:10-35, Psalm 16:1-11"],
        27:
        [False, "Job 4, Job 5, Job 6, Job 7, Matthew 19:1-15, Psalm 17:1-5"],
        28:
        [False, "Job 8, Job 9, Job 10, Matthew 19:16-30, Proverbs 3:11-20"],
        29: [
            False,
            "Job 11, Job 12, Job 13, Job 14, Matthew 20:1-19, Psalm 17:6-12"
        ],
        30: [
            False,
            "Job 15, Job 16, Job 17, Job 18, Matthew 20:20-34, Psalm 17:13-15"
        ],
        31: [False, "Job 19, Job 20, Job 21, Matthew 21:1-17, Psalm 18:1-6"],
        32:
        [False, "Job 22, Job 23, Job 24, Matthew 21:18-32, Proverbs 3:21-35"],
        33: [
            False,
            "Job 25, Job 26, Job 27, Job 28, Job 29, Matthew 21:33-46, Matthew 22:1-14, Psalm 18:7-15"
        ],
        34:
        [False, "Job 30, Job 31, Job 32, Matthew 22:15-46, Psalm 18:16-24"],
        35: [False, "Job 33, Job 34, Matthew 23:1-39, Psalm 18:25-36"],
        36: [False, "Job 35, Job 36, Job 37, Matthew 24:1-31, Proverbs 4:1-9"],
        37: [
            False,
            "Job 38, Job 39, Job 40:1-2, Matthew 24:32-51, Matthew 25:1-13, Psalm 18:37-42"
        ],
        38: [
            False,
            "Job 40:3-24, Job 41, Job 42, Matthew 25:14-46, Psalm 18:43-50"
        ],
        39:
        [False, "Exodus 1, Exodus 2, Exodus 3, Matthew 26:1-30, Psalm 19:1-6"],
        40: [
            False,
            "Exodus 4, Exodus 5, Exodus 6:1-12, Matthew 26:31-46, Proverbs 4:10-19"
        ],
        41: [
            False,
            "Exodus 6:13-30, Exodus 7,Exodus 8, Matthew 26:47-68, Psalm 19:7-14"
        ],
        42: [
            False,
            "Exodus 9, Exodus 10, Matthew 26:69-75, Matthew 27:1-10, Psalm 20:1-9"
        ],
        43: [False, "Exodus 11, Exodus 12, Matthew 27:11-44, Psalm 21:1-7"],
        44:
        [False, "Exodus 13, Exodus 14, Matthew 27:45-66, Proverbs 4:20-27"],
        45: [False, "Exodus 15, Exodus 16, Matthew 28:1-20, Psalm 21:8-13"],
        46: [False, "Exodus 17, Exodus 18, Mark 1:1-28, Psalm 22:1-11"],
        47: [
            False,
            "Exodus 19, Exodus 20, Mark 1:29-45, Mark 2:1-17, Psalm 22:12-21"
        ],
        48: [
            False,
            "Exodus 21, Exodus 22, Mark 2:18-27, Mark 3:1-30, Proverbs 5:1-14"
        ],
        49: [
            False,
            "Exodus 23, Exodus 24, Mark 3:31-35, Mark 4:1-29, Psalm 22:22-31"
        ],
        50: [
            False,
            "Exodus 25, Exodus 26, Mark 4:30-41, Mark 5:1-20, Psalm 23:1-6"
        ],
        51: [
            False,
            "Exodus 27, Exodus 28, Mark 5:21-43, Mark 6:1-6, Psalm 24:1-10"
        ],
        52: [False, "Exodus 29, Exodus 30, Mark 6:7-29, Proverbs 5:15-23"],
        53: [
            False,
            "Exodus 31, Exodus 32, Exodus 33:1-6, Mark 6:30-56, Psalm 25:1-7"
        ],
        54: [False, "Exodus 33:7-23, Exodus 34, Mark 7:1-30, Psalm 25:8-15"],
        55: [
            False,
            "Exodus 35, Exodus 36, Mark 7:31-37, Mark 8:1-13, Psalm 25:16-22"
        ],
        56: [
            False,
            "Exodus 37, Exodus 38, Mark 8:14-38, Mark 9:1, Proverbs 6:1-11"
        ],
        57: [False, "Exodus 39, Exodus 40, Mark 9:2-32, Psalm 26:1-12"],
        58: [
            False,
            "Leviticus 1, Leviticus 2, Leviticus 3, Mark 9:33-50, Mark 10:1-12, Psalm 27:1-6"
        ],
        59:
        [False, "Leviticus 4, Leviticus 5:1-13, Mark 10:13-31, Psalm 27:7-14"],
        60: [
            False,
            "Leviticus 5:14-19, Leviticus 6, Leviticus 7:1-10, Mark 10:32-52, Proverbs 6:12-19"
        ],
        61: [False, "Leviticus 7:11-38, Leviticus 8, Mark 11:1-27, Psalm 28"],
        62: [
            False,
            "Leviticus 9, Leviticus 10, Mark 11:28-33, Mark 12:1-12, Psalm 29"
        ],
        63: [False, "Leviticus 11, Leviticus 12, Mark 12:13-27, Psalm 30:1-7"],
        64: [False, "Leviticus 13:1-59, Mark 12:28-44, Proverbs 6:20-29"],
        65: [False, "Leviticus 14:1-57, Mark 13:1-31, Psalm 30:8-12"],
        66: [
            False,
            "Leviticus 15, Leviticus 16, Mark 13:32-37, Mark 14:1-16, Psalm 31:1-8"
        ],
        67:
        [False, "Leviticus 17, Leviticus 18, Mark 14:17-42, Psalm 31:9-18"],
        68:
        [False, "Leviticus 19, Leviticus 20, Mark 14:43-72, Proverbs 6:30-35"],
        69: [
            False, "Leviticus 21, Leviticus 22, Mark 15:1-32, Psalm 31:19-24"
        ],
        70: [False, "Leviticus 23, Leviticus 24, Mark 15:33-47, Psalm 32"],
        71: [
            False,
            "Leviticus 25, Leviticus 26:1-13, Mark 16:1-20, Psalm 33:1-11"
        ],
        72: [
            False,
            "Leviticus 26:14-46, Leviticus 27, Luke 1:1-25, Proverbs 7:1-5"
        ],
        73: [False, "Numbers 1, Numbers 2:1-9, Luke 1:26-38, Psalm 33:12-22"],
        74: [False, "Numbers 2:10-34, Numbers 3, Luke 1:39-56, Psalm 34:1-10"],
        75: [False, "Numbers 4, Numbers 5:1-10, Luke 1:57-80, Psalm 34:11-22"],
        76: [
            False,
            "Numbers 5:11-31, Numbers 6:1-27, Luke 2:1-20, Proverbs 7:6-20"
        ],
        77: [False, "Numbers 7:1-65, Luke 2:21-40, Psalm 35:1-10"],
        78: [
            False,
            "Numbers 7:66-89, Numbers 8, Numbers 9:1-14, Luke 2:41-52, Psalm 35:11-18"
        ],
        79: [
            False,
            "Numbers 9:15-23, Numbers 10, Numbers 11:1-3, Luke 3:1-22, Psalm 35:19-28"
        ],
        80: [
            False,
            "Numbers 11:4-35, Numbers 12, Numbers 13:1-25, Luke 3:23-38, Luke 4:1-13, Proverbs 7:21-27"
        ],
        81: [
            False, "Numbers 13:26-33, Numbers 14, Luke 4:14-37, Psalm 36:1-12"
        ],
        82: [
            False,
            "Numbers 15, Numbers 16:1-35, Luke 4:38-44, Luke 5:1-16, Psalm 37:1-9"
        ],
        83: [
            False,
            "Numbers 16:36-50, Numbers 17, Numbers 18, Luke 5:17-32, Psalm 37:10-20"
        ],
        84: [
            False,
            "Numbers 19, Numbers 20, Numbers 21:1-3, Luke 5:33-39, Luke 6:1-11, Proverbs 8:1-11"
        ],
        85: [
            False,
            "Numbers 21:4-35, Numbers 22:1-20, Luke 6:12-36, Psalm 37:21-31"
        ],
        86: [
            False,
            "Numbers 22:21-41, Numbers 23:1-26, Luke 6:37-49, Luke 7:1-10, Psalm 37:32-40"
        ],
        87: [
            False,
            "Numbers 23:27-30, Numbers 24, Numbers 25, Luke 7:11-35, Psalm 38:1-11"
        ],
        88:
        [False, "Numbers 26, Numbers 27:1-11, Luke 7:36-50, Proverbs 8:12-21"],
        89: [
            False,
            "Numbers 27:12-23, Numbers 28, Numbers 29:1-11, Luke 8:1-18, Psalm 38:12-22"
        ],
        90: [
            False,
            "Numbers 29:12-40, Numbers 30, Numbers 31:1-24, Luke 8:19-39, Psalm 39:1-13"
        ],
        91: [
            False,
            "Numbers 31:25-54, Numbers 32, Luke 8:40-56, Luke 9:1-9, Psalm 40:1-8"
        ],
        92: [False, "Numbers 33, Numbers 34, Luke 9:10-27, Proverbs 8:22-31"],
        93: [
            False, "Numbers 35, Numbers 36:1-12, Luke 9:28-56, Psalm 40:9-17"
        ],
        94: [
            False,
            "Deuteronomy 1, Deuteronomy 2:1-23, Luke 9:57-62, Luke 10:1-24, Psalm 41:1-6"
        ],
        95: [
            False,
            "Deuteronomy 2:24-37, Deuteronomy 3, Deuteronomy 4:1-14, Luke 10:25-42, Luke 11:1-4, Psalm 41:7-13"
        ],
        96: [
            False,
            "Deuteronomy 4:15-49, Deuteronomy 5, Luke 11:5-32, Proverbs 8:32-36"
        ],
        97: [
            False,
            "Deuteronomy 6, Deuteronomy 7, Deuteronomy 8, Luke 11:33-54, Psalm 42:1-6"
        ],
        98: [
            False, "Deuteronomy 9, Deuteronomy 10, Luke 12:1-34, Psalm 42:7-11"
        ],
        99:
        [False, "Deuteronomy 11, Deuteronomy 12, Luke 12:35-59, Psalm 43:1-5"],
        100: [
            False,
            "Deuteronomy 13, Deuteronomy 14, Luke 13:1-30, Proverbs 9:1-12"
        ],
        101: [
            False,
            "Deuteronomy 15, Deuteronomy 16:1-20, Luke 13:31-35, Luke 14:1-14, Psalm 44:1-12"
        ],
        102: [
            False,
            "Deuteronomy 16:21-22, Deuteronomy 17, Deuteronomy 18, Luke 14:15-35, Psalm 44:13-26"
        ],
        103: [
            False, "Deuteronomy 19, Deuteronomy 20, Luke 15:1-32, Psalm 45:1-9"
        ],
        104: [
            False,
            "Deuteronomy 21, Deuteronomy 22, Luke 16:1-18, Proverbs 9:13-18"
        ],
        105: [
            False,
            "Deuteronomy 23, Deuteronomy 24, Deuteronomy 25:1-19, Luke 16:19-31, Luke 17:1-10, Psalm 45:10-17"
        ],
        106: [
            False,
            "Deuteronomy 26, Deuteronomy 27, Deuteronomy 28:1-14, Luke 17:11-37, Psalm 46:1-11"
        ],
        107: [False, "Deuteronomy 28:15-68, Luke 18:1-30, Psalm 47:1-9"],
        108: [
            False,
            "Deuteronomy 29, Deuteronomy 30:1-10, Luke 18:31-43, Luke 19:1-10, Proverbs 10:1-10"
        ],
        109: [
            False,
            "Deuteronomy 30:11-20, Deuteronomy 31:1-29, Luke 19:11-44, Psalm 48:1-8"
        ],
        110: [
            False,
            "Deuteronomy 31:30, Deuteronomy 32, Luke 19:45-48, Luke 20:1-26, Psalm 48:9-14"
        ],
        111: [
            False,
            "Deuteronomy 33, Deuteronomy 34:1-12, Luke 20:27-47, Luke 21:1-4, Psalm 49:1-20"
        ],
        112: [False, "Joshua 1, Joshua 2, Luke 21:5-38, Proverbs 10:11-20"],
        113: [
            False,
            "Joshua 3, Joshua 4, Joshua 5:1-12, Luke 22:1-38, Psalm 50:1-15"
        ],
        114: [
            False,
            "Joshua 5:13-15, Joshua 6, Joshua 7, Luke 22:39-62, Psalm 50:16-23"
        ],
        115: [
            False,
            "Joshua 8, Joshua 9:1-15, Luke 22:63-71, Luke 23:1-25, Psalm 51:1-9"
        ],
        116: [
            False,
            "Joshua 9:16-27, Joshua 10, Luke 23:26-56, Proverbs 10:21-30"
        ],
        117: [False, "Joshua 11, Joshua 12, Luke 24:1-35, Psalm 51:10-19"],
        118: [False, "Joshua 13, Joshua 14, Luke 24:36-53, Psalm 52:1-9"],
        119: [False, "Joshua 15, Joshua 16, John 1:1-28, Psalm 53:1-6"],
        120: [
            False,
            "Joshua 17, Joshua 18, John 1:29-51, Proverbs 10:31-32, Proverbs 11:1-8"
        ],
        121: [
            False,
            "Joshua 19, Joshua 20, Joshua 21:1-19, John 2:1-25, Psalm 54:1-7"
        ],
        122: [False, "Joshua 21:20-45, Joshua 22, John 3:1-21, Psalm 55:1-11"],
        123: [False, "Joshua 23, Joshua 24, John 3:22-36, Psalm 55:12-23"],
        124: [False, "Judges 1, Judges 2:1-5, John 4:1-26, Proverbs 11:9-18"],
        125: [False, "Judges 2:6-23, Judges 3, John 4:27-42, Psalm 56:1-13"],
        126: [
            False,
            "Judges 4, Judges 5, John 4:43-54, John 5:1-15, Psalm 57:1-6"
        ],
        127: [False, "Judges 6, Judges 7:1-8, John 5:16-30, Psalm 57:7-11"],
        128: [
            False, "Judges 7:8-25, Judges 8, John 5:31-47, Proverbs 11:19-28"
        ],
        129: [False, "Judges 9, John 6:1-24, Psalm 58:1-11"],
        130: [False, "Judges 10, Judges 11, John 6:25-59, Psalm 59:1-8"],
        131: [
            False,
            "Judges 12, Judges 13, John 6:60-71, John 7:1-13, Psalm 59:9-19"
        ],
        132: [
            False,
            "Judges 14, Judges 15, John 7:14-44, Proverbs 11:29-31, Proverbs 12:1-7"
        ],
        133: [
            False,
            "Judges 16, Judges 17, John 7:45-53, John 8:1-11, Psalm 60:1-4"
        ],
        134: [False, "Judges 18, Judges 19, John 8:12-30, Psalm 60:5-12"],
        135: [False, "Judges 20, Judges 21, John 8:31-59, Psalm 61:1-8"],
        136: [False, "Ruth 1, Ruth 2, John 9:1-34, Proverbs 12:8-17"],
        137: [
            False, "Ruth 3, Ruth 4, John 9:35-41, John 10:1-21, Psalm 62:1-12"
        ],
        138: [
            False, "1 Samuel 1, 1 Samuel 2:1-26, John 10:22-42, Psalm 63:1-11"
        ],
        139: [
            False,
            "1 Samuel 2:27-36, 1 Samuel 3, 1 Samuel 4, John 11:1-44, Psalm 64:1-10"
        ],
        140: [
            False,
            "1 Samuel 5, 1 Samuel 6, 1 Samuel 7, John 11:45-57, John 12:1-11, Proverbs 12:18-27"
        ],
        141: [
            False,
            "1 Samuel 8, 1 Samuel 9, 1 Samuel 10:1-8, John 12:12-26, Psalm 65:1-13"
        ],
        142: [
            False,
            "1 Samuel 10:9-27, 1 Samuel 11, 1 Samuel 12, John 12:37-50, John 13:1-17, Psalm 66:1-12"
        ],
        143: [
            False,
            "1 Samuel 13, 1 Samuel 14:1-23, John 13:18-38, Psalm 66:13-20"
        ],
        144: [
            False,
            "1 Samuel 14:24-52, 1 Samuel 15, John 14:1-31, Proverbs 12:28, Proverbs 13:1-9"
        ],
        145: [
            False,
            "1 Samuel 16, 1 Samuel 17:1-37, John 15, John 16:1-4, Psalm 67:1-7"
        ],
        146: [
            False,
            "1 Samuel 17:38-58, 1 Samuel 18, John 16:5-33, John 17:1-5, Psalm 68:1-6"
        ],
        147: [False, "1 Samuel 19, 1 Samuel 20, John 17:6-26, Psalm 68:7-14"],
        148: [
            False,
            "1 Samuel 21, 1 Samuel 22, 1 Samuel 23, John 18:1-24, Proverbs 13:10-19"
        ],
        149: [
            False, "1 Samuel 24, 1 Samuel 25, John 18:25-40, Psalm 68:15-20"
        ],
        150: [
            False,
            "1 Samuel 26, 1 Samuel 27, 1 Samuel 28, John 19:1-27, Psalm 68:21-27"
        ],
        151: [
            False,
            "1 Samuel 29, 1 Samuel 30, 1 Samuel 31, John 19:28-42, John 20:1-9, Psalm 68:28-35"
        ],
        152: [
            False,
            "2 Samuel 1, 2 Samuel 2:1-7, John 20:10-31, Proverbs 13:20-25, Proverbs 14:1-4"
        ],
        153: [
            False,
            "2 Samuel 2:8-32, 2 Samuel 3:1-21, John 21:1-25, Psalm 69:1-12"
        ],
        154: [
            False,
            "2 Samuel 3:22-39, 2 Samuel 4, 2 Samuel 5:1-5, Acts 1:1-22, Psalm 69:13-28"
        ],
        155: [
            False,
            "2 Samuel 5:6-25, 2 Samuel 6, Acts 1:23-26, Acts 2:1-21, Psalm 69:29-36"
        ],
        156: [False, "2 Samuel 7, 2 Samuel 8, Acts 2:22-47, Proverbs 14:4-14"],
        157: [False, "2 Samuel 9, 2 Samuel 10, Acts 3, Psalm 70:1-5"],
        158: [False, "2 Samuel 11, 2 Samuel 12, Acts 4:1-22, Psalm 71:1-8"],
        159: [False, "2 Samuel 13, Acts 4:23-37, Acts 5:1-11, Psalm 71:9-18"],
        160: [
            False,
            "2 Samuel 14, 2 Samuel 15:1-12, Acts 5:12-42, Proverbs 14:15-24"
        ],
        161: [
            False,
            "2 Samuel 15:13-37, 2 Samuel 16:1-14, Acts 6, Acts 7:1-19, Psalm 71:19-24"
        ],
        162: [
            False,
            "2 Samuel 16:15-23, 2 Samuel 17, 2 Samuel 18:1-18, Acts 7:20-43, Psalm 72:1-20"
        ],
        163: [
            False,
            "2 Samuel 18:19-33, 2 Samuel 19, Acts 7:44-60, Acts 8:1-3, Psalm 73:1-14"
        ],
        164: [
            False, "2 Samuel 20, 2 Samuel 21, Acts 8:4-40, Proverbs 14:25-35"
        ],
        165: [
            False, "2 Samuel 22, 2 Samuel 23:1-7, Acts 9:1-31, Psalm 73:15-28"
        ],
        166: [
            False,
            "2 Samuel 23:8-39, 2 Samuel 24:1-25, Acts 9:32-43, Acts 10:1-23, Psalms 74:1-9"
        ],
        167: [
            False,
            "1 Kings 1, 1 Kings 2:1-12, Acts 10:23-48, Acts 11:1-18, Psalm 74:10-17"
        ],
        168: [
            False,
            "1 Kings 2:13-46, 1 Kings 3:1-15, Acts 11:19-30, Acts 12:1-19, Proverbs 15:1-10"
        ],
        169: [
            False,
            "1 Kings 3:16-28, 1 Kings 4, 1 Kings 5, Acts 12:19-25, Acts 13:1-12, Psalm 74:18-23"
        ],
        170: [
            False, "1 Kings 6, 1 Kings 7:1-22, Acts 13:13-41, Psalm 75:1-10"
        ],
        171: [
            False,
            "1 Kings 7:23-51, 1 Kings 8:1-21, Acts 13:42-52, Acts 14:1-7, Psalm 76:1-12"
        ],
        172: [
            False,
            "1 Kings 8:22-66, 1 Kings 9:1-9, Acts 14:8-28, Proverbs 15:11-20"
        ],
        173: [
            False,
            "1 Kings 9:10-28, 1 Kings 10, 1 Kings 11:1-13, Acts 15:1-21, Psalm 77:1-9"
        ],
        174: [
            False,
            "1 Kings 11:14-43, 1 Kings 12:1-24, Acts 15:22-41, Psalm 77:10-20"
        ],
        175: [
            False,
            "1 Kings 12:25-33, 1 Kings 13, 1 Kings 14:1-20, Acts 16:1-15, Psalm 78:1-8"
        ],
        176: [
            False,
            "1 Kings 14:21-31, 1 Kings 15, 1 Kings 16:1-7, Acts 16:16-40, Proverbs 15:21-30"
        ],
        177: [
            False,
            "1 Kings 16:8-34, 1 Kings 17, 1 Kings 18:1-15, Acts 17:1-21, Psalm 78:9-16"
        ],
        178: [
            False,
            "1 Kings 18:16-46, 1 Kings 19, Acts 17:22-34, Acts 18:1-8, Psalm 78:17-31"
        ],
        179: [
            False,
            "1 Kings 20, 1 Kings 21, Acts 18:9-28, Acts 19:1-13, Psalm 78:32-39"
        ],
        180: [
            False,
            "1 Kings 22:1-53, Acts 19:14-41, Proverbs 15:31-33, Proverbs 16:1-7"
        ],
        181: [
            False, "2 Kings 1, 2 Kings 2:1-25, Acts 20:1-38, Psalm 78:40-55"
        ],
        182: [
            False, "2 Kings 3, 2 Kings 4:1-37, Acts 21:1-26, Psalm 78:56-72"
        ],
        183: [
            False,
            "2 Kings 4:38-44, 2 Kings 5, 2 Kings 6:1-23, Acts 21:27-40, Acts 22:1-22, Psalm 79:1-13"
        ],
        184: [
            False,
            "2 Kings 6:24-33, 2 Kings 7, 2 Kings 8:1-15, Acts 22:22-30, Acts 23:1-11, Proverbs 16:8-17"
        ],
        185: [
            False, "2 Kings 8:16-29, 2 Kings 9, Acts 23:12-35, Psalm 80:1-7"
        ],
        186: [False, "2 Kings 10, 2 Kings 11, Acts 24:1-27, Psalm 80:8-19"],
        187: [
            False,
            "2 Kings 12, 2 Kings 13, 2 Kings 14:1-22, Acts 25:1-22, Psalm 81:1-7"
        ],
        188: [
            False,
            "2 Kings 14:23-29, 2 Kings 15, Acts 25:23-27, Acts 26:1-23, Proverbs 16:18-27"
        ],
        189: [
            False,
            "2 Kings 16, 2 Kings 17, Acts 26:24-32, Acts 27:1-12, Psalm 81:8-16"
        ],
        190: [
            False, "2 Kings 18, 2 Kings 19:1-13, Acts 27:13-44, Psalm 82:1-8"
        ],
        191: [
            False, "2 Kings 19:14-37, 2 Kings 20, Acts 28:1-16, Psalm 83:1-18"
        ],
        192: [
            False,
            "2 Kings 21, 2 Kings 22, Acts 28:17-31, Proverbs 16:28-33, Proverbs 17:1-4"
        ],
        193: [
            False, "2 Kings 23, 2 Kings 24:1-7, Romans 1:1-17, Psalm 84:1-7"
        ],
        194: [
            False, "2 Kings 24:8-20, 2 Kings 25, Romans 1:18-32, Psalm 84:8-12"
        ],
        195: [
            False,
            "Jonah 1, Jonah 2, Jonah 3, Jonah 4, Romans 2:1-16, Psalm 85:1-7"
        ],
        196: [
            False,
            "Amos 1, Amos 2, Romans 2:17-29, Romans 3:1-8, Proverbs 17:5-14"
        ],
        197: [False, "Amos 3, Amos 4, Romans 3:9-31, Psalm 85:8-13"],
        198: [False, "Amos 5, Romans 4:1-15, Psalm 86:1-10"],
        199: [
            False,
            "Amos 6, Amos 7, Romans 4:16-25, Romans 5:1-11, Psalm 86:11-17"
        ],
        200: [False, "Amos 8, Amos 9, Romans 5:12-21, Proverbs 17:15-24"],
        201: [False, "Hosea 1, Hosea 2, Romans 6:1-14, Psalm 87:1-7"],
        202: [
            False,
            "Hosea 3, Hosea 4, Hosea 5, Romans 6:15-23, Romans 7:1-6, Psalm 88:1-9"
        ],
        203: [False, "Hosea 6, Hosea 7, Romans 7:7-25, Psalm 88:9-18"],
        204: [
            False,
            "Hosea 8, Hosea 9, Romans 8:1-17, Proverbs 17:25-28, Proverbs 18:1-6"
        ],
        205: [False, "Hosea 10, Hosea 11, Romans 8:18-39, Psalm 89:1-8"],
        206: [
            False,
            "Hosea 11, Hosea 12, Hosea 13, Hosea 14, Romans 9:1-21, Psalm 89:9-13"
        ],
        207: [
            False,
            "1 Chronicles 1, 1 Chronicles 2:1-17, Romans 9:22-33, Romans 10:1-4, Psalm 89:14-18"
        ],
        208: [
            False,
            "1 Chronicles 2:18-55, 1 Chronicles 3, 1 Chronicles 4:1-8, Romans 10:5-21, Romans 11:1-10, Proverbs 18:7-16"
        ],
        209: [
            False,
            "1 Chronicles 4:9-43, 1 Chronicles 5, Romans 11:11-32, Psalm 89:19-29"
        ],
        210: [
            False,
            "1 Chronicles 6, Romans 11:33-36, Romans 12:1-21, Psalm 89:30-37"
        ],
        211: [
            False,
            "1 Chronicles 7, 1 Chronicles 8, Romans 13:1-14, Psalm 89:38-45"
        ],
        212: [
            False,
            "1 Chronicles 9, 1 Chronicles 10:1-14, Romans 14:1-18, Proverbs 18:17-24, Proverbs 19:1-2"
        ],
        213: [
            False,
            "1 Chronicles 11, 1 Chronicles 12:1-22, Romans 14:19-23, Romans 15:1-13, Psalm 89:46-52"
        ],
        214: [
            False,
            "1 Chronicles 12:23-40, 1 Chronicles 13, 1 Chronicles 14, Romans 15:14-33, Psalm 90:1-10"
        ],
        215: [
            False,
            "1 Chronicles 15, 1 Chronicles 16:1-36, Romans 16, Psalm 90:11-17"
        ],
        216: [
            False,
            "1 Chronicles 16:37-43, 1 Chronicles 17, 1 Chronicles 18, 1 Corinthians 1:1-17, Proverbs 19:3-12"
        ],
        217: [
            False,
            "1 Chronicles 19, 1 Chronicles 20, 1 Chronicles 21, 1 Corinthians 1:18-31, 1 Corinthians 2:1-5, Psalm 91:1-8"
        ],
        218: [
            False,
            "1 Chronicles 22, 1 Chronicles 23, 1 Corinthians 2:6-16, Psalm 91:9-16"
        ],
        219: [
            False,
            "1 Chronicles 24, 1 Chronicles 25, 1 Chronicles 26:1-19, 1 Corinthians 3, Psalm 92:1-15"
        ],
        220: [
            False,
            "1 Chronicles 26:20-32, 1 Chronicles 27, 1 Corinthians 4, Proverbs 19:13-22"
        ],
        221: [
            False,
            "1 Chronicles 28, 1 Chronicles 29, 1 Corinthians 5, Psalm 93:1-5"
        ],
        222: [False, "2 Chronicles 1:1-17, 1 Corinthians 6, Psalm 94:1-11"],
        223: [
            False,
            "Ecclesiastes 1, Ecclesiastes 2, Ecclesiastes 3:1-22, 1 Corinthians 7:1-16, Psalm 94:12-23"
        ],
        224: [
            False,
            "Ecclesiastes 4, Ecclesiastes 5, Ecclesiastes 6, 1 Corinthians 7:17-35, Proverbs 19:23-29, Proverbs 20:1-4"
        ],
        225: [
            False,
            "Ecclesiastes 7, Ecclesiastes 8, Ecclesiastes 9:1-12, 1 Corinthians 7:36-40, 1 Corinthians 8:1-13, Psalm 95:1-11"
        ],
        226: [
            False,
            "Ecclesiastes 9:13-18, Ecclesiastes 10, Ecclesiastes 11, Ecclesiastes 12, 1 Corinthians 9:1-18, Psalm 96:1-13"
        ],
        227: [
            False,
            "2 Chronicles 2, 2 Chronicles 3, 2 Chronicles 4, 2 Chronicles 5:1, 1 Corinthians 9:19-27, 1 Corinthians 10:1-13, Psalm 97:1-12"
        ],
        228: [
            False,
            "2 Chronicles 5:2-14, 2 Chronicles 6, 2 Chronicles 7:1-10, 1 Corinthians 10:14-33, 1 Corinthians 11:1, Proverbs 20:5-14"
        ],
        229: [
            False,
            "2 Chronicles 7:11-22, 2 Chronicles 8, 2 Chronicles 9, 1 Corinthians 11:2-34, Psalm 98:1-9"
        ],
        230: [
            False,
            "Song 1, Song 2, Song 3, Song 4, 1 Corinthians 12:1-26, Psalm 99:1-9"
        ],
        231: [
            False,
            "Song 5, Song 6, Song 7, Song 8, 1 Corinthians 12:27-31, 1 Corinthians 13:1-13, Psalm 100:1-5"
        ],
        232: [
            False,
            "2 Chronicles 10, 2 Chronicles 11, 2 Chronicles 12, 1 Corinthians 14:1-19, Proverbs 20:15-24"
        ],
        233: [
            False,
            "2 Chronicles 13, 2 Chronicles 14, 2 Chronicles 15, 1 Corinthians 14:20-40, Psalm 101:1-8"
        ],
        234: [
            False,
            "2 Chronicles 16, 2 Chronicles 17, 2 Chronicles 18:1-27, 1 Corinthians 15:1-34, Psalm 102:1-11"
        ],
        235: [
            False,
            "2 Chronicles 18:28-34, 2 Chronicles 19, 2 Chronicles 20, 1 Corinthians 15:35-49, Psalm 102:12-17"
        ],
        236: [
            False,
            "2 Chronicles 21, 2 Chronicles 22, 2 Chronicles 23, 1 Corinthians 15:50-58, 1 Corinthians 16:1-4, Proverbs 20:25-30, Proverbs 21:1-4"
        ],
        237: [
            False,
            "2 Chronicles 24, 2 Chronicles 25, 1 Corinthians 16:5-24, Psalm 102:18-28"
        ],
        238: [
            False,
            "2 Chronicles 26, 2 Chronicles 27, 2 Chronicles 28, 2 Corinthians 1:1-11, Psalm 103:1-12"
        ],
        239: [
            False,
            "2 Chronicles 29, 2 Chronicles 30, 2 Chronicles 31:1, 2 Corinthians 1:12-22, Psalm 103:13-22"
        ],
        240: [
            False,
            "2 Chronicles 31:2-21, 2 Chronicles 32, 2 Chronicles 33:1-20, 2 Corinthians 1:23, 2 Corinthians 2:1-11, Proverbs 21:5-16"
        ],
        241: [
            False,
            "2 Chronicles 33:21-24, 2 Chronicles 34, 2 Chronicles 35:1-19, 2 Corinthians 2:12-17, 2 Corinthians 3:1-6, Psalm 104:1-18"
        ],
        242: [
            False,
            "2 Chronicles 35:20-27, 2 Chronicles 36, 2 Corinthians 3:7-18, Psalm 104:19-30"
        ],
        243: [
            False,
            "Micah 1, Micah 2, Micah 3, Micah 4, 2 Corinthians 4, Psalm 104:31-35"
        ],
        244: [
            False,
            "Micah 5, Micah 6, Micah 7, 2 Corinthians 5:1-10, Proverbs 21:17-26"
        ],
        245: [
            False,
            "Isaiah 1, Isaiah 2, 2 Corinthians 5:11-21, 2 Corinthians 6:1-2, Psalm 105:1-11"
        ],
        246: [
            False,
            "Isaiah 3, Isaiah 4, Isaiah 5:1-7, 2 Corinthians 6:3-18, 2 Corinthians 7:1, Psalm 105:12-22"
        ],
        247: [
            False,
            "Isaiah 5:8-30, Isaiah 6, Isaiah 7, Isaiah 8:1-10, 2 Corinthians 7:2-16, Psalm 105:23-36"
        ],
        248: [
            False,
            "Isaiah 8:11-22, Isaiah 9, Isaiah 10:1-19, 2 Corinthians 8:1-15, Proverbs 21:27-31, Proverbs 22:1-6"
        ],
        249: [
            False,
            "Isaiah 10:20-34, Isaiah 11, Isaiah 12, Isaiah 13, 2 Corinthians 8:16-24, 2 Corinthians 9:1-5, Psalm 105:37-45"
        ],
        250: [
            False,
            "Isaiah 14, Isaiah 15, Isaiah 16, 2 Corinthians 9:6-15, Psalm 106:1-15"
        ],
        251: [
            False,
            "Isaiah 17, Isaiah 18, Isaiah 19, 2 Corinthians 10, Psalm 106:16-31"
        ],
        252: [
            False,
            "Isaiah 20, Isaiah 21, Isaiah 22, Isaiah 23, 2 Corinthians 11:1-15, Proverbs 22:7-16"
        ],
        253: [
            False,
            "Isaiah 24, Isaiah 25, Isaiah 26, 2 Corinthians 11:16-33, Psalm 106:32-39"
        ],
        254: [
            False,
            "Isaiah 27, Isaiah 28, 2 Corinthians 12:1-10, Psalm 106:40-48"
        ],
        255: [
            False,
            "Isaiah 29, Isaiah 30:1-18, 2 Corinthians 12:11-21, Psalm 107:1-9"
        ],
        256: [
            False,
            "Isaiah 30:19-33, Isaiah 31, Isaiah 32, 2 Corinthians 13, Proverbs 22:17-27"
        ],
        257: [
            False,
            "Isaiah 33, Isaiah 34, Isaiah 35, Galatians 1, Psalm 107:10-22"
        ],
        258: [
            False, "Isaiah 36, Isaiah 37, Galatians 2:1-10, Psalm 107:23-32"
        ],
        259: [
            False,
            "Isaiah 38, Isaiah 39, Isaiah 40, Galatians 2:11-21, Galatians 3:1-9, Psalm 107:33-43"
        ],
        260: [
            False,
            "Isaiah 41, Isaiah 42, Galatians 3:10-25, Proverbs 22:28-29, Proverbs 23:1-9"
        ],
        261: [
            False,
            "Isaiah 43, Isaiah 44:1-23, Galatians 3:26-29, Galatians 4:1-20, Psalm 108:1-5"
        ],
        262: [
            False,
            "Isaiah 44:24-28, Isaiah 45, Isaiah 46, Galatians 4:21-31, Galatians 5:1-6, Psalm 108:6-13"
        ],
        263: [
            False,
            "Isaiah 47, Isaiah 48, Isaiah 49:1-7, Galatians 5:7-26, Psalm 109:1-20"
        ],
        264: [
            False,
            "Isaiah 49:8-26, Isaiah 50, Isaiah 51:1-16, Galatians 6, Proverbs 23:10-18"
        ],
        265: [
            False,
            "Isaiah 51:17-23, Isaiah 52, Isaiah 53, Isaiah 54, Ephesians 1, Psalm 109:21-31"
        ],
        266: [
            False,
            "Isaiah 55, Isaiah 56, Isaiah 57:1-13, Ephesians 2, Psalm 110:1-7"
        ],
        267: [
            False,
            "Isaiah 57:14-21, Isaiah 58, Isaiah 59, Ephesians 3, Psalm 111:1-10"
        ],
        268: [
            False,
            "Isaiah 60, Isaiah 61, Isaiah 62, Ephesians 4:1-16, Proverbs 23:19-28"
        ],
        269: [
            False,
            "Isaiah 63, Isaiah 64, Isaiah 65:1-16, Ephesians 4:17-32, Ephesians 5:1-7, Psalm 112:1-10"
        ],
        270: [
            False,
            "Isaiah 65:17-25, Isaiah 66, Ephesians 5:8-33, Psalm 113:1-9"
        ],
        271: [False, "Nahum 1, Nahum 2, Nahum 3, Ephesians 6, Psalm 114:1-8"],
        272: [
            False,
            "Zephaniah 1, Zephaniah 2, Zephaniah 3, Philippians 1:1-26, Proverbs 23:29-35, Proverbs 24:1-4"
        ],
        273: [
            False,
            "Jeremiah 1, Jeremiah 2:1-30, Philippians 1:27-30, Philippians 2:1-11, Psalm 115:1-11"
        ],
        274: [
            False,
            "Jeremiah 2:31-47, Jeremiah 3, Jeremiah 4:1-9, Philippians 2:12-30, Psalm 115:12-18"
        ],
        275: [
            False,
            "Jeremiah 4:10-31, Jeremiah 5, Philippians 3, Philippians 4:1, Psalm 116:1-11"
        ],
        276: [
            False,
            "Jeremiah 6, Jeremiah 7:1-29, Philippians 4:2-23, Proverbs 24:5-14"
        ],
        277: [
            False,
            "Jeremiah 7:30-34, Jeremiah 8, Jeremiah 9:1-16, Colossians 1:1-23, Psalm 116:12-19"
        ],
        278: [
            False,
            "Jeremiah 9:17-26, Jeremiah 10, Jeremiah 11:1-17, Colossians 1:24-29, Colossians 2:1-5, Psalm 117:1-2"
        ],
        279: [
            False,
            "Jeremiah 11:18-23, Jeremiah 12, Jeremiah 13, Colossians 2:6-23, Psalm 118:1-16"
        ],
        280: [
            False,
            "Jeremiah 14, Jeremiah 15, Colossians 3, Colossians 4:1, Proverbs 24:15-22"
        ],
        281: [
            False,
            "Jeremiah 16, Jeremiah 17, Colossians 4:2-18, Psalm 118:17-29"
        ],
        282: [
            False,
            "Jeremiah 18, Jeremiah 19, Jeremiah 20, 1 Thessalonians 1, 1 Thessalonians 2:1-16, Psalm 119:1-8"
        ],
        283: [
            False,
            "Jeremiah 21, Jeremiah 22, Jeremiah 23:1-8, 1 Thessalonians 2:17-19, 1 Thessalonians 3, Psalm 119:9-16"
        ],
        284: [
            False,
            "Jeremiah 23:9-40, Jeremiah 24, Jeremiah 25:1-14, 1 Thessalonians 4, Proverbs 24:23-34"
        ],
        285: [
            False,
            "Jeremiah 25:15-38, Jeremiah 26, 1 Thessalonians 5, Psalm 119:17-24"
        ],
        286: [
            False,
            "Jeremiah 27, Jeremiah 28, Jeremiah 29:1-23, 2 Thessalonians 1, Psalm 119:25-32"
        ],
        287: [
            False,
            "Jeremiah 29:24-32, Jeremiah 30, Jeremiah 31:1-14, 2 Thessalonians 2, Psalm 119:33-40"
        ],
        288: [
            False,
            "Jeremiah 31:15-40, Jeremiah 32:1-25, 2 Thessalonians 3, Proverbs 25:1-10"
        ],
        289: [
            False,
            "Jeremiah 32:26-44, Jeremiah 33, Jeremiah 34, 1 Timothy 1, Psalm 119:41-48"
        ],
        290: [
            False,
            "Jeremiah 35, Jeremiah 36, Jeremiah 37, 1 Timothy 2, Psalm 119:49-56"
        ],
        291: [
            False,
            "Jeremiah 38, Jeremiah 39, Jeremiah 40:1-6, 1 Timothy 3, Psalm 119:57-64"
        ],
        292: [
            False,
            "Jeremiah 40:7-16, Jeremiah 41, Jeremiah 42, 1 Timothy 4, Proverbs 25:11-20"
        ],
        293: [
            False,
            "Jeremiah 43, Jeremiah 44, Jeremiah 45, 1 Timothy 5, 1 Timothy 6:1-2, Psalm 119:65-72"
        ],
        294: [
            False,
            "Jeremiah 46, Jeremiah 47, 1 Timothy 6:3-21, Psalm 119:73-80"
        ],
        295: [
            False, "Jeremiah 48, Jeremiah 49:1-6, 2 Timothy 1, Psalm 119:81-88"
        ],
        296: [
            False,
            "Jeremiah 49:7-39, Jeremiah 50:1-10, 2 Timothy 2, Proverbs 25:21-28, Proverbs 26:1-2"
        ],
        297: [
            False,
            "Jeremiah 50:11-46, Jeremiah 51:1-14, 2 Timothy 3, Psalm 119:89-96"
        ],
        298: [False, "Jeremiah 51:15-64, 2 Timothy 4, Psalm 119:97-104"],
        299: [False, "Jeremiah 52, Titus 1, Psalm 119:105-112"],
        300: [
            False,
            "Habakkuk 1, Habakkuk 2, Habakkuk 3:1-19, Titus 2, Proverbs 26:3-12"
        ],
        301: [
            False,
            "Lamentations 1, Lamentations 2:1-6, Titus 3, Psalm 119:113-120"
        ],
        302: [
            False,
            "Lamentations 2:7-27, Lamentations 3:1-39, Philemon 1, Psalm 119:121-128"
        ],
        303: [
            False,
            "Lamentations 3:40-66, Lamentations 4, Lamentations 5, Hebrews 1, Psalm 119:129-136"
        ],
        304: [False, "Obadiah 1, Hebrews 2, Proverbs 26:13-22"],
        305: [False, "Joel 1, Joel 2:1-17, Hebrews 3, Psalm 119:137-144"],
        306: [
            False, "Joel 2:18-32, Joel 3, Hebrews 4:1-13, Psalm 119:145-152"
        ],
        307: [
            False,
            "Ezekiel 1, Ezekiel 2, Ezekiel 3, Hebrews 4:14-16, Hebrews 5:1-10, Psalm 119:153-160"
        ],
        308: [
            False,
            "Ezekiel 4, Ezekiel 5, Ezekiel 6, Hebrews 5:11-14, Hebrews 6:1-12, Proverbs 26:23-28, Proverbs 27:1-4"
        ],
        309: [
            False,
            "Ezekiel 7, Ezekiel 8, Ezekiel 9, Hebrews 6:13-20, Hebrews 7:1-10, Psalm 119:161-168"
        ],
        310: [
            False,
            "Ezekiel 10, Ezekiel 11, Ezekiel 12, Hebrews 7:11-28, Psalm 119:169-176"
        ],
        311: [
            False,
            "Ezekiel 13, Ezekiel 14, Ezekiel 15, Hebrews 8, Psalm 120:1-7"
        ],
        312: [False, "Ezekiel 16, Hebrews 9:1-15, Proverbs 27:5-14"],
        313: [False, "Ezekiel 17, Ezekiel 18, Hebrews 9:16-28, Psalm 121:1-8"],
        314: [
            False,
            "Ezekiel 19, Ezekiel 20:1-44, Hebrews 10:1-18, Psalm 122:1-9"
        ],
        315: [
            False,
            "Ezekiel 20:45-49, Ezekiel 21, Ezekiel 22:1-22, Hebrews 10:19-39, Psalm 123:1-4"
        ],
        316: [
            False,
            "Ezekiel 22:23-31, Ezekiel 23, Hebrews 11:1-16, Proverbs 27:15-22"
        ],
        317: [
            False, "Ezekiel 24, Ezekiel 25, Hebrews 11:17-40, Psalm 124:1-8"
        ],
        318: [False, "Ezekiel 26, Ezekiel 27, Hebrews 12:1-13, Psalm 125:1-5"],
        319: [
            False, "Ezekiel 28, Ezekiel 29, Hebrews 12:14-29, Psalm 126:1-6"
        ],
        320: [
            False,
            "Ezekiel 30, Ezekiel 31, Hebrews 13, Proverbs 27:23-27, Proverbs 28:1-6"
        ],
        321: [False, "Ezekiel 32, Ezekiel 33:1-32, James 1, Psalm 127:1-5"],
        322: [
            False,
            "Ezekiel 33:21-33, Ezekiel 34, Ezekiel 35, James 2, Psalm 128:1-6"
        ],
        323: [False, "Ezekiel 36, Ezekiel 37, James 3, Psalm 129:1-8"],
        324: [False, "Ezekiel 38, Ezekiel 39, James 4, Proverbs 28:7-17"],
        325: [False, "Ezekiel 40, James 5, Psalm 130:1-8"],
        326: [
            False,
            "Ezekiel 41, Ezekiel 42, 1 Peter 1, 1 Peter 2:1-3, Psalm 131:1-3"
        ],
        327: [False, "Ezekiel 43, Ezekiel 44, 1 Peter 2:4-25, Psalm 132:1-18"],
        328: [False, "Ezekiel 45, Ezekiel 46, 1 Peter 3, Proverbs 28:18-28"],
        329: [False, "Ezekiel 47, Ezekiel 48, 1 Peter 4, Psalm 133:1-3"],
        330: [False, "Daniel 1, Daniel 2:1-23, 1 Peter 5, Psalm 134:1-3"],
        331: [
            False, "Daniel 2:24-49, Daniel 3:1-12, 2 Peter 1, Psalm 135:1-12"
        ],
        332: [
            False, "Daniel 3:13-30, Daniel 4:1-18, 2 Peter 2, Proverbs 29:1-9"
        ],
        333: [
            False, "Daniel 4:19-37, Daniel 5:1-16, 2 Peter 3, Psalm 135:13-21"
        ],
        334: [
            False,
            "Daniel 5:17-31, Daniel 6:1-28, 1 John 1, 1 John 2, Psalm 136:1-12"
        ],
        335: [
            False, "Daniel 7, Daniel 8:1-14, 1 John 2:12-27, Psalm 136:13-26"
        ],
        336: [
            False,
            "Daniel 8:15-27, Daniel 9:1-19, 1 John 2:28-29, 1 John 3:1-10, Proverbs 29:10-18"
        ],
        337: [
            False,
            "Daniel 9:20-27, Daniel 10, Daniel 11:1, 1 John 3:11-24, 1 John 4:1-6, Psalm 137:1-9"
        ],
        338: [False, "Daniel 11:2-35, 1 John 4:7-21, Psalm 138:1-8"],
        339: [
            False, "Daniel 11:36-45, Daniel 12, 1 John 5:1-21, Psalm 139:1-10"
        ],
        340: [
            False, "Haggai 1, Haggai 2:1-23, 2 John 1:1-13, Proverbs 29:19-27"
        ],
        341: [
            False,
            "Zechariah 1, Zechariah 2, Zechariah 3, Zechariah 4, 3 John 1:1-14, Psalm 139:11-16"
        ],
        342: [
            False,
            "Zechariah 5, Zechariah 6, Zechariah 7, Zechariah 8, Jude 1:1-25, Psalm 139:17-24"
        ],
        343: [
            False,
            "Zechariah 9, Zechariah 10, Zechariah 11, Revelation 1, Psalm 140:1-5"
        ],
        344: [
            False,
            "Zechariah 12, Zechariah 13, Zechariah 14, Revelation 2:1-17, Proverbs 30:1-10"
        ],
        345: [
            False,
            "Esther 1, Esther 2:1-18, Revelation 2:18-29, Revelation 3:1-6, Psalm 140:6-13"
        ],
        346: [
            False,
            "Esther 2:19-23, Esther 3, Esther 4, Esther 5, Revelation 3:7-22, Psalm 141:1-10"
        ],
        347: [
            False, "Esther 6, Esther 7, Esther 8, Revelation 4, Psalm 142:1-11"
        ],
        348: [False, "Esther 9, Esther 10, Revelation 5, Proverbs 30:11-23"],
        349: [
            False, "Malachi 1, Malachi 2:1-16, Revelation 6, Psalm 143:1-12"
        ],
        350: [
            False,
            "Malachi 2:17, Malachi 3, Malachi 4, Revelation 7, Psalm 144:1-8"
        ],
        351: [
            False,
            "Ezra 1, Ezra 2:1-67, Revelation 8, Revelation 9:1-12, Psalm 144:9-15"
        ],
        352: [
            False,
            "Ezra 2:68-70, Ezra 3, Ezra 4:1-5, Revelation 9:13-21, Revelation 10, Proverbs 30:24-33"
        ],
        353: [False, "Ezra 4:6-24, Ezra 5, Revelation 11, Psalm 145:1-7"],
        354: [
            False,
            "Ezra 6, Ezra 7:1-10, Revelation 12, Revelation 13:1, Psalm 145:8-13"
        ],
        355: [
            False,
            "Ezra 7:11-28, Ezra 8:1-14, Revelation 13:1-18, Psalm 145:13-21"
        ],
        356: [
            False,
            "Ezra 8:15-36, Ezra 9:1-15, Revelation 14:1-13, Proverbs 31:1-9"
        ],
        357: [
            False,
            "Ezra 10, Revelation 14:14-20, Revelation 15, Psalm 146:1-10"
        ],
        358: [False, "Nehemiah 1, Nehemiah 2, Revelation 16, Psalm 147:1-11"],
        359: [False, "Nehemiah 3, Nehemiah 4, Revelation 17, Psalm 147:12-20"],
        360: [
            False,
            "Nehemiah 5, Nehemiah 6, Nehemiah 7:1-3, Revelation 18:1-17, Proverbs 31:10-20"
        ],
        361: [
            False,
            "Nehemiah 7:4-73, Nehemiah 8, Revelation 18:17-24, Revelation 19:1-10, Psalm 148:1-6"
        ],
        362: [False, "Nehemiah 9:1-37, Revelation 19:11-21, Psalm 148:7-14"],
        363: [
            False,
            "Nehemiah 9:38, Nehemiah 10, Nehemiah 11:1-21, Revelation 20, Psalm 149:1-9"
        ],
        364: [
            False,
            "Nehemiah 11:22-36, Nehemiah 12:1-47, Revelation 21, Proverbs 31:21-31"
        ],
        365: [False, "Nehemiah 13, Revelation 22, Psalm 150:1-6"],
    }

    translation = (
        "Bible Reading Plan",
        "Today is ",
        "Search: ",
        "Open in Tabs",
        "Hide Checked Items",
        "Show Checked Items",
        "Reset All Items",
        "Save Reading Progress",
        "Day ",
        "",
        "Your reading progress is saved in the following location:",
        "Failed to save your progress locally.  You may need to grant write permission to UBA.",
    )

    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        # set title
        self.setWindowTitle(self.translation[0])
        self.setMinimumSize(830, 500)
        # set variables
        self.setupVariables()
        # setup interface
        self.setupUI()

    def setupVariables(self):
        import copy, os
        from datetime import date
        self.today = date.today()
        self.todayNo = int(format(self.today, '%j'))
        if self.todayNo > 365:
            self.todayNo = 365
        self.progressFile = os.path.join(os.getcwd(), "plugins", "menu",
                                         "{0}.txt".format(self.translation[0]))
        if os.path.isfile(self.progressFile):
            from ast import literal_eval
            with open(self.progressFile, "r") as fileObj:
                self.plan = literal_eval(fileObj.read())
        else:
            self.plan = copy.deepcopy(self.template)
        self.hideCheckedItems = False

    def setupUI(self):
        from qtpy.QtGui import QStandardItemModel
        from qtpy.QtWidgets import (QPushButton, QLabel, QListView,
                                    QAbstractItemView, QHBoxLayout,
                                    QVBoxLayout, QLineEdit)

        mainLayout = QVBoxLayout()

        readingListLayout = QVBoxLayout()

        readingListLayout.addWidget(QLabel(self.translation[0]))
        readingListLayout.addWidget(
            QLabel("{0}{1}".format(self.translation[1], self.today)))

        filterLayout = QHBoxLayout()
        filterLayout.addWidget(QLabel(self.translation[2]))
        self.filterEntry = QLineEdit()
        self.filterEntry.textChanged.connect(self.resetItems)
        filterLayout.addWidget(self.filterEntry)
        readingListLayout.addLayout(filterLayout)

        self.readingList = QListView()
        self.readingList.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.readingListModel = QStandardItemModel(self.readingList)
        self.readingList.setModel(self.readingListModel)
        self.resetItems()
        self.readingListModel.itemChanged.connect(self.itemChanged)
        #print(self.readingList.currentIndex().row())
        #self.readingList.selectionModel().selectionChanged.connect(self.function)
        readingListLayout.addWidget(self.readingList)

        buttonsLayout = QHBoxLayout()

        button = QPushButton(self.translation[3])
        button.clicked.connect(self.openInTabs)
        buttonsLayout.addWidget(button)

        self.hideShowButton = QPushButton(self.translation[4])
        self.hideShowButton.clicked.connect(self.hideShowCheckedItems)
        buttonsLayout.addWidget(self.hideShowButton)

        button = QPushButton(self.translation[6])
        button.clicked.connect(self.resetAllItems)
        buttonsLayout.addWidget(button)

        button = QPushButton(self.translation[7])
        button.clicked.connect(self.saveProgress)
        buttonsLayout.addWidget(button)

        mainLayout.addLayout(readingListLayout)
        mainLayout.addLayout(buttonsLayout)

        self.setLayout(mainLayout)

    def itemChanged(self, standardItem):
        from qtpy.QtCore import Qt
        key = int(standardItem.text().split(".")[0])
        if standardItem.checkState() is Qt.CheckState.Checked:
            self.plan[key][0] = True
        elif standardItem.checkState() is Qt.CheckState.Unchecked:
            self.plan[key][0] = False
        if self.hideCheckedItems:
            self.resetItems()

    def resetItems(self):
        from qtpy.QtGui import QStandardItem
        from qtpy.QtCore import Qt
        # Empty the model before reset
        self.readingListModel.clear()
        # Reset
        index = 0
        todayIndex = None
        filterEntry = self.filterEntry.text()
        for key, value in self.plan.items():
            checked, passages = value
            if not (self.hideCheckedItems and checked) and (
                    filterEntry == "" or
                (filterEntry != ""
                 and filterEntry.lower() in passages.lower())):
                item = QStandardItem("{0}. {1}".format(key, passages))
                item.setToolTip("{0}{1}{2}".format(self.translation[8], key,
                                                   self.translation[9]))
                if key == self.todayNo:
                    todayIndex = index
                item.setCheckable(True)
                item.setCheckState(Qt.CheckState.Checked if checked else Qt.
                                   CheckState.Unchecked)
                self.readingListModel.appendRow(item)
                index += 1
        if todayIndex is not None:
            self.readingList.setCurrentIndex(
                self.readingListModel.index(todayIndex, 0))

    def hideShowCheckedItems(self):
        self.hideCheckedItems = not self.hideCheckedItems
        self.resetItems()
        self.hideShowButton.setText(self.translation[5] if self.
                                    hideCheckedItems else self.translation[4])

    def resetAllItems(self):
        import copy
        self.plan = copy.deepcopy(self.template)
        self.resetItems()

    def translateIntoChinese(self):
        import copy, pprint
        from BibleBooks import BibleBooks
        plan = copy.deepcopy(self.template)
        filePath = "{0}_zh".format(self.progressFile)
        with open(filePath, "w", encoding="utf-8") as fileObj:
            fileObj.write(pprint.pformat(plan))
        with open(filePath, "r") as fileObj:
            text = fileObj.read()
        translateDict = {}
        bookNames = []
        for key, value in BibleBooks.eng.items():
            bookName = value[-1]
            bookNames.append(bookName)
            translateDict[bookName] = BibleBooks.sc[key][-1]
        bookNames = sorted(bookNames, key=len, reverse=True)
        #print(bookNames)
        for name in bookNames:
            text = text.replace(name, translateDict[name])
        text = text.replace("Psalm", "诗篇")
        with open(filePath, "w", encoding="utf-8") as fileObj:
            fileObj.write(text)

    def saveProgress(self):
        import pprint
        from qtpy.QtWidgets import QMessageBox
        try:
            with open(self.progressFile, "w", encoding="utf-8") as fileObj:
                fileObj.write(pprint.pformat(self.plan))
            message = "{0}\n'{1}'".format(self.translation[10],
                                          self.progressFile)
        except:
            message = self.translation[11]
        QMessageBox.information(self, self.translation[0], message)

    def openInTabs(self):
        dayNo = self.readingList.currentIndex().row() + 1
        todayReading = self.plan[dayNo][-1].split(", ")
        openBibleWindowContentOnNextTab = config.openBibleWindowContentOnNextTab
        config.openBibleWindowContentOnNextTab = True
        for reading in todayReading:
            command = "MAIN:::{0}".format(reading)
            self.parent.runTextCommand(command)
        config.openBibleWindowContentOnNextTab = openBibleWindowContentOnNextTab
        self.close()
コード例 #11
0
class NotificationWidget(QWidget, VCPWidget):
    def __init__(self, parent=None):
        super(NotificationWidget, self).__init__(parent)
        self.notification_channel = getPlugin("notifications")

        self.main_layout = QVBoxLayout()
        self.button_layout = QHBoxLayout()

        self.all_button = QPushButton()
        self.info_button = QPushButton()
        self.warn_button = QPushButton()
        self.error_button = QPushButton()
        self.debug_button = QPushButton()
        self.clear_button = QPushButton()

        self.all_button.setText("all")
        self.info_button.setText("info")
        self.warn_button.setText("warn")
        self.error_button.setText("error")
        self.debug_button.setText("debug")
        self.clear_button.setText("clear")

        self.all_button.setCheckable(True)
        self.info_button.setCheckable(True)
        self.warn_button.setCheckable(True)
        self.error_button.setCheckable(True)
        self.debug_button.setCheckable(True)

        self.all_button.setChecked(True)
        self.info_button.setChecked(False)
        self.warn_button.setChecked(False)
        self.error_button.setChecked(False)
        self.debug_button.setChecked(False)

        self.clear_button.clicked.connect(self.clear_all_notifications)

        self.button_layout.addWidget(self.all_button)
        self.button_layout.addWidget(self.info_button)
        self.button_layout.addWidget(self.warn_button)
        self.button_layout.addWidget(self.error_button)
        self.button_layout.addWidget(self.debug_button)
        self.button_layout.addWidget(self.clear_button)

        self.notification_name = QLabel()
        self.notification_name.setAlignment(Qt.AlignCenter)
        self.notification_name.setText("All Notifications")

        self.all_notification_view = QListView()

        self.all_notification_model = QStandardItemModel(
            self.all_notification_view)
        self.all_notification_model_proxy = QSortFilterProxyModel(
            self.all_notification_view)

        self.all_notification_model_proxy.setSourceModel(
            self.all_notification_model)

        # self.all_notification_view.setModel(self.all_notification_model)
        self.all_notification_view.setModel(self.all_notification_model_proxy)

        self.all_notifications = list()

        self.main_layout.addWidget(self.notification_name)
        self.main_layout.addWidget(self.all_notification_view)
        self.main_layout.addLayout(self.button_layout)

        self.setLayout(self.main_layout)

        self.notification_channel.info_message.notify(self.on_info_message)
        self.notification_channel.warn_message.notify(self.on_warn_message)
        self.notification_channel.error_message.notify(self.on_error_message)
        self.notification_channel.debug_message.notify(self.on_debug_message)

        self.all_button.clicked.connect(self.show_all_notifications)
        self.info_button.clicked.connect(self.show_info_notifications)
        self.warn_button.clicked.connect(self.show_warn_notifications)
        self.error_button.clicked.connect(self.show_error_notifications)
        self.debug_button.clicked.connect(self.show_debug_notifications)

    def on_info_message(self, message):
        timestamp = time()
        dt_object = datetime.fromtimestamp(timestamp)

        current_time = str(dt_object)

        msg = 'INFO:\nTIME {}\n  {}'.format(current_time, message)
        notification_item = QStandardItem()
        notification_item.setText(msg)
        notification_item.setIcon(QIcon.fromTheme('dialog-information'))
        notification_item.setEditable(False)
        self.all_notification_model.appendRow(notification_item)

    def on_warn_message(self, message):
        timestamp = time()
        dt_object = datetime.fromtimestamp(timestamp)

        current_time = str(dt_object)

        msg = 'WARNING:\nTIME {}\n  {}'.format(current_time, message)
        notification_item = QStandardItem()
        notification_item.setText(msg)
        notification_item.setIcon(QIcon.fromTheme('dialog-warning'))
        notification_item.setEditable(False)
        self.all_notification_model.appendRow(notification_item)

    def on_error_message(self, message):
        timestamp = time()
        dt_object = datetime.fromtimestamp(timestamp)

        current_time = str(dt_object)

        msg = 'ERROR:\nTIME {}\n  {}'.format(current_time, message)
        notification_item = QStandardItem()
        notification_item.setText(msg)
        notification_item.setIcon(QIcon.fromTheme('dialog-error'))
        notification_item.setEditable(False)
        self.all_notification_model.appendRow(notification_item)

    def on_debug_message(self, message):
        timestamp = time()
        dt_object = datetime.fromtimestamp(timestamp)

        current_time = str(dt_object)

        msg = 'DEBUG\nTIME {}\n  {}'.format(current_time, message)
        notification_item = QStandardItem()
        notification_item.setText(msg)
        notification_item.setIcon(QIcon.fromTheme('dialog-question'))
        notification_item.setEditable(False)
        self.all_notification_model.appendRow(notification_item)

    def show_all_notifications(self):
        self.all_button.setChecked(True)
        self.info_button.setChecked(False)
        self.warn_button.setChecked(False)
        self.error_button.setChecked(False)
        self.debug_button.setChecked(False)

        self.notification_name.setText("All Notifications")
        self.all_notification_model_proxy.setFilterRegExp(None)

    def show_info_notifications(self):
        self.all_button.setChecked(False)
        self.info_button.setChecked(True)
        self.warn_button.setChecked(False)
        self.error_button.setChecked(False)
        self.debug_button.setChecked(False)

        self.notification_name.setText("Information Notifications")
        self.all_notification_model_proxy.setFilterRegExp(
            QRegExp("INFO", Qt.CaseSensitive, QRegExp.FixedString))

    def show_warn_notifications(self):
        self.all_button.setChecked(False)
        self.info_button.setChecked(False)
        self.warn_button.setChecked(True)
        self.error_button.setChecked(False)
        self.debug_button.setChecked(False)

        self.notification_name.setText("Warning Notifications")
        self.all_notification_model_proxy.setFilterRegExp(
            QRegExp("WANRNING", Qt.CaseSensitive, QRegExp.FixedString))

    def show_error_notifications(self):
        self.all_button.setChecked(False)
        self.info_button.setChecked(False)
        self.warn_button.setChecked(False)
        self.error_button.setChecked(True)
        self.debug_button.setChecked(False)

        self.notification_name.setText("Error Notifications")
        self.all_notification_model_proxy.setFilterRegExp(
            QRegExp("ERROR", Qt.CaseInsensitive, QRegExp.FixedString))

    def show_debug_notifications(self):
        self.all_button.setChecked(False)
        self.info_button.setChecked(False)
        self.warn_button.setChecked(False)
        self.error_button.setChecked(False)
        self.debug_button.setChecked(True)

        self.notification_name.setText("Debug Notifications")
        self.all_notification_model_proxy.setFilterRegExp(
            QRegExp("DEBUG", Qt.CaseSensitive, QRegExp.FixedString))

    def clear_all_notifications(self):
        self.all_notification_model.clear()
コード例 #12
0
class QJackCaptureMainWindow(QDialog):
    sample_formats = {
        "32-bit float": "FLOAT",
        "8-bit integer": "8",
        "16-bit integer": "16",
        "24-bit integer": "24",
        "32-bit integer": "32",
    }

    def __init__(self, parent, jack_client, jack_name=PROGRAM):
        QDialog.__init__(self, parent)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        self.fFreewheel = False
        self.fLastTime = -1
        self.fMaxTime = 180

        self.fTimer = QTimer(self)
        self.fProcess = QProcess(self)
        self.fJackClient = jack_client
        self.fJackName = jack_name

        self.fBufferSize = self.fJackClient.get_buffer_size()
        self.fSampleRate = self.fJackClient.get_sample_rate()

        # Selected ports used as recording sources
        self.rec_sources = set()

        self.createUi()
        self.loadSettings()
        self.populatePortLists(init=True)

        # listen to changes to JACK ports
        self._refresh_timer = None
        self.fJackClient.ports_changed.connect(self.slot_refreshPortsLists)

    @Slot()
    def slot_refreshPortsLists(self, delay=200):
        if not self._refresh_timer or not self._refresh_timer.isActive():
            log.debug("Scheduling port lists refresh in %i ms...", delay)
            self._refresh_timer = QTimer()
            self._refresh_timer.setSingleShot(True)
            self._refresh_timer.timeout.connect(self.populatePortLists)
            self._refresh_timer.start(delay)

    def populateFileFormats(self):
        # Get list of supported file formats
        self.fProcess.start(gJackCapturePath, ["-pf"])
        self.fProcess.waitForFinished()

        formats = []

        for fmt in str(self.fProcess.readAllStandardOutput(),
                       encoding="utf-8").split():
            fmt = fmt.strip()
            if fmt:
                formats.append(fmt)

        # Put all file formats in combo-box, select 'wav' option
        self.ui.cb_format.clear()
        for i, fmt in enumerate(sorted(formats)):
            self.ui.cb_format.addItem(fmt)

            if fmt == "wav":
                self.ui.cb_format.setCurrentIndex(i)

    def populateSampleFormats(self):
        # Put all sample formats in combo-box, select 'FLOAT' option
        self.ui.cb_depth.clear()
        for i, (label, fmt) in enumerate(self.sample_formats.items()):
            self.ui.cb_depth.addItem(label, fmt)

            if fmt == "FLOAT":
                self.ui.cb_depth.setCurrentIndex(i)

    def populatePortLists(self, init=False):
        log.debug("Populating port lists (init=%s)...", init)
        if init:
            self.outputs_model = QStandardItemModel(0, 1, self)
            self.inputs_model = QStandardItemModel(0, 1, self)
        else:
            self.outputs_model.clear()
            self.inputs_model.clear()

        output_ports = list(self.fJackClient.get_output_ports())
        self.populatePortList(self.outputs_model, self.ui.tree_outputs,
                              output_ports)
        input_ports = list(self.fJackClient.get_input_ports())
        self.populatePortList(self.inputs_model, self.ui.tree_inputs,
                              input_ports)

        # Remove ports, which are no longer present, from recording sources
        all_ports = set((p.client, p.name) for p in output_ports)
        all_ports |= set((p.client, p.name) for p in input_ports)
        self.rec_sources.intersection_update(all_ports)
        self.slot_toggleRecordingSource()

    def makePortTooltip(self, port):
        s = []
        if port.pretty_name:
            s.append(f"<b>Pretty name:</b> <em>{port.pretty_name}</em><br>")
        s.append(f"<b>Port:</b> <tt>{port.client}:{port.name}</tt><br>")
        for i, alias in enumerate(port.aliases, 1):
            s.append(f"<b>Alias {i}:</b> <tt>{alias}</tt><br>")
        s.append(f"<b>UUID:</b> <tt>{port.uuid}</tt>")
        return "<small>{}</small>".format("\n".join(s))

    def populatePortList(self, model, tv, ports):
        tv.setModel(model)
        root = model.invisibleRootItem()

        portsdict = {}
        for port in ports:
            if port.client not in portsdict:
                portsdict[port.client] = []
            portsdict[port.client].append(port)

        for client in humansorted(portsdict):
            clientitem = QStandardItem(client)

            for port in humansorted(portsdict[client],
                                    key=attrgetter("group", "order", "name")):
                portspec = (port.client, port.name)
                if port.pretty_name:
                    label = "%s (%s)" % (port.pretty_name, port.name)
                else:
                    label = port.name

                portitem = QStandardItem(label)
                portitem.setData(portspec)
                portitem.setCheckable(True)
                portitem.setUserTristate(False)
                # Check box toggling is done in the treeview clicked handler "on_port_clicked"
                portitem.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)

                portitem.setToolTip(self.makePortTooltip(port))

                if portspec in self.rec_sources:
                    portitem.setCheckState(2)

                clientitem.appendRow(portitem)

            root.appendRow(clientitem)

        tv.expandAll()

    def createUi(self):
        # -------------------------------------------------------------
        # Set-up GUI stuff

        for i in range(self.ui.cb_buffer_size.count()):
            if int(self.ui.cb_buffer_size.itemText(i)) == self.fBufferSize:
                self.ui.cb_buffer_size.setCurrentIndex(i)
                break
        else:
            self.ui.cb_buffer_size.addItem(str(self.fBufferSize))
            self.ui.cb_buffer_size.setCurrentIndex(
                self.ui.cb_buffer_size.count() - 1)

        self.populateFileFormats()
        self.populateSampleFormats()

        self.ui.rb_stereo.setChecked(True)
        self.ui.te_end.setTime(QTime(0, 3, 0))
        self.ui.progressBar.setFormat("")
        self.ui.progressBar.setMinimum(0)
        self.ui.progressBar.setMaximum(1)
        self.ui.progressBar.setValue(0)

        self.ui.b_render.setIcon(get_icon("media-record"))
        self.ui.b_stop.setIcon(get_icon("media-playback-stop"))
        self.ui.b_close.setIcon(get_icon("window-close"))
        self.ui.b_open.setIcon(get_icon("document-open"))
        self.ui.b_stop.setVisible(False)
        self.ui.le_folder.setText(expanduser("~"))

        # -------------------------------------------------------------
        # Set-up connections

        self.ui.b_render.clicked.connect(self.slot_renderStart)
        self.ui.b_stop.clicked.connect(self.slot_renderStop)
        self.ui.b_open.clicked.connect(self.slot_getAndSetPath)
        self.ui.b_now_start.clicked.connect(self.slot_setStartNow)
        self.ui.b_now_end.clicked.connect(self.slot_setEndNow)
        self.ui.te_start.timeChanged.connect(self.slot_updateStartTime)
        self.ui.te_end.timeChanged.connect(self.slot_updateEndTime)
        self.ui.group_time.clicked.connect(self.slot_transportChecked)
        self.ui.rb_source_default.toggled.connect(
            self.slot_toggleRecordingSource)
        self.ui.rb_source_manual.toggled.connect(
            self.slot_toggleRecordingSource)
        self.ui.rb_source_selected.toggled.connect(
            self.slot_toggleRecordingSource)
        self.fTimer.timeout.connect(self.slot_updateProgressbar)

        for tv in (self.ui.tree_outputs, self.ui.tree_inputs):
            menu = QMenu()
            menu.addAction(get_icon("expand-all"), self.tr("E&xpand all"),
                           tv.expandAll)
            menu.addAction(get_icon("collapse-all"), self.tr("&Collapse all"),
                           tv.collapseAll)
            menu.addSeparator()
            menu.addAction(
                get_icon("list-select-all"),
                self.tr("&Select all in group"),
                partial(self.on_select_port_group, tv),
            )
            menu.addAction(
                get_icon("list-select-none"),
                self.tr("&Unselect all in group"),
                partial(self.on_select_port_group, tv, enable=False),
            )
            menu.addSeparator()
            if tv is self.ui.tree_outputs:
                menu.addAction(
                    get_icon("select-none"),
                    self.tr("Unselect all &outputs"),
                    partial(self.on_clear_all_ports, tv),
                )
            else:
                menu.addAction(
                    get_icon("select-none"),
                    self.tr("Unselect all &inputs"),
                    partial(self.on_clear_all_ports, tv),
                )

            tv.setContextMenuPolicy(Qt.CustomContextMenu)
            tv.customContextMenuRequested.connect(
                partial(self.on_port_menu, treeview=tv, menu=menu))
            tv.clicked.connect(self.on_port_clicked)

    def enable_port(self, item, enable=True):
        item.setCheckState(2 if enable else 0)
        port = item.data()
        if enable:
            self.rec_sources.add(port)
        else:
            self.rec_sources.discard(port)

    def on_port_menu(self, pos, treeview=None, menu=None):
        if treeview and menu:
            menu.popup(treeview.viewport().mapToGlobal(pos))

    def foreach_item(self, model, parent, func, leaves_only=True):
        for row in range(model.rowCount(parent)):
            index = model.index(row, 0, parent)
            is_leaf = not model.hasChildren(index)

            if is_leaf or not leaves_only:
                func(model.itemFromIndex(index))

            if not is_leaf:
                self.foreach_item(model, index, func)

    def on_clear_all_ports(self, treeview):
        self.foreach_item(treeview.model(), QModelIndex(),
                          partial(self.enable_port, enable=False))
        self.checkRecordEnable()

    def on_port_clicked(self, index):
        model = index.model()
        item = model.itemFromIndex(index)
        if not model.hasChildren(index):
            self.enable_port(item, not item.checkState())
            self.checkRecordEnable()

    def on_select_port_group(self, treeview, enable=True):
        index = treeview.currentIndex()
        model = index.model()

        if not model.hasChildren(index):
            index = index.parent()

        self.foreach_item(model, index, partial(self.enable_port,
                                                enable=enable))
        self.checkRecordEnable()

    @Slot()
    def slot_renderStart(self):
        if not exists(self.ui.le_folder.text()):
            QMessageBox.warning(
                self,
                self.tr("Warning"),
                self.
                tr("The selected directory does not exist. Please choose a valid one."
                   ),
            )
            return

        timeStart = self.ui.te_start.time()
        timeEnd = self.ui.te_end.time()
        minTime = (timeStart.hour() * 3600) + (timeStart.minute() *
                                               60) + (timeStart.second())
        maxTime = (timeEnd.hour() * 3600) + (timeEnd.minute() *
                                             60) + (timeEnd.second())

        newBufferSize = int(self.ui.cb_buffer_size.currentText())
        useTransport = self.ui.group_time.isChecked()

        self.fFreewheel = self.ui.rb_freewheel.isChecked()
        self.fLastTime = -1
        self.fMaxTime = maxTime

        if self.fFreewheel:
            self.fTimer.setInterval(100)
        else:
            self.fTimer.setInterval(500)

        self.ui.group_render.setEnabled(False)
        self.ui.group_time.setEnabled(False)
        self.ui.group_encoding.setEnabled(False)
        self.ui.b_render.setVisible(False)
        self.ui.b_stop.setVisible(True)
        self.ui.b_close.setEnabled(False)

        if useTransport:
            self.ui.progressBar.setFormat("%p%")
            self.ui.progressBar.setMinimum(minTime)
            self.ui.progressBar.setMaximum(maxTime)
            self.ui.progressBar.setValue(minTime)
        else:
            self.ui.progressBar.setFormat("")
            self.ui.progressBar.setMinimum(0)
            self.ui.progressBar.setMaximum(0)
            self.ui.progressBar.setValue(0)

        self.ui.progressBar.update()

        arguments = []

        # JACK client name
        arguments.append("-jn")
        arguments.append(self.fJackName)

        # Filename prefix
        arguments.append("-fp")
        arguments.append(self.ui.le_prefix.text())

        # File format
        arguments.append("-f")
        arguments.append(self.ui.cb_format.currentText())

        # Sanple format (bit depth, int/float)
        arguments.append("-b")
        arguments.append(self.ui.cb_depth.currentData())

        # Channels
        arguments.append("-c")
        if self.ui.rb_mono.isChecked():
            arguments.append("1")
        elif self.ui.rb_stereo.isChecked():
            arguments.append("2")
        else:
            arguments.append(str(self.ui.sb_channels.value()))

        # Recording sources
        if self.ui.rb_source_manual.isChecked():
            arguments.append("-mc")
        elif self.ui.rb_source_selected.isChecked():
            for client, port in self.rec_sources:
                arguments.append("-p")
                arguments.append("{}:{}".format(client, port))

        # Controlled only by freewheel
        if self.fFreewheel:
            arguments.append("-jf")

        # Controlled by transport
        elif useTransport:
            arguments.append("-jt")

        # Silent mode
        arguments.append("--daemon")

        # Extra arguments
        extra_args = self.ui.le_extra_args.text().strip()

        if extra_args:
            arg_list = shlex.split(extra_args)
            arguments.extend(arg_list)

        # Change current directory
        os.chdir(self.ui.le_folder.text())

        if newBufferSize != self.fJackClient.get_buffer_size():
            log.info("Buffer size changed before render.")
            self.fJackClient.set_buffer_size(newBufferSize)

        if useTransport:
            if self.fJackClient.transport_running():
                # rolling or starting
                self.fJackClient.transport_stop()

            self.fJackClient.transport_locate(minTime * self.fSampleRate)

        log.debug("jack_capture command line args: %r", arguments)
        self.fProcess.start(gJackCapturePath, arguments)
        status = self.fProcess.waitForStarted()

        if not status:
            self.fProcess.close()
            log.error("Could not start jack_capture.")
            return

        if self.fFreewheel:
            log.info("Rendering in freewheel mode.")
            sleep(1)
            self.fJackClient.set_freewheel(True)

        if useTransport:
            log.info("Rendering using JACK transport.")
            self.fTimer.start()
            self.fJackClient.transport_start()

    @Slot()
    def slot_renderStop(self):
        useTransport = self.ui.group_time.isChecked()

        if useTransport:
            self.fJackClient.transport_stop()

        if self.fFreewheel:
            self.fJackClient.set_freewheel(False)
            sleep(1)

        self.fProcess.terminate()
        # self.fProcess.waitForFinished(5000)

        if useTransport:
            self.fTimer.stop()

        self.ui.group_render.setEnabled(True)
        self.ui.group_time.setEnabled(True)
        self.ui.group_encoding.setEnabled(True)
        self.ui.b_render.setVisible(True)
        self.ui.b_stop.setVisible(False)
        self.ui.b_close.setEnabled(True)

        self.ui.progressBar.setFormat("")
        self.ui.progressBar.setMinimum(0)
        self.ui.progressBar.setMaximum(1)
        self.ui.progressBar.setValue(0)
        self.ui.progressBar.update()

        # Restore buffer size
        newBufferSize = self.fJackClient.get_buffer_size()

        if newBufferSize != self.fBufferSize:
            self.fJackClient.set_buffer_size(newBufferSize)

    @Slot()
    def slot_getAndSetPath(self):
        new_path = QFileDialog.getExistingDirectory(self, self.tr("Set Path"),
                                                    self.ui.le_folder.text(),
                                                    QFileDialog.ShowDirsOnly)

        if new_path:
            self.ui.le_folder.setText(new_path)

    @Slot()
    def slot_setStartNow(self):
        time = self.fJackClient.transport_frame() // self.fSampleRate
        secs = time % 60
        mins = int(time / 60) % 60
        hrs = int(time / 3600) % 60
        self.ui.te_start.setTime(QTime(hrs, mins, secs))

    @Slot()
    def slot_setEndNow(self):
        time = self.fJackClient.transport_frame() // self.fSampleRate
        secs = time % 60
        mins = int(time / 60) % 60
        hrs = int(time / 3600) % 60
        self.ui.te_end.setTime(QTime(hrs, mins, secs))

    @Slot(QTime)
    def slot_updateStartTime(self, time):
        if time >= self.ui.te_end.time():
            self.ui.te_end.setTime(time)
            renderEnabled = False
        else:
            renderEnabled = True

        if self.ui.group_time.isChecked():
            self.ui.b_render.setEnabled(renderEnabled)

    @Slot(QTime)
    def slot_updateEndTime(self, time):
        if time <= self.ui.te_start.time():
            self.ui.te_start.setTime(time)
            renderEnabled = False
        else:
            renderEnabled = True

        if self.ui.group_time.isChecked():
            self.ui.b_render.setEnabled(renderEnabled)

    @Slot(bool)
    def slot_toggleRecordingSource(self, dummy=None):
        enabled = self.ui.rb_source_selected.isChecked()
        self.ui.tree_outputs.setEnabled(enabled)
        self.ui.tree_inputs.setEnabled(enabled)
        self.checkRecordEnable()

    @Slot(bool)
    def slot_transportChecked(self, dummy=None):
        self.checkRecordEnable()

    @Slot()
    def slot_updateProgressbar(self):
        time = self.fJackClient.transport_frame() / self.fSampleRate
        self.ui.progressBar.setValue(time)

        if time > self.fMaxTime or (self.fLastTime > time
                                    and not self.fFreewheel):
            self.slot_renderStop()

        self.fLastTime = time

    def checkRecordEnable(self):
        enable = True

        if self.ui.rb_source_selected.isChecked() and not self.rec_sources:
            enable = False

        if self.ui.group_time.isChecked(
        ) and self.ui.te_end.time() <= self.ui.te_start.time():
            enable = False

        self.ui.b_render.setEnabled(enable)
        log.debug("Recording sources: %s", ", ".join(
            ("%s:%s" % (c, p) for c, p in self.rec_sources)))

    def saveSettings(self):
        settings = QSettings(ORGANIZATION, PROGRAM)

        if self.ui.rb_mono.isChecked():
            channels = 1
        elif self.ui.rb_stereo.isChecked():
            channels = 2
        else:
            channels = self.ui.sb_channels.value()

        settings.setValue("Geometry", self.saveGeometry())
        settings.setValue("OutputFolder", self.ui.le_folder.text())
        settings.setValue("FilenamePrefix", self.ui.le_prefix.text())
        settings.setValue("EncodingFormat", self.ui.cb_format.currentText())
        settings.setValue("EncodingDepth", self.ui.cb_depth.currentData())
        settings.setValue("EncodingChannels", channels)
        settings.setValue("UseTransport", self.ui.group_time.isChecked())
        settings.setValue("StartTime", self.ui.te_start.time())
        settings.setValue("EndTime", self.ui.te_end.time())
        settings.setValue("ExtraArgs", self.ui.le_extra_args.text().strip())

        if self.ui.rb_source_default.isChecked():
            settings.setValue("RecordingSource", 0)
        elif self.ui.rb_source_manual.isChecked():
            settings.setValue("RecordingSource", 1)
        elif self.ui.rb_source_selected.isChecked():
            settings.setValue("RecordingSource", 2)

        settings.beginWriteArray("Sources")
        for i, (client, port) in enumerate(self.rec_sources):
            settings.setArrayIndex(i)
            settings.setValue("Client", client)
            settings.setValue("Port", port)
        settings.endArray()

    def loadSettings(self):
        settings = QSettings(ORGANIZATION, PROGRAM)

        self.restoreGeometry(settings.value("Geometry", b""))

        outputFolder = settings.value("OutputFolder", get_user_dir("MUSIC"))

        if isdir(outputFolder):
            self.ui.le_folder.setText(outputFolder)

        self.ui.le_prefix.setText(
            settings.value("FilenamePrefix", "jack_capture_"))

        encFormat = settings.value("EncodingFormat", "wav", type=str)

        for i in range(self.ui.cb_format.count()):
            if self.ui.cb_format.itemText(i) == encFormat:
                self.ui.cb_format.setCurrentIndex(i)
                break

        encDepth = settings.value("EncodingDepth", "FLOAT", type=str)

        for i in range(self.ui.cb_depth.count()):
            if self.ui.cb_depth.itemData(i) == encDepth:
                self.ui.cb_depth.setCurrentIndex(i)
                break

        encChannels = settings.value("EncodingChannels", 2, type=int)

        if encChannels == 1:
            self.ui.rb_mono.setChecked(True)
        elif encChannels == 2:
            self.ui.rb_stereo.setChecked(True)
        else:
            self.ui.rb_outro.setChecked(True)
            self.ui.sb_channels.setValue(encChannels)

        recSource = settings.value("RecordingSource", 0, type=int)

        if recSource == 1:
            self.ui.rb_source_manual.setChecked(True)
        elif recSource == 2:
            self.ui.rb_source_selected.setChecked(True)
        else:
            self.ui.rb_source_default.setChecked(True)

        self.ui.group_time.setChecked(
            settings.value("UseTransport", False, type=bool))
        self.ui.te_start.setTime(
            settings.value("StartTime", self.ui.te_start.time(), type=QTime))
        self.ui.te_end.setTime(
            settings.value("EndTime", self.ui.te_end.time(), type=QTime))

        self.ui.le_extra_args.setText(settings.value("ExtraArgs", "",
                                                     type=str))

        size = settings.beginReadArray("Sources")
        for i in range(size):
            settings.setArrayIndex(i)
            client = settings.value("Client", type=str)
            port = settings.value("Port", type=str)
            if client and port:
                self.rec_sources.add((client, port))
        settings.endArray()

    def closeEvent(self, event):
        self.saveSettings()
        self.fJackClient.close()
        QDialog.closeEvent(self, event)

    def done(self, r):
        QDialog.done(self, r)
        self.close()
コード例 #13
0
class BibleReadingPlan(QWidget):

    template = {
        # Source / credits of the following plan: https://www.biblica.com/resources/reading-plans/
        1: [False, '創世記 1, 創世記 2:1-17, 馬太福音 1:1-25, 詩篇 1:1-6'],
        2:
        [False, '創世記 2:18-25, 創世記 3, 創世記 4:1-16, 馬太福音 2:1-18, 詩篇 '
         '2:1-12'],
        3: [
            False, '創世記 4:17-26, 創世記 5, 創世記 6, 馬太福音 2:19-23, 馬太福音 3, 詩篇 '
            '3:1-8'
        ],
        4: [False, '創世記 7, 創世記 8, 創世記 9:1-17, 馬太福音 4:1-22, 箴言 1:1-7'],
        5: [
            False, '創世記 9:18-29, 創世記 10, 創世記 11:1-9, 馬太福音 4:23-25, 馬太福音 '
            '5:1-20, 詩篇 4:1-8'
        ],
        6: [False, '創世記 11:10-32, 創世記 12, 創世記 13, 馬太福音 5:21-42, 詩篇 5:1-12'],
        7:
        [False, '創世記 14, 創世記 15, 創世記 16, 馬太福音 5:43-48, 馬太福音 6:1-24, '
         '詩篇 6'],
        8: [False, '創世記 17, 創世記 18, 馬太福音 6:25-34, 馬太福音 7:1-23, 箴言 '
            '1:8-19'],
        9:
        [False, '創世記 19, 創世記 20:1-18, 馬太福音 7:24-29, 馬太福音 8:1-22, 詩篇 '
         '7:1-9'],
        10: [
            False, '創世記 21, 創世記 22, 創世記 23, 馬太福音 8:23-34, 馬太福音 9:1-13, '
            '詩篇 7:10-17'
        ],
        11: [False, '創世記 24:1-67, 馬太福音 9:14-38, 詩篇 8:1-9'],
        12: [False, '創世記 25, 創世記 26, 馬太福音 10:1-31, 箴言 1:20-33'],
        13: [
            False, '創世記 27, 創世記 28:1-22, 馬太福音 10:32-42, 馬太福音 11:1-15, 詩篇 '
            '9:1-6'
        ],
        14: [False, '創世記 29, 創世記 30, 馬太福音 11:16-30, 詩篇 9:7-12'],
        15: [False, '創世記 31:1-55, 馬太福音 12:1-21, 詩篇 9:13-20'],
        16: [False, '創世記 32, 創世記 33, 馬太福音 12:22-45, 箴言 2:1-11'],
        17:
        [False, '創世記 34, 創世記 35, 馬太福音 12:46-50, 馬太福音 13:1-17, 詩篇 '
         '10:1-11'],
        18: [False, '創世記 36, 創世記 37, 馬太福音 13:18-35, 詩篇 10:12-18'],
        19: [False, '創世記 38, 創世記 39, 馬太福音 13:36-58, 詩篇 11:1-7'],
        20: [False, '創世記 40, 創世記 41:1-40, 馬太福音 14:1-21, 箴言 2:12-22'],
        21: [
            False, '創世記 41:41-57, 創世記 42, 馬太福音 14:22-36, 馬太福音 15:1-9, 詩篇 '
            '12:1-8'
        ],
        22: [False, '創世記 43, 創世記 44, 馬太福音 15:10-39, 詩篇 13:1-6'],
        23: [False, '創世記 45, 創世記 46, 創世記 47:1-12, 馬太福音 16:1-20, 詩篇 14:1-7'],
        24: [
            False, '創世記 47:13-31, 創世記 48, 馬太福音 16:21-28, 馬太福音 17:1-13, '
            '箴言 3:1-10'
        ],
        25: [False, '創世記 49, 創世記 50, 馬太福音 17:14-27, 馬太福音 18:1-9, 詩篇 15:1-5'],
        26: [False, '約伯記 1, 約伯記 2, 約伯記 3, 馬太福音 18:10-35, 詩篇 16:1-11'],
        27: [False, '約伯記 4, 約伯記 5, 約伯記 6, 約伯記 7, 馬太福音 19:1-15, 詩篇 17:1-5'],
        28: [False, '約伯記 8, 約伯記 9, 約伯記 10, 馬太福音 19:16-30, 箴言 3:11-20'],
        29:
        [False, '約伯記 11, 約伯記 12, 約伯記 13, 約伯記 14, 馬太福音 20:1-19, 詩篇 17:6-12'],
        30:
        [False, '約伯記 15, 約伯記 16, 約伯記 17, 約伯記 18, 馬太福音 20:20-34, 詩篇 17:13-15'],
        31: [False, '約伯記 19, 約伯記 20, 約伯記 21, 馬太福音 21:1-17, 詩篇 18:1-6'],
        32: [False, '約伯記 22, 約伯記 23, 約伯記 24, 馬太福音 21:18-32, 箴言 3:21-35'],
        33: [
            False,
            '約伯記 25, 約伯記 26, 約伯記 27, 約伯記 28, 約伯記 29, 馬太福音 21:33-46, 馬太福音 '
            '22:1-14, 詩篇 18:7-15'
        ],
        34: [False, '約伯記 30, 約伯記 31, 約伯記 32, 馬太福音 22:15-46, 詩篇 18:16-24'],
        35: [False, '約伯記 33, 約伯記 34, 馬太福音 23:1-39, 詩篇 18:25-36'],
        36: [False, '約伯記 35, 約伯記 36, 約伯記 37, 馬太福音 24:1-31, 箴言 4:1-9'],
        37: [
            False,
            '約伯記 38, 約伯記 39, 約伯記 40:1-2, 馬太福音 24:32-51, 馬太福音 25:1-13, 詩篇 '
            '18:37-42'
        ],
        38: [False, '約伯記 40:3-24, 約伯記 41, 約伯記 42, 馬太福音 25:14-46, 詩篇 18:43-50'],
        39: [False, '出埃及記 1, 出埃及記 2, 出埃及記 3, 馬太福音 26:1-30, 詩篇 19:1-6'],
        40: [False, '出埃及記 4, 出埃及記 5, 出埃及記 6:1-12, 馬太福音 26:31-46, 箴言 4:10-19'],
        41: [False, '出埃及記 6:13-30, 出埃及記 7,出埃及記 8, 馬太福音 26:47-68, 詩篇 19:7-14'],
        42: [False, '出埃及記 9, 出埃及記 10, 馬太福音 26:69-75, 馬太福音 27:1-10, 詩篇 20:1-9'],
        43: [False, '出埃及記 11, 出埃及記 12, 馬太福音 27:11-44, 詩篇 21:1-7'],
        44: [False, '出埃及記 13, 出埃及記 14, 馬太福音 27:45-66, 箴言 4:20-27'],
        45: [False, '出埃及記 15, 出埃及記 16, 馬太福音 28:1-20, 詩篇 21:8-13'],
        46: [False, '出埃及記 17, 出埃及記 18, 馬可福音 1:1-28, 詩篇 22:1-11'],
        47:
        [False, '出埃及記 19, 出埃及記 20, 馬可福音 1:29-45, 馬可福音 2:1-17, 詩篇 22:12-21'],
        48: [False, '出埃及記 21, 出埃及記 22, 馬可福音 2:18-27, 馬可福音 3:1-30, 箴言 5:1-14'],
        49:
        [False, '出埃及記 23, 出埃及記 24, 馬可福音 3:31-35, 馬可福音 4:1-29, 詩篇 22:22-31'],
        50: [False, '出埃及記 25, 出埃及記 26, 馬可福音 4:30-41, 馬可福音 5:1-20, 詩篇 23:1-6'],
        51: [False, '出埃及記 27, 出埃及記 28, 馬可福音 5:21-43, 馬可福音 6:1-6, 詩篇 24:1-10'],
        52: [False, '出埃及記 29, 出埃及記 30, 馬可福音 6:7-29, 箴言 5:15-23'],
        53: [False, '出埃及記 31, 出埃及記 32, 出埃及記 33:1-6, 馬可福音 6:30-56, 詩篇 25:1-7'],
        54: [False, '出埃及記 33:7-23, 出埃及記 34, 馬可福音 7:1-30, 詩篇 25:8-15'],
        55:
        [False, '出埃及記 35, 出埃及記 36, 馬可福音 7:31-37, 馬可福音 8:1-13, 詩篇 25:16-22'],
        56: [False, '出埃及記 37, 出埃及記 38, 馬可福音 8:14-38, 馬可福音 9:1, 箴言 6:1-11'],
        57: [False, '出埃及記 39, 出埃及記 40, 馬可福音 9:2-32, 詩篇 26:1-12'],
        58: [
            False, '利未記 1, 利未記 2, 利未記 3, 馬可福音 9:33-50, 馬可福音 10:1-12, '
            '詩篇 27:1-6'
        ],
        59: [False, '利未記 4, 利未記 5:1-13, 馬可福音 10:13-31, 詩篇 27:7-14'],
        60: [
            False, '利未記 5:14-19, 利未記 6, 利未記 7:1-10, 馬可福音 10:32-52, '
            '箴言 6:12-19'
        ],
        61: [False, '利未記 7:11-38, 利未記 8, 馬可福音 11:1-27, 詩篇 28'],
        62: [False, '利未記 9, 利未記 10, 馬可福音 11:28-33, 馬可福音 12:1-12, 詩篇 29'],
        63: [False, '利未記 11, 利未記 12, 馬可福音 12:13-27, 詩篇 30:1-7'],
        64: [False, '利未記 13:1-59, 馬可福音 12:28-44, 箴言 6:20-29'],
        65: [False, '利未記 14:1-57, 馬可福音 13:1-31, 詩篇 30:8-12'],
        66: [False, '利未記 15, 利未記 16, 馬可福音 13:32-37, 馬可福音 14:1-16, 詩篇 31:1-8'],
        67: [False, '利未記 17, 利未記 18, 馬可福音 14:17-42, 詩篇 31:9-18'],
        68: [False, '利未記 19, 利未記 20, 馬可福音 14:43-72, 箴言 6:30-35'],
        69: [False, '利未記 21, 利未記 22, 馬可福音 15:1-32, 詩篇 31:19-24'],
        70: [False, '利未記 23, 利未記 24, 馬可福音 15:33-47, 詩篇 32'],
        71: [False, '利未記 25, 利未記 26:1-13, 馬可福音 16:1-20, 詩篇 33:1-11'],
        72: [False, '利未記 26:14-46, 利未記 27, 路加福音 1:1-25, 箴言 7:1-5'],
        73: [False, '民數記 1, 民數記 2:1-9, 路加福音 1:26-38, 詩篇 33:12-22'],
        74: [False, '民數記 2:10-34, 民數記 3, 路加福音 1:39-56, 詩篇 34:1-10'],
        75: [False, '民數記 4, 民數記 5:1-10, 路加福音 1:57-80, 詩篇 34:11-22'],
        76: [False, '民數記 5:11-31, 民數記 6:1-27, 路加福音 2:1-20, 箴言 7:6-20'],
        77: [False, '民數記 7:1-65, 路加福音 2:21-40, 詩篇 35:1-10'],
        78: [
            False, '民數記 7:66-89, 民數記 8, 民數記 9:1-14, 路加福音 2:41-52, 詩篇 '
            '35:11-18'
        ],
        79: [
            False, '民數記 9:15-23, 民數記 10, 民數記 11:1-3, 路加福音 3:1-22, 詩篇 '
            '35:19-28'
        ],
        80: [
            False, '民數記 11:4-35, 民數記 12, 民數記 13:1-25, 路加福音 3:23-38, 路加福音 '
            '4:1-13, 箴言 7:21-27'
        ],
        81: [False, '民數記 13:26-33, 民數記 14, 路加福音 4:14-37, 詩篇 36:1-12'],
        82:
        [False, '民數記 15, 民數記 16:1-35, 路加福音 4:38-44, 路加福音 5:1-16, 詩篇 37:1-9'],
        83: [False, '民數記 16:36-50, 民數記 17, 民數記 18, 路加福音 5:17-32, 詩篇 37:10-20'],
        84: [
            False, '民數記 19, 民數記 20, 民數記 21:1-3, 路加福音 5:33-39, 路加福音 6:1-11, '
            '箴言 8:1-11'
        ],
        85: [False, '民數記 21:4-35, 民數記 22:1-20, 路加福音 6:12-36, 詩篇 37:21-31'],
        86: [
            False, '民數記 22:21-41, 民數記 23:1-26, 路加福音 6:37-49, 路加福音 7:1-10, 詩篇 '
            '37:32-40'
        ],
        87: [False, '民數記 23:27-30, 民數記 24, 民數記 25, 路加福音 7:11-35, 詩篇 38:1-11'],
        88: [False, '民數記 26, 民數記 27:1-11, 路加福音 7:36-50, 箴言 8:12-21'],
        89: [
            False, '民數記 27:12-23, 民數記 28, 民數記 29:1-11, 路加福音 8:1-18, 詩篇 '
            '38:12-22'
        ],
        90: [
            False, '民數記 29:12-40, 民數記 30, 民數記 31:1-24, 路加福音 8:19-39, 詩篇 '
            '39:1-13'
        ],
        91:
        [False, '民數記 31:25-54, 民數記 32, 路加福音 8:40-56, 路加福音 9:1-9, 詩篇 40:1-8'],
        92: [False, '民數記 33, 民數記 34, 路加福音 9:10-27, 箴言 8:22-31'],
        93: [False, '民數記 35, 民數記 36:1-12, 路加福音 9:28-56, 詩篇 40:9-17'],
        94:
        [False, '申命記 1, 申命記 2:1-23, 路加福音 9:57-62, 路加福音 10:1-24, 詩篇 '
         '41:1-6'],
        95: [
            False, '申命記 2:24-37, 申命記 3, 申命記 4:1-14, 路加福音 10:25-42, '
            '路加福音 11:1-4, 詩篇 41:7-13'
        ],
        96: [False, '申命記 4:15-49, 申命記 5, 路加福音 11:5-32, 箴言 8:32-36'],
        97: [False, '申命記 6, 申命記 7, 申命記 8, 路加福音 11:33-54, 詩篇 '
             '42:1-6'],
        98: [False, '申命記 9, 申命記 10, 路加福音 12:1-34, 詩篇 42:7-11'],
        99: [False, '申命記 11, 申命記 12, 路加福音 12:35-59, 詩篇 43:1-5'],
        100: [False, '申命記 13, 申命記 14, 路加福音 13:1-30, 箴言 9:1-12'],
        101: [
            False, '申命記 15, 申命記 16:1-20, 路加福音 13:31-35, 路加福音 14:1-14, '
            '詩篇 44:1-12'
        ],
        102: [
            False, '申命記 16:21-22, 申命記 17, 申命記 18, 路加福音 14:15-35, '
            '詩篇 44:13-26'
        ],
        103: [False, '申命記 19, 申命記 20, 路加福音 15:1-32, 詩篇 45:1-9'],
        104: [False, '申命記 21, 申命記 22, 路加福音 16:1-18, 箴言 9:13-18'],
        105: [
            False, '申命記 23, 申命記 24, 申命記 25:1-19, 路加福音 16:19-31, '
            '路加福音 17:1-10, 詩篇 45:10-17'
        ],
        106: [
            False, '申命記 26, 申命記 27, 申命記 28:1-14, 路加福音 17:11-37, '
            '詩篇 46:1-11'
        ],
        107: [False, '申命記 28:15-68, 路加福音 18:1-30, 詩篇 47:1-9'],
        108: [
            False, '申命記 29, 申命記 30:1-10, 路加福音 18:31-43, 路加福音 19:1-10, '
            '箴言 10:1-10'
        ],
        109: [False, '申命記 30:11-20, 申命記 31:1-29, 路加福音 19:11-44, 詩篇 '
              '48:1-8'],
        110: [
            False, '申命記 31:30, 申命記 32, 路加福音 19:45-48, 路加福音 20:1-26, 詩篇 '
            '48:9-14'
        ],
        111: [
            False, '申命記 33, 申命記 34:1-12, 路加福音 20:27-47, 路加福音 21:1-4, 詩篇 '
            '49:1-20'
        ],
        112: [False, '約書亞記 1, 約書亞記 2, 路加福音 21:5-38, 箴言 10:11-20'],
        113: [False, '約書亞記 3, 約書亞記 4, 約書亞記 5:1-12, 路加福音 22:1-38, 詩篇 50:1-15'],
        114: [
            False, '約書亞記 5:13-15, 約書亞記 6, 約書亞記 7, 路加福音 22:39-62, 詩篇 50:16-23'
        ],
        115:
        [False, '約書亞記 8, 約書亞記 9:1-15, 路加福音 22:63-71, 路加福音 23:1-25, 詩篇 51:1-9'],
        116: [False, '約書亞記 9:16-27, 約書亞記 10, 路加福音 23:26-56, 箴言 10:21-30'],
        117: [False, '約書亞記 11, 約書亞記 12, 路加福音 24:1-35, 詩篇 51:10-19'],
        118: [False, '約書亞記 13, 約書亞記 14, 路加福音 24:36-53, 詩篇 52:1-9'],
        119: [False, '約書亞記 15, 約書亞記 16, 約翰福音 1:1-28, 詩篇 53:1-6'],
        120: [
            False, '約書亞記 17, 約書亞記 18, 約翰福音 1:29-51, 箴言 10:31-32, 箴言 '
            '11:1-8'
        ],
        121: [False, '約書亞記 19, 約書亞記 20, 約書亞記 21:1-19, 約翰福音 2:1-25, 詩篇 54:1-7'],
        122: [False, '約書亞記 21:20-45, 約書亞記 22, 約翰福音 3:1-21, 詩篇 55:1-11'],
        123: [False, '約書亞記 23, 約書亞記 24, 約翰福音 3:22-36, 詩篇 55:12-23'],
        124: [False, '士師記 1, 士師記 2:1-5, 約翰福音 4:1-26, 箴言 11:9-18'],
        125: [False, '士師記 2:6-23, 士師記 3, 約翰福音 4:27-42, 詩篇 56:1-13'],
        126: [False, '士師記 4, 士師記 5, 約翰福音 4:43-54, 約翰福音 5:1-15, 詩篇 57:1-6'],
        127: [False, '士師記 6, 士師記 7:1-8, 約翰福音 5:16-30, 詩篇 57:7-11'],
        128: [False, '士師記 7:8-25, 士師記 8, 約翰福音 5:31-47, 箴言 11:19-28'],
        129: [False, '士師記 9, 約翰福音 6:1-24, 詩篇 58:1-11'],
        130: [False, '士師記 10, 士師記 11, 約翰福音 6:25-59, 詩篇 59:1-8'],
        131: [False, '士師記 12, 士師記 13, 約翰福音 6:60-71, 約翰福音 7:1-13, 詩篇 59:9-19'],
        132: [
            False, '士師記 14, 士師記 15, 約翰福音 7:14-44, 箴言 11:29-31, 箴言 '
            '12:1-7'
        ],
        133: [False, '士師記 16, 士師記 17, 約翰福音 7:45-53, 約翰福音 8:1-11, 詩篇 60:1-4'],
        134: [False, '士師記 18, 士師記 19, 約翰福音 8:12-30, 詩篇 60:5-12'],
        135: [False, '士師記 20, 士師記 21, 約翰福音 8:31-59, 詩篇 61:1-8'],
        136: [False, '路得記 1, 路得記 2, 約翰福音 9:1-34, 箴言 12:8-17'],
        137: [False, '路得記 3, 路得記 4, 約翰福音 9:35-41, 約翰福音 10:1-21, 詩篇 62:1-12'],
        138: [False, '撒母耳記上 1, 撒母耳記上 2:1-26, 約翰福音 10:22-42, 詩篇 63:1-11'],
        139: [
            False, '撒母耳記上 2:27-36, 撒母耳記上 3, 撒母耳記上 4, 約翰福音 11:1-44, 詩篇 64:1-10'
        ],
        140: [
            False, '撒母耳記上 5, 撒母耳記上 6, 撒母耳記上 7, 約翰福音 11:45-57, 約翰福音 12:1-11, '
            '箴言 12:18-27'
        ],
        141: [
            False, '撒母耳記上 8, 撒母耳記上 9, 撒母耳記上 10:1-8, 約翰福音 12:12-26, 詩篇 65:1-13'
        ],
        142: [
            False, '撒母耳記上 10:9-27, 撒母耳記上 11, 撒母耳記上 12, 約翰福音 12:37-50, 約翰福音 '
            '13:1-17, 詩篇 66:1-12'
        ],
        143: [False, '撒母耳記上 13, 撒母耳記上 14:1-23, 約翰福音 13:18-38, 詩篇 66:13-20'],
        144: [
            False, '撒母耳記上 14:24-52, 撒母耳記上 15, 約翰福音 14:1-31, 箴言 12:28, 箴言 '
            '13:1-9'
        ],
        145: [
            False, '撒母耳記上 16, 撒母耳記上 17:1-37, 約翰福音 15, 約翰福音 16:1-4, 詩篇 67:1-7'
        ],
        146: [
            False, '撒母耳記上 17:38-58, 撒母耳記上 18, 約翰福音 16:5-33, 約翰福音 17:1-5, 詩篇 '
            '68:1-6'
        ],
        147: [False, '撒母耳記上 19, 撒母耳記上 20, 約翰福音 17:6-26, 詩篇 68:7-14'],
        148: [
            False, '撒母耳記上 21, 撒母耳記上 22, 撒母耳記上 23, 約翰福音 18:1-24, 箴言 '
            '13:10-19'
        ],
        149: [False, '撒母耳記上 24, 撒母耳記上 25, 約翰福音 18:25-40, 詩篇 68:15-20'],
        150: [
            False, '撒母耳記上 26, 撒母耳記上 27, 撒母耳記上 28, 約翰福音 19:1-27, 詩篇 68:21-27'
        ],
        151: [
            False, '撒母耳記上 29, 撒母耳記上 30, 撒母耳記上 31, 約翰福音 19:28-42, 約翰福音 20:1-9, '
            '詩篇 68:28-35'
        ],
        152: [
            False, '撒母耳記下 1, 撒母耳記下 2:1-7, 約翰福音 20:10-31, 箴言 13:20-25, 箴言 '
            '14:1-4'
        ],
        153: [False, '撒母耳記下 2:8-32, 撒母耳記下 3:1-21, 約翰福音 21:1-25, 詩篇 69:1-12'],
        154: [
            False, '撒母耳記下 3:22-39, 撒母耳記下 4, 撒母耳記下 5:1-5, 使徒行傳 1:1-22, 詩篇 '
            '69:13-28'
        ],
        155: [
            False, '撒母耳記下 5:6-25, 撒母耳記下 6, 使徒行傳 1:23-26, 使徒行傳 2:1-21, 詩篇 '
            '69:29-36'
        ],
        156: [False, '撒母耳記下 7, 撒母耳記下 8, 使徒行傳 2:22-47, 箴言 14:4-14'],
        157: [False, '撒母耳記下 9, 撒母耳記下 10, 使徒行傳 3, 詩篇 70:1-5'],
        158: [False, '撒母耳記下 11, 撒母耳記下 12, 使徒行傳 4:1-22, 詩篇 71:1-8'],
        159: [False, '撒母耳記下 13, 使徒行傳 4:23-37, 使徒行傳 5:1-11, 詩篇 71:9-18'],
        160: [False, '撒母耳記下 14, 撒母耳記下 15:1-12, 使徒行傳 5:12-42, 箴言 14:15-24'],
        161: [
            False, '撒母耳記下 15:13-37, 撒母耳記下 16:1-14, 使徒行傳 6, 使徒行傳 7:1-19, 詩篇 '
            '71:19-24'
        ],
        162: [
            False, '撒母耳記下 16:15-23, 撒母耳記下 17, 撒母耳記下 18:1-18, 使徒行傳 7:20-43, 詩篇 '
            '72:1-20'
        ],
        163: [
            False, '撒母耳記下 18:19-33, 撒母耳記下 19, 使徒行傳 7:44-60, 使徒行傳 8:1-3, 詩篇 '
            '73:1-14'
        ],
        164: [False, '撒母耳記下 20, 撒母耳記下 21, 使徒行傳 8:4-40, 箴言 14:25-35'],
        165: [False, '撒母耳記下 22, 撒母耳記下 23:1-7, 使徒行傳 9:1-31, 詩篇 73:15-28'],
        166: [
            False,
            '撒母耳記下 23:8-39, 撒母耳記下 24:1-25, 使徒行傳 9:32-43, 使徒行傳 10:1-23, 詩篇 '
            '74:1-9'
        ],
        167: [
            False, '列王紀上 1, 列王紀上 2:1-12, 使徒行傳 10:23-48, 使徒行傳 11:1-18, 詩篇 '
            '74:10-17'
        ],
        168: [
            False,
            '列王紀上 2:13-46, 列王紀上 3:1-15, 使徒行傳 11:19-30, 使徒行傳 12:1-19, 箴言 '
            '15:1-10'
        ],
        169: [
            False,
            '列王紀上 3:16-28, 列王紀上 4, 列王紀上 5, 使徒行傳 12:19-25, 使徒行傳 13:1-12, '
            '詩篇 74:18-23'
        ],
        170: [False, '列王紀上 6, 列王紀上 7:1-22, 使徒行傳 13:13-41, 詩篇 75:1-10'],
        171: [
            False, '列王紀上 7:23-51, 列王紀上 8:1-21, 使徒行傳 13:42-52, 使徒行傳 14:1-7, 詩篇 '
            '76:1-12'
        ],
        172: [False, '列王紀上 8:22-66, 列王紀上 9:1-9, 使徒行傳 14:8-28, 箴言 15:11-20'],
        173: [
            False, '列王紀上 9:10-28, 列王紀上 10, 列王紀上 11:1-13, 使徒行傳 15:1-21, 詩篇 '
            '77:1-9'
        ],
        174: [
            False, '列王紀上 11:14-43, 列王紀上 12:1-24, 使徒行傳 15:22-41, 詩篇 77:10-20'
        ],
        175: [
            False, '列王紀上 12:25-33, 列王紀上 13, 列王紀上 14:1-20, 使徒行傳 16:1-15, 詩篇 '
            '78:1-8'
        ],
        176: [
            False, '列王紀上 14:21-31, 列王紀上 15, 列王紀上 16:1-7, 使徒行傳 16:16-40, 箴言 '
            '15:21-30'
        ],
        177: [
            False, '列王紀上 16:8-34, 列王紀上 17, 列王紀上 18:1-15, 使徒行傳 17:1-21, 詩篇 '
            '78:9-16'
        ],
        178: [
            False, '列王紀上 18:16-46, 列王紀上 19, 使徒行傳 17:22-34, 使徒行傳 18:1-8, 詩篇 '
            '78:17-31'
        ],
        179: [
            False, '列王紀上 20, 列王紀上 21, 使徒行傳 18:9-28, 使徒行傳 19:1-13, 詩篇 78:32-39'
        ],
        180: [False, '列王紀上 22:1-53, 使徒行傳 19:14-41, 箴言 15:31-33, 箴言 16:1-7'],
        181: [False, '列王紀下 1, 列王紀下 2:1-25, 使徒行傳 20:1-38, 詩篇 78:40-55'],
        182: [False, '列王紀下 3, 列王紀下 4:1-37, 使徒行傳 21:1-26, 詩篇 78:56-72'],
        183: [
            False, '列王紀下 4:38-44, 列王紀下 5, 列王紀下 6:1-23, 使徒行傳 21:27-40, 使徒行傳 '
            '22:1-22, 詩篇 79:1-13'
        ],
        184: [
            False, '列王紀下 6:24-33, 列王紀下 7, 列王紀下 8:1-15, 使徒行傳 22:22-30, 使徒行傳 '
            '23:1-11, 箴言 16:8-17'
        ],
        185: [False, '列王紀下 8:16-29, 列王紀下 9, 使徒行傳 23:12-35, 詩篇 80:1-7'],
        186: [False, '列王紀下 10, 列王紀下 11, 使徒行傳 24:1-27, 詩篇 80:8-19'],
        187: [
            False, '列王紀下 12, 列王紀下 13, 列王紀下 14:1-22, 使徒行傳 25:1-22, 詩篇 81:1-7'
        ],
        188: [
            False, '列王紀下 14:23-29, 列王紀下 15, 使徒行傳 25:23-27, 使徒行傳 26:1-23, 箴言 '
            '16:18-27'
        ],
        189: [
            False, '列王紀下 16, 列王紀下 17, 使徒行傳 26:24-32, 使徒行傳 27:1-12, 詩篇 81:8-16'
        ],
        190: [False, '列王紀下 18, 列王紀下 19:1-13, 使徒行傳 27:13-44, 詩篇 82:1-8'],
        191: [False, '列王紀下 19:14-37, 列王紀下 20, 使徒行傳 28:1-16, 詩篇 83:1-18'],
        192: [
            False, '列王紀下 21, 列王紀下 22, 使徒行傳 28:17-31, 箴言 16:28-33, 箴言 '
            '17:1-4'
        ],
        193: [False, '列王紀下 23, 列王紀下 24:1-7, 羅馬書 1:1-17, 詩篇 84:1-7'],
        194: [False, '列王紀下 24:8-20, 列王紀下 25, 羅馬書 1:18-32, 詩篇 84:8-12'],
        195: [False, '約拿書 1, 約拿書 2, 約拿書 3, 約拿書 4, 羅馬書 2:1-16, 詩篇 85:1-7'],
        196: [False, '阿摩司書 1, 阿摩司書 2, 羅馬書 2:17-29, 羅馬書 3:1-8, 箴言 17:5-14'],
        197: [False, '阿摩司書 3, 阿摩司書 4, 羅馬書 3:9-31, 詩篇 85:8-13'],
        198: [False, '阿摩司書 5, 羅馬書 4:1-15, 詩篇 86:1-10'],
        199: [False, '阿摩司書 6, 阿摩司書 7, 羅馬書 4:16-25, 羅馬書 5:1-11, 詩篇 86:11-17'],
        200: [False, '阿摩司書 8, 阿摩司書 9, 羅馬書 5:12-21, 箴言 17:15-24'],
        201: [False, '何西阿書 1, 何西阿書 2, 羅馬書 6:1-14, 詩篇 87:1-7'],
        202: [
            False, '何西阿書 3, 何西阿書 4, 何西阿書 5, 羅馬書 6:15-23, 羅馬書 7:1-6, 詩篇 88:1-9'
        ],
        203: [False, '何西阿書 6, 何西阿書 7, 羅馬書 7:7-25, 詩篇 88:9-18'],
        204: [False, '何西阿書 8, 何西阿書 9, 羅馬書 8:1-17, 箴言 17:25-28, 箴言 18:1-6'],
        205: [False, '何西阿書 10, 何西阿書 11, 羅馬書 8:18-39, 詩篇 89:1-8'],
        206: [
            False, '何西阿書 11, 何西阿書 12, 何西阿書 13, 何西阿書 14, 羅馬書 9:1-21, 詩篇 89:9-13'
        ],
        207: [
            False, '歷代志上 1, 歷代志上 2:1-17, 羅馬書 9:22-33, 羅馬書 10:1-4, '
            '詩篇 89:14-18'
        ],
        208: [
            False, '歷代志上 2:18-55, 歷代志上 3, 歷代志上 4:1-8, 羅馬書 '
            '10:5-21, 羅馬書 11:1-10, 箴言 18:7-16'
        ],
        209: [False, '歷代志上 4:9-43, 歷代志上 5, 羅馬書 11:11-32, 詩篇 89:19-29'],
        210: [False, '歷代志上 6, 羅馬書 11:33-36, 羅馬書 12:1-21, 詩篇 89:30-37'],
        211: [False, '歷代志上 7, 歷代志上 8, 羅馬書 13:1-14, 詩篇 89:38-45'],
        212: [
            False, '歷代志上 9, 歷代志上 10:1-14, 羅馬書 14:1-18, 箴言 '
            '18:17-24, 箴言 19:1-2'
        ],
        213: [
            False, '歷代志上 11, 歷代志上 12:1-22, 羅馬書 14:19-23, 羅馬書 '
            '15:1-13, 詩篇 89:46-52'
        ],
        214: [
            False, '歷代志上 12:23-40, 歷代志上 13, 歷代志上 14, 羅馬書 '
            '15:14-33, 詩篇 90:1-10'
        ],
        215: [False, '歷代志上 15, 歷代志上 16:1-36, 羅馬書 16, 詩篇 90:11-17'],
        216: [
            False, '歷代志上 16:37-43, 歷代志上 17, 歷代志上 18, 哥林多前書 '
            '1:1-17, 箴言 19:3-12'
        ],
        217: [
            False, '歷代志上 19, 歷代志上 20, 歷代志上 21, 哥林多前書 '
            '1:18-31, 哥林多前書 2:1-5, 詩篇 91:1-8'
        ],
        218: [False, '歷代志上 22, 歷代志上 23, 哥林多前書 2:6-16, 詩篇 91:9-16'],
        219: [False, '歷代志上 24, 歷代志上 25, 歷代志上 26:1-19, 哥林多前書 '
              '3, 詩篇 92:1-15'],
        220: [False, '歷代志上 26:20-32, 歷代志上 27, 哥林多前書 4, 箴言 '
              '19:13-22'],
        221: [False, '歷代志上 28, 歷代志上 29, 哥林多前書 5, 詩篇 93:1-5'],
        222: [False, '歷代志下 1:1-17, 哥林多前書 6, 詩篇 94:1-11'],
        223: [False, '傳道書 1, 傳道書 2, 傳道書 3:1-22, 哥林多前書 '
              '7:1-16, 詩篇 94:12-23'],
        224: [
            False, '傳道書 4, 傳道書 5, 傳道書 6, 哥林多前書 7:17-35, '
            '箴言 19:23-29, 箴言 20:1-4'
        ],
        225: [
            False, '傳道書 7, 傳道書 8, 傳道書 9:1-12, 哥林多前書 '
            '7:36-40, 哥林多前書 8:1-13, 詩篇 95:1-11'
        ],
        226: [
            False, '傳道書 9:13-18, 傳道書 10, 傳道書 11, 傳道書 '
            '12, 哥林多前書 9:1-18, 詩篇 96:1-13'
        ],
        227: [
            False,
            '歷代志下 2, 歷代志下 3, 歷代志下 4, 歷代志下 5:1, 哥林多前書 9:19-27, 哥林多前書 10:1-13, 詩篇 97:1-12'
        ],
        228: [
            False,
            '歷代志下 5:2-14, 歷代志下 6, 歷代志下 7:1-10, 哥林多前書 10:14-33, 哥林多前書 11:1, 箴言 20:5-14'
        ],
        229: [
            False, '歷代志下 7:11-22, 歷代志下 8, 歷代志下 9, 哥林多前書 '
            '11:2-34, 詩篇 98:1-9'
        ],
        230: [False, '雅歌 1, 雅歌 2, 雅歌 3, 雅歌 4, 哥林多前書 12:1-26, 詩篇 99:1-9'],
        231: [
            False, '雅歌 5, 雅歌 6, 雅歌 7, 雅歌 8, 哥林多前書 12:27-31, 哥林多前書 '
            '13:1-13, 詩篇 100:1-5'
        ],
        232: [
            False, '歷代志下 10, 歷代志下 11, 歷代志下 12, 哥林多前書 '
            '14:1-19, 箴言 20:15-24'
        ],
        233: [
            False, '歷代志下 13, 歷代志下 14, 歷代志下 15, 哥林多前書 '
            '14:20-40, 詩篇 101:1-8'
        ],
        234: [
            False, '歷代志下 16, 歷代志下 17, 歷代志下 18:1-27, 哥林多前書 '
            '15:1-34, 詩篇 102:1-11'
        ],
        235: [
            False, '歷代志下 18:28-34, 歷代志下 19, 歷代志下 20, 哥林多前書 '
            '15:35-49, 詩篇 102:12-17'
        ],
        236: [
            False, '歷代志下 21, 歷代志下 22, 歷代志下 23, 哥林多前書 '
            '15:50-58, 哥林多前書 16:1-4, 箴言 20:25-30, 箴言 21:1-4'
        ],
        237: [False, '歷代志下 24, 歷代志下 25, 哥林多前書 16:5-24, 詩篇 '
              '102:18-28'],
        238: [
            False, '歷代志下 26, 歷代志下 27, 歷代志下 28, 哥林多後書 '
            '1:1-11, 詩篇 103:1-12'
        ],
        239: [
            False, '歷代志下 29, 歷代志下 30, 歷代志下 31:1, 哥林多後書 '
            '1:12-22, 詩篇 103:13-22'
        ],
        240: [
            False,
            '歷代志下 31:2-21, 歷代志下 32, 歷代志下 33:1-20, 哥林多後書 1:23, 哥林多後書 2:1-11, 箴言 21:5-16'
        ],
        241: [
            False,
            '歷代志下 33:21-24, 歷代志下 34, 歷代志下 35:1-19, 哥林多後書 2:12-17, 哥林多後書 3:1-6, 詩篇 104:1-18'
        ],
        242: [False, '歷代志下 35:20-27, 歷代志下 36, 哥林多後書 3:7-18, 詩篇 '
              '104:19-30'],
        243: [False, '彌迦書 1, 彌迦書 2, 彌迦書 3, 彌迦書 4, 哥林多後書 4, 詩篇 104:31-35'],
        244: [False, '彌迦書 5, 彌迦書 6, 彌迦書 7, 哥林多後書 5:1-10, 箴言 21:17-26'],
        245: [
            False, '以賽亞書 1, 以賽亞書 2, 哥林多後書 5:11-21, 哥林多後書 6:1-2, 詩篇 '
            '105:1-11'
        ],
        246: [
            False, '以賽亞書 3, 以賽亞書 4, 以賽亞書 5:1-7, 哥林多後書 6:3-18, 哥林多後書 '
            '7:1, 詩篇 105:12-22'
        ],
        247: [
            False, '以賽亞書 5:8-30, 以賽亞書 6, 以賽亞書 7, 以賽亞書 8:1-10, 哥林多後書 '
            '7:2-16, 詩篇 105:23-36'
        ],
        248: [
            False, '以賽亞書 8:11-22, 以賽亞書 9, 以賽亞書 10:1-19, 哥林多後書 8:1-15, '
            '箴言 21:27-31, 箴言 22:1-6'
        ],
        249: [
            False, '以賽亞書 10:20-34, 以賽亞書 11, 以賽亞書 12, 以賽亞書 13, 哥林多後書 '
            '8:16-24, 哥林多後書 9:1-5, 詩篇 105:37-45'
        ],
        250: [False, '以賽亞書 14, 以賽亞書 15, 以賽亞書 16, 哥林多後書 9:6-15, 詩篇 106:1-15'],
        251: [False, '以賽亞書 17, 以賽亞書 18, 以賽亞書 19, 哥林多後書 10, 詩篇 106:16-31'],
        252: [
            False, '以賽亞書 20, 以賽亞書 21, 以賽亞書 22, 以賽亞書 23, 哥林多後書 11:1-15, '
            '箴言 22:7-16'
        ],
        253: [
            False, '以賽亞書 24, 以賽亞書 25, 以賽亞書 26, 哥林多後書 11:16-33, 詩篇 '
            '106:32-39'
        ],
        254: [False, '以賽亞書 27, 以賽亞書 28, 哥林多後書 12:1-10, 詩篇 106:40-48'],
        255: [False, '以賽亞書 29, 以賽亞書 30:1-18, 哥林多後書 12:11-21, 詩篇 107:1-9'],
        256: [
            False, '以賽亞書 30:19-33, 以賽亞書 31, 以賽亞書 32, 哥林多後書 13, 箴言 '
            '22:17-27'
        ],
        257: [False, '以賽亞書 33, 以賽亞書 34, 以賽亞書 35, 加拉太書 1, 詩篇 107:10-22'],
        258: [False, '以賽亞書 36, 以賽亞書 37, 加拉太書 2:1-10, 詩篇 107:23-32'],
        259: [
            False, '以賽亞書 38, 以賽亞書 39, 以賽亞書 40, 加拉太書 2:11-21, 加拉太書 3:1-9, '
            '詩篇 107:33-43'
        ],
        260: [
            False, '以賽亞書 41, 以賽亞書 42, 加拉太書 3:10-25, 箴言 22:28-29, 箴言 '
            '23:1-9'
        ],
        261: [
            False, '以賽亞書 43, 以賽亞書 44:1-23, 加拉太書 3:26-29, 加拉太書 4:1-20, 詩篇 '
            '108:1-5'
        ],
        262: [
            False, '以賽亞書 44:24-28, 以賽亞書 45, 以賽亞書 46, 加拉太書 4:21-31, 加拉太書 '
            '5:1-6, 詩篇 108:6-13'
        ],
        263: [
            False, '以賽亞書 47, 以賽亞書 48, 以賽亞書 49:1-7, 加拉太書 5:7-26, 詩篇 109:1-20'
        ],
        264: [
            False, '以賽亞書 49:8-26, 以賽亞書 50, 以賽亞書 51:1-16, 加拉太書 6, 箴言 '
            '23:10-18'
        ],
        265: [
            False, '以賽亞書 51:17-23, 以賽亞書 52, 以賽亞書 53, 以賽亞書 54, 以弗所書 1, 詩篇 '
            '109:21-31'
        ],
        266: [False, '以賽亞書 55, 以賽亞書 56, 以賽亞書 57:1-13, 以弗所書 2, 詩篇 110:1-7'],
        267: [False, '以賽亞書 57:14-21, 以賽亞書 58, 以賽亞書 59, 以弗所書 3, 詩篇 111:1-10'],
        268: [False, '以賽亞書 60, 以賽亞書 61, 以賽亞書 62, 以弗所書 4:1-16, 箴言 23:19-28'],
        269: [
            False, '以賽亞書 63, 以賽亞書 64, 以賽亞書 65:1-16, 以弗所書 4:17-32, 以弗所書 '
            '5:1-7, 詩篇 112:1-10'
        ],
        270: [False, '以賽亞書 65:17-25, 以賽亞書 66, 以弗所書 5:8-33, 詩篇 113:1-9'],
        271: [False, '那鴻書 1, 那鴻書 2, 那鴻書 3, 以弗所書 6, 詩篇 114:1-8'],
        272: [
            False, '西番雅書 1, 西番雅書 2, 西番雅書 3, 腓立比書 1:1-26, 箴言 '
            '23:29-35, 箴言 24:1-4'
        ],
        273: [
            False, '耶利米書 1, 耶利米書 2:1-30, 腓立比書 1:27-30, 腓立比書 2:1-11, '
            '詩篇 115:1-11'
        ],
        274: [
            False, '耶利米書 2:31-47, 耶利米書 3, 耶利米書 4:1-9, 腓立比書 2:12-30, '
            '詩篇 115:12-18'
        ],
        275: [False, '耶利米書 4:10-31, 耶利米書 5, 腓立比書 3, 腓立比書 4:1, 詩篇 '
              '116:1-11'],
        276: [False, '耶利米書 6, 耶利米書 7:1-29, 腓立比書 4:2-23, 箴言 24:5-14'],
        277: [
            False, '耶利米書 7:30-34, 耶利米書 8, 耶利米書 9:1-16, 歌羅西書 1:1-23, '
            '詩篇 116:12-19'
        ],
        278: [
            False, '耶利米書 9:17-26, 耶利米書 10, 耶利米書 11:1-17, 歌羅西書 1:24-29, '
            '歌羅西書 2:1-5, 詩篇 117:1-2'
        ],
        279: [
            False, '耶利米書 11:18-23, 耶利米書 12, 耶利米書 13, 歌羅西書 2:6-23, 詩篇 '
            '118:1-16'
        ],
        280: [False, '耶利米書 14, 耶利米書 15, 歌羅西書 3, 歌羅西書 4:1, 箴言 '
              '24:15-22'],
        281: [False, '耶利米書 16, 耶利米書 17, 歌羅西書 4:2-18, 詩篇 118:17-29'],
        282: [
            False,
            '耶利米書 18, 耶利米書 19, 耶利米書 20, 帖撒羅尼迦前書 1, 帖撒羅尼迦前書 2:1-16, 詩篇 119:1-8'
        ],
        283: [
            False,
            '耶利米書 21, 耶利米書 22, 耶利米書 23:1-8, 帖撒羅尼迦前書 2:17-19, 帖撒羅尼迦前書 3, 詩篇 119:9-16'
        ],
        284: [
            False, '耶利米書 23:9-40, 耶利米書 24, 耶利米書 25:1-14, 帖撒羅尼迦前書 4, '
            '箴言 24:23-34'
        ],
        285: [False, '耶利米書 25:15-38, 耶利米書 26, 帖撒羅尼迦前書 5, 詩篇 119:17-24'],
        286: [
            False, '耶利米書 27, 耶利米書 28, 耶利米書 29:1-23, 帖撒羅尼迦後書 1, 詩篇 '
            '119:25-32'
        ],
        287: [
            False, '耶利米書 29:24-32, 耶利米書 30, 耶利米書 31:1-14, 帖撒羅尼迦後書 2, '
            '詩篇 119:33-40'
        ],
        288: [False, '耶利米書 31:15-40, 耶利米書 32:1-25, 帖撒羅尼迦後書 3, 箴言 '
              '25:1-10'],
        289: [
            False, '耶利米書 32:26-44, 耶利米書 33, 耶利米書 34, 提摩太前書 1, 詩篇 '
            '119:41-48'
        ],
        290: [False, '耶利米書 35, 耶利米書 36, 耶利米書 37, 提摩太前書 2, 詩篇 119:49-56'],
        291: [
            False, '耶利米書 38, 耶利米書 39, 耶利米書 40:1-6, 提摩太前書 3, 詩篇 '
            '119:57-64'
        ],
        292: [
            False, '耶利米書 40:7-16, 耶利米書 41, 耶利米書 42, 提摩太前書 4, 箴言 '
            '25:11-20'
        ],
        293: [
            False, '耶利米書 43, 耶利米書 44, 耶利米書 45, 提摩太前書 5, 提摩太前書 6:1-2, '
            '詩篇 119:65-72'
        ],
        294: [False, '耶利米書 46, 耶利米書 47, 提摩太前書 6:3-21, 詩篇 119:73-80'],
        295: [False, '耶利米書 48, 耶利米書 49:1-6, 提摩太後書 1, 詩篇 119:81-88'],
        296: [
            False, '耶利米書 49:7-39, 耶利米書 50:1-10, 提摩太後書 2, 箴言 25:21-28, '
            '箴言 26:1-2'
        ],
        297: [False, '耶利米書 50:11-46, 耶利米書 51:1-14, 提摩太後書 3, 詩篇 119:89-96'],
        298: [False, '耶利米書 51:15-64, 提摩太後書 4, 詩篇 119:97-104'],
        299: [False, '耶利米書 52, 提多書 1, 詩篇 119:105-112'],
        300: [False, '哈巴谷書 1, 哈巴谷書 2, 哈巴谷書 3:1-19, 提多書 2, 箴言 26:3-12'],
        301: [False, '耶利米哀歌 1, 耶利米哀歌 2:1-6, 提多書 3, 詩篇 119:113-120'],
        302: [False, '耶利米哀歌 2:7-27, 耶利米哀歌 3:1-39, 腓利門書 1, 詩篇 '
              '119:121-128'],
        303: [
            False, '耶利米哀歌 3:40-66, 耶利米哀歌 4, 耶利米哀歌 5, 希伯來書 1, 詩篇 '
            '119:129-136'
        ],
        304: [False, '俄巴底亞書 1, 希伯來書 2, 箴言 26:13-22'],
        305: [False, '約珥書 1, 約珥書 2:1-17, 希伯來書 3, 詩篇 119:137-144'],
        306: [False, '約珥書 2:18-32, 約珥書 3, 希伯來書 4:1-13, 詩篇 119:145-152'],
        307: [
            False, '以西結書 1, 以西結書 2, 以西結書 3, 希伯來書 4:14-16, 希伯來書 5:1-10, '
            '詩篇 119:153-160'
        ],
        308: [
            False, '以西結書 4, 以西結書 5, 以西結書 6, 希伯來書 5:11-14, 希伯來書 6:1-12, '
            '箴言 26:23-28, 箴言 27:1-4'
        ],
        309: [
            False, '以西結書 7, 以西結書 8, 以西結書 9, 希伯來書 6:13-20, 希伯來書 7:1-10, '
            '詩篇 119:161-168'
        ],
        310: [
            False, '以西結書 10, 以西結書 11, 以西結書 12, 希伯來書 7:11-28, 詩篇 '
            '119:169-176'
        ],
        311: [False, '以西結書 13, 以西結書 14, 以西結書 15, 希伯來書 8, 詩篇 120:1-7'],
        312: [False, '以西結書 16, 希伯來書 9:1-15, 箴言 27:5-14'],
        313: [False, '以西結書 17, 以西結書 18, 希伯來書 9:16-28, 詩篇 121:1-8'],
        314: [False, '以西結書 19, 以西結書 20:1-44, 希伯來書 10:1-18, 詩篇 122:1-9'],
        315: [
            False, '以西結書 20:45-49, 以西結書 21, 以西結書 22:1-22, 希伯來書 10:19-39, 詩篇 '
            '123:1-4'
        ],
        316: [False, '以西結書 22:23-31, 以西結書 23, 希伯來書 11:1-16, 箴言 27:15-22'],
        317: [False, '以西結書 24, 以西結書 25, 希伯來書 11:17-40, 詩篇 124:1-8'],
        318: [False, '以西結書 26, 以西結書 27, 希伯來書 12:1-13, 詩篇 125:1-5'],
        319: [False, '以西結書 28, 以西結書 29, 希伯來書 12:14-29, 詩篇 126:1-6'],
        320: [False, '以西結書 30, 以西結書 31, 希伯來書 13, 箴言 27:23-27, 箴言 '
              '28:1-6'],
        321: [False, '以西結書 32, 以西結書 33:1-32, 雅各書 1, 詩篇 127:1-5'],
        322: [False, '以西結書 33:21-33, 以西結書 34, 以西結書 35, 雅各書 2, 詩篇 128:1-6'],
        323: [False, '以西結書 36, 以西結書 37, 雅各書 3, 詩篇 129:1-8'],
        324: [False, '以西結書 38, 以西結書 39, 雅各書 4, 箴言 28:7-17'],
        325: [False, '以西結書 40, 雅各書 5, 詩篇 130:1-8'],
        326: [False, '以西結書 41, 以西結書 42, 彼得前書 1, 彼得前書 2:1-3, 詩篇 131:1-3'],
        327: [False, '以西結書 43, 以西結書 44, 彼得前書 2:4-25, 詩篇 132:1-18'],
        328: [False, '以西結書 45, 以西結書 46, 彼得前書 3, 箴言 28:18-28'],
        329: [False, '以西結書 47, 以西結書 48, 彼得前書 4, 詩篇 133:1-3'],
        330: [False, '但以理書 1, 但以理書 2:1-23, 彼得前書 5, 詩篇 134:1-3'],
        331: [False, '但以理書 2:24-49, 但以理書 3:1-12, 彼得後書 1, 詩篇 135:1-12'],
        332: [False, '但以理書 3:13-30, 但以理書 4:1-18, 彼得後書 2, 箴言 29:1-9'],
        333: [False, '但以理書 4:19-37, 但以理書 5:1-16, 彼得後書 3, 詩篇 135:13-21'],
        334: [False, '但以理書 5:17-31, 但以理書 6:1-28, 約翰一書 1, 約翰一書 2, 詩篇 136:1-12'],
        335: [False, '但以理書 7, 但以理書 8:1-14, 約翰一書 2:12-27, 詩篇 136:13-26'],
        336: [
            False, '但以理書 8:15-27, 但以理書 9:1-19, 約翰一書 2:28-29, 約翰一書 3:1-10, 箴言 '
            '29:10-18'
        ],
        337: [
            False,
            '但以理書 9:20-27, 但以理書 10, 但以理書 11:1, 約翰一書 3:11-24, 約翰一書 4:1-6, '
            '詩篇 137:1-9'
        ],
        338: [False, '但以理書 11:2-35, 約翰一書 4:7-21, 詩篇 138:1-8'],
        339: [False, '但以理書 11:36-45, 但以理書 12, 約翰一書 5:1-21, 詩篇 139:1-10'],
        340: [False, '哈該書 1, 哈該書 2:1-23, 約翰二書 1:1-13, 箴言 29:19-27'],
        341: [
            False, '撒迦利亞書 1, 撒迦利亞書 2, 撒迦利亞書 3, 撒迦利亞書 4, 約翰三書 1:1-14, '
            '詩篇 139:11-16'
        ],
        342: [
            False, '撒迦利亞書 5, 撒迦利亞書 6, 撒迦利亞書 7, 撒迦利亞書 8, 猶大書 1:1-25, 詩篇 '
            '139:17-24'
        ],
        343: [False, '撒迦利亞書 9, 撒迦利亞書 10, 撒迦利亞書 11, 啟示錄 1, 詩篇 140:1-5'],
        344: [
            False, '撒迦利亞書 12, 撒迦利亞書 13, 撒迦利亞書 14, 啟示錄 2:1-17, 箴言 '
            '30:1-10'
        ],
        345: [
            False, '以斯帖記 1, 以斯帖記 2:1-18, 啟示錄 2:18-29, 啟示錄 3:1-6, 詩篇 '
            '140:6-13'
        ],
        346: [
            False, '以斯帖記 2:19-23, 以斯帖記 3, 以斯帖記 4, 以斯帖記 5, 啟示錄 3:7-22, 詩篇 '
            '141:1-10'
        ],
        347: [False, '以斯帖記 6, 以斯帖記 7, 以斯帖記 8, 啟示錄 4, 詩篇 142:1-11'],
        348: [False, '以斯帖記 9, 以斯帖記 10, 啟示錄 5, 箴言 30:11-23'],
        349: [False, '瑪拉基書 1, 瑪拉基書 2:1-16, 啟示錄 6, 詩篇 143:1-12'],
        350: [False, '瑪拉基書 2:17, 瑪拉基書 3, 瑪拉基書 4, 啟示錄 7, 詩篇 144:1-8'],
        351: [False, '以斯拉記 1, 以斯拉記 2:1-67, 啟示錄 8, 啟示錄 9:1-12, 詩篇 144:9-15'],
        352: [
            False, '以斯拉記 2:68-70, 以斯拉記 3, 以斯拉記 4:1-5, 啟示錄 9:13-21, 啟示錄 10, '
            '箴言 30:24-33'
        ],
        353: [False, '以斯拉記 4:6-24, 以斯拉記 5, 啟示錄 11, 詩篇 145:1-7'],
        354: [False, '以斯拉記 6, 以斯拉記 7:1-10, 啟示錄 12, 啟示錄 13:1, 詩篇 145:8-13'],
        355: [False, '以斯拉記 7:11-28, 以斯拉記 8:1-14, 啟示錄 13:1-18, 詩篇 145:13-21'],
        356: [False, '以斯拉記 8:15-36, 以斯拉記 9:1-15, 啟示錄 14:1-13, 箴言 31:1-9'],
        357: [False, '以斯拉記 10, 啟示錄 14:14-20, 啟示錄 15, 詩篇 146:1-10'],
        358: [False, '尼希米記 1, 尼希米記 2, 啟示錄 16, 詩篇 147:1-11'],
        359: [False, '尼希米記 3, 尼希米記 4, 啟示錄 17, 詩篇 147:12-20'],
        360: [
            False, '尼希米記 5, 尼希米記 6, 尼希米記 7:1-3, 啟示錄 18:1-17, 箴言 '
            '31:10-20'
        ],
        361: [
            False, '尼希米記 7:4-73, 尼希米記 8, 啟示錄 18:17-24, 啟示錄 19:1-10, '
            '詩篇 148:1-6'
        ],
        362: [False, '尼希米記 9:1-37, 啟示錄 19:11-21, 詩篇 148:7-14'],
        363: [
            False, '尼希米記 9:38, 尼希米記 10, 尼希米記 11:1-21, 啟示錄 20, 詩篇 '
            '149:1-9'
        ],
        364: [False, '尼希米記 11:22-36, 尼希米記 12:1-47, 啟示錄 21, 箴言 31:21-31'],
        365: [False, '尼希米記 13, 啟示錄 22, 詩篇 150:1-6'],
    }

    translation = (
        "聖經閱讀計劃",
        "今天是 ",
        "搜索:",
        "聖經視窗頁籤中開啓",
        "隱藏已經閱讀的經文",
        "顯示已經閱讀的經文",
        "重新設置",
        "儲存閱讀進度",
        "第",
        "天",
        "您的閱讀進度儲存在下面這個檔案:",
        "沒法把您的閱讀進度儲存在您現在使用的電腦上!",
    )

    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        # set title
        self.setWindowTitle(self.translation[0])
        self.setMinimumSize(830, 500)
        # set variables
        self.setupVariables()
        # setup interface
        self.setupUI()

    def setupVariables(self):
        import copy, os
        from datetime import date
        self.today = date.today()
        self.todayNo = int(format(self.today, '%j'))
        if self.todayNo > 365:
            self.todayNo = 365
        self.progressFile = os.path.join(os.getcwd(), "plugins", "menu",
                                         "{0}.txt".format(self.translation[0]))
        if os.path.isfile(self.progressFile):
            from ast import literal_eval
            with open(self.progressFile, "r") as fileObj:
                self.plan = literal_eval(fileObj.read())
        else:
            self.plan = copy.deepcopy(self.template)
        self.hideCheckedItems = False

    def setupUI(self):
        from qtpy.QtGui import QStandardItemModel
        from qtpy.QtWidgets import (QPushButton, QLabel, QListView,
                                    QAbstractItemView, QHBoxLayout,
                                    QVBoxLayout, QLineEdit)

        mainLayout = QVBoxLayout()

        readingListLayout = QVBoxLayout()

        readingListLayout.addWidget(QLabel(self.translation[0]))
        readingListLayout.addWidget(
            QLabel("{0}{1}".format(self.translation[1], self.today)))

        filterLayout = QHBoxLayout()
        filterLayout.addWidget(QLabel(self.translation[2]))
        self.filterEntry = QLineEdit()
        self.filterEntry.textChanged.connect(self.resetItems)
        filterLayout.addWidget(self.filterEntry)
        readingListLayout.addLayout(filterLayout)

        self.readingList = QListView()
        self.readingList.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.readingListModel = QStandardItemModel(self.readingList)
        self.readingList.setModel(self.readingListModel)
        self.resetItems()
        self.readingListModel.itemChanged.connect(self.itemChanged)
        #print(self.readingList.currentIndex().row())
        #self.readingList.selectionModel().selectionChanged.connect(self.function)
        readingListLayout.addWidget(self.readingList)

        buttonsLayout = QHBoxLayout()

        button = QPushButton(self.translation[3])
        button.clicked.connect(self.openInTabs)
        buttonsLayout.addWidget(button)

        self.hideShowButton = QPushButton(self.translation[4])
        self.hideShowButton.clicked.connect(self.hideShowCheckedItems)
        buttonsLayout.addWidget(self.hideShowButton)

        button = QPushButton(self.translation[6])
        button.clicked.connect(self.resetAllItems)
        buttonsLayout.addWidget(button)

        button = QPushButton(self.translation[7])
        button.clicked.connect(self.saveProgress)
        buttonsLayout.addWidget(button)

        mainLayout.addLayout(readingListLayout)
        mainLayout.addLayout(buttonsLayout)

        self.setLayout(mainLayout)

    def itemChanged(self, standardItem):
        from qtpy.QtCore import Qt
        key = int(standardItem.text().split(".")[0])
        if standardItem.checkState() is Qt.CheckState.Checked:
            self.plan[key][0] = True
        elif standardItem.checkState() is Qt.CheckState.Unchecked:
            self.plan[key][0] = False
        if self.hideCheckedItems:
            self.resetItems()

    def resetItems(self):
        from qtpy.QtGui import QStandardItem
        from qtpy.QtCore import Qt
        # Empty the model before reset
        self.readingListModel.clear()
        # Reset
        index = 0
        todayIndex = None
        filterEntry = self.filterEntry.text()
        for key, value in self.plan.items():
            checked, passages = value
            if not (self.hideCheckedItems and checked) and (
                    filterEntry == "" or
                (filterEntry != ""
                 and filterEntry.lower() in passages.lower())):
                item = QStandardItem("{0}. {1}".format(key, passages))
                item.setToolTip("{0}{1}{2}".format(self.translation[8], key,
                                                   self.translation[9]))
                if key == self.todayNo:
                    todayIndex = index
                item.setCheckable(True)
                item.setCheckState(Qt.CheckState.Checked if checked else Qt.
                                   CheckState.Unchecked)
                self.readingListModel.appendRow(item)
                index += 1
        if todayIndex is not None:
            self.readingList.setCurrentIndex(
                self.readingListModel.index(todayIndex, 0))

    def hideShowCheckedItems(self):
        self.hideCheckedItems = not self.hideCheckedItems
        self.resetItems()
        self.hideShowButton.setText(self.translation[5] if self.
                                    hideCheckedItems else self.translation[4])

    def resetAllItems(self):
        import copy
        self.plan = copy.deepcopy(self.template)
        self.resetItems()

    def translateIntoChinese(self):
        import copy, pprint
        from BibleBooks import BibleBooks
        plan = copy.deepcopy(self.template)
        filePath = "{0}_zh".format(self.progressFile)
        with open(filePath, "w", encoding="utf-8") as fileObj:
            fileObj.write(pprint.pformat(plan))
        with open(filePath, "r") as fileObj:
            text = fileObj.read()
        translateDict = {}
        bookNames = []
        for key, value in BibleBooks.eng.items():
            bookName = value[-1]
            bookNames.append(bookName)
            translateDict[bookName] = BibleBooks.sc[key][-1]
        bookNames = sorted(bookNames, key=len, reverse=True)
        #print(bookNames)
        for name in bookNames:
            text = text.replace(name, translateDict[name])
        text = text.replace("Psalm", "诗篇")
        with open(filePath, "w", encoding="utf-8") as fileObj:
            fileObj.write(text)

    def saveProgress(self):
        import pprint
        from qtpy.QtWidgets import QMessageBox
        try:
            with open(self.progressFile, "w", encoding="utf-8") as fileObj:
                fileObj.write(pprint.pformat(self.plan))
            message = "{0}\n'{1}'".format(self.translation[10],
                                          self.progressFile)
        except:
            message = self.translation[11]
        QMessageBox.information(self, self.translation[0], message)

    def openInTabs(self):
        dayNo = self.readingList.currentIndex().row() + 1
        todayReading = self.plan[dayNo][-1].split(", ")
        openBibleWindowContentOnNextTab = config.openBibleWindowContentOnNextTab
        config.openBibleWindowContentOnNextTab = True
        for reading in todayReading:
            command = "MAIN:::{0}".format(reading)
            self.parent.runTextCommand(command)
        config.openBibleWindowContentOnNextTab = openBibleWindowContentOnNextTab
        self.close()
コード例 #14
0
class PreprocessWidget(QSplitter):
    def __init__(self, headermodel, selectionmodel):
        super(PreprocessWidget, self).__init__()
        self.headermodel = headermodel
        self.mapselectmodel = selectionmodel
        self.selectMapidx = 0
        self.resultDict = {}
        self.isBatchProcessOn = False
        self.out = None
        self.dfDict = None
        self.reportList = ['preprocess_method', 'wav_anchor', 'interp_method', 'w_regions']
        self.arrayList = ['kohlerDebased', 'kohlerBaseline', 'rubberDebased', 'deriv2_kohler', 'deriv2_rubber']
        self.mousePosList = []

        # split between spectrum parameters and viewwindow, vertical split
        self.params_and_specview = QSplitter()
        self.params_and_specview.setOrientation(Qt.Vertical)
        # split between buttons and parameters
        self.buttons_and_params = QSplitter()
        self.buttons_and_params.setOrientation(Qt.Horizontal)
        # split between speclist and report
        self.speclist_and_report = QSplitter()
        self.speclist_and_report.setOrientation(Qt.Vertical)

        # buttons layout
        self.buttons = QWidget()
        self.buttonlayout = QGridLayout()
        self.buttons.setLayout(self.buttonlayout)
        # set up buttons
        self.fontSize = 12
        font = QFont("Helvetica [Cronyx]", self.fontSize)
        self.loadBtn = QPushButton()
        self.loadBtn.setText('Load spectra')
        self.loadBtn.setFont(font)
        self.removeBtn = QPushButton()
        self.removeBtn.setText('Remove spectrum')
        self.removeBtn.setFont(font)
        self.normBox = QComboBox()
        self.normBox.addItems(['Raw spectrum',
                               'Kohler EMSC baseline',
                               'Rubberband baseline',
                               'Kohler EMSC + 2nd derivative',
                               'Rubberband + 2nd derivative',
                               ])
        self.normBox.setFont(font)
        self.batchBtn = QPushButton()
        self.batchBtn.setText('Batch process')
        self.batchBtn.setFont(font)
        self.saveResultBox = QComboBox()
        self.saveResultBox.addItems(['Save kohler',
                                     'Save kohler baseline',
                                     'Save rubberband',
                                     'Save kohler 2nd derivative',
                                     'Save rubberband 2nd derivative',
                                     'Save all',
                                     ])
        self.saveResultBox.setFont(font)
        # add all buttons
        self.buttonlayout.addWidget(self.loadBtn)
        self.buttonlayout.addWidget(self.removeBtn)
        self.buttonlayout.addWidget(self.normBox)
        self.buttonlayout.addWidget(self.batchBtn)
        self.buttonlayout.addWidget(self.saveResultBox)
        # define report
        self.reportWidget = QWidget()
        self.reportWidget.setLayout(QVBoxLayout())
        self.infoBox = QTextEdit()
        reportTitle = QLabel('Preprocess results')
        reportTitle.setFont(font)
        self.reportWidget.layout().addWidget(reportTitle)
        self.reportWidget.layout().addWidget(self.infoBox)
        # spectrum list view
        self.specItemModel = QStandardItemModel()
        self.specSelectModel = QItemSelectionModel(self.specItemModel)
        self.speclistview = QListView()
        self.speclistview.setModel(self.specItemModel)
        self.speclistview.setSelectionModel(self.specSelectModel)
        # add title to list view
        self.specListWidget = QWidget()
        self.listLayout = QVBoxLayout()
        self.specListWidget.setLayout(self.listLayout)
        specListTitle = QLabel('Spectrum List')
        specListTitle.setFont(font)
        self.listLayout.addWidget(specListTitle)
        self.listLayout.addWidget(self.speclistview)

        # spectrum plot
        self.rawSpectra = baselinePlotWidget()
        self.resultSpectra = baselinePlotWidget()
        # ParameterTree
        self.parametertree = PreprocessParameters()
        self.parameter = self.parametertree.parameter
        self.processArgs = self.parametertree.processArgs
        self.argMap = self.parametertree.argMap

        # assemble widgets
        self.buttons_and_params.addWidget(self.parametertree)
        self.buttons_and_params.addWidget(self.buttons)
        self.buttons_and_params.setSizes([1000, 100])
        self.params_and_specview.addWidget(self.buttons_and_params)
        self.params_and_specview.addWidget(self.rawSpectra)
        self.params_and_specview.addWidget(self.resultSpectra)
        self.params_and_specview.setSizes([150, 50, 50])
        self.speclist_and_report.addWidget(self.specListWidget)
        self.speclist_and_report.addWidget(self.reportWidget)
        self.speclist_and_report.setSizes([150, 100])
        self.addWidget(self.params_and_specview)
        self.addWidget(self.speclist_and_report)
        self.setSizes([1000, 200])

        # Connect signals
        self.loadBtn.clicked.connect(self.loadData)
        self.removeBtn.clicked.connect(self.removeSpec)
        self.batchBtn.clicked.connect(self.batchProcess)
        self.saveResultBox.currentIndexChanged.connect(self.saveResults)
        self.specSelectModel.selectionChanged.connect(self.updateSpecPlot)
        self.normBox.currentIndexChanged.connect(self.updateSpecPlot)
        self.parametertree.sigParamChanged.connect(self.updateSpecPlot)
        self.rawSpectra.scene().sigMouseClicked.connect(self.setAnchors)
        self.parameter.child('Preprocess method').sigValueChanged.connect(self.updateMethod)

    def setHeader(self, field: str):
        self.headers = [self.headermodel.item(i).header for i in range(self.headermodel.rowCount())]
        self.field = field
        self.wavenumberList = []
        self.rc2indList = []
        self.ind2rcList = []
        self.pathList = []
        self.dataSets = []

        # get wavenumbers, rc2ind
        for header in self.headers:
            dataEvent = next(header.events(fields=[field]))
            self.wavenumberList.append(dataEvent['wavenumbers'])
            self.rc2indList.append(dataEvent['rc_index'])
            self.ind2rcList.append(dataEvent['index_rc'])
            self.pathList.append(dataEvent['path'])
            # get raw spectra
            data = None
            try:  # spectra datasets
                data = header.meta_array('spectra')
            except IndexError:
                msg.logMessage('Header object contained no frames with field ''{field}''.', msg.ERROR)
            if data is not None:
                self.dataSets.append(data)

    def isMapOpen(self):
        if not self.mapselectmodel.selectedIndexes():  # no map is open
            return False
        else:
            self.selectMapidx = self.mapselectmodel.selectedIndexes()[0].row()
            return True

    def setAnchors(self, event):
        # get current map idx and selected spectrum idx
        specidx = self.getCurrentSpecid()
        plotChoice = self.normBox.currentIndex()
        if (not self.isMapOpen()) or (self.specItemModel.rowCount() == 0) or (specidx is None) or (plotChoice not in [2, 4]):
            return

        pos = event.pos()
        button = event.button()
        parser = Preprocessor(self.wavenumberList[self.selectMapidx], self.dataSets[self.selectMapidx][specidx])
        parser.parse_anchors(self.parameter['Anchor points'])
        anchor_low, anchor_high = parser.wav_anchor[0], parser.wav_anchor[-1]
        if self.rawSpectra.getViewBox().sceneBoundingRect().contains(pos):
            mousePoint = self.rawSpectra.getViewBox().mapToView(pos)
            x = mousePoint.x()
            if anchor_low < x < anchor_high:
                if button == Qt.LeftButton:# left click, add point to mousePosList
                    self.mousePosList.append(x)
                elif (button == Qt.MidButton) and self.mousePosList :# right click, remove last point from mousePosList
                    self.mousePosList.pop()
                # set anchors list
                anchors = [anchor_low] + sorted(self.mousePosList) + [anchor_high]
                txt = ', '.join([str(int(round(x))) for x in anchors])
                self.parameter.child('Anchor points').setValue(txt)

    def getCurrentSpecid(self):
        # get selected spectrum idx
        specidx = None  # default value
        if self.specSelectModel.selectedIndexes():
            selectedSpecRow = self.specSelectModel.selectedIndexes()[0].row()
            currentSpecItem = self.specItemModel.item(selectedSpecRow)
            specidx = currentSpecItem.idx
        return specidx

    def updateMethod(self):
        if self.parameter["Preprocess method"] == 'Kohler_EMSC':
            self.normBox.setCurrentIndex(1)
        else:
            self.normBox.setCurrentIndex(2)

    def updateSpecPlot(self):
        # get current map idx and selected spectrum idx
        specidx = self.getCurrentSpecid()
        if not self.isMapOpen():
            return
        elif self.specItemModel.rowCount() == 0:
            MsgBox('No spectrum is loaded.\nPlease click "Load spectra" to import data.')
            return
        elif specidx is None:
            return

        # get plotchoice
        plotChoice = self.normBox.currentIndex()

        # create Preprocessor object
        self.out = Preprocessor(self.wavenumberList[self.selectMapidx], self.dataSets[self.selectMapidx][specidx])
        baselineOK = self.out.rubber_band(**self.processArgs) and self.out.kohler(**self.processArgs)

        if not baselineOK:
            return

        # make results report
        if plotChoice != 0:
            self.getReport(self.out, plotChoice)

        # if not batch processing, show plots
        if not self.isBatchProcessOn:
            # clean up plots
            self.rawSpectra.clearAll()
            self.resultSpectra.clearAll()
            if plotChoice == 0:  # plot raw spectrum
                self.infoBox.setText('')  # clear txt
                self.rawSpectra.plotBase(self.out, plotType='raw')
            elif plotChoice == 1:  # plot raw, kohler
                self.rawSpectra.plotBase(self.out, plotType='kohler_base')
                self.resultSpectra.plotBase(self.out, plotType='kohler')
            elif plotChoice == 2:  # plot raw, rubberband
                self.rawSpectra.plotBase(self.out, plotType='rubber_base')
                self.resultSpectra.plotBase(self.out, plotType='rubberband')
            elif plotChoice == 3:  # plot raw, kohler 2nd derivative
                self.rawSpectra.plotBase(self.out, plotType='kohler_base')
                self.resultSpectra.plotBase(self.out, plotType='deriv2_kohler')
            elif plotChoice == 4:  # plot raw, rubberband 2nd derivative
                self.rawSpectra.plotBase(self.out, plotType='rubber_base')
                self.resultSpectra.plotBase(self.out, plotType='deriv2_rubberband')

            if plotChoice in [1, 3]:
                self.parameter.child('Preprocess method').setValue('Kohler_EMSC', blockSignal=self.updateMethod)
            elif plotChoice in [2, 4]:
                self.parameter.child('Preprocess method').setValue('Rubberband', blockSignal=self.updateMethod)

    def getReport(self, output, plotChoice):
        resultTxt = ''
        # get baseline results
        reportList = self.reportList.copy()
        if plotChoice in [2, 4]:
            reportList = self.reportList[:-1]
            output.preprocess_method = 'rubberband'
        elif plotChoice in [1, 3]:
            reportList = [self.reportList[0], self.reportList[-1]]
            output.preprocess_method = 'kohler'

        for item in dir(output):
            if item in reportList:
                if item == 'wav_anchor':
                    val = getattr(output, item)
                    printFormat = ('{:.2f}, ' * len(val))[:-1]
                    resultTxt += item + ': ' + printFormat.format(*val) + '\n'
                else:
                    resultTxt += item + ': ' + str(getattr(output, item)) + '\n'
            if (item in self.arrayList) or (item in self.reportList):
                self.resultDict[item] = getattr(output, item)

        # send text to report info box
        self.infoBox.setText(resultTxt)

    def loadData(self):
        # get current map idx
        if not self.isMapOpen():
            return
        # pass the selected map data to plotwidget
        self.rawSpectra.setHeader(self.headers[self.selectMapidx], 'spectra')
        currentMapItem = self.headermodel.item(self.selectMapidx)
        rc2ind = self.rc2indList[self.selectMapidx]
        # get current map name
        mapName = currentMapItem.data(0)
        # get current selected pixels
        pixelCoord = currentMapItem.selectedPixels
        # get selected specIds
        spectraIds = []
        if currentMapItem.selectedPixels is None:  # select all
            spectraIds = list(range(len(rc2ind)))
        else:
            for i in range(len(pixelCoord)):
                row_col = tuple(pixelCoord[i])
                spectraIds.append(rc2ind[row_col])
            spectraIds = sorted(spectraIds)
        # add specitem model
        self.specItemModel.clear()
        for idx in spectraIds:
            item = QStandardItem(mapName + '# ' + str(idx))
            item.idx = idx
            self.specItemModel.appendRow(item)

    def removeSpec(self):
        # get current selectedSpecRow
        if self.specSelectModel.selectedIndexes():
            selectedSpecRow = self.specSelectModel.selectedIndexes()[0].row()
            self.specSelectModel.blockSignals(True)
            self.specItemModel.removeRow(selectedSpecRow)
            self.specSelectModel.blockSignals(False)
            # clean up plots
            self.rawSpectra.clearAll()
            self.resultSpectra.clearAll()
            self.infoBox.setText('')

    def cleanUp(self):
        self.specItemModel.clear()
        self.rawSpectra.clearAll()
        self.resultSpectra.clearAll()
        self.infoBox.setText('')
        self.mousePosList = []
        self.normBox.setCurrentIndex(0)

    def batchProcess(self):
        # get current map idx
        if not self.isMapOpen():
            return
        elif self.specItemModel.rowCount() == 0:
            MsgBox('No spectrum is loaded.\nPlease click "Load spectra" to import data.')
            return
        # check if baseline fit OK
        if self.out is None:
            self.out = Preprocessor(self.wavenumberList[self.selectMapidx], self.dataSets[self.selectMapidx][0])

        # get plotchoice
        plotChoice = self.normBox.currentIndex()
        if plotChoice != 0:
            # calculate rubberband and kohler baseline
            baselineOK = self.out.rubber_band(**self.processArgs) and self.out.kohler(**self.processArgs)
        else:
            MsgBox('Plot type is "Raw spectrum".\nPlease change plot type to "Kohler" or "Rubberband".')
            return
        if not baselineOK:
            return

        # notice to user
        userMsg = YesNoDialog(f'Ready to batch process selected spectra.\nDo you want to continue?')
        userChoice = userMsg.choice()
        if userChoice == QMessageBox.No:  # user choose to stop
            return

        self.isBatchProcessOn = True

        # init resultSetsDict, paramsDict
        self.resultSetsDict = {}
        self.paramsDict = {}
        self.paramsDict['specID'] = []
        self.paramsDict['row_column'] = []
        ind2rc = self.ind2rcList[self.selectMapidx]
        energy = self.out.energy
        n_energy = len(energy)
        for item in self.arrayList:
            self.resultSetsDict[item] = np.empty((0, n_energy))
        for item in self.reportList:
            self.paramsDict[item] = []
        # batch process begins
        n_spectra = self.specItemModel.rowCount()
        for i in range(n_spectra):
            msg.showMessage(f'Processing {i + 1}/{n_spectra} spectra')
            # select each spec and collect results
            self.specSelectModel.select(self.specItemModel.index(i, 0), QItemSelectionModel.ClearAndSelect)
            # get spec idx
            currentSpecItem = self.specItemModel.item(i)
            self.paramsDict['specID'].append(currentSpecItem.idx)
            self.paramsDict['row_column'].append(ind2rc[currentSpecItem.idx])
            # append all results into a single array/list
            for item in self.arrayList:
                self.resultSetsDict[item] = np.append(self.resultSetsDict[item], self.resultDict[item].reshape(1, -1),
                                                      axis=0)
            for item in self.reportList:
                self.paramsDict[item].append(self.resultDict[item])

        # result collection completed. convert paramsDict to df
        self.dfDict = {}
        self.dfDict['param'] = pd.DataFrame(self.paramsDict).set_index('specID')
        for item in self.arrayList:
            # convert resultSetsDict to df
            self.dfDict[item] = pd.DataFrame(self.resultSetsDict[item], columns=energy.tolist(),
                                        index=self.paramsDict['specID'])

        # batch process completed
        self.isBatchProcessOn = False
        msg.showMessage(f'Batch processing is completed! Saving results to csv files.')
        #  save df to files
        self.saveResults()

    def saveResults(self):
        if self.dfDict is None:
            return
        filePath = self.pathList[self.selectMapidx]
        energy = self.out.energy
        saveDataChoice = self.saveResultBox.currentIndex()
        if saveDataChoice != 5:  # save a single result
            saveDataType = self.arrayList[saveDataChoice]
            dirName, csvName, h5Name = self.saveToFiles(energy, self.dfDict, filePath, saveDataType)
            if h5Name is None:
                MsgBox(f'Processed data was saved as csv file at: \n{dirName + csvName}')
            else:
                MsgBox(
                    f'Processed data was saved as: \n\ncsv file at: {dirName + csvName} and \n\nHDF5 file at: {dirName + h5Name}')
        else:  # save all results
            csvList = []
            h5List = []
            for saveDataType in self.arrayList:
                dirName, csvName, h5Name = self.saveToFiles(energy, self.dfDict, filePath, saveDataType)
                csvList.append(csvName)
                h5List.append(h5Name)

            allcsvName = (', ').join(csvList)
            if h5Name is None:
                MsgBox(f'Processed data was saved as csv files at: \n{dirName + allcsvName}')
            else:
                allh5Name = (', ').join(h5List)
                MsgBox(
                    f'Processed data was saved as: \n\ncsv files at: {dirName + allcsvName} and \n\nHDF5 files at: {dirName + allh5Name}')

        # save parameter
        xlsName = csvName[:-4] + '_param.xlsx'
        self.dfDict['param'].to_excel(dirName + xlsName)

    def saveToFiles(self, energy, dfDict, filePath, saveDataType):

        ind2rc = self.ind2rcList[self.selectMapidx]
        n_spectra = self.specItemModel.rowCount()

        # get dirname and old filename
        dirName = os.path.dirname(filePath)
        dirName += '/'
        oldFileName = os.path.basename(filePath)

        # save dataFrames to csv file
        csvName = oldFileName[:-3] + '_' + saveDataType + '.csv'
        dfDict[saveDataType].to_csv(dirName + csvName)

        # if a full map is processed, also save results to a h5 file
        h5Name = None
        if n_spectra == len(ind2rc):
            fullMap = ir_map(filename=filePath)
            fullMap.add_image_cube()
            fullMap.wavenumbers = energy
            fullMap.N_w = len(energy)
            fullMap.data = np.zeros((fullMap.data.shape[0], fullMap.N_w))
            fullMap.imageCube = np.zeros((fullMap.imageCube.shape[0], fullMap.imageCube.shape[1], fullMap.N_w))
            for i in self.paramsDict['specID']:
                fullMap.data[i, :] = self.resultSetsDict[saveDataType][i, :]
                row, col = ind2rc[i]
                fullMap.imageCube[row, col, :] = fullMap.data[i, :] = self.resultSetsDict[saveDataType][i, :]
            # save data as hdf5
            h5Name = oldFileName[:-3] + '_' + saveDataType + '.h5'
            fullMap.write_as_hdf5(dirName + h5Name)

        return dirName, csvName, h5Name
コード例 #15
0
class LibraryCatalogDialog(QDialog):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.setWindowTitle(config.thisTranslation["libraryCatalog"])
        self.setMinimumSize(700, 500)
        self.setupVariables()
        self.setupUI()

    def setupVariables(self):
        self.isUpdating = False
        self.catalogEntryId = None
        self.localCatalog = CatalogUtil.loadLocalCatalog()
        self.remoteCatalog = gitHubRepoCacheData
        self.localCatalogData = self.getLocalCatalogItems()
        self.remoteCatalogData = self.getRemoteCatalogItems()
        self.location = "local"
        self.textButtonStyle = "QPushButton {background-color: #333972; color: white;} QPushButton:hover {background-color: #333972;} QPushButton:pressed { background-color: #515790;}"

    def setupUI(self):
        mainLayout = QVBoxLayout()

        filterLayout = QHBoxLayout()
        filterLayout.addWidget(QLabel(config.thisTranslation["menu5_search"]))
        self.filterEntry = QLineEdit()
        self.filterEntry.setClearButtonEnabled(True)
        self.filterEntry.textChanged.connect(self.resetItems)
        filterLayout.addWidget(self.filterEntry)
        mainLayout.addLayout(filterLayout)

        self.searchTypeBox = QGroupBox("")
        locationLayout = QHBoxLayout()
        self.localRadioButton = QRadioButton("Local")
        self.localRadioButton.setChecked(True)
        self.localRadioButton.toggled.connect(
            lambda: self.setLocation("local"))
        locationLayout.addWidget(self.localRadioButton)
        self.remoteRadioButton = QRadioButton("Remote")
        self.remoteRadioButton.toggled.connect(
            lambda: self.setLocation("remote"))
        locationLayout.addWidget(self.remoteRadioButton)
        self.searchTypeBox.setLayout(locationLayout)
        mainLayout.addWidget(self.searchTypeBox)

        typesLayout = QHBoxLayout()
        button = QPushButton("All")
        button.setStyleSheet(self.textButtonStyle)
        button.clicked.connect(lambda: self.selectAllTypes(True))
        typesLayout.addWidget(button)
        button = QPushButton("None")
        button.setStyleSheet(self.textButtonStyle)
        button.clicked.connect(lambda: self.selectAllTypes(False))
        typesLayout.addWidget(button)
        self.bookCheckbox = QCheckBox("BOOK")
        self.bookCheckbox.setChecked(True)
        self.bookCheckbox.stateChanged.connect(self.resetItems)
        typesLayout.addWidget(self.bookCheckbox)
        self.pdfCheckbox = QCheckBox("PDF")
        self.pdfCheckbox.setChecked(True)
        self.pdfCheckbox.stateChanged.connect(self.resetItems)
        typesLayout.addWidget(self.pdfCheckbox)
        self.docxCheckbox = QCheckBox("DOCX")
        self.docxCheckbox.setChecked(True)
        self.docxCheckbox.stateChanged.connect(self.resetItems)
        typesLayout.addWidget(self.docxCheckbox)
        self.devotionalCheckbox = QCheckBox("DEVOTIONAL")
        self.devotionalCheckbox.setChecked(True)
        self.devotionalCheckbox.stateChanged.connect(self.resetItems)
        typesLayout.addWidget(self.devotionalCheckbox)
        self.commCheckbox = QCheckBox("COMM")
        self.commCheckbox.setChecked(True)
        self.commCheckbox.stateChanged.connect(self.resetItems)
        typesLayout.addWidget(self.commCheckbox)
        self.mp3Checkbox = QCheckBox("MP3")
        self.mp3Checkbox.setChecked(True)
        self.mp3Checkbox.stateChanged.connect(self.resetItems)
        typesLayout.addWidget(self.mp3Checkbox)
        self.mp4Checkbox = QCheckBox("MP4")
        self.mp4Checkbox.setChecked(True)
        self.mp4Checkbox.stateChanged.connect(self.resetItems)
        typesLayout.addWidget(self.mp4Checkbox)
        mainLayout.addLayout(typesLayout)

        self.dataView = QTableView()
        self.dataView.clicked.connect(self.itemClicked)
        self.dataView.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.dataView.setSortingEnabled(True)
        self.dataViewModel = QStandardItemModel(self.dataView)
        self.dataView.setModel(self.dataViewModel)
        self.resetItems()
        mainLayout.addWidget(self.dataView)

        buttonLayout = QHBoxLayout()
        self.openButton = QPushButton(config.thisTranslation["open"])
        self.openButton.setEnabled(True)
        self.openButton.setStyleSheet(self.textButtonStyle)
        self.openButton.clicked.connect(self.open)
        buttonLayout.addWidget(self.openButton)
        self.downloadButton = QPushButton(config.thisTranslation["download"])
        self.downloadButton.setEnabled(False)
        self.downloadButton.clicked.connect(self.download)
        buttonLayout.addWidget(self.downloadButton)
        button = QPushButton(config.thisTranslation["close"])
        button.setStyleSheet(self.textButtonStyle)
        button.clicked.connect(self.close)
        buttonLayout.addWidget(button)
        mainLayout.addLayout(buttonLayout)

        self.setLayout(mainLayout)

    def setLocation(self, location):
        self.location = location
        self.resetItems()
        if location == "local":
            self.openButton.setEnabled(True)
            self.openButton.setStyleSheet(self.textButtonStyle)
            self.downloadButton.setEnabled(False)
        else:
            self.openButton.setEnabled(False)
            self.openButton.setStyleSheet("")
            self.downloadButton.setEnabled(True)

    def selectAllTypes(self, value):
        self.pdfCheckbox.setChecked(value)
        self.mp3Checkbox.setChecked(value)
        self.mp4Checkbox.setChecked(value)
        self.bookCheckbox.setChecked(value)
        self.docxCheckbox.setChecked(value)
        self.devotionalCheckbox.setChecked(value)
        self.commCheckbox.setChecked(value)

    def getLocalCatalogItems(self):
        return self.getCatalogItems(self.localCatalog)

    def getRemoteCatalogItems(self):
        return self.getCatalogItems(self.remoteCatalog)

    def getCatalogItems(self, catalog):
        data = {}
        pdfCount = 0
        mp3Count = 0
        mp4Count = 0
        bookCount = 0
        docxCount = 0
        commCount = 0
        lexCount = 0
        devotionalCount = 0
        for filename, type, directory, file, description, repo, installDirectory, sha in catalog:
            id = "UNKNOWN"
            if type == "PDF":
                pdfCount += 1
                id = "{0}-{1}".format(type, pdfCount)
            elif type == "MP3":
                mp3Count += 1
                id = "{0}-{1}".format(type, mp3Count)
            elif type == "MP4":
                mp4Count += 1
                id = "{0}-{1}".format(type, mp4Count)
            elif type == "BOOK":
                bookCount += 1
                id = "{0}-{1}".format(type, bookCount)
            elif type == "DOCX":
                docxCount += 1
                id = "{0}-{1}".format(type, docxCount)
            elif type == "COMM":
                commCount += 1
                id = "{0}-{1}".format(type, commCount)
            elif type == "LEX":
                lexCount += 1
                id = "{0}-{1}".format(type, lexCount)
            elif type == "DEVOTIONAL":
                devotionalCount += 1
                id = "{0}-{1}".format(type, devotionalCount)
            data[id] = [
                id, filename, type, directory, file, description, repo,
                installDirectory, sha
            ]
        return data

    def resetItems(self):
        self.isUpdating = True
        self.dataViewModel.clear()
        filterEntry = self.filterEntry.text().lower()
        rowCount = 0
        colCount = 0
        catalogData = self.localCatalogData
        if self.location == "remote":
            catalogData = self.remoteCatalogData
        for id, value in catalogData.items():
            id2, filename, type, directory, file, description, repo, installDirectory, sha = value
            if (filterEntry == "" or filterEntry in filename.lower()
                    or filterEntry in description.lower()):
                if (not self.pdfCheckbox.isChecked() and type == "PDF") or \
                        (not self.mp3Checkbox.isChecked() and type == "MP3") or \
                        (not self.mp4Checkbox.isChecked() and type == "MP4") or \
                        (not self.bookCheckbox.isChecked() and type == "BOOK") or \
                        (not self.docxCheckbox.isChecked() and type == "DOCX") or \
                        (not self.devotionalCheckbox.isChecked() and type == "DEVOTIONAL") or \
                        (not self.commCheckbox.isChecked() and type == "COMM"):
                    continue
                enable = True
                if self.location == "remote":
                    installDirectory = os.path.join(config.marvelData,
                                                    installDirectory)
                    if FileUtil.regexFileExists(
                            "{0}.*".format(GithubUtil.getShortname(filename)),
                            installDirectory):
                        enable = False
                item = QStandardItem(id)
                item.setEnabled(enable)
                self.dataViewModel.setItem(rowCount, colCount, item)
                colCount += 1
                item = QStandardItem(file)
                item.setEnabled(enable)
                self.dataViewModel.setItem(rowCount, colCount, item)
                colCount += 1
                item = QStandardItem(directory)
                item.setEnabled(enable)
                self.dataViewModel.setItem(rowCount, colCount, item)
                colCount += 1
                # item = QStandardItem(description)
                # self.dataViewModel.setItem(rowCount, colCount, item)
                # colCount += 1
                # add row count
                rowCount += 1
                colCount = 0
        self.dataViewModel.setHorizontalHeaderLabels([
            "#",
            config.thisTranslation["file"],
            config.thisTranslation["directory"],
            # config.thisTranslation["description"]
        ])
        self.dataView.resizeColumnsToContents()
        self.isUpdating = False

    def itemClicked(self, index):
        selectedRow = index.row()
        self.catalogEntryId = self.dataViewModel.item(selectedRow, 0).text()
        if self.location == "remote":
            item = self.remoteCatalogData[self.catalogEntryId]
            id, filename, type, directory, file, description, repo, installDirectory, sha = item
            installDirectory = os.path.join(config.marvelData,
                                            installDirectory)
            if FileUtil.regexFileExists(
                    "{0}.*".format(GithubUtil.getShortname(filename)),
                    installDirectory):
                self.downloadButton.setEnabled(False)
                self.downloadButton.setStyleSheet("")
            else:
                self.downloadButton.setEnabled(True)
                self.downloadButton.setStyleSheet(self.textButtonStyle)

    def displayMessage(self, message="", title="UniqueBible"):
        QMessageBox.information(self, title, message)

    def saveRemoteCatalogToCache(self):
        data = CatalogUtil.loadRemoteCatalog()
        with open("util/GitHubRepoCache.py", "w", encoding="utf-8") as fileObj:
            fileObj.write("gitHubRepoCacheData = {0}\n".format(
                pprint.pformat(data)))

    def fixDirectory(self, directory, type):
        if type == "PDF":
            directory = directory.replace(config.marvelData, "")
            directory = directory.replace("/pdf", "")
        if len(directory) > 0 and not directory.endswith("/"):
            directory += "/"
        if len(directory) > 0 and directory.startswith("/"):
            directory = directory[1:]
        return directory

    def open(self):
        item = self.localCatalogData[self.catalogEntryId]
        id, filename, type, directory, file, description, repo, installDirectory, sha = item
        directory = self.fixDirectory(directory, type)
        command = ""
        if type == "PDF":
            command = "PDF:::{0}{1}".format(directory, file)
        elif type == "MP3":
            command = "VLC:::{0}{1}".format(directory, file)
        elif type == "MP4":
            command = "VLC:::{0}{1}".format(directory, file)
        elif type == "BOOK":
            if file.endswith(".book"):
                file = file.replace(".book", "")
            config.booksFolder = directory
            command = "BOOK:::{0}".format(file)
        elif type == "COMM":
            file = file.replace(".commentary", "")
            file = file[1:]
            config.commentariesFolder = directory
            command = "COMMENTARY:::{0}:::{1} {2}".format(
                file, BibleBooks.eng[str(config.mainB)][0], config.mainC)
        elif type == "DOCX":
            command = "DOCX:::{0}".format(file)
        elif type == "DEVOTIONAL":
            file = file.replace(".devotional", "")
            command = "DEVOTIONAL:::{0}".format(file)
        self.parent.runTextCommand(command)

    def download(self):
        self.downloadButton.setEnabled(False)
        self.downloadButton.setStyleSheet("")
        item = self.remoteCatalogData[self.catalogEntryId]
        id, filename, type, directory, file, description, repo, installDirectory, sha = item
        github = GithubUtil(repo)
        installDirectory = os.path.join(config.marvelData, installDirectory)
        file = os.path.join(installDirectory, filename + ".zip")
        github.downloadFile(file, sha)
        with zipfile.ZipFile(file, 'r') as zipped:
            zipped.extractall(installDirectory)
        os.remove(file)
        self.displayMessage(filename + " " +
                            config.thisTranslation["message_installed"])
        self.localCatalog = CatalogUtil.reloadLocalCatalog()
        self.localCatalogData = self.getLocalCatalogItems()
        self.resetItems()
コード例 #16
0
class LiveFilterDialog(QDialog):

    JS_HIDE = """
            count = 0;
            searchResultCount = document.getElementById("searchResultCount");
            divs = document.querySelectorAll("div");
            for (var i = 0, len = divs.length; i < len; i++) {{
                div = divs[i];
                div.hidden = {0};
                count++;
            }};
            if (searchResultCount) {{
                searchResultCount.innerHTML = count;
            }}
            """

    JS_SHOW = """
            wordSets = [{0}];
            count = 0;
            searchResultCount = document.getElementById("searchResultCount");
            divs = document.querySelectorAll("div");
            for (var i=0, len=divs.length; i < len; i++) {{
                div = divs[i];
                var found = true;
                for (var j=0, len2=wordSets.length; j < len2; j++) {{
                    wordSet = wordSets[j];
                    var regex;
                    if (wordSet.startsWith("'")) {{
                        wordSet = wordSet.replace("'", "");
                        wordSet = wordSet.replace("'", "");
                        regex = new RegExp(wordSet);
                    }} else {{
                        regex = new RegExp(wordSet, "i");
                    }}
                    found &= regex.test(div.innerHTML);
                }}
                if (found) {{
                    div.hidden = false;
                    count++;
                }}
            }};
            if (searchResultCount) {{
                searchResultCount.innerHTML = count;
            }}
            """

    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.setWindowTitle(config.thisTranslation["liveFilter"])
        self.setMinimumSize(400, 400)
        self.selectedFilter = None
        self.selectedPattern = None
        self.settingBibles = False
        self.db = LiveFilterSqlite()
        self.filters = None
        self.saveReadFormattedBibles = config.readFormattedBibles
        if config.readFormattedBibles:
            self.parent.disableBiblesInParagraphs()
        self.setupUI()

    def setupUI(self):
        mainLayout = QVBoxLayout()

        title = QLabel(config.thisTranslation["liveFilter"])
        mainLayout.addWidget(title)

        self.filtersTable = QTableView()
        self.filtersTable.setEnabled(True)
        self.filtersTable.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.filtersTable.setSortingEnabled(True)
        self.dataViewModel = QStandardItemModel(self.filtersTable)
        self.filtersTable.setModel(self.dataViewModel)
        self.dataViewModel.itemChanged.connect(self.filterSelectionChanged)
        self.selectionModel = self.filtersTable.selectionModel()
        self.selectionModel.selectionChanged.connect(self.handleSelection)
        mainLayout.addWidget(self.filtersTable)
        self.reloadFilters()

        buttonsLayout = QHBoxLayout()
        addButton = QPushButton(config.thisTranslation["add"])
        addButton.clicked.connect(self.addNewFilter)
        buttonsLayout.addWidget(addButton)
        removeButton = QPushButton(config.thisTranslation["remove"])
        removeButton.clicked.connect(self.removeFilter)
        buttonsLayout.addWidget(removeButton)
        editButton = QPushButton(config.thisTranslation["edit"])
        editButton.clicked.connect(self.editFilter)
        buttonsLayout.addWidget(editButton)
        importButton = QPushButton(config.thisTranslation["import"])
        importButton.clicked.connect(self.importFile)
        buttonsLayout.addWidget(importButton)
        buttonsLayout.addStretch()
        mainLayout.addLayout(buttonsLayout)

        buttons = QDialogButtonBox.Ok
        self.buttonBox = QDialogButtonBox(buttons)
        self.buttonBox.accepted.connect(self.accept)
        self.buttonBox.accepted.connect(self.close)
        self.buttonBox.rejected.connect(self.reject)
        mainLayout.addWidget(self.buttonBox)

        self.setLayout(mainLayout)

    def close(self):
        pass

    def reloadFilters(self):
        self.filters = self.db.getAll()
        self.dataViewModel.clear()
        rowCount = 0
        for bible, description in self.filters:
            item = QStandardItem(bible)
            item.setToolTip(bible)
            item.setCheckable(True)
            self.dataViewModel.setItem(rowCount, 0, item)
            item = QStandardItem(description)
            self.dataViewModel.setItem(rowCount, 1, item)
            rowCount += 1
        self.dataViewModel.setHorizontalHeaderLabels([
            config.thisTranslation["filter2"],
            config.thisTranslation["pattern"]
        ])
        self.filtersTable.resizeColumnsToContents()

    def handleSelection(self, selected, deselected):
        for item in selected:
            row = item.indexes()[0].row()
            filter = self.dataViewModel.item(row, 0)
            self.selectedFilter = filter.text()
            pattern = self.dataViewModel.item(row, 1)
            self.selectedPattern = pattern.text()

    def filterSelectionChanged(self, item):
        try:
            numChecked = 0
            for index in range(self.dataViewModel.rowCount()):
                item = self.dataViewModel.item(index)
                if item.checkState() == Qt.Checked:
                    numChecked += 1
            if numChecked == 0:
                config.mainWindow.studyPage.runJavaScript(
                    self.JS_HIDE.format("false"))
            else:
                sets = []
                config.mainWindow.studyPage.runJavaScript(
                    self.JS_HIDE.format("true"))
                for index in range(self.dataViewModel.rowCount()):
                    item = self.dataViewModel.item(index)
                    if item.checkState() == Qt.Checked:
                        sets.append('"{0}"'.format(self.filters[index][1]))
                wordSets = ",".join(sets)
                js = self.JS_SHOW.format(wordSets)
                config.mainWindow.studyPage.runJavaScript(js)
        except Exception as e:
            print(str(e))

    def addNewFilter(self):
        fields = [(config.thisTranslation["filter2"], ""),
                  (config.thisTranslation["pattern"], "")]
        dialog = MultiLineInputDialog("New Filter", fields)
        if dialog.exec():
            data = dialog.getInputs()
            self.db.insert(data[0], data[1])
            self.reloadFilters()

    def removeFilter(self):
        reply = QMessageBox.question(
            self, "Delete",
            'Delete {0} {1}'.format(self.selectedFilter,
                                    config.thisTranslation["filter2"]),
            QMessageBox.Yes | QMessageBox.No)
        if reply == QMessageBox.Yes:
            self.db.delete(self.selectedFilter)
            self.reloadFilters()

    def editFilter(self):
        fields = [(config.thisTranslation["filter2"], self.selectedFilter),
                  (config.thisTranslation["pattern"], self.selectedPattern)]
        dialog = MultiLineInputDialog("Edit Filter", fields)
        if dialog.exec():
            data = dialog.getInputs()
            self.db.delete(self.selectedFilter)
            self.db.insert(data[0], data[1])
            self.reloadFilters()

    def importFile(self):
        options = QFileDialog.Options()
        filename, filtr = QFileDialog.getOpenFileName(
            self, config.thisTranslation["import"],
            config.thisTranslation["liveFilter"], "File (*.*)", "", options)
        if filename:
            try:
                with open(filename, errors='ignore') as f:
                    for line in f:
                        data = line.split(":::")
                        filter = data[0].strip()
                        pattern = data[1].strip()
                        if self.db.checkFilterExists(filter):
                            self.db.delete(filter)
                        self.db.insert(filter, pattern)
            except Exception as e:
                print(e)
            self.reloadFilters()
コード例 #17
0
class ConfigFlagsWindow(QDialog):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        # set title
        self.setWindowTitle(config.thisTranslation["menu_config_flags"])
        self.setMinimumSize(830, 500)
        # set variables
        self.setupVariables()
        # setup interface
        self.setupUI()

    def setupVariables(self):
        self.isUpdating = False

    def setupUI(self):
        mainLayout = QVBoxLayout()

        title = QLabel(config.thisTranslation["menu_config_flags"])
        title.mouseReleaseEvent = self.openWiki
        mainLayout.addWidget(title)

        filterLayout = QHBoxLayout()
        filterLayout.addWidget(QLabel(config.thisTranslation["menu5_search"]))
        self.filterEntry = QLineEdit()
        self.filterEntry.textChanged.connect(self.resetItems)
        filterLayout.addWidget(self.filterEntry)
        mainLayout.addLayout(filterLayout)

        self.dataView = QTableView()
        self.dataView.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.dataView.setSortingEnabled(True)
        self.dataViewModel = QStandardItemModel(self.dataView)
        self.dataView.setModel(self.dataViewModel)
        self.resetItems()
        self.dataViewModel.itemChanged.connect(self.itemChanged)
        mainLayout.addWidget(self.dataView)

        buttonLayout = QHBoxLayout()
        button = QPushButton(config.thisTranslation["close"])
        button.clicked.connect(self.close)
        buttonLayout.addWidget(button)
        button = QPushButton(config.thisTranslation["restoreAllDefaults"])
        button.clicked.connect(self.restoreAllDefaults)
        buttonLayout.addWidget(button)
        mainLayout.addLayout(buttonLayout)

        self.setLayout(mainLayout)

    def getOptions(self):
        options = [
            ("showControlPanelOnStartup", config.showControlPanelOnStartup,
             self.showControlPanelOnStartupChanged, False,
             config.thisTranslation["showControlPanelOnStartup"]),
            ("preferControlPanelForCommandLineEntry",
             config.preferControlPanelForCommandLineEntry,
             self.preferControlPanelForCommandLineEntryChanged, False,
             config.thisTranslation["preferControlPanelForCommandLineEntry"]),
            ("closeControlPanelAfterRunningCommand",
             config.closeControlPanelAfterRunningCommand,
             self.closeControlPanelAfterRunningCommandChanged, True,
             config.thisTranslation["closeControlPanelAfterRunningCommand"]),
            ("restrictControlPanelWidth", config.restrictControlPanelWidth,
             self.restrictControlPanelWidthChanged, False,
             config.thisTranslation["restrictControlPanelWidth"]),
            ("clearCommandEntry", config.clearCommandEntry,
             self.clearCommandEntryChanged, False,
             config.thisTranslation["clearCommandEntry"]),
            ("openBibleWindowContentOnNextTab",
             config.openBibleWindowContentOnNextTab,
             self.openBibleWindowContentOnNextTabChanged, False,
             config.thisTranslation["openBibleWindowContentOnNextTab"]),
            ("openStudyWindowContentOnNextTab",
             config.openStudyWindowContentOnNextTab,
             self.openStudyWindowContentOnNextTabChanged, True,
             config.thisTranslation["openStudyWindowContentOnNextTab"]),
            ("populateTabsOnStartup", config.populateTabsOnStartup,
             self.populateTabsOnStartupChanged, False,
             config.thisTranslation["populateTabsOnStartup"]),
            ("qtMaterial", config.qtMaterial, self.qtMaterialChanged, False,
             config.thisTranslation["qtMaterial"]),
            ("addBreakAfterTheFirstToolBar",
             config.addBreakAfterTheFirstToolBar,
             self.addBreakAfterTheFirstToolBarChanged, True,
             config.thisTranslation["addBreakAfterTheFirstToolBar"]),
            ("addBreakBeforeTheLastToolBar",
             config.addBreakBeforeTheLastToolBar,
             self.addBreakBeforeTheLastToolBarChanged, False,
             config.thisTranslation["addBreakBeforeTheLastToolBar"]),
            ("parserStandarisation", (config.parserStandarisation == "YES"),
             self.parserStandarisationChanged, False,
             config.thisTranslation["parserStandarisation"]),
            ("useLiteVerseParsing", config.useLiteVerseParsing,
             self.useLiteVerseParsingChanged, False,
             config.thisTranslation["useLiteVerseParsing"]),
            ("parseEnglishBooksOnly", config.parseEnglishBooksOnly,
             self.parseEnglishBooksOnlyChanged, False,
             config.thisTranslation["parseEnglishBooksOnly"]),
            ("parseWordDocument", config.parseWordDocument,
             self.parseWordDocumentChanged, True,
             config.thisTranslation["parseWordDocument"]),
            ("convertChapterVerseDotSeparator",
             config.convertChapterVerseDotSeparator,
             self.convertChapterVerseDotSeparatorChanged, True,
             config.thisTranslation["convertChapterVerseDotSeparator"]),
            ("parseBookChapterWithoutSpace",
             config.parseBookChapterWithoutSpace,
             self.parseBookChapterWithoutSpaceChanged, True,
             config.thisTranslation["parseBookChapterWithoutSpace"]),
            ("parseBooklessReferences", config.parseBooklessReferences,
             self.parseBooklessReferencesChanged, True,
             config.thisTranslation["parseBooklessReferences"]),
            ("searchBibleIfCommandNotFound",
             config.searchBibleIfCommandNotFound,
             self.searchBibleIfCommandNotFoundChanged, True,
             config.thisTranslation["searchBibleIfCommandNotFound"]),
            ("regexSearchBibleIfCommandNotFound",
             config.regexSearchBibleIfCommandNotFound,
             self.regexSearchBibleIfCommandNotFoundChanged, False,
             config.thisTranslation["regexSearchBibleIfCommandNotFound"]),
            ("preferHtmlMenu", config.preferHtmlMenu,
             self.preferHtmlMenuChanged, False,
             config.thisTranslation["preferHtmlMenu"]),
            ("showVerseNumbersInRange", config.showVerseNumbersInRange,
             self.showVerseNumbersInRangeChanged, True,
             config.thisTranslation["showVerseNumbersInRange"]),
            ("addFavouriteToMultiRef", config.addFavouriteToMultiRef,
             self.addFavouriteToMultiRefChanged, False,
             config.thisTranslation["addFavouriteToMultiRef"]),
            ("enableVerseHighlighting", config.enableVerseHighlighting,
             self.enableVerseHighlightingChanged, True,
             config.thisTranslation["enableVerseHighlighting"]),
            ("regexCaseSensitive", config.regexCaseSensitive,
             self.regexCaseSensitiveChanged, False,
             config.thisTranslation["regexCaseSensitive"]),
            ("alwaysDisplayStaticMaps", config.alwaysDisplayStaticMaps,
             self.alwaysDisplayStaticMapsChanged, False,
             config.thisTranslation["alwaysDisplayStaticMaps"]),
            ("exportEmbeddedImages", config.exportEmbeddedImages,
             self.exportEmbeddedImagesChanged, True,
             config.thisTranslation["exportEmbeddedImages"]),
            ("clickToOpenImage", config.clickToOpenImage,
             self.clickToOpenImageChanged, True,
             config.thisTranslation["clickToOpenImage"]),
            ("showNoteIndicatorOnBibleChapter",
             config.showNoteIndicatorOnBibleChapter,
             self.parent.enableNoteIndicatorButtonClicked, True,
             config.thisTranslation["showNoteIndicatorOnBibleChapter"]),
            ("openBibleNoteAfterSave", config.openBibleNoteAfterSave,
             self.openBibleNoteAfterSaveChanged, False,
             config.thisTranslation["openBibleNoteAfterSave"]),
            ("openBibleNoteAfterEditorClosed",
             config.openBibleNoteAfterEditorClosed,
             self.openBibleNoteAfterEditorClosedChanged, False,
             config.thisTranslation["openBibleNoteAfterEditorClosed"]),
            ("hideNoteEditorStyleToolbar", config.hideNoteEditorStyleToolbar,
             self.hideNoteEditorStyleToolbarChanged, False,
             config.thisTranslation["hideNoteEditorStyleToolbar"]),
            ("hideNoteEditorTextUtility", config.hideNoteEditorTextUtility,
             self.hideNoteEditorTextUtilityChanged, True,
             config.thisTranslation["hideNoteEditorTextUtility"]),
            ("overwriteNoteFont", config.overwriteNoteFont,
             self.overwriteNoteFontChanged, True,
             config.thisTranslation["overwriteNoteFont"]),
            ("overwriteNoteFontSize", config.overwriteNoteFontSize,
             self.overwriteNoteFontSizeChanged, True,
             config.thisTranslation["overwriteNoteFontSize"]),
            ("overwriteBookFont", config.overwriteBookFont,
             self.overwriteBookFontChanged, True,
             config.thisTranslation["overwriteBookFont"]),
            ("overwriteBookFontSize", config.overwriteBookFontSize,
             self.overwriteBookFontSizeChanged, True,
             config.thisTranslation["overwriteBookFontSize"]),
            ("openBookInNewWindow", config.openBookInNewWindow,
             self.openBookInNewWindowChanged, False,
             config.thisTranslation["openBookInNewWindow"]),
            ("openPdfViewerInNewWindow", config.openPdfViewerInNewWindow,
             self.openPdfViewerInNewWindowChanged, False,
             config.thisTranslation["openPdfViewerInNewWindow"]),
            ("virtualKeyboard", config.virtualKeyboard,
             self.virtualKeyboardChanged, False,
             config.thisTranslation["virtualKeyboard"]),
            ("useWebbrowser", config.useWebbrowser, self.useWebbrowserChanged,
             True, config.thisTranslation["useWebbrowser"]),
            ("removeHighlightOnExit", config.removeHighlightOnExit,
             self.removeHighlightOnExitChanged, False,
             config.thisTranslation["removeHighlightOnExit"]),
            ("disableModulesUpdateCheck", config.disableModulesUpdateCheck,
             self.disableModulesUpdateCheckChanged, True,
             config.thisTranslation["disableModulesUpdateCheck"]),
            ("updateWithGitPull", config.updateWithGitPull,
             self.updateWithGitPullChanged, False,
             config.thisTranslation["updateWithGitPull"]),
            ("enableGist", config.enableGist, self.enableGistChanged, False,
             config.thisTranslation["enableGist"]),
            ("enableMacros", config.enableMacros, self.enableMacrosChanged,
             False, config.thisTranslation["enableMacros"]),
            ("enablePlugins", config.enablePlugins, self.enablePluginsChanged,
             True, config.thisTranslation["enablePlugins"]),
            ("hideBlankVerseCompare", config.hideBlankVerseCompare,
             self.hideBlankVerseCompareChanged, False,
             config.thisTranslation["hideBlankVerseCompare"]),
            ("enforceCompareParallel", config.enforceCompareParallel,
             self.parent.enforceCompareParallelButtonClicked, False,
             config.thisTranslation["enforceCompareParallel"]),
            ("enableMenuUnderline", config.enableMenuUnderline,
             self.enableMenuUnderlineChanged, True,
             config.thisTranslation["enableMenuUnderline"]),
            ("openBibleInMainViewOnly", config.openBibleInMainViewOnly,
             self.parent.enableStudyBibleButtonClicked, False,
             config.thisTranslation["openBibleInMainViewOnly"]),
            ("addOHGBiToMorphologySearch", config.addOHGBiToMorphologySearch,
             self.addOHGBiToMorphologySearchChanged, True,
             config.thisTranslation["addOHGBiToMorphologySearch"]),
            ("includeStrictDocTypeInNote", config.includeStrictDocTypeInNote,
             self.includeStrictDocTypeInNoteChanged, False,
             config.thisTranslation["includeStrictDocTypeInNote"]),
            ("parseTextConvertNotesToBook", config.parseTextConvertNotesToBook,
             self.parseTextConvertNotesToBookChanged, False,
             config.thisTranslation["parseTextConvertNotesToBook"]),
            ("parseTextConvertHTMLToBook", config.parseTextConvertHTMLToBook,
             self.parseTextConvertHTMLToBookChanged, False,
             config.thisTranslation["parseTextConvertHTMLToBook"]),
            ("displayCmdOutput", config.displayCmdOutput,
             self.displayCmdOutputChanged, False,
             config.thisTranslation["displayCmdOutput"]),
            ("disableLoadLastOpenFilesOnStartup",
             config.disableLoadLastOpenFilesOnStartup,
             self.disableLoadLastOpenFilesOnStartupChanged, False,
             config.thisTranslation["disableLoadLastOpenFilesOnStartup"]),
            ("disableOpenPopupWindowOnStartup",
             config.disableOpenPopupWindowOnStartup,
             self.disableOpenPopupWindowOnStartupChanged, True,
             config.thisTranslation["disableOpenPopupWindowOnStartup"]),
            ("showMiniKeyboardInMiniControl",
             config.showMiniKeyboardInMiniControl,
             self.showMiniKeyboardInMiniControlChanged, False,
             config.thisTranslation["showMiniKeyboardInMiniControl"]),
        ]
        if config.isTtsInstalled:
            options += [
                ("useLangDetectOnTts", config.useLangDetectOnTts,
                 self.useLangDetectOnTtsChanged, False,
                 config.thisTranslation["useLangDetectOnTts"]),
                ("ttsEnglishAlwaysUS", config.ttsEnglishAlwaysUS,
                 self.ttsEnglishAlwaysUSChanged, False,
                 config.thisTranslation["ttsEnglishAlwaysUS"]),
                ("ttsEnglishAlwaysUK", config.ttsEnglishAlwaysUK,
                 self.ttsEnglishAlwaysUKChanged, False,
                 config.thisTranslation["ttsEnglishAlwaysUK"]),
                ("ttsChineseAlwaysMandarin", config.ttsChineseAlwaysMandarin,
                 self.ttsChineseAlwaysMandarinChanged, False,
                 config.thisTranslation["ttsChineseAlwaysMandarin"]),
                ("ttsChineseAlwaysCantonese", config.ttsChineseAlwaysCantonese,
                 self.ttsChineseAlwaysCantoneseChanged, False,
                 config.thisTranslation["ttsChineseAlwaysCantonese"]),
            ]
        if platform.system() == "Linux":
            options += [
                ("linuxStartFullScreen", config.linuxStartFullScreen,
                 self.linuxStartFullScreenChanged, False,
                 config.thisTranslation["linuxStartFullScreen"]),
                ("fcitx", config.fcitx, self.fcitxChanged, False,
                 config.thisTranslation["fcitx"]),
                ("ibus", config.ibus, self.ibusChanged, False,
                 config.thisTranslation["ibus"]),
                ("espeak", config.espeak, self.espeakChanged, False,
                 config.thisTranslation["espeak"]),
            ]
        if config.developer:
            options += [
                ("forceGenerateHtml", config.forceGenerateHtml,
                 self.forceGenerateHtmlChanged, False,
                 config.thisTranslation["forceGenerateHtml"]),
                ("enableLogging", config.enableLogging,
                 self.enableLoggingChanged, False,
                 config.thisTranslation["enableLogging"]),
                ("logCommands", config.logCommands, self.logCommandsChanged,
                 False, config.thisTranslation["logCommands"]),
            ]
        data = {}
        for flag, configValue, action, default, tooltip in options:
            data[flag] = [configValue, default, tooltip, action]
        return data

    def restoreAllDefaults(self):
        for key, value in self.data.items():
            code = "config.{0} = {1}".format(key, value[1])
            exec(code)
        self.resetItems()
        self.displayMessage(config.thisTranslation["message_restart"])

    def itemChanged(self, standardItem):
        flag = standardItem.text()
        if flag in self.data and not self.isUpdating:
            self.data[flag][-1]()

    def resetItems(self):
        self.isUpdating = True
        # Empty the model before reset
        self.dataViewModel.clear()
        # Reset
        self.data = self.getOptions()
        filterEntry = self.filterEntry.text().lower()
        rowCount = 0
        for flag, value in self.data.items():
            configValue, default, tooltip, *_ = value
            if filterEntry == "" or (filterEntry != "" and
                                     (filterEntry in flag.lower()
                                      or filterEntry in tooltip.lower())):
                # 1st column
                item = QStandardItem(flag)
                item.setToolTip(tooltip)
                item.setCheckable(True)
                item.setCheckState(Qt.CheckState.Checked
                                   if configValue else Qt.CheckState.Unchecked)
                self.dataViewModel.setItem(rowCount, 0, item)
                # 2nd column
                item = QStandardItem(str(default))
                self.dataViewModel.setItem(rowCount, 1, item)
                # 3rd column
                tooltip = tooltip.replace("\n", " ")
                item = QStandardItem(tooltip)
                item.setToolTip(tooltip)
                self.dataViewModel.setItem(rowCount, 2, item)
                # add row count
                rowCount += 1
        self.dataViewModel.setHorizontalHeaderLabels([
            config.thisTranslation["flag"], config.thisTranslation["default"],
            config.thisTranslation["description"]
        ])
        self.dataView.resizeColumnsToContents()
        self.isUpdating = False

    def displayMessage(self, message="", title="UniqueBible"):
        QMessageBox.information(self, title, message)

    def openWiki(self, event):
        wikiLink = "https://github.com/eliranwong/UniqueBible/wiki/Config-file-reference"
        webbrowser.open(wikiLink)

    def ibusChanged(self):
        config.ibus = not config.ibus
        if config.fcitx and config.ibus:
            config.fcitx = not config.fcitx
        if config.virtualKeyboard and config.ibus:
            config.virtualKeyboard = not config.virtualKeyboard
        self.displayMessage(config.thisTranslation["message_restart"])

    def fcitxChanged(self):
        config.fcitx = not config.fcitx
        if config.fcitx and config.ibus:
            config.ibus = not config.ibus
        if config.fcitx and config.virtualKeyboard:
            config.virtualKeyboard = not config.virtualKeyboard
        self.displayMessage(config.thisTranslation["message_restart"])

    def virtualKeyboardChanged(self):
        config.virtualKeyboard = not config.virtualKeyboard
        if config.fcitx and config.virtualKeyboard:
            config.fcitx = not config.fcitx
        if config.virtualKeyboard and config.ibus:
            config.ibus = not config.ibus
        self.displayMessage(config.thisTranslation["message_restart"])

    def parseWordDocumentChanged(self):
        config.parseWordDocument = not config.parseWordDocument

    def useLangDetectOnTtsChanged(self):
        config.useLangDetectOnTts = not config.useLangDetectOnTts

    def ttsEnglishAlwaysUSChanged(self):
        config.ttsEnglishAlwaysUS = not config.ttsEnglishAlwaysUS
        if config.ttsEnglishAlwaysUK and config.ttsEnglishAlwaysUS:
            config.ttsEnglishAlwaysUK = not config.ttsEnglishAlwaysUK

    def ttsEnglishAlwaysUKChanged(self):
        config.ttsEnglishAlwaysUK = not config.ttsEnglishAlwaysUK
        if config.ttsEnglishAlwaysUK and config.ttsEnglishAlwaysUS:
            config.ttsEnglishAlwaysUS = not config.ttsEnglishAlwaysUS

    def ttsChineseAlwaysMandarinChanged(self):
        config.ttsChineseAlwaysMandarin = not config.ttsChineseAlwaysMandarin
        if config.ttsChineseAlwaysMandarin and config.ttsChineseAlwaysCantonese:
            config.ttsChineseAlwaysCantonese = not config.ttsChineseAlwaysCantonese

    def ttsChineseAlwaysCantoneseChanged(self):
        config.ttsChineseAlwaysCantonese = not config.ttsChineseAlwaysCantonese
        if config.ttsChineseAlwaysMandarin and config.ttsChineseAlwaysCantonese:
            config.ttsChineseAlwaysMandarin = not config.ttsChineseAlwaysMandarin

    def showVerseNumbersInRangeChanged(self):
        config.showVerseNumbersInRange = not config.showVerseNumbersInRange

    #def customPythonOnStartupChanged(self):
    #    config.customPythonOnStartup = not config.customPythonOnStartup

    def openBibleWindowContentOnNextTabChanged(self):
        config.openBibleWindowContentOnNextTab = not config.openBibleWindowContentOnNextTab
        self.newTabException = False

    def showControlPanelOnStartupChanged(self):
        config.showControlPanelOnStartup = not config.showControlPanelOnStartup
        self.displayMessage(config.thisTranslation["message_restart"])

    def preferControlPanelForCommandLineEntryChanged(self):
        config.preferControlPanelForCommandLineEntry = not config.preferControlPanelForCommandLineEntry
        self.displayMessage(config.thisTranslation["message_restart"])

    def closeControlPanelAfterRunningCommandChanged(self):
        config.closeControlPanelAfterRunningCommand = not config.closeControlPanelAfterRunningCommand

    def restrictControlPanelWidthChanged(self):
        config.restrictControlPanelWidth = not config.restrictControlPanelWidth
        self.parent.reloadControlPanel(False)

    def regexCaseSensitiveChanged(self):
        config.regexCaseSensitive = not config.regexCaseSensitive

    def openStudyWindowContentOnNextTabChanged(self):
        config.openStudyWindowContentOnNextTab = not config.openStudyWindowContentOnNextTab
        self.newTabException = False

    def addFavouriteToMultiRefChanged(self):
        config.addFavouriteToMultiRef = not config.addFavouriteToMultiRef

    def addOHGBiToMorphologySearchChanged(self):
        config.addOHGBiToMorphologySearch = not config.addOHGBiToMorphologySearch

    def exportEmbeddedImagesChanged(self):
        config.exportEmbeddedImages = not config.exportEmbeddedImages

    def clickToOpenImageChanged(self):
        config.clickToOpenImage = not config.clickToOpenImage

    def openBibleNoteAfterEditorClosedChanged(self):
        config.openBibleNoteAfterEditorClosed = not config.openBibleNoteAfterEditorClosed

    def preferHtmlMenuChanged(self):
        config.preferHtmlMenu = not config.preferHtmlMenu

    def hideNoteEditorStyleToolbarChanged(self):
        config.hideNoteEditorStyleToolbar = not config.hideNoteEditorStyleToolbar

    def hideNoteEditorTextUtilityChanged(self):
        config.hideNoteEditorTextUtility = not config.hideNoteEditorTextUtility

    def populateTabsOnStartupChanged(self):
        config.populateTabsOnStartup = not config.populateTabsOnStartup

    def openBookInNewWindowChanged(self):
        config.openBookInNewWindow = not config.openBookInNewWindow

    def convertChapterVerseDotSeparatorChanged(self):
        config.convertChapterVerseDotSeparator = not config.convertChapterVerseDotSeparator

    def updateWithGitPullChanged(self):
        config.updateWithGitPull = not config.updateWithGitPull
        if config.updateWithGitPull and not os.path.isdir(".git"):
            config.updateWithGitPull = False

    def parseBookChapterWithoutSpaceChanged(self):
        config.parseBookChapterWithoutSpace = not config.parseBookChapterWithoutSpace

    def parseBooklessReferencesChanged(self):
        config.parseBooklessReferences = not config.parseBooklessReferences

    def openPdfViewerInNewWindowChanged(self):
        config.openPdfViewerInNewWindow = not config.openPdfViewerInNewWindow

    def searchBibleIfCommandNotFoundChanged(self):
        config.searchBibleIfCommandNotFound = not config.searchBibleIfCommandNotFound

    def regexSearchBibleIfCommandNotFoundChanged(self):
        config.regexSearchBibleIfCommandNotFound = not config.regexSearchBibleIfCommandNotFound
        if config.regexSearchBibleIfCommandNotFound and not config.searchBibleIfCommandNotFound:
            config.searchBibleIfCommandNotFound = True

    def overwriteNoteFontChanged(self):
        config.overwriteNoteFont = not config.overwriteNoteFont

    def overwriteNoteFontSizeChanged(self):
        config.overwriteNoteFontSize = not config.overwriteNoteFontSize

    def overwriteBookFontChanged(self):
        config.overwriteBookFont = not config.overwriteBookFont

    def useWebbrowserChanged(self):
        config.useWebbrowser = not config.useWebbrowser

    def removeHighlightOnExitChanged(self):
        config.removeHighlightOnExit = not config.removeHighlightOnExit

    def overwriteBookFontSizeChanged(self):
        config.overwriteBookFontSize = not config.overwriteBookFontSize

    def alwaysDisplayStaticMapsChanged(self):
        config.alwaysDisplayStaticMaps = not config.alwaysDisplayStaticMaps

    def openBibleNoteAfterSaveChanged(self):
        config.openBibleNoteAfterSave = not config.openBibleNoteAfterSave

    def addBreakAfterTheFirstToolBarChanged(self):
        config.addBreakAfterTheFirstToolBar = not config.addBreakAfterTheFirstToolBar
        self.displayMessage(config.thisTranslation["message_restart"])

    def addBreakBeforeTheLastToolBarChanged(self):
        config.addBreakBeforeTheLastToolBar = not config.addBreakBeforeTheLastToolBar
        self.displayMessage(config.thisTranslation["message_restart"])

    def disableModulesUpdateCheckChanged(self):
        config.disableModulesUpdateCheck = not config.disableModulesUpdateCheck

    def forceGenerateHtmlChanged(self):
        config.forceGenerateHtml = not config.forceGenerateHtml

    def parserStandarisationChanged(self):
        if config.parserStandarisation == "YES":
            config.parserStandarisation = "NO"
        else:
            config.parserStandarisation = "YES"

    def linuxStartFullScreenChanged(self):
        config.linuxStartFullScreen = not config.linuxStartFullScreen
        self.displayMessage(config.thisTranslation["message_restart"])

    def espeakChanged(self):
        config.espeak = not config.espeak
        self.displayMessage(config.thisTranslation["message_restart"])

    def enableLoggingChanged(self):
        config.enableLogging = not config.enableLogging
        self.displayMessage(config.thisTranslation["message_restart"])

    def logCommandsChanged(self):
        config.logCommands = not config.logCommands

    def enableVerseHighlightingChanged(self):
        config.enableVerseHighlighting = not config.enableVerseHighlighting
        self.displayMessage(config.thisTranslation["message_restart"])

    def useLiteVerseParsingChanged(self):
        config.useLiteVerseParsing = not config.useLiteVerseParsing

    def parseEnglishBooksOnlyChanged(self):
        config.parseEnglishBooksOnly = not config.parseEnglishBooksOnly

    def enableMacrosChanged(self):
        config.enableMacros = not config.enableMacros
        self.displayMessage(config.thisTranslation["message_restart"])

    def enablePluginsChanged(self):
        config.enablePlugins = not config.enablePlugins
        self.parent.setMenuLayout(config.menuLayout)

    def clearCommandEntryChanged(self):
        config.clearCommandEntry = not config.clearCommandEntry

    def qtMaterialChanged(self):
        if not config.qtMaterial:
            self.parent.enableQtMaterial(True)
        else:
            self.parent.enableQtMaterial(False)

    def enableGistChanged(self):
        if not config.enableGist and config.isPygithubInstalled:
            config.enableGist = True
            self.displayMessage(config.thisTranslation["message_restart"])
        elif config.enableGist:
            config.enableGist = not config.enableGist
            self.displayMessage(config.thisTranslation["message_restart"])
        else:
            self.displayMessage(config.thisTranslation["message_noSupport"])

    def hideBlankVerseCompareChanged(self):
        config.hideBlankVerseCompare = not config.hideBlankVerseCompare

    def enableMenuUnderlineChanged(self):
        config.enableMenuUnderline = not config.enableMenuUnderline
        if config.enableMenuUnderline:
            config.menuUnderline = "&"
        else:
            config.menuUnderline = ""
        self.parent.setMenuLayout(config.menuLayout)

    def includeStrictDocTypeInNoteChanged(self):
        config.includeStrictDocTypeInNote = not config.includeStrictDocTypeInNote

    def parseTextConvertNotesToBookChanged(self):
        config.parseTextConvertNotesToBook = not config.parseTextConvertNotesToBook

    def parseTextConvertHTMLToBookChanged(self):
        config.parseTextConvertHTMLToBook = not config.parseTextConvertHTMLToBook

    def displayCmdOutputChanged(self):
        config.displayCmdOutput = not config.displayCmdOutput

    def disableLoadLastOpenFilesOnStartupChanged(self):
        config.disableLoadLastOpenFilesOnStartup = not config.disableLoadLastOpenFilesOnStartup

    def disableOpenPopupWindowOnStartupChanged(self):
        config.disableOpenPopupWindowOnStartup = not config.disableOpenPopupWindowOnStartup

    def showMiniKeyboardInMiniControlChanged(self):
        config.showMiniKeyboardInMiniControl = not config.showMiniKeyboardInMiniControl
コード例 #18
0
class BibleCollectionDialog(QDialog):
    def __init__(self, parent):
        super().__init__()
        self.setWindowTitle(config.thisTranslation["bibleCollections"])
        self.setMinimumSize(680, 500)
        self.selectedCollection = None
        self.settingBibles = False
        self.bibles = self.getBibles()
        self.setupUI()
        self.parent = parent

    def setupUI(self):
        mainLayout = QVBoxLayout()

        title = QLabel(config.thisTranslation["bibleCollections"])
        mainLayout.addWidget(title)

        self.collectionsLayout = QVBoxLayout()
        self.collectionsList = QListWidget()
        self.collectionsList.setMaximumHeight(90)
        self.collectionsLayout.addWidget(self.collectionsList)
        mainLayout.addLayout(self.collectionsLayout)
        self.showListOfCollections()

        buttonsLayout = QHBoxLayout()
        addButton = QPushButton(config.thisTranslation["add"])
        addButton.clicked.connect(self.addNewCollection)
        buttonsLayout.addWidget(addButton)
        removeButton = QPushButton(config.thisTranslation["remove"])
        removeButton.clicked.connect(self.removeCollection)
        buttonsLayout.addWidget(removeButton)
        renameButton = QPushButton(config.thisTranslation["rename"])
        renameButton.clicked.connect(self.renameCollection)
        buttonsLayout.addWidget(renameButton)
        buttonsLayout.addStretch()
        mainLayout.addLayout(buttonsLayout)

        self.biblesTable = QTableView()
        self.biblesTable.setEnabled(False)
        self.biblesTable.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.biblesTable.setSortingEnabled(True)
        self.dataViewModel = QStandardItemModel(self.biblesTable)
        self.biblesTable.setModel(self.dataViewModel)
        self.loadBibleSelection()
        self.dataViewModel.itemChanged.connect(self.bibleSelectionChanged)
        mainLayout.addWidget(self.biblesTable)

        buttonLayout = QHBoxLayout()
        button = QPushButton(config.thisTranslation["close"])
        button.clicked.connect(self.reloadControlPanel)
        button.clicked.connect(self.close)
        buttonLayout.addWidget(button)
        mainLayout.addLayout(buttonLayout)

        self.setLayout(mainLayout)

    def showListOfCollections(self):
        self.collectionsList.clear()
        if len(config.bibleCollections) > 0:
            for collection in sorted(config.bibleCollections.keys()):
                showBibleSelection = QRadioButton()
                showBibleSelection.setChecked(False)
                self.collectionsList.itemClicked.connect(self.selectCollection)
                self.collectionsList.addItem(collection)
        else:
            self.collectionsList.addItem("[No collection defined]")

    def addNewCollection(self):
        name, ok = QInputDialog.getText(self, 'Collection', 'Collection name:')
        if ok and len(name) > 0 and name != "All":
            config.bibleCollections[name] = {}
            self.showListOfCollections()
            self.biblesTable.setEnabled(False)

    def removeCollection(self):
        config.bibleCollections.pop(self.selectedCollection, None)
        self.showListOfCollections()
        self.biblesTable.setEnabled(False)

    def renameCollection(self):
        name, ok = QInputDialog.getText(self,
                                        'Collection',
                                        'Collection name:',
                                        text=self.selectedCollection)
        if ok and len(name) > 0 and name != "All":
            biblesInCollection = config.bibleCollections[
                self.selectedCollection]
            config.bibleCollections.pop(self.selectedCollection, None)
            self.selectedCollection = name
            config.bibleCollections[name] = biblesInCollection
            self.showListOfCollections()
            self.biblesTable.setEnabled(False)

    def getBibles(self):
        from db.BiblesSqlite import BiblesSqlite
        from db.BiblesSqlite import Bible

        bibles = BiblesSqlite().getBibleList()
        bibleInfo = []
        for bible in bibles:
            description = Bible(bible).bibleInfo()
            bibleInfo.append((bible, description))
        return bibleInfo

    def selectCollection(self, item):
        self.selectedCollection = item.text()
        self.biblesTable.setEnabled(True)
        self.loadBibleSelection()

    def bibleSelectionChanged(self, item):
        if not self.settingBibles:
            if self.selectedCollection is not None:
                text = item.text()
                biblesInCollection = config.bibleCollections[
                    self.selectedCollection]
                if len(biblesInCollection) == 0:
                    biblesInCollection = []
                if text in biblesInCollection:
                    biblesInCollection.remove(text)
                else:
                    biblesInCollection.append(text)
                config.bibleCollections[
                    self.selectedCollection] = biblesInCollection

    def loadBibleSelection(self):
        self.settingBibles = True
        self.dataViewModel.clear()
        biblesInCollection = []
        if self.selectedCollection is not None:
            biblesInCollection = config.bibleCollections[
                self.selectedCollection]
        rowCount = 0
        for bible, description in self.bibles:
            item = QStandardItem(bible)
            item.setToolTip(bible)
            item.setCheckable(True)
            if bible in biblesInCollection:
                item.setCheckState(Qt.Checked)
            self.dataViewModel.setItem(rowCount, 0, item)
            item = QStandardItem(description)
            self.dataViewModel.setItem(rowCount, 1, item)
            rowCount += 1
        self.dataViewModel.setHorizontalHeaderLabels([
            config.thisTranslation["bible"],
            config.thisTranslation["description"]
        ])
        self.biblesTable.resizeColumnsToContents()
        self.settingBibles = False

    def reloadControlPanel(self):
        self.parent.reloadControlPanel(False)