Exemplo n.º 1
0
    def getItemModel(self):
        """Return a QModel made from the current workspace. This should be set
        onto a QTableView
        """
        def create_table_item(column, itemname, callable, *args):
            item = QStandardItem()
            item.setEditable(False)
            try:
                item.setText(callable(*args))
            except Exception as exc:
                logger.warning("Error setting column {} for log {}: {}".format(
                    column, itemname, str(exc)))

            return item

        model = QStandardItemModel()
        model.setHorizontalHeaderLabels(["Name", "Type", "Value", "Units"])
        model.setColumnCount(4)
        for key in self.get_log_names():
            log = self.run.getLogData(key)
            name = create_table_item("Name", key, lambda: log.name)
            log_type = create_table_item("Type", key, get_type, log)
            value = create_table_item("Value", key,
                                      lambda log: str(get_value(log)), log)
            unit = create_table_item("Units", key, lambda: log.units)
            model.appendRow((name, log_type, value, unit))

        model.sort(0)
        return model
Exemplo n.º 2
0
 def commentaryListView(self):
     # https://doc.qt.io/archives/qtforpython-5.12/PySide2/QtCore/QStringListModel.html
     # https://gist.github.com/minoue/9f384cd36339429eb0bf
     # https://www.pythoncentral.io/pyside-pyqt-tutorial-qlistview-and-qstandarditemmodel/
     list = QListView()
     list.setEditTriggers(QAbstractItemView.NoEditTriggers)
     model = QStandardItemModel(list)
     for index, commentary in enumerate(self.parent.commentaryFullNameList):
         item = QStandardItem(commentary)
         item.setToolTip(self.parent.commentaryList[index])
         #item.setCheckable(True)
         #item.setCheckState(Qt.CheckState.Checked)
         #item.setCheckState(Qt.CheckState.Unchecked)
         #print(item.checkState() is Qt.CheckState.Checked)
         model.appendRow(item)
     #model = QStringListModel(self.parent.commentaryList)
     #model = QStringListModel(self.parent.commentaryFullNameList)
     list.setModel(model)
     if config.commentaryText in self.parent.commentaryList:
         list.setCurrentIndex(
             model.index(
                 self.parent.commentaryList.index(config.commentaryText),
                 0))
     list.selectionModel().selectionChanged.connect(self.commentarySelected)
     return list
    def update_channels(self,
                        channels: List[Channel],
                        selected_channel: Channel = None):
        """
        Update / repopulate the list of selectable channels
        Inputs:
            channels: List of channel names
        """
        self._reset_combo_box(self._combo_channels)

        if channels is None or len(channels) == 0:
            self._combo_channels.setEnabled(False)
        else:
            model = QStandardItemModel()
            model.appendRow(QStandardItem(self._combo_channels.itemText(0)))

            for channel in channels:
                item = QStandardItem(channel.display_name)
                item.setData(channel, QtCore.Qt.UserRole)
                model.appendRow(item)

            self._combo_channels.setModel(model)

            if selected_channel is not None:
                # TODO relying on display name isn't the best as it will probably
                #      cause issues if channel names aren't unique
                # TODO refactor by making Channel derive from QStandardItem and do something like this:
                #      selected_index = model.indexFromItem(selected_channel)
                #      self.combo_channels.setCurrentIndex(selected_index)
                self._combo_channels.setCurrentText(
                    selected_channel.display_name)

            self._combo_channels.setEnabled(True)
Exemplo n.º 4
0
    def loadServos(self):
        model = QStandardItemModel()

        devs = il.devices(il.NET_PROT.EUSB)
        for dev in devs:
            try:
                net = il.Network(il.NET_PROT.EUSB, dev)
            except il.exceptions.ILCreationError:
                continue

            found = net.servos()
            for servo_id in found:
                try:
                    servo = il.Servo(net, servo_id)
                except il.exceptions.ILCreationError:
                    continue

                item = QStandardItem('0x{:02x} ({})'.format(servo_id, dev))
                item.setData(servo, Qt.UserRole)

                image = QImage(join(_RESOURCES, 'images', 'triton-core.png'))
                item.setData(QPixmap.fromImage(image), Qt.DecorationRole)

                model.appendRow([item])

        self.cboxServos.setModel(model)
Exemplo n.º 5
0
    def __init__(self):
        QDialog.__init__(self)

        # setup UI
        self.form = QFormLayout(self)
        self.editPosition = QLineEdit('')
        self.form.addRow(QLabel('Position'), self.editPosition)
        self.editVelocity = QLineEdit('')
        self.form.addRow(QLabel('Velocity'), self.editVelocity)

        # configure network (take first available servo)
        self._net, self._servo = il.lucky(il.NET_PROT.EUSB)

        # create data model
        model = QStandardItemModel()
        pos = QStandardItem()
        vel = QStandardItem()
        model.appendRow([pos, vel])

        # configure and start watcher
        self._watcher = RegisterWatcher(self._servo)
        self._watcher.add(POS_ACT, 500, pos)
        self._watcher.add(VEL_ACT, 100, vel)
        self._watcher.start(100)

        # map model fields to widgets
        self._mapper = QDataWidgetMapper()
        self._mapper.setModel(model)
        self._mapper.addMapping(self.editPosition, 0)
        self._mapper.addMapping(self.editVelocity, 1)
        self._mapper.toFirst()
Exemplo n.º 6
0
    def getItemModel(self, searched_key=''):
        """Return a QModel made from the current workspace. This should be set
        onto a QTableView. The searched_key allows for filtering log entries.
        """
        def create_table_item(column, itemname, invalid_value_count, log_size,
                              callable, *args):
            item = QStandardItem()
            item.setEditable(False)
            #format if there is invalid data entries
            if invalid_value_count == -1:
                item.setData(DEEP_RED, Qt.BackgroundRole)
                item.setToolTip(
                    "All of the values in the log are marked invalid, none of them are filtered."
                )
            elif invalid_value_count > 0:
                saturation = 10 + (170 * (invalid_value_count /
                                          (log_size + invalid_value_count)))
                item.setData(QColor.fromHsv(0, saturation, 255),
                             Qt.BackgroundRole)
                aux_verb = "is" if invalid_value_count == 1 else "are"
                item.setToolTip(
                    f"{invalid_value_count}/{log_size+invalid_value_count} of the values in the log"
                    f" {aux_verb} marked invalid, and {aux_verb} filtered.")
            try:
                item.setText(callable(*args))
            except Exception as exc:
                logger.warning("Error setting column {} for log {}: {}".format(
                    column, itemname, str(exc)))

            return item

        model = QStandardItemModel()
        model.setHorizontalHeaderLabels(["Name", "Type", "Value", "Units"])
        model.setColumnCount(4)
        logs_to_highlight = self.get_logs_with_invalid_data()
        logs_to_hide = self.get_hidden_logs()
        for key in self.get_log_names():
            if key in logs_to_hide:
                continue
            if searched_key.casefold() not in key.casefold():
                continue
            invalid_value_count = 0
            if key in logs_to_highlight.keys():
                invalid_value_count = logs_to_highlight[key]
            log = self.run.getLogData(key)
            size = log.size() if hasattr(log, 'size') else 0
            name = create_table_item("Name", key, invalid_value_count, size,
                                     lambda: log.name)
            log_type = create_table_item("Type", key, invalid_value_count,
                                         size, get_type, log)
            value = create_table_item("Value", key, invalid_value_count, size,
                                      lambda log: get_value(log), log)
            unit = create_table_item("Units", key, invalid_value_count, size,
                                     lambda: log.units)
            model.appendRow((name, log_type, value, unit))

        model.sort(0)
        return model
Exemplo n.º 7
0
 def videoListView(self):
     list = QListView()
     list.setEditTriggers(QAbstractItemView.NoEditTriggers)
     model = QStandardItemModel(list)
     for file in self.videoList:
         item = QStandardItem(file)
         model.appendRow(item)
     list.setModel(model)
     list.selectionModel().selectionChanged.connect(self.playSelectedVideo)
     return list
Exemplo n.º 8
0
 def devotionsListView(self):
     list = QListView()
     list.setEditTriggers(QAbstractItemView.NoEditTriggers)
     model = QStandardItemModel(list)
     for devotional in self.devotionals:
         item = QStandardItem(devotional)
         model.appendRow(item)
     list.setModel(model)
     list.selectionModel().selectionChanged.connect(self.devotionalSelected)
     return list
Exemplo n.º 9
0
 def pdfListView(self):
     list = QListView()
     list.setEditTriggers(QAbstractItemView.NoEditTriggers)
     model = QStandardItemModel(list)
     for pdf in self.pdfList:
         item = QStandardItem(pdf)
         model.appendRow(item)
     list.setModel(model)
     if config.pdfText in self.parent.pdfList:
         list.setCurrentIndex(
             model.index(self.parent.pdfList.index(config.pdfText), 0))
     list.selectionModel().selectionChanged.connect(self.pdfSelected)
     return list
Exemplo n.º 10
0
class StringListWidget(QListView):
    """Display list of strings."""
    def __init__(self, parent):
        super(StringListWidget, self).__init__(parent)
        self.model = QStandardItemModel(self)
        self.setModel(self.model)

    def setData(self, data: list):
        for entry in data:
            item = QStandardItem(str(entry))
            self.model.appendRow(item)

    def addData(self, data: str):
        item = QStandardItem(str(data))
        self.model.appendRow(item)
Exemplo n.º 11
0
def test_metadataview(qtbot, catalog):
    catalogmodel = QStandardItemModel()
    selectionmodel = QItemSelectionModel()
    selectionmodel.setModel(catalogmodel)

    item = QStandardItem()
    item.setData('test catalog', Qt.DisplayRole)
    item.setData(catalog, Qt.UserRole)
    catalogmodel.appendRow(item)
    catalogmodel.dataChanged.emit(item.index(), item.index())

    selectionmodel.setCurrentIndex(catalogmodel.indexFromItem(item), selectionmodel.SelectCurrent)

    w = MetadataView(catalogmodel, selectionmodel)
    w.show()
Exemplo n.º 12
0
class InfoFrame(ConfigBaseFrame):

    def __init__(self, parent):
        super(InfoFrame, self).__init__(parent)

        self.widget_layout = QVBoxLayout(self)
        self.setLayout(self.widget_layout)

        self.section_label = QLabel(self)
        self.section_label.setText("Informations")
        self.widget_layout.addWidget(self.section_label)

        self.label = QLabel(self)
        self.label.setText("Select information to collect after successfull connection. Keep in mind that the more "
                           "data you collect the more suspicious you are for antivirus software. You can "
                           "change these settings later.")
        self.label.setWordWrap(True)
        self.widget_layout.addWidget(self.label)

        self.list = QListView(self)
        self.model = QStandardItemModel(self.list)
        self.widget_layout.addWidget(self.list)
        self.list.setModel(self.model)

        self.item_string = {}
        infos = ConfigManager.get_infos()

        for info in infos:
            self.item_string[info] = {"name": " ".join(info.capitalize().split("_"))}

        for string in self.item_string:
            item = QStandardItem(self.item_string.get(string).get("name"))
            item.setFlags(Qt.ItemIsEnabled)
            item.setData(QVariant(Qt.Checked), Qt.CheckStateRole)
            self.model.appendRow(item)

    @Slot()
    def collect_info(self):
        infos = []
        for info in self.item_string:
            item = self.model.findItems(self.item_string.get(info).get("name"))[0]
            if item.checkState() == Qt.Checked:
                infos.append(info)

        return {"after_connection_infos":  infos}
Exemplo n.º 13
0
class XDFStreamsDialog(QDialog):
    def __init__(self, parent, rows, selected=None, disabled=None):
        super().__init__(parent)
        self.setWindowTitle("Select XDF Stream")

        self.model = QStandardItemModel()
        self.model.setHorizontalHeaderLabels(
            ["ID", "Name", "Type", "Channels", "Format", "Sampling Rate"])

        for index, stream in enumerate(rows):
            items = []
            for item in stream:
                tmp = QStandardItem()
                tmp.setData(item, Qt.DisplayRole)
                items.append(tmp)
            for item in items:
                item.setEditable(False)
                if disabled is not None and index in disabled:
                    item.setFlags(Qt.NoItemFlags)
            self.model.appendRow(items)

        self.view = QTableView()
        self.view.setModel(self.model)
        self.view.verticalHeader().setVisible(False)
        self.view.horizontalHeader().setStretchLastSection(True)
        self.view.setShowGrid(False)
        self.view.setSelectionMode(QAbstractItemView.SingleSelection)
        self.view.setSelectionBehavior(QAbstractItemView.SelectRows)
        if selected is not None:
            self.view.selectRow(selected)
        self.view.setSortingEnabled(True)
        self.view.sortByColumn(0, Qt.AscendingOrder)

        vbox = QVBoxLayout(self)
        vbox.addWidget(self.view)
        self.buttonbox = QDialogButtonBox(QDialogButtonBox.Ok
                                          | QDialogButtonBox.Cancel)
        vbox.addWidget(self.buttonbox)
        self.buttonbox.accepted.connect(self.accept)
        self.buttonbox.rejected.connect(self.reject)

        self.resize(775, 650)
        self.view.setColumnWidth(0, 100)
        self.view.setColumnWidth(1, 200)
        self.view.setColumnWidth(2, 120)
Exemplo n.º 14
0
    def loadServos(self):
        model = QStandardItemModel()

        net, servo = il.lucky(il.NET_PROT.ETH,
                              "../../resources/dictionaries/eve-net_1.7.1.xdf",
                              address_ip='192.168.2.22',
                              port_ip=1061,
                              protocol=2)

        if net is not None and servo is not None:
            item = QStandardItem('0x{:02x} ({})'.format(1, "Everest"))
            item.setData(servo, Qt.UserRole)

            image = QImage(join(_RESOURCES, 'images', 'eve-xcr.png'))
            item.setData(QPixmap.fromImage(image), Qt.DecorationRole)

            model.appendRow([item])

            self.cboxServos.setModel(model)
Exemplo n.º 15
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)
Exemplo n.º 16
0
    def __init__(self):
        QDialog.__init__(self)

        # setup UI
        self.form = QFormLayout(self)
        self.editPosition = QLineEdit('')
        self.form.addRow(QLabel('Position'), self.editPosition)
        self.editVelocity = QLineEdit('')
        self.form.addRow(QLabel('Velocity'), self.editVelocity)

        # configure network (take first available servo)
        self._net, self._servo = il.lucky(
            il.NET_PROT.ETH,
            "../../resources/dictionaries/eve-net_1.7.1.xdf",
            address_ip='192.168.2.22',
            port_ip=1061,
            protocol=2)

        # create data model
        model = QStandardItemModel()
        pos = QStandardItem()
        vel = QStandardItem()
        model.appendRow([pos, vel])

        # configure and start watcher
        self._watcher = RegisterWatcher(self._servo)
        self._watcher.add(POS_ACT, 1000, pos)
        self._watcher.add(VEL_ACT, 1000, vel)
        self._watcher.start(100)

        # map model fields to widgets
        self._mapper = QDataWidgetMapper()
        self._mapper.setModel(model)
        self._mapper.addMapping(self.editPosition, 0)
        self._mapper.addMapping(self.editVelocity, 1)
        self._mapper.toFirst()
Exemplo n.º 17
0
 def getItemModel(self):
     """Return a QModel made from the current workspace. This should be set
     onto a QTableView
     """
     model = QStandardItemModel()
     model.setHorizontalHeaderLabels(["Name", "Type", "Value", "Units"])
     model.setColumnCount(4)
     for key in self.get_log_names():
         log = self.run.getLogData(key)
         name = QStandardItem()
         name.setText(log.name)
         name.setEditable(False)
         log_type = QStandardItem()
         log_type.setText(get_type(log))
         log_type.setEditable(False)
         value = QStandardItem()
         value.setText(str(get_value(log)))
         value.setEditable(False)
         unit = QStandardItem()
         unit.setText(log.units)
         unit.setEditable(False)
         model.appendRow((name, log_type, value, unit))
     model.sort(0)
     return model
Exemplo n.º 18
0
 def getItemModel(self):
     """Return a QModel made from the current workspace. This should be set
     onto a QTableView
     """
     model = QStandardItemModel()
     model.setHorizontalHeaderLabels(["Name", "Type", "Value", "Units"])
     model.setColumnCount(4)
     for key in self.get_log_names():
         log = self.run.getLogData(key)
         name = QStandardItem()
         name.setText(log.name)
         name.setEditable(False)
         log_type = QStandardItem()
         log_type.setText(get_type(log))
         log_type.setEditable(False)
         value = QStandardItem()
         value.setText(str(get_value(log)))
         value.setEditable(False)
         unit = QStandardItem()
         unit.setText(log.units)
         unit.setEditable(False)
         model.appendRow((name, log_type, value, unit))
     model.sort(0)
     return model
Exemplo n.º 19
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()
Exemplo n.º 20
0
class MyGUIPlugin(GUIPlugin):
    name = "My Plugin"

    def __init__(self):
        #Define workflows
        self.my_workflow = MyWorkflow()
        #self.my_workflow_editor = WorkflowEditor(self.my_workflow)
        # Define a GUILayout
        # GUILayouts must provide a center widget
        #self.container_widget = QWidget()
        self.split_widget = CatalogAndAnalysisSplitWidget()
        layout = QVBoxLayout()  # what kind of layout
        self.button = QPushButton("push this")
        #layout.addWidget(self.split_widget) # add things to the layout one by one
        #layout.addWidget(self.button)
        #self.container_widget.setLayout(layout) # apply layout to the basic widget
        bottom_widget = QLabel('bottom')
        left_widget = QLabel('left')
        leftbottom_widget = QLabel('left bottom')
        right_widget = QLabel('right')

        self.model = QStandardItemModel()
        self.selectionmodel = QItemSelectionModel(self.model)
        stream = 'primary'
        field = 'img'
        self.test_tab_view = TabView(self.model,
                                     self.selectionmodel,
                                     widgetcls=MyImageView,
                                     stream=stream,
                                     field=field)

        self.parameter_view = ParameterTree()
        for operation in self.my_workflow.operations:
            parameter_dict = operation.as_parameter()
            for list_parameter in parameter_dict:
                parameter = Parameter.create(**list_parameter)
                self.parameter_view.addParameters(parameter)
        stage_layout = GUILayout(
            center=self.split_widget,
            #left=left_widget,
            #right=right_widget,
            #leftbottom = leftbottom_widget,
            bottom=self.button,
        )
        second_stage_layout = GUILayout(center=self.test_tab_view,
                                        righttop=self.parameter_view)
        #workflow_stage = GUILayout(center=self.test_tab_view, righttop=self.my_workflow_editor)
        #self.button.clicked.connect(self.update_label)
        #self.button.clicked.connect(self.show_message)
        self.button.clicked.connect(self.run_workflow)
        self.stages = {
            'catalogviewer and fft': stage_layout,
            'Something Else': second_stage_layout,
            #'testing workflow editor': workflow_stage,
        }

        super(MyGUIPlugin, self).__init__()

    def appendCatalog(self, catalog: BlueskyRun):
        # give catalog to out catalog viewer
        self.split_widget.catalog_view.setCatalog(catalog, 'primary', 'img')

        display_name = f"scan:{catalog.metadata['start']['scan_id']}"
        item = QStandardItem()
        item.setData(display_name, Qt.DisplayRole)
        item.setData(catalog, Qt.UserRole)
        self.model.appendRow(item)
        # tell our model that the data has been updated, the two parameters are rox index and column index
        self.model.dataChanged.emit(item.index(), item.index())

    def update_label(self):
        current_text = self.label.text()
        current_text += "1"
        self.label.setText(current_text)

    def show_message(self):
        notifyMessage("Add another 1.")

    def show_message(self, catalog: BlueskyRun):
        notifyMessage(f'Added catalog {catalog}')

    def run_workflow(self):
        # workflow has an exec() and an exec_all() to run itself
        # extract data from loaded catalog
        if not self.split_widget.catalog_view.catalog:
            notifyMessage("A catalog is not yet loaded, please load one first")
            return
        image_data = self.split_widget.catalog_view.catalog.primary.to_dask(
        )['img'].compute()
        # primary.image.read()
        # execute wkll take in named inputs in your OperationPlugin,
        # and a callback_slot will be called when it finished execution
        self.my_workflow.execute(input_image=image_data,
                                 callback_slot=self.show_fft)

    def show_fft(
            self,
            *results):  # workflow results have a *results passed into them
        # results : a list of dictionarys result objects, eg [ {"output_image":
        #from qtpy.QtCore import pyqtRemoveInputHook
        #pyqtRemoveInputHook()
        #import pdb
        #pdb.set_trace()
        #print(results)
        fft_image = results[-1]['output_image']
        self.split_widget.results_view.setImage(fft_image)
Exemplo n.º 21
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]
    """

    # 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)
Exemplo n.º 22
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)
Exemplo n.º 23
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
Exemplo n.º 24
0
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()
Exemplo n.º 25
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')
Exemplo n.º 26
0
    def setupUI(self):
        mainLayout = QVBoxLayout()

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

        subLayout = QHBoxLayout()

        layout = QVBoxLayout()
        layout.addWidget(QLabel(self.translation[1]))
        dataView1 = QListView()
        dataView1.setEditTriggers(QAbstractItemView.NoEditTriggers)
        dataViewModel1 = QStandardItemModel(dataView1)
        dataView1.setModel(dataViewModel1)
        for plugin in FileUtil.fileNamesWithoutExtension(
                os.path.join("plugins", "startup"), "py"):
            item = QStandardItem(plugin)
            item.setToolTip(plugin)
            item.setCheckable(True)
            item.setCheckState(
                Qt.CheckState.Unchecked if plugin in
                config.excludeStartupPlugins else Qt.CheckState.Checked)
            dataViewModel1.appendRow(item)
        dataViewModel1.itemChanged.connect(self.itemChanged1)
        layout.addWidget(dataView1)
        subLayout.addLayout(layout)

        layout = QVBoxLayout()
        layout.addWidget(QLabel(self.translation[2]))
        dataView2 = QListView()
        dataView2.setEditTriggers(QAbstractItemView.NoEditTriggers)
        dataViewModel2 = QStandardItemModel(dataView2)
        dataView2.setModel(dataViewModel2)
        for plugin in FileUtil.fileNamesWithoutExtension(
                os.path.join("plugins", "menu"), "py"):
            item = QStandardItem(plugin)
            item.setToolTip(plugin)
            item.setCheckable(True)
            item.setCheckState(Qt.CheckState.Unchecked if plugin in config.
                               excludeMenuPlugins else Qt.CheckState.Checked)
            dataViewModel2.appendRow(item)
        dataViewModel2.itemChanged.connect(self.itemChanged2)
        layout.addWidget(dataView2)
        subLayout.addLayout(layout)

        layout = QVBoxLayout()
        layout.addWidget(QLabel(self.translation[3]))
        dataView3 = QListView()
        dataView3.setEditTriggers(QAbstractItemView.NoEditTriggers)
        dataViewModel3 = QStandardItemModel(dataView3)
        dataView3.setModel(dataViewModel3)
        for plugin in FileUtil.fileNamesWithoutExtension(
                os.path.join("plugins", "context"), "py"):
            item = QStandardItem(plugin)
            item.setToolTip(plugin)
            item.setCheckable(True)
            item.setCheckState(
                Qt.CheckState.Unchecked if plugin in
                config.excludeContextPlugins else Qt.CheckState.Checked)
            dataViewModel3.appendRow(item)
        dataViewModel3.itemChanged.connect(self.itemChanged3)
        layout.addWidget(dataView3)
        subLayout.addLayout(layout)

        layout = QVBoxLayout()
        layout.addWidget(QLabel(self.translation[4]))
        dataView4 = QListView()
        dataView4.setEditTriggers(QAbstractItemView.NoEditTriggers)
        dataViewModel4 = QStandardItemModel(dataView4)
        dataView4.setModel(dataViewModel4)
        for plugin in FileUtil.fileNamesWithoutExtension(
                os.path.join("plugins", "shutdown"), "py"):
            item = QStandardItem(plugin)
            item.setToolTip(plugin)
            item.setCheckable(True)
            item.setCheckState(
                Qt.CheckState.Unchecked if plugin in
                config.excludeShutdownPlugins else Qt.CheckState.Checked)
            dataViewModel4.appendRow(item)
        dataViewModel4.itemChanged.connect(self.itemChanged4)
        layout.addWidget(dataView4)
        subLayout.addLayout(layout)

        mainLayout.addLayout(subLayout)

        self.setLayout(mainLayout)
Exemplo n.º 27
0
class SpectrumSelection(QDialog):
    """
    A widget providing a simple dialog for selecting spectra to be loaded.

    The widget itself knows nothing about spectral data objects, but instead
    just presents a list of strings which represent the names of the available
    spectra, and returns a list of strings which represent the names of the
    spectra that were actually selected.
    """

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        loadUi(os.path.abspath(
            os.path.join(os.path.dirname(__file__),
                         "ui", "spectrum_selection.ui")), self)

        self.setWindowTitle('Spectrum Selection')

        self._model = QStandardItemModel(self.spectrumList)
        self.spectrumList.setModel(self._model)
        self._selected = False

        self.buttonBox.accepted.connect(self._confirm_selection)

        self.selectAllButton.clicked.connect(self._select_all)
        self.deselectAllButton.clicked.connect(self._deselect_all)

    def populate(self, names):
        """
        Add a list of names to be displayed as list items in the dialog

        Parameters
        ----------
        names : `list`
            The list of names to be populated in the dialog
        """
        for s in names:
            item = QStandardItem(s)
            item.setCheckable(True)
            item.setCheckState(Qt.Checked)
            self._model.appendRow(item)

    def get_selected(self):
        """
        Get the list of names that were actually checked when the dialog closes

        This method will return an empty list in the following cases:
            * No items were selected when the dialog closes
            * The "Cancel" button was hit by the user instead of "Open"
            * This method is called before the dialog closes

        Returns
        -------
        names : `list`
            The list of names that were selected when the dialog closes
        """
        if not self._selected:
            return []

        selected = []
        for i in range(self._model.rowCount()):
            item = self._model.item(i)
            if item.checkState() == Qt.Checked:
                selected.append(item.text())
        return selected

    def _confirm_selection(self):
        self._selected = True

    def _select_all(self):
        for i in range(self._model.rowCount()):
            item = self._model.item(i)
            item.setCheckState(Qt.Checked)

    def _deselect_all(self):
        for i in range(self._model.rowCount()):
            item = self._model.item(i)
            item.setCheckState(Qt.Unchecked)
Exemplo n.º 28
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()
Exemplo n.º 29
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
Exemplo n.º 30
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)
Exemplo n.º 31
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()