예제 #1
0
class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()
        self.resize(600, 300)
        self.model = QDirModel(self)  # 1
        self.model.setReadOnly(False)
        self.model.setSorting(QDir.Name | QDir.IgnoreCase)

        self.tree = QTreeView(self)  # 2
        self.tree.setModel(self.model)
        self.tree.clicked.connect(self.show_info)
        self.index = self.model.index(QDir.currentPath())
        self.tree.expand(self.index)
        self.tree.scrollTo(self.index)

        self.info_label = QLabel(self)  # 3

        self.v_layout = QVBoxLayout()
        self.v_layout.addWidget(self.tree)
        self.v_layout.addWidget(self.info_label)
        self.setLayout(self.v_layout)

    def show_info(self):  # 4
        index = self.tree.currentIndex()
        file_name = self.model.fileName(index)
        file_path = self.model.filePath(index)
        file_info = 'File Name: {}\nFile Path: {}'.format(file_name, file_path)
        self.info_label.setText(file_info)
예제 #2
0
class Packet_Area_Binary_Component():
    def __init__(self, proxy_server=Proxy_Server()):
        self.proxy_server = proxy_server

    def install_widgets(self, parent=None):
        self.list = QTreeView(parent)
        self.list.setModel(self.proxy_server.intercept_queue.packet_list_model)
        self.list.clicked.connect(self.on_clicked)
        self.list.show()

    def on_clicked(self, index):
        self.list.expand(index)
예제 #3
0
class Packet_Area_Component():
    def __init__(self,
                 proxy_server=Proxy_Server(),
                 field_area=Field_Area_Component()):
        self.proxy_server = proxy_server
        self.field_area = field_area

    def install_widgets(self, parent=None):
        self.list = QTreeView(parent)
        self.list.setModel(self.proxy_server.intercept_queue.packet_list_model)
        self.list.clicked.connect(self.on_clicked)
        self.list.show()

    def on_clicked(self, index):
        self.list.expand(index)
        item = index.model().itemFromIndex(index)
        if item.parent() != None:
            self.proxy_server.intercept_queue.populate_field_area(
                item.parent().row(), index.row())
            self.field_area.set_selected_packet(
                item.parent().row(),
                index.row())  # Populate the field area when you click a layer!
예제 #4
0
class FileTreeView(QWidget):
    on_menu_select = pyqtSignal(str, str)
    on_dir_change = pyqtSignal()

    def __init__(self, parent: Application):
        super().__init__()
        self.stopped = threading.Event()
        self.tree = QTreeView()
        self.handle = self.Handler(self)
        self.get_data_file()
        self.model = QtGui.QStandardItemModel()
        self.item_construct = {}

        v_box = QVBoxLayout()
        v_box.addWidget(self.finder())
        v_box.addWidget(self.tree_view())
        self.setLayout(v_box)
        self.watch_dog()
        parent.app_close.connect(self.exit_push)

        self.tree.setAlternatingRowColors(True)

    def exit_push(self):
        self.stopped.set()

    def finder(self):
        w_find = QLineEdit()
        w_find.setSizePolicy(
            QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed))
        w_find.textChanged.connect(self.sort_list)
        w_find.setPlaceholderText("Search file..")

        return w_find

    def sort_list(self, text):
        list_sort = []
        if text == "":
            self.show_tree(self.list_file)
        else:
            for data in self.list_file:
                if text.lower() in data.name().lower():
                    list_sort.append(data)
            self.show_tree(list_sort)

    def watch_dog(self):
        watch = ProcessRunnable(target=watch_winform,
                                args=(get_data_folder(), self.handle,
                                      self.stopped))
        watch.start()

    class Handler(watchdog.events.PatternMatchingEventHandler):
        def __init__(self, parent):
            super().__init__()
            self.parent = parent

        def on_created(self, event):
            print("Watchdog received created event", event.src_path, sep=" : ")
            asyncio.run(
                self.parent.file_change('create', event.src_path,
                                        event.is_directory))

        def on_modified(self, event):
            print("Watchdog received modified event",
                  event.src_path,
                  sep=" : ")
            asyncio.run(
                self.parent.file_change('modify', event.src_path,
                                        event.is_directory))

        def on_moved(self, event):
            print("Watchdog received move event",
                  event.src_path,
                  event.dest_path,
                  sep=" : ")
            asyncio.run(
                self.parent.file_change('move', event.src_path,
                                        event.is_directory, event.dest_path))

        def on_deleted(self, event):
            print("Watchdog received delete event", event.src_path, sep=" : ")
            asyncio.run(
                self.parent.file_change('delete', event.src_path,
                                        event.is_directory))

    async def file_change(self, tpe, old, is_directory, new=""):
        if tpe == "move":
            self.import_single(self.model.invisibleRootItem(), new)
            self.remove_single(old)
        elif tpe == "delete":
            self.remove_single(old)
        elif tpe == "create":
            self.import_single(self.model.invisibleRootItem(), old)

        if is_directory:
            self.on_dir_change.emit()

    def get_data_file(self):
        self.list_file = []
        self.create_list(get_data_folder())

    def create_list(self, dir):
        lst = os.listdir(path=dir)
        for f in lst:
            path = os.path.join(dir, f)
            file = MyFile()
            if os.path.isdir(path):
                file.setParentName(os.path.basename(dir))
                file.setParent(dir)
                file.setName(f)
                file.setDir(True)
                self.list_file.append(file)
                self.create_list(path)
            else:
                file.setParentName(os.path.basename(dir))
                file.setParent(dir)
                file.setName(f)
                file.setDir(False)
                self.list_file.append(file)

    def tree_view(self):
        self.tree.setSizePolicy(
            QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding))

        self.tree.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tree.customContextMenuRequested.connect(self.open_menu)
        self.tree.doubleClicked.connect(self.open_event)
        # self.model.itemChanged.connect(self.data_change)
        self.tree.setModel(self.model)
        self.show_tree(self.list_file)
        return self.tree

    def show_tree(self, list_file):
        self.model.clear()
        self.model.setHorizontalHeaderLabels(['List API'])
        self.tree.header().setDefaultSectionSize(180)
        parent = self.model.invisibleRootItem()
        self.item_construct = {}
        self.import_data(parent, list_file)
        self.tree.expandAll()

    def import_data_by_path(self, parent, path: list, index):
        if index < len(path):
            full = os.sep.join(path[:index + 1])
            if full in self.item_construct:
                item = self.item_construct[full]
            else:
                item = QStandardItem(path[index])
                if os.path.isfile(os.path.join(get_data_folder(), full)):
                    item.setToolTip(
                        self.read_description(
                            os.path.join(get_data_folder(), full)))
                item.setEditable(False)
                if not os.path.isdir(os.path.join(get_data_folder(), full)):
                    item.setIcon(QIcon(get_icon_link("text_snippet.svg")))
                else:
                    item.setIcon(QIcon(get_icon_link("folder_yellow.svg")))

                item.setData(full)
                parent.appendRow(item)
                self.item_construct[full] = item

            self.import_data_by_path(item, path, index + 1)

    def read_description(self, path):
        try:
            data = json.loads(open(path, encoding='utf-8').read())
            json_data = APIData()
            json_data.construct(data)
            x = json_data.parseSave().description()
            if x.isspace() or x == "":
                return ".."
            else:
                return x
        except Exception as ex:
            print(ex)
            return ".."

    def import_data(self, parent, list_data):
        for i in list_data:
            self.import_single(parent, i)

    def import_single(self, parent, file_path):
        path = self.path_extract(file_path)
        self.import_data_by_path(parent, path, 0)

    def remove_single(self, file_path):
        path = self.path_extract(file_path)
        full = os.sep.join(path[:len(path)])
        if full in self.item_construct:
            item = self.item_construct[full]
            (item.parent()
             or self.model.invisibleRootItem()).removeRow(item.row())
            del self.item_construct[full]

    def path_extract(self, file_path):
        if isinstance(file_path, MyFile):
            path = os.path.join(file_path.parent(), file_path.name())
        else:
            path = file_path

        path = path.replace(get_data_folder(), "")
        if path.startswith(os.sep):
            path = path.replace(os.sep, "", 1)
        path = path.split(os.sep)
        return path

    def open_menu(self, position):
        indexes = self.tree.selectedIndexes()
        level = 0
        data = ""
        item = None
        if len(indexes) > 0:
            index = indexes[0]
            item = self.model.itemFromIndex(index)
            data = item.data()
            data = os.path.join(get_data_folder(), data)
            if os.path.isdir(data):
                level = 1
            else:
                level = 2

        menu = QMenu()
        menu.setStyleSheet(open(get_stylesheet()).read())

        rename_action = QAction(QIcon(get_icon_link('edit.svg')), '&Rename',
                                self)
        rename_action.setStatusTip('Rename')

        new_action = QAction(QIcon(get_icon_link('create_new_folder.svg')),
                             '&New Folder', self)
        new_action.setStatusTip('New Folder')

        refresh_action = QAction(QIcon(get_icon_link('refresh.svg')),
                                 '&Refresh', self)
        refresh_action.setStatusTip('Refresh')

        delete_action = QAction(QIcon(get_icon_link('delete_forever.svg')),
                                '&Delete', self)
        delete_action.setStatusTip('Delete')

        open_action = QAction(QIcon(get_icon_link('open_in_new.svg')), '&Open',
                              self)
        open_action.setStatusTip('Open file')

        expand_action = QAction(QIcon(), '&Expand', self)
        expand_action.setStatusTip('Expand')

        collapse_action = QAction(QIcon(), '&Collapse', self)
        collapse_action.setStatusTip('Collapse')

        duplicate_action = QAction(QIcon(get_icon_link('content_copy.svg')),
                                   '&Duplicate', self)
        duplicate_action.setStatusTip('Duplicate')

        copy_action = QAction(QIcon(get_icon_link('content_copy.svg')),
                              '&Copy', self)
        copy_action.setStatusTip('Copy')

        move_action = QAction(QIcon(get_icon_link('zoom_out_map.svg')),
                              '&Move', self)
        move_action.setStatusTip('Move')

        if level == 1:
            menu.addAction(rename_action)
            menu.addAction(new_action)
            menu.addSeparator()
            menu.addAction(refresh_action)
            menu.addAction(expand_action)
            menu.addAction(collapse_action)
            menu.addSeparator()
            menu.addAction(delete_action)
        elif level == 2:
            menu.addAction(open_action)
            menu.addAction(new_action)
            menu.addAction(refresh_action)
            menu.addSeparator()
            menu.addAction(rename_action)
            menu.addAction(duplicate_action)
            menu.addAction(copy_action)
            menu.addAction(move_action)
            menu.addSeparator()
            menu.addAction(delete_action)
        else:
            menu.addAction(new_action)
            menu.addAction(refresh_action)

        action = menu.exec_(self.tree.viewport().mapToGlobal(position))

        if action == open_action:
            if data != "":
                self.on_menu_select.emit("open", data)
        elif action == refresh_action:
            self.get_data_file()
            self.show_tree(self.list_file)
        elif action == expand_action:
            if item is not None:
                self.tree.expand(item.index())
        elif action == collapse_action:
            if item is not None:
                self.tree.collapse(item.index())
        elif action == delete_action:
            if data != "":
                msg = QMessageBox()
                msg.setStyleSheet(open(get_stylesheet()).read())
                msg.setIcon(QMessageBox.Warning)
                msg.setBaseSize(QSize(500, 300))
                msg.setText("Delete file.")
                msg.setInformativeText("Are you sure to detele " +
                                       os.path.basename(data) + "?")
                msg.setWindowTitle("Delete Warning!!!")
                msg.addButton('Delete', QMessageBox.YesRole)
                msg.addButton('Move to Trash', QMessageBox.YesRole)
                msg.addButton('Cancel', QMessageBox.NoRole)

                rs = msg.exec_()
                if rs == 0:
                    if os.path.isdir(data):
                        shutil.rmtree(data)
                    else:
                        os.remove(data)
                elif rs == 1:
                    send2trash(data)
        elif action == new_action:
            if data == "":
                data = get_data_folder()
            # input_name = QInputDialog()
            # input_name.setStyleSheet(open(get_stylesheet()).read())
            # text, ok = input_name.getText(self, 'New Folder', 'Folder name:')
            inp = QComboDialog('New Folder', 'Folder name:', QComboDialog.Text)
            ok = inp.exec_()
            if ok and inp.select:
                if os.path.isdir(data):
                    try:
                        os.mkdir(os.path.join(data, inp.select))
                    except Exception as ex:
                        alert = Alert("Error", "Create folder error", str(ex))
                        alert.exec_()
                        print(ex)
                else:
                    new = os.path.join(os.path.dirname(data), inp.select)
                    try:
                        os.mkdir(new)
                    except Exception as ex:
                        alert = Alert("Error", "Create folder error", str(ex))
                        alert.exec_()
                        print(ex)
        elif action == rename_action:
            if data != "":
                # input_name = QInputDialog()
                # input_name.setStyleSheet(open(get_stylesheet()).read())
                # text, ok = input_name.getText(self, 'Rename file', 'New name:')
                inp = QComboDialog('Rename file', 'New name:',
                                   QComboDialog.Text)
                ok = inp.exec_()
                if ok and inp.select:
                    if os.path.isdir(data):
                        new = os.path.join(os.path.dirname(data), inp.select)
                        try:
                            os.rename(data, new)
                        except Exception as ex:
                            alert = Alert("Error", "Rename folder error",
                                          str(ex))
                            alert.exec_()
                            print(ex)
                    else:
                        filename, file_extension = os.path.splitext(data)
                        new = os.path.join(os.path.dirname(data),
                                           inp.select + file_extension)
                        try:
                            os.rename(data, new)
                        except Exception as ex:
                            alert = Alert("Error", "Rename file error",
                                          str(ex))
                            alert.exec_()
                            print(ex)
        elif action == move_action:
            if data != "":
                items = get_list_folder(get_data_folder(), get_data_folder())
                #
                # item, ok = QInputDialog.getItem(self, "Select folder dialog",
                #                                 "Select the destination folder", items, 0, False)

                inp = QComboDialog("Move", "Select the destination folder",
                                   QComboDialog.ComboBox, items)
                ok = inp.exec_()

                if ok and inp.select:
                    folder = inp.select
                    if inp.select.startswith(os.sep):
                        folder = inp.select.replace(os.sep, "", 1)
                    new = os.path.join(get_data_folder(), folder,
                                       os.path.basename(data))
                    try:
                        os.rename(data, new)
                    except Exception as ex:
                        alert = Alert("Error", "Move file error", str(ex))
                        alert.exec_()
                        print(ex)
        elif action == duplicate_action:
            if data != "":
                # input_name = QInputDialog()
                # input_name.setStyleSheet(open(get_stylesheet()).read())
                # text, ok = input_name.getText(self, 'Duplicate file', 'New name:')
                inp = QComboDialog('Duplicate file', 'New name:',
                                   QComboDialog.Text)
                filename, file_extension = os.path.splitext(data)
                inp.set_init_text(filename)
                ok = inp.exec_()
                if ok and inp.select:
                    new = os.path.join(os.path.dirname(data),
                                       inp.select + file_extension)
                    try:
                        copyfile(data, new)
                    except Exception as ex:
                        alert = Alert("Error", "Duplicate file error", str(ex))
                        alert.exec_()
                        print(ex)
        elif action == copy_action:
            if data != "":
                items = get_list_folder(get_data_folder(), get_data_folder())
                inp = QComboDialog("Copy", "Select the destination folder",
                                   QComboDialog.ComboBox, items)
                ok = inp.exec_()
                # item, ok = QInputDialog.getItem(self, "Select folder dialog",
                #                                 "Select the destination folder", items, 0, False)

                if ok and inp.select:
                    folder = inp.select
                    if inp.select.startswith(os.sep):
                        folder = inp.select.replace(os.sep, "", 1)
                    new = os.path.join(get_data_folder(), folder,
                                       os.path.basename(data))
                    try:
                        copyfile(data, new)
                    except Exception as ex:
                        alert = Alert("Error", "Copy file error", str(ex))
                        alert.exec_()
                        print(ex)

    def open_event(self, index):
        item = self.model.itemFromIndex(index)
        if item is not None:
            data = item.data()
            data = os.path.join(get_data_folder(), data)
            if os.path.isdir(data):
                level = 1
            else:
                level = 2
            if data != "":
                if level == 2:
                    self.on_menu_select.emit("open", data)
                elif level == 1:
                    if not self.tree.isExpanded(item.index()):
                        self.tree.collapse(item.index())
                    else:
                        self.tree.expand(item.index())
예제 #5
0
파일: tdm.py 프로젝트: kylegordon/tdm
class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        self._version = "0.1.20"
        self.setWindowIcon(QIcon("GUI/icons/logo.png"))
        self.setWindowTitle("Tasmota Device Manager {}".format(self._version))

        self.main_splitter = QSplitter()
        self.devices_splitter = QSplitter(Qt.Vertical)

        self.mqtt_queue = []
        self.devices = {}

        self.fulltopic_queue = []
        old_settings = QSettings()

        self.settings = QSettings("{}/TDM/tdm.cfg".format(QDir.homePath()),
                                  QSettings.IniFormat)
        self.setMinimumSize(QSize(1280, 800))

        for k in old_settings.allKeys():
            self.settings.setValue(k, old_settings.value(k))
            old_settings.remove(k)

        self.device_model = TasmotaDevicesModel()
        self.telemetry_model = TasmotaDevicesTree()
        self.console_model = ConsoleModel()

        self.sorted_console_model = QSortFilterProxyModel()
        self.sorted_console_model.setSourceModel(self.console_model)
        self.sorted_console_model.setFilterKeyColumn(CnsMdl.FRIENDLY_NAME)

        self.setup_mqtt()
        self.setup_telemetry_view()
        self.setup_main_layout()
        self.add_devices_tab()
        self.build_toolbars()
        self.setStatusBar(QStatusBar())

        self.queue_timer = QTimer()
        self.queue_timer.timeout.connect(self.mqtt_publish_queue)
        self.queue_timer.start(500)

        self.auto_timer = QTimer()
        self.auto_timer.timeout.connect(self.autoupdate)

        self.load_window_state()

        if self.settings.value("connect_on_startup", False, bool):
            self.actToggleConnect.trigger()

    def setup_main_layout(self):
        self.mdi = QMdiArea()
        self.mdi.setActivationOrder(QMdiArea.ActivationHistoryOrder)
        self.mdi.setViewMode(QMdiArea.TabbedView)
        self.mdi.setDocumentMode(True)

        mdi_widget = QWidget()
        mdi_widget.setLayout(VLayout())
        mdi_widget.layout().addWidget(self.mdi)

        self.devices_splitter.addWidget(mdi_widget)

        vl_console = VLayout()
        hl_filter = HLayout()
        self.cbFilter = QCheckBox("Console filtering")
        self.cbxFilterDevice = QComboBox()
        self.cbxFilterDevice.setEnabled(False)
        self.cbxFilterDevice.setFixedWidth(200)
        self.cbxFilterDevice.setModel(self.device_model)
        self.cbxFilterDevice.setModelColumn(DevMdl.FRIENDLY_NAME)
        hl_filter.addWidgets([self.cbFilter, self.cbxFilterDevice])
        hl_filter.addStretch(0)
        vl_console.addLayout(hl_filter)

        self.console_view = TableView()
        self.console_view.setModel(self.console_model)
        self.console_view.setupColumns(columns_console)
        self.console_view.setAlternatingRowColors(True)
        self.console_view.verticalHeader().setDefaultSectionSize(20)
        self.console_view.setMinimumHeight(200)

        vl_console.addWidget(self.console_view)

        console_widget = QWidget()
        console_widget.setLayout(vl_console)

        self.devices_splitter.addWidget(console_widget)
        self.main_splitter.insertWidget(0, self.devices_splitter)
        self.setCentralWidget(self.main_splitter)
        self.console_view.clicked.connect(self.select_cons_entry)
        self.console_view.doubleClicked.connect(self.view_payload)

        self.cbFilter.toggled.connect(self.toggle_console_filter)
        self.cbxFilterDevice.currentTextChanged.connect(
            self.select_console_filter)

    def setup_telemetry_view(self):
        tele_widget = QWidget()
        vl_tele = VLayout()
        self.tview = QTreeView()
        self.tview.setMinimumWidth(300)
        self.tview.setModel(self.telemetry_model)
        self.tview.setAlternatingRowColors(True)
        self.tview.setUniformRowHeights(True)
        self.tview.setIndentation(15)
        self.tview.setSizePolicy(
            QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Minimum))
        self.tview.expandAll()
        self.tview.resizeColumnToContents(0)
        vl_tele.addWidget(self.tview)
        tele_widget.setLayout(vl_tele)
        self.main_splitter.addWidget(tele_widget)

    def setup_mqtt(self):
        self.mqtt = MqttClient()
        self.mqtt.connecting.connect(self.mqtt_connecting)
        self.mqtt.connected.connect(self.mqtt_connected)
        self.mqtt.disconnected.connect(self.mqtt_disconnected)
        self.mqtt.connectError.connect(self.mqtt_connectError)
        self.mqtt.messageSignal.connect(self.mqtt_message)

    def add_devices_tab(self):
        tabDevicesList = DevicesListWidget(self)
        self.mdi.addSubWindow(tabDevicesList)
        tabDevicesList.setWindowState(Qt.WindowMaximized)

    def load_window_state(self):
        wndGeometry = self.settings.value('window_geometry')
        if wndGeometry:
            self.restoreGeometry(wndGeometry)
        spltState = self.settings.value('splitter_state')
        if spltState:
            self.main_splitter.restoreState(spltState)

    def build_toolbars(self):
        main_toolbar = Toolbar(orientation=Qt.Horizontal,
                               iconsize=16,
                               label_position=Qt.ToolButtonTextBesideIcon)
        main_toolbar.setObjectName("main_toolbar")
        self.addToolBar(main_toolbar)

        main_toolbar.addAction(QIcon("./GUI/icons/connections.png"), "Broker",
                               self.setup_broker)
        self.actToggleConnect = QAction(QIcon("./GUI/icons/disconnect.png"),
                                        "MQTT")
        self.actToggleConnect.setCheckable(True)
        self.actToggleConnect.toggled.connect(self.toggle_connect)
        main_toolbar.addAction(self.actToggleConnect)

        self.actToggleAutoUpdate = QAction(QIcon("./GUI/icons/automatic.png"),
                                           "Auto telemetry")
        self.actToggleAutoUpdate.setCheckable(True)
        self.actToggleAutoUpdate.toggled.connect(self.toggle_autoupdate)
        main_toolbar.addAction(self.actToggleAutoUpdate)

        main_toolbar.addSeparator()
        main_toolbar.addAction(QIcon("./GUI/icons/bssid.png"), "BSSId",
                               self.bssid)
        main_toolbar.addAction(QIcon("./GUI/icons/export.png"), "Export list",
                               self.export)

    def initial_query(self, idx, queued=False):
        for q in initial_queries:
            topic = "{}status".format(self.device_model.commandTopic(idx))
            if queued:
                self.mqtt_queue.append([topic, q])
            else:
                self.mqtt.publish(topic, q, 1)
            self.console_log(topic, "Asked for STATUS {}".format(q), q)

    def setup_broker(self):
        brokers_dlg = BrokerDialog()
        if brokers_dlg.exec_(
        ) == QDialog.Accepted and self.mqtt.state == self.mqtt.Connected:
            self.mqtt.disconnect()

    def toggle_autoupdate(self, state):
        if state:
            self.auto_timer.setInterval(5000)
            self.auto_timer.start()

    def toggle_connect(self, state):
        if state and self.mqtt.state == self.mqtt.Disconnected:
            self.broker_hostname = self.settings.value('hostname', 'localhost')
            self.broker_port = self.settings.value('port', 1883, int)
            self.broker_username = self.settings.value('username')
            self.broker_password = self.settings.value('password')

            self.mqtt.hostname = self.broker_hostname
            self.mqtt.port = self.broker_port

            if self.broker_username:
                self.mqtt.setAuth(self.broker_username, self.broker_password)
            self.mqtt.connectToHost()
        elif not state and self.mqtt.state == self.mqtt.Connected:
            self.mqtt_disconnect()

    def autoupdate(self):
        if self.mqtt.state == self.mqtt.Connected:
            for d in range(self.device_model.rowCount()):
                idx = self.device_model.index(d, 0)
                cmnd = self.device_model.commandTopic(idx)
                self.mqtt.publish(cmnd + "STATUS", payload=8)

    def mqtt_connect(self):
        self.broker_hostname = self.settings.value('hostname', 'localhost')
        self.broker_port = self.settings.value('port', 1883, int)
        self.broker_username = self.settings.value('username')
        self.broker_password = self.settings.value('password')

        self.mqtt.hostname = self.broker_hostname
        self.mqtt.port = self.broker_port

        if self.broker_username:
            self.mqtt.setAuth(self.broker_username, self.broker_password)

        if self.mqtt.state == self.mqtt.Disconnected:
            self.mqtt.connectToHost()

    def mqtt_disconnect(self):
        self.mqtt.disconnectFromHost()

    def mqtt_connecting(self):
        self.statusBar().showMessage("Connecting to broker")

    def mqtt_connected(self):
        self.actToggleConnect.setIcon(QIcon("./GUI/icons/connect.png"))
        self.statusBar().showMessage("Connected to {}:{} as {}".format(
            self.broker_hostname, self.broker_port,
            self.broker_username if self.broker_username else '[anonymous]'))

        self.mqtt_subscribe()

        for d in range(self.device_model.rowCount()):
            idx = self.device_model.index(d, 0)
            self.initial_query(idx)

    def mqtt_subscribe(self):
        main_topics = ["+/stat/+", "+/tele/+", "stat/#", "tele/#"]

        for d in range(self.device_model.rowCount()):
            idx = self.device_model.index(d, 0)
            if not self.device_model.isDefaultTemplate(idx):
                main_topics.append(self.device_model.commandTopic(idx))
                main_topics.append(self.device_model.statTopic(idx))

        for t in main_topics:
            self.mqtt.subscribe(t)

    def mqtt_publish_queue(self):
        for q in self.mqtt_queue:
            t, p = q
            self.mqtt.publish(t, p)
            self.mqtt_queue.pop(self.mqtt_queue.index(q))

    def mqtt_disconnected(self):
        self.actToggleConnect.setIcon(QIcon("./GUI/icons/disconnect.png"))
        self.statusBar().showMessage("Disconnected")

    def mqtt_connectError(self, rc):
        reason = {
            1: "Incorrect protocol version",
            2: "Invalid client identifier",
            3: "Server unavailable",
            4: "Bad username or password",
            5: "Not authorized",
        }
        self.statusBar().showMessage("Connection error: {}".format(reason[rc]))
        self.actToggleConnect.setChecked(False)

    def mqtt_message(self, topic, msg):
        found = self.device_model.findDevice(topic)
        if found.reply == 'LWT':
            if not msg:
                msg = "offline"

            if found.index.isValid():
                self.console_log(topic, "LWT update: {}".format(msg), msg)
                self.device_model.updateValue(found.index, DevMdl.LWT, msg)
                self.initial_query(found.index, queued=True)

            elif msg == "Online":
                self.console_log(
                    topic,
                    "LWT for unknown device '{}'. Asking for FullTopic.".
                    format(found.topic), msg, False)
                self.mqtt_queue.append(
                    ["cmnd/{}/fulltopic".format(found.topic), ""])
                self.mqtt_queue.append(
                    ["{}/cmnd/fulltopic".format(found.topic), ""])

        elif found.reply == 'RESULT':
            try:
                full_topic = loads(msg).get('FullTopic')
                new_topic = loads(msg).get('Topic')
                template_name = loads(msg).get('NAME')
                ota_url = loads(msg).get('OtaUrl')
                teleperiod = loads(msg).get('TelePeriod')

                if full_topic:
                    # TODO: update FullTopic for existing device AFTER the FullTopic changes externally (the message will arrive from new FullTopic)
                    if not found.index.isValid():
                        self.console_log(
                            topic, "FullTopic for {}".format(found.topic), msg,
                            False)

                        new_idx = self.device_model.addDevice(found.topic,
                                                              full_topic,
                                                              lwt='online')
                        tele_idx = self.telemetry_model.addDevice(
                            TasmotaDevice, found.topic)
                        self.telemetry_model.devices[found.topic] = tele_idx
                        #TODO: add QSortFilterProxyModel to telemetry treeview and sort devices after adding

                        self.initial_query(new_idx)
                        self.console_log(
                            topic,
                            "Added {} with fulltopic {}, querying for STATE".
                            format(found.topic, full_topic), msg)
                        self.tview.expand(tele_idx)
                        self.tview.resizeColumnToContents(0)

                elif new_topic:
                    if found.index.isValid() and found.topic != new_topic:
                        self.console_log(
                            topic, "New topic for {}".format(found.topic), msg)

                        self.device_model.updateValue(found.index,
                                                      DevMdl.TOPIC, new_topic)

                        tele_idx = self.telemetry_model.devices.get(
                            found.topic)

                        if tele_idx:
                            self.telemetry_model.setDeviceName(
                                tele_idx, new_topic)
                            self.telemetry_model.devices[
                                new_topic] = self.telemetry_model.devices.pop(
                                    found.topic)

                elif template_name:
                    self.device_model.updateValue(
                        found.index, DevMdl.MODULE,
                        "{} (0)".format(template_name))

                elif ota_url:
                    self.device_model.updateValue(found.index, DevMdl.OTA_URL,
                                                  ota_url)

                elif teleperiod:
                    self.device_model.updateValue(found.index,
                                                  DevMdl.TELEPERIOD,
                                                  teleperiod)

            except JSONDecodeError as e:
                self.console_log(
                    topic,
                    "JSON payload decode error. Check error.log for additional info."
                )
                with open("{}/TDM/error.log".format(QDir.homePath()),
                          "a+") as l:
                    l.write("{}\t{}\t{}\t{}\n".format(
                        QDateTime.currentDateTime().toString(
                            "yyyy-MM-dd hh:mm:ss"), topic, msg, e.msg))

        elif found.index.isValid():
            ok = False
            try:
                if msg.startswith("{"):
                    payload = loads(msg)
                else:
                    payload = msg
                ok = True
            except JSONDecodeError as e:
                self.console_log(
                    topic,
                    "JSON payload decode error. Check error.log for additional info."
                )
                with open("{}/TDM/error.log".format(QDir.homePath()),
                          "a+") as l:
                    l.write("{}\t{}\t{}\t{}\n".format(
                        QDateTime.currentDateTime().toString(
                            "yyyy-MM-dd hh:mm:ss"), topic, msg, e.msg))

            if ok:
                try:
                    if found.reply == 'STATUS':
                        self.console_log(topic, "Received device status", msg)
                        payload = payload['Status']
                        self.device_model.updateValue(
                            found.index, DevMdl.FRIENDLY_NAME,
                            payload['FriendlyName'][0])
                        self.telemetry_model.setDeviceFriendlyName(
                            self.telemetry_model.devices[found.topic],
                            payload['FriendlyName'][0])
                        module = payload['Module']
                        if module == 0:
                            self.mqtt.publish(
                                self.device_model.commandTopic(found.index) +
                                "template")
                        else:
                            self.device_model.updateValue(
                                found.index, DevMdl.MODULE,
                                modules.get(module, 'Unknown'))
                        self.device_model.updateValue(found.index,
                                                      DevMdl.MODULE_ID, module)

                    elif found.reply == 'STATUS1':
                        self.console_log(topic, "Received program information",
                                         msg)
                        payload = payload['StatusPRM']
                        self.device_model.updateValue(
                            found.index, DevMdl.RESTART_REASON,
                            payload.get('RestartReason'))
                        self.device_model.updateValue(found.index,
                                                      DevMdl.OTA_URL,
                                                      payload.get('OtaUrl'))

                    elif found.reply == 'STATUS2':
                        self.console_log(topic,
                                         "Received firmware information", msg)
                        payload = payload['StatusFWR']
                        self.device_model.updateValue(found.index,
                                                      DevMdl.FIRMWARE,
                                                      payload['Version'])
                        self.device_model.updateValue(found.index, DevMdl.CORE,
                                                      payload['Core'])

                    elif found.reply == 'STATUS3':
                        self.console_log(topic, "Received syslog information",
                                         msg)
                        payload = payload['StatusLOG']
                        self.device_model.updateValue(found.index,
                                                      DevMdl.TELEPERIOD,
                                                      payload['TelePeriod'])

                    elif found.reply == 'STATUS5':
                        self.console_log(topic, "Received network status", msg)
                        payload = payload['StatusNET']
                        self.device_model.updateValue(found.index, DevMdl.MAC,
                                                      payload['Mac'])
                        self.device_model.updateValue(found.index, DevMdl.IP,
                                                      payload['IPAddress'])

                    elif found.reply in ('STATE', 'STATUS11'):
                        self.console_log(topic, "Received device state", msg)
                        if found.reply == 'STATUS11':
                            payload = payload['StatusSTS']
                        self.parse_state(found.index, payload)

                    elif found.reply in ('SENSOR', 'STATUS8'):
                        self.console_log(topic, "Received telemetry", msg)
                        if found.reply == 'STATUS8':
                            payload = payload['StatusSNS']
                        self.parse_telemetry(found.index, payload)

                    elif found.reply.startswith('POWER'):
                        self.console_log(
                            topic, "Received {} state".format(found.reply),
                            msg)
                        payload = {found.reply: msg}
                        self.parse_power(found.index, payload)

                except KeyError as k:
                    self.console_log(
                        topic,
                        "JSON key error. Check error.log for additional info.")
                    with open("{}/TDM/error.log".format(QDir.homePath()),
                              "a+") as l:
                        l.write("{}\t{}\t{}\tKeyError: {}\n".format(
                            QDateTime.currentDateTime().toString(
                                "yyyy-MM-dd hh:mm:ss"), topic, payload,
                            k.args[0]))

    def parse_power(self, index, payload, from_state=False):
        old = self.device_model.power(index)
        power = {
            k: payload[k]
            for k in payload.keys() if k.startswith("POWER")
        }
        # TODO: fix so that number of relays get updated properly after module/no. of relays change
        needs_update = False
        if old:
            # if from_state and len(old) != len(power):
            #     needs_update = True
            #
            # else:
            for k in old.keys():
                needs_update |= old[k] != power.get(k, old[k])
                if needs_update:
                    break
        else:
            needs_update = True

        if needs_update:
            self.device_model.updateValue(index, DevMdl.POWER, power)

    def parse_state(self, index, payload):
        bssid = payload['Wifi'].get('BSSId')
        if not bssid:
            bssid = payload['Wifi'].get('APMac')
        self.device_model.updateValue(index, DevMdl.BSSID, bssid)
        self.device_model.updateValue(index, DevMdl.SSID,
                                      payload['Wifi']['SSId'])
        self.device_model.updateValue(index, DevMdl.CHANNEL,
                                      payload['Wifi'].get('Channel', "n/a"))
        self.device_model.updateValue(index, DevMdl.RSSI,
                                      payload['Wifi']['RSSI'])
        self.device_model.updateValue(index, DevMdl.UPTIME, payload['Uptime'])
        self.device_model.updateValue(index, DevMdl.LOADAVG,
                                      payload.get('LoadAvg'))
        self.device_model.updateValue(index, DevMdl.LINKCOUNT,
                                      payload['Wifi'].get('LinkCount', "n/a"))
        self.device_model.updateValue(index, DevMdl.DOWNTIME,
                                      payload['Wifi'].get('Downtime', "n/a"))

        self.parse_power(index, payload, True)

        tele_idx = self.telemetry_model.devices.get(
            self.device_model.topic(index))

        if tele_idx:
            tele_device = self.telemetry_model.getNode(tele_idx)
            self.telemetry_model.setDeviceFriendlyName(
                tele_idx, self.device_model.friendly_name(index))

            pr = tele_device.provides()
            for k in pr.keys():
                self.telemetry_model.setData(pr[k], payload.get(k))

    def parse_telemetry(self, index, payload):
        device = self.telemetry_model.devices.get(
            self.device_model.topic(index))
        if device:
            node = self.telemetry_model.getNode(device)
            time = node.provides()['Time']
            if 'Time' in payload:
                self.telemetry_model.setData(time, payload.pop('Time'))

            temp_unit = "C"
            pres_unit = "hPa"

            if 'TempUnit' in payload:
                temp_unit = payload.pop('TempUnit')

            if 'PressureUnit' in payload:
                pres_unit = payload.pop('PressureUnit')

            for sensor in sorted(payload.keys()):
                if sensor == 'DS18x20':
                    for sns_name in payload[sensor].keys():
                        d = node.devices().get(sensor)
                        if not d:
                            d = self.telemetry_model.addDevice(
                                DS18x20, payload[sensor][sns_name]['Type'],
                                device)
                        self.telemetry_model.getNode(d).setTempUnit(temp_unit)
                        payload[sensor][sns_name]['Id'] = payload[sensor][
                            sns_name].pop('Address')

                        pr = self.telemetry_model.getNode(d).provides()
                        for pk in pr.keys():
                            self.telemetry_model.setData(
                                pr[pk], payload[sensor][sns_name].get(pk))
                        self.tview.expand(d)

                elif sensor.startswith('DS18B20'):
                    d = node.devices().get(sensor)
                    if not d:
                        d = self.telemetry_model.addDevice(
                            DS18x20, sensor, device)
                    self.telemetry_model.getNode(d).setTempUnit(temp_unit)
                    pr = self.telemetry_model.getNode(d).provides()
                    for pk in pr.keys():
                        self.telemetry_model.setData(pr[pk],
                                                     payload[sensor].get(pk))
                    self.tview.expand(d)

                if sensor == 'COUNTER':
                    d = node.devices().get(sensor)
                    if not d:
                        d = self.telemetry_model.addDevice(
                            CounterSns, "Counter", device)
                    pr = self.telemetry_model.getNode(d).provides()
                    for pk in pr.keys():
                        self.telemetry_model.setData(pr[pk],
                                                     payload[sensor].get(pk))
                    self.tview.expand(d)

                else:
                    d = node.devices().get(sensor)
                    if not d:
                        d = self.telemetry_model.addDevice(
                            sensor_map.get(sensor, Node), sensor, device)
                    pr = self.telemetry_model.getNode(d).provides()
                    if 'Temperature' in pr:
                        self.telemetry_model.getNode(d).setTempUnit(temp_unit)
                    if 'Pressure' in pr or 'SeaPressure' in pr:
                        self.telemetry_model.getNode(d).setPresUnit(pres_unit)
                    for pk in pr.keys():
                        self.telemetry_model.setData(pr[pk],
                                                     payload[sensor].get(pk))
                    self.tview.expand(d)
        # self.tview.resizeColumnToContents(0)

    def console_log(self, topic, description, payload="", known=True):
        longest_tp = 0
        longest_fn = 0
        short_topic = "/".join(topic.split("/")[0:-1])
        fname = self.devices.get(short_topic, "")
        if not fname:
            device = self.device_model.findDevice(topic)
            fname = self.device_model.friendly_name(device.index)
            self.devices.update({short_topic: fname})
        self.console_model.addEntry(topic, fname, description, payload, known)

        if len(topic) > longest_tp:
            longest_tp = len(topic)
            self.console_view.resizeColumnToContents(1)

        if len(fname) > longest_fn:
            longest_fn = len(fname)
            self.console_view.resizeColumnToContents(1)

    def view_payload(self, idx):
        if self.cbFilter.isChecked():
            idx = self.sorted_console_model.mapToSource(idx)
        row = idx.row()
        timestamp = self.console_model.data(
            self.console_model.index(row, CnsMdl.TIMESTAMP))
        topic = self.console_model.data(
            self.console_model.index(row, CnsMdl.TOPIC))
        payload = self.console_model.data(
            self.console_model.index(row, CnsMdl.PAYLOAD))

        dlg = PayloadViewDialog(timestamp, topic, payload)
        dlg.exec_()

    def select_cons_entry(self, idx):
        self.cons_idx = idx

    def export(self):
        fname, _ = QFileDialog.getSaveFileName(self,
                                               "Export device list as...",
                                               directory=QDir.homePath(),
                                               filter="CSV files (*.csv)")
        if fname:
            if not fname.endswith(".csv"):
                fname += ".csv"

            with open(fname, "w", encoding='utf8') as f:
                column_titles = [
                    'mac', 'topic', 'friendly_name', 'full_topic',
                    'cmnd_topic', 'stat_topic', 'tele_topic', 'module',
                    'module_id', 'firmware', 'core'
                ]
                c = csv.writer(f)
                c.writerow(column_titles)

                for r in range(self.device_model.rowCount()):
                    d = self.device_model.index(r, 0)
                    c.writerow([
                        self.device_model.mac(d),
                        self.device_model.topic(d),
                        self.device_model.friendly_name(d),
                        self.device_model.fullTopic(d),
                        self.device_model.commandTopic(d),
                        self.device_model.statTopic(d),
                        self.device_model.teleTopic(d),
                        modules.get(self.device_model.module(d)),
                        self.device_model.module(d),
                        self.device_model.firmware(d),
                        self.device_model.core(d)
                    ])

    def bssid(self):
        BSSIdDialog().exec_()
        # if dlg.exec_() == QDialog.Accepted:

    def toggle_console_filter(self, state):
        self.cbxFilterDevice.setEnabled(state)
        if state:
            self.console_view.setModel(self.sorted_console_model)
        else:
            self.console_view.setModel(self.console_model)

    def select_console_filter(self, fname):
        self.sorted_console_model.setFilterFixedString(fname)

    def closeEvent(self, e):
        self.settings.setValue("window_geometry", self.saveGeometry())
        self.settings.setValue("splitter_state",
                               self.main_splitter.saveState())
        self.settings.sync()
        e.accept()
예제 #6
0
class MainWindow(QtWidgets.QMainWindow):

    Folder = 1
    File = 2
    Table = 3

    LogInfo = 101
    LogWarning = 102
    LogError = 103


    def LoadTableData(self,data,model):
        
        try:
            value = json.loads(data)
            table = value['table']
            model.setColumnCount(len(table))

            data = value['data']
            model.setRowCount(len(data) + 2)

            for v in table:
                model.setHeaderData(v[0],Qt.Horizontal,v[1])
                model.setData(model.index(0,v[0]),v[2])
                model.setData(model.index(1,v[0]),v[3])

            for i in range(0,len(data)):
                v = data[i]
                for j in range(0,len(v)):
                    model.setData(model.index(i+2,j),v[j])
                
            model.activeColumn = value['activeColumn']
        except Exception as e:
            pass

    def AddTreeItem(self,parent,text,type,isexpand = True):

        if parent == None:
            texts = text.split('.')
            if len(texts) > 1:
                rootItem = self.rootItem
                for i in range(0,len(texts)-1):
                    t = texts[i]
                    childItem = None
                    for j in range(0,rootItem.rowCount()):
                        childItem = rootItem.child(j,0)
                        if t == childItem.data():
                            break

                    rootItem = childItem
                parent = rootItem
                text = texts[-1]
            else:
                parent = self.rootItem

        lastFolderItem = None
        for i in range(0,parent.rowCount()):
            childItem = self.model.itemFromIndex(self.model.index(i,0,parent.index()))
            if childItem.data() == MainWindow.Folder:
                lastFolderItem = childItem

            if text == childItem.text():
                return None

        icon = None
        if type == MainWindow.Folder:
            icon = self.iconProvider.icon(QFileIconProvider.Folder)
        elif type == MainWindow.File:
            icon = self.iconProvider.icon(QFileIconProvider.File)
        elif type == MainWindow.Table:
            icon = self.iconProvider.icon(QFileIconProvider.Desktop)

        item = QStandardItem(parent)
        
        item.setIcon(icon)
        item.setText(text)
        item.setData(type)
        item.setFlags(QtCore.Qt.ItemIsEnabled|QtCore.Qt.ItemIsSelectable)

        if type == MainWindow.Folder and lastFolderItem != None:
            parent.insertRow(lastFolderItem.row()+1,item)
        else:
            parent.appendRow(item)
        
        if isexpand == True:
            self.tree.expand(parent.index())

        return item

    def SetRootTreeItem(self,text):
        self.rootItem = QStandardItem()
        self.rootItem.setIcon(self.iconProvider.icon(QFileIconProvider.Folder))
        self.rootItem.setText(text)
        self.rootItem.setData(MainWindow.Folder)

        self.model.appendRow(self.rootItem)

        for i in range(0,self.model.columnCount()):
            colItem = self.model.itemFromIndex(self.model.index(0,i))
            colItem.setFlags(QtCore.Qt.ItemIsEnabled|QtCore.Qt.ItemIsSelectable)

    def GetTreeItemShortPath(self,item):
        tempItem = item
        names = []
        while True:
            names.append(tempItem.text())
            tempItem = tempItem.parent()
            if tempItem == self.rootItem:
                break

        return '.'.join(reversed(names))

    def OnTreeCustomContextMenuRequested(self,pt):
        index = self.tree.indexAt(pt);  
        if index.isValid():

            item = self.model.itemFromIndex(index)

            parent = item.parent()
            if parent != None:
                item = self.model.itemFromIndex(self.model.index(item.row(),0,parent.index()))

            def OnAddTreeItem(self,item,type):
                inputDialog = InputDialog.InputDialog(self)
                ret = inputDialog.exec_()
                inputDialog.destroy()

                if QtWidgets.QDialog.Rejected == ret:
                    return

                if len(inputDialog.GetTextValue()) == 0:
                    return

                itemTable = self.AddTreeItem(item,str(inputDialog.GetTextValue()),type)

                if MainWindow.Table == type:
                    model = GridTableView.TableViewItemModel(2,0)
                    model.setParent(self.tree)
                    itemTable.setData(model,Qt.UserRole+2)

                cursor = None
                try:
                    cursor = self.db.cursor()
                    cursor.execute('insert into datas (k, v, t) values (\'{}\', \'{}\', {})'.format(self.GetTreeItemShortPath(itemTable),"None",type))
                    self.db.commit()
                except Exception as e:
                    pass
                finally:
                    cursor.close()

            def OnAddFolder(index,self = self,item = item):
                OnAddTreeItem(self,item,MainWindow.Folder)

            def OnAddFile(index,self = self,item = item):
                OnAddTreeItem(self,item,MainWindow.File)

            def OnAddTable(index,self = self,item = item):
                OnAddTreeItem(self,item,MainWindow.Table)

            def OnRename(index,self = self,item = item):
                inputDialog = InputDialog.InputDialog(self,item.text())
                ret = inputDialog.exec_()
                inputDialog.destroy()

                if QtWidgets.QDialog.Rejected == ret:
                    return

                text = inputDialog.GetTextValue()
                if len(text) == 0:
                    return

                #old_shortpath = self.GetTreeItemShortPath(item)
                

                items = []
                oldpaths = []

                if item.data() == MainWindow.Table:
                    items.append(item)
                else:
                    def GetAllChildItems(items,item):
                        for i in range(0,item.rowCount()):
                            childItem = item.child(i,0)

                            if childItem.data() != MainWindow.Table:
                                GetAllChildItems(items,childItem)
                            else:
                                items.append(childItem)
                        items.append(item)

                    GetAllChildItems(items,item)
                for v in items:
                    oldpaths.append(self.GetTreeItemShortPath(v))

                item.setText(text)
                cursor = self.db.cursor()
                for i in range(0,len(items)):
                    v = items[i]
                    oldpath = oldpaths[i]
                    cursor.execute('update datas set k=? where k=?', (self.GetTreeItemShortPath(v),oldpath))

                    findTabIndex = False
                    for i in range(0,self.tabWidget.count()):
                        if findTabIndex == True:
                            continue

                        if oldpath == self.tabWidget.tabToolTip(i):
                            findTabIndex = True
                            self.tabWidget.setTabToolTip(i,self.GetTreeItemShortPath(v))
                            if v == item and item.data() == MainWindow.Table:
                                self.tabWidget.setTabText(i,text)  
                    
                cursor.close()      
                self.db.commit()

            def OnDelete(index,self = self,item = item):
                if item == self.rootItem:
                    return

                deleyeKeys = set()
                cursor = self.db.cursor()

                if item.data() == MainWindow.Folder or item.data() == MainWindow.File:
                    cursor.execute('select * from datas')
                    shortpath = self.GetTreeItemShortPath(item)
                    for i in range(0,self.tabWidget.count()):
                        tabText = self.tabWidget.tabToolTip(i)
                        if len(tabText) >= len(shortpath) and tabText[0:len(shortpath)] == shortpath:
                            self.tabWidget.removeTab(i)
                            #if self.OnCloseTab(i) == False:
                            #    return

                    def DeleteChildItems(cursor,item):
                        for i in range(0,item.rowCount()):
                            childItem = item.child(i,0)

                            if item.data() != MainWindow.Table:
                                cursor.execute('delete from datas where k=?', (self.GetTreeItemShortPath(childItem),))
                                DeleteChildItems(cursor,childItem)
                        cursor.execute('delete from datas where k=?', (self.GetTreeItemShortPath(item),))

                    DeleteChildItems(cursor,item)
                    self.model.removeRow(item.row(),item.parent().index())

                elif item.data() == MainWindow.Table:
                    shortpath = self.GetTreeItemShortPath(item)
                    for i in range(0,self.tabWidget.count()):
                        if self.tabWidget.tabToolTip(i) == shortpath:
                            self.tabWidget.removeTab(i)
                            #if self.OnCloseTab(i) == False:
                            #    return
                    deleyeKeys.add(shortpath)
                    self.model.removeRow(item.row(),item.parent().index())

                for v in deleyeKeys:
                    try:
                        cursor.execute('delete from datas where k=?', (v,))
                    except Exception as e:
                        pass
                cursor.close()      
                self.db.commit()
            
            action_AddDir = QtWidgets.QAction("添加目录",None,triggered=OnAddFolder)
            action_AddConfig = QtWidgets.QAction("添加文件",None,triggered=OnAddFile)
            action_AddTable = QtWidgets.QAction("添加配置表",None,triggered=OnAddTable)
            action_Rename = QtWidgets.QAction("重命名",None,triggered=OnRename)
            action_Delete = QtWidgets.QAction("删除",None,triggered=OnDelete)

            menuTree = QtWidgets.QMenu("menuTree",self.tree)
            menuTree.addAction(action_AddDir)
            menuTree.addAction(action_AddConfig)
            menuTree.addAction(action_AddTable)
            menuTree.addSeparator()
            menuTree.addAction(action_Rename)
            menuTree.addSeparator()
            menuTree.addAction(action_Delete)

            if item == self.rootItem:
                action_Rename.setDisabled(True)

            if item.data() == MainWindow.Folder:
                action_AddTable.setDisabled(True)
                if item == self.rootItem:
                    action_Delete.setDisabled(True)
            elif item.data() == MainWindow.File:
                action_AddDir.setDisabled(True)
                action_AddConfig.setDisabled(True)
            elif item.data() == MainWindow.Table:
                action_AddTable.setDisabled(True)
                action_AddDir.setDisabled(True)
                action_AddConfig.setDisabled(True)
            else:
                return

            menuTree.exec_(QtGui.QCursor.pos())
            menuTree.destroy()

    def closeEvent(self, event):
        
        count = self.tabWidget.count()
        for i in reversed(range(0,count)):
            self.OnCloseTab(i)

        event.accept()

    def OnPaste(self):
        tableView = self.tabWidget.currentWidget()
        if tableView != None:
            if tableView.IsChanged == True:
                if QMessageBox.Yes == QMessageBox.information(self,'Save','Do you save changes?',QMessageBox.Yes|QMessageBox.No|QMessageBox.Cancel,QMessageBox.Yes):
                    tableView.Save()

            fileName,fileType = QtWidgets.QFileDialog.getOpenFileName(self,'Open File','','Excel File(*.xls *.xlsx)')
            if os.path.exists(fileName) and os.path.isfile(fileName):
                tableView.Paste(fileName)

    def OnExport(self):

        dialog = ExportDialog.ExportDialog(self)
        dialog.exec_()

    def DoSave(self,tableView):
        datas = tableView.Save()
        if datas != None:
            tabIndex = self.tabWidget.indexOf(tableView)

            cursor = None
        
            try:
                cursor = self.db.cursor()

                k = self.tabWidget.tabToolTip(tabIndex)
                cursor.execute('select * from datas where k=?', (k,))
                values = cursor.fetchall()

                if len(values) > 0 and values[0][0] == k:
                    cursor.execute('update datas set v=? where k=?', (datas,k))
                else:
                    cursor.execute('insert into datas (k, v, t) values (\'{}\', \'{}\', {})', (k,datas,MainWindow.Table))
                self.db.commit()
            except Exception as e:
                pass
            finally:
                if cursor != None:
                    cursor.close()

    def OnSave(self):
        tableView = self.tabWidget.currentWidget()
        if tableView != None and tableView.IsChanged == True:
            self.DoSave(tableView)
            tabIndex = self.tabWidget.indexOf(tableView)
            self.tabWidget.tabBar().setTabTextColor(tabIndex,QColor(0,0,0))

    def OnSaveAll(self):
        for i in range(0,self.tabWidget.count()):
            tableView = self.tabWidget.widget(i)
            if tableView.IsChanged == True:
                self.DoSave(tableView)
                self.tabWidget.tabBar().setTabTextColor(i,QColor(0,0,0))


    def OnUndo(self):
        tableView = self.tabWidget.currentWidget()
        if tableView != None:
            tableView.Undo()

    def OnRedo(self):
        tableView = self.tabWidget.currentWidget()
        if tableView != None:
            tableView.Redo()

    def EnableSave(self,enable,tableView):

        if enable == True:
            self.tabWidget.tabBar().setTabTextColor(self.tabWidget.indexOf(tableView),QColor(233,21,10))
        else:
            self.tabWidget.tabBar().setTabTextColor(self.tabWidget.indexOf(tableView),QColor(0,0,0))

    def OnTreeDoubleClicked(self,index):
        if index.isValid() == False:
            return

        item = self.model.itemFromIndex(index)
        shortpath = self.GetTreeItemShortPath(item)

        findTabIndex = -1
        if item.data() == MainWindow.Table:
            for i in range(0,self.tabWidget.count()):
                if self.tabWidget.tabToolTip(i) == shortpath:
                    findTabIndex = i
                    break
            if findTabIndex != -1:
                self.tabWidget.setCurrentIndex(findTabIndex)
            else:
                tableView = GridTableView.GridTableView(item.data(Qt.UserRole+2),self.tabWidget)

                tabIndex = self.tabWidget.addTab(tableView,item.text())
                self.tabWidget.setTabToolTip(tabIndex,shortpath)
                self.tabWidget.setCurrentWidget(tableView)
            pass
        pass

    def OnCloseTab(self,tabId):
        tableView = self.tabWidget.widget(tabId)
        if tableView.IsChanged == True:
            ret = QMessageBox.information(self,'Save','Do you save changes?',QMessageBox.Yes|QMessageBox.No|QMessageBox.Cancel,QMessageBox.Yes)
            if QMessageBox.Yes == ret:
                self.DoSave(tableView)
            elif QMessageBox.Cancel == ret:
                return False

        self.tabWidget.removeTab(tabId)
        return True

    @property
    def Settings(self):

        if self.setting == None:
            self.setting = {}
            try:
                with open("Settings.cfg",'r') as f:
                    self.setting = json.load(f)
            except IOError as e:
                pass

        return self.setting

    def GetTreeModel(self):
        return self.model

    def __del__(self):
        print('MainWindow.__del__')

        if self.db!= None:
            self.db.close()

        try:
            with open("Settings.cfg",'w') as f:
                json.dump(self.setting,f)
        except IOError as e:
            pass
        finally:
            pass
        pass


    def __init__(self): 
        super(MainWindow, self).__init__()
        
        uic.loadUi('MainWindow.ui', self)

        self.db = None
        self.rootItem = None
        self.setting = None
        self.fileSystemWatcher = QFileSystemWatcher()

        self.oldWindowTitle = self.windowTitle()
        
        self.iconProvider = QFileIconProvider()

        splitterH = QSplitter(self.centralwidget)
        self.verticalLayout.addWidget(splitterH)

        self.tree = QTreeView(splitterH)

        self.model = QStandardItemModel(self.tree)
        self.model.setHorizontalHeaderLabels(['Name'])
        self.model.setColumnCount(1)

        self.tree.setModel(self.model)

        selectionModel = QItemSelectionModel(self.model)
        self.tree.setSelectionModel(selectionModel)
        self.tree.setUniformRowHeights(True)
        self.tree.header().setStretchLastSection(False)
        self.tree.viewport().setAttribute(Qt.WA_StaticContents)
        self.tree.setAttribute(Qt.WA_MacShowFocusRect, False)

        self.tree.header().setSectionResizeMode(QHeaderView.ResizeToContents)
        self.tree.header().setStretchLastSection(False);

        self.tree.setHeaderHidden(True)

        self.tree.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tree.customContextMenuRequested['QPoint'].connect(self.OnTreeCustomContextMenuRequested)
        self.tree.doubleClicked.connect(self.OnTreeDoubleClicked)

        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
        sizePolicy.setHorizontalStretch(1)
        self.tree.setSizePolicy(sizePolicy)

        splitterH.addWidget(self.tree)

        self.setStatusBar(None)

        self.tabWidget = QTabWidget(splitterH)
        self.tabWidget.setTabsClosable(True)

        self.tabWidget.resize(self.tabWidget.size().width(),self.size().height()/3*1)

        self.tabWidget.tabCloseRequested['int'].connect(self.OnCloseTab)
        

        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
        sizePolicy.setHorizontalStretch(7)
        self.tabWidget.setSizePolicy(sizePolicy)

        splitterH.addWidget(self.tabWidget)
       
        self.action_Save.setShortcut(Qt.CTRL|Qt.Key_S)
        self.action_Save.triggered.connect(self.OnSave)

        self.actionParse_Excel.triggered.connect(self.OnPaste)
        #self.action_Export_Code.triggered.connect(self.OnExportData)

        self.actionUndo.setShortcut(Qt.CTRL|Qt.Key_Z)
        self.actionUndo.triggered.connect(self.OnUndo)

        self.actionRedo.setShortcut(Qt.CTRL|Qt.Key_Y)
        self.actionRedo.triggered.connect(self.OnRedo)

        self.SetRootTreeItem('root')

        #self.currentZip = ''
        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.DelayStart)
        self.timer.start(100)

    def GetAllDatasFromDB(self):
        cursor = self.db.cursor()
        cursor.execute('select * from datas order by k asc')
        values = cursor.fetchall()
        cursor.close()
        return values

    @property
    def TreeRootItem(self):
        return self.rootItem

    def DelayStart(self):
        self.timer.stop()
        self.timer = None
   
        sf = SelectFolder.SelectFolder(self)
        if sf.exec_() == QtWidgets.QDialog.Rejected:
            self.close()

        sf.destroy()
        currentPath = sf.GetFolder()

        if os.path.exists(currentPath) == False:
            return

        self.setWindowTitle(self.oldWindowTitle + ' - ' + currentPath)

        self.db = sqlite3.connect(currentPath)
        values = self.GetAllDatasFromDB()
        for k,v,t in values:
            item = self.AddTreeItem(None,k, t,False)

            if t == MainWindow.Table:  
                model = GridTableView.TableViewItemModel(2,0)
                model.setParent(self.tree)
                if v != 'None':
                    self.LoadTableData(v,model)

                item.setData(model,Qt.UserRole+2)

        self.tree.expand(self.rootItem.index())



        
예제 #7
0
class Watch(QWidget):
    def __init__(self, parent=None):
        super(QWidget, self).__init__(parent)
        self.initUI()
        self.initVariables()

    def initUI(self):
        mainlayout = QFormLayout()
        self.model = CheckableDirModel()
        #self.model = QFileSystemModel()

        self.tree = QTreeView()
        self.tree.setModel(self.model)

        self.tree.setAnimated(False)
        self.tree.setIndentation(20)
        self.tree.setColumnHidden(1, True)
        self.tree.setColumnHidden(2, True)
        self.tree.setColumnHidden(3, True)
        self.tree.setSortingEnabled(False)
        self.tree.setHeaderHidden(True)
        self.model.updateCheckBoxSignal.connect(self.updateCheckBoxes)

        buttonLayout = QHBoxLayout()
        self.applyButton = QPushButton("Apply", self)
        self.applyButton.clicked.connect(self.apply)
        self.applyButton.setEnabled(False)
        self.resetButton = QPushButton("Reset", self)
        hspacer = QWidget()
        hspacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        buttonLayout.addWidget(hspacer)
        buttonLayout.addWidget(self.applyButton)
        buttonLayout.addWidget(self.resetButton)

        vspacer = QWidget()
        vspacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        # Add to layout
        mainlayout.addRow(self.tree)
        mainlayout.addRow(vspacer)
        mainlayout.addRow(buttonLayout)
        self.setLayout(mainlayout)

    def initVariables(self):
        settings = readSettingItems(['Watched'])
        if 'Watched' in settings.keys():
            if len(settings['Watched']):
                self.watchList = settings['Watched']
                if len(self.watchList):
                    for watchItem in self.watchList:
                        tempPath = watchItem[0]
                        tempIndex = self.model.index(tempPath, 0)
                        retData = self.model.filePath(tempIndex)
                        # Set checkbox
                        if len(retData):
                            self.model.setData(tempIndex, watchItem[1], Qt.CheckStateRole)
                            # Expand path
                            while tempIndex.parent().isValid():
                                tempIndex = tempIndex.parent()
                                self.tree.expand(tempIndex)

            else:
                self.watchList = []

    def updateCheckBoxes(self, index, value):
        changeFlag = False
        fullpath = self.model.filePath(index)
        newWatchMission = [fullpath, value]
        if newWatchMission in self.watchList:
            pass
        else:
            if len(self.watchList):
                tempList = list(filter(lambda x: fullpath in x, self.watchList))
                if len(tempList) == 1:
                    tempMissionIndex = self.watchList.index(tempList[0])
                    if value == 0:
                        self.watchList.pop(tempMissionIndex)
                    else:
                        self.watchList[tempMissionIndex] = newWatchMission
                elif len(tempList) == 0:
                    if value != 0:
                        self.watchList.append(newWatchMission)
                changeFlag = True
        self.applyButton.setEnabled(changeFlag)
        # to do: update checkboxes states: 0, 1, 2.

    def apply(self):
        data = {'Watched': self.watchList }
        writeSettingItems(data)
        self.applyButton.setEnabled(False)
예제 #8
0
파일: __init__.py 프로젝트: Zubax/kucher
class RegisterViewWidget(WidgetBase):
    def __init__(self, parent: QWidget):
        super(RegisterViewWidget, self).__init__(parent)

        self._registers = []
        self._running_task: asyncio.Task = None

        self._visibility_selector = QComboBox(self)
        self._visibility_selector.addItem("Show all registers", lambda _: True)
        self._visibility_selector.addItem("Only configuration parameters",
                                          lambda r: r.mutable and r.persistent)

        # noinspection PyUnresolvedReferences
        self._visibility_selector.currentIndexChanged.connect(
            lambda _: self._on_visibility_changed())

        self._reset_selected_button = make_button(
            self,
            "Reset selected",
            icon_name="clear-symbol",
            tool_tip=f"Reset the currently selected registers to their default "
            f"values. The restored values will be committed "
            f"immediately. This function is available only if a "
            f"default value is defined. [{RESET_SELECTED_SHORTCUT}]",
            on_clicked=self._do_reset_selected,
        )

        self._reset_all_button = make_button(
            self,
            "Reset all",
            icon_name="skull-crossbones",
            tool_tip=f"Reset the all registers to their default "
            f"values. The restored values will be committed "
            f"immediately.",
            on_clicked=self._do_reset_all,
        )

        self._read_selected_button = make_button(
            self,
            "Read selected",
            icon_name="process",
            tool_tip=f"Read the currently selected registers only "
            f"[{READ_SELECTED_SHORTCUT}]",
            on_clicked=self._do_read_selected,
        )

        self._read_all_button = make_button(
            self,
            "Read all",
            icon_name="process-plus",
            tool_tip="Read all registers from the device",
            on_clicked=self._do_read_all,
        )

        self._export_button = make_button(
            self,
            "Export",
            icon_name="export",
            tool_tip="Export configuration parameters",
            on_clicked=self._do_export,
        )

        self._import_button = make_button(
            self,
            "Import",
            icon_name="import",
            tool_tip="Import configuration parameters",
            on_clicked=self._do_import,
        )

        self._expand_all_button = make_button(
            self,
            "",
            icon_name="expand-arrow",
            tool_tip="Expand all namespaces",
            on_clicked=lambda: self._tree.expandAll(),
        )

        self._collapse_all_button = make_button(
            self,
            "",
            icon_name="collapse-arrow",
            tool_tip="Collapse all namespaces",
            on_clicked=lambda: self._tree.collapseAll(),
        )

        self._status_display = QLabel(self)
        self._status_display.setWordWrap(True)

        self._reset_selected_button.setEnabled(False)
        self._reset_all_button.setEnabled(False)
        self._read_selected_button.setEnabled(False)
        self._read_all_button.setEnabled(False)
        self._export_button.setEnabled(False)
        self._import_button.setEnabled(False)

        self._tree = QTreeView(self)
        self._tree.setVerticalScrollMode(QTreeView.ScrollPerPixel)
        self._tree.setHorizontalScrollMode(QTreeView.ScrollPerPixel)
        self._tree.setAnimated(True)
        self._tree.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self._tree.setAlternatingRowColors(True)
        self._tree.setContextMenuPolicy(Qt.ActionsContextMenu)

        # Not sure about this one. This hardcoded value may look bad on some platforms.
        self._tree.setIndentation(20)

        def add_action(
            callback: typing.Callable[[], None],
            icon_name: str,
            name: str,
            shortcut: typing.Optional[str] = None,
        ):
            action = QAction(get_icon(icon_name), name, self)
            # noinspection PyUnresolvedReferences
            action.triggered.connect(callback)
            if shortcut:
                action.setShortcut(shortcut)
                action.setAutoRepeat(False)
                try:
                    action.setShortcutVisibleInContextMenu(True)
                except AttributeError:
                    pass  # This feature is not available in PyQt before 5.10

            self._tree.addAction(action)

        add_action(self._do_read_all, "process-plus", "Read all registers")
        add_action(
            self._do_read_selected,
            "process",
            "Read selected registers",
            READ_SELECTED_SHORTCUT,
        )
        add_action(
            self._do_reset_selected,
            "clear-symbol",
            "Reset selected to default",
            RESET_SELECTED_SHORTCUT,
        )

        self._tree.setItemDelegateForColumn(
            int(Model.ColumnIndices.VALUE),
            EditorDelegate(self._tree, self._display_status),
        )

        # It doesn't seem to be explicitly documented, but it seems to be necessary to select either top or bottom
        # decoration position in order to be able to use center alignment. Left or right positions do not work here.
        self._tree.setItemDelegateForColumn(
            int(Model.ColumnIndices.FLAGS),
            StyleOptionModifyingDelegate(
                self._tree,
                decoration_position=QStyleOptionViewItem.Top,  # Important
                decoration_alignment=Qt.AlignCenter,
            ),
        )

        header: QHeaderView = self._tree.header()
        header.setSectionResizeMode(QHeaderView.ResizeToContents)
        header.setStretchLastSection(
            False)  # Horizontal scroll bar doesn't work if this is enabled

        buttons_layout = QGridLayout()
        buttons_layout.addWidget(self._read_selected_button, 0, 0)
        buttons_layout.addWidget(self._reset_selected_button, 0, 2)
        buttons_layout.addWidget(self._read_all_button, 1, 0)
        buttons_layout.addWidget(self._reset_all_button, 1, 2)
        buttons_layout.addWidget(self._import_button, 2, 0)
        buttons_layout.addWidget(self._export_button, 2, 2)

        for col in range(3):
            buttons_layout.setColumnStretch(col, 1)

        layout = lay_out_vertically(
            (self._tree, 1),
            buttons_layout,
            lay_out_horizontally(
                self._visibility_selector,
                (None, 1),
                self._expand_all_button,
                self._collapse_all_button,
            ),
            self._status_display,
        )

        self.setLayout(layout)

    def reset(self):
        self.setup([])

    def setup(self, registers: typing.Iterable[Register]):
        self._registers = list(registers)
        self._on_visibility_changed()

    def _replace_model(
            self, register_visibility_predicate: typing.Callable[[Register],
                                                                 bool]):
        # Cancel all operations that might be pending on the old model
        self._cancel_task()

        old_model = self._tree.model()

        # Configure the new model
        filtered_registers = list(
            filter(register_visibility_predicate, self._registers))
        # It is important to set the Tree widget as the parent in order to let the widget take ownership
        new_model = Model(self._tree, filtered_registers)
        _logger.info("New model %r", new_model)
        self._tree.setModel(new_model)

        # The selection model is implicitly replaced when we replace the model, so it has to be reconfigured
        self._tree.selectionModel().selectionChanged.connect(
            lambda *_: self._on_selection_changed())

        # TODO: Something fishy is going on. Something keeps the old model alive when we're replacing it.
        #       We could call deleteLater() on it, but it seems dangerous, because if that something ever decided
        #       to refer to that dead model later for any reason, we'll get a rougue dangling pointer access on
        #       our hands. The horror!
        if old_model is not None:
            import gc

            model_referrers = gc.get_referrers(old_model)
            if len(model_referrers) > 1:
                _logger.warning(
                    "Extra references to the old model %r: %r",
                    old_model,
                    model_referrers,
                )

        # Update the widget - all root items are expanded by default
        for row in itertools.count():
            index = self._tree.model().index(row, 0)
            if not index.isValid():
                break

            self._tree.expand(index)

        self._reset_selected_button.setEnabled(False)
        self._read_selected_button.setEnabled(False)
        self._read_all_button.setEnabled(len(filtered_registers) > 0)
        self._reset_all_button.setEnabled(len(filtered_registers) > 0)
        self._export_button.setEnabled(len(filtered_registers) > 0)
        self._import_button.setEnabled(len(filtered_registers) > 0)

        self._display_status(f"{len(filtered_registers)} registers loaded")

    def _on_visibility_changed(self):
        self._replace_model(self._visibility_selector.currentData())

    def _on_selection_changed(self):
        selected = self._get_selected_registers()

        self._reset_selected_button.setEnabled(
            any(map(lambda r: r.has_default_value, selected)))
        self._read_selected_button.setEnabled(len(selected) > 0)

    def _do_read_selected(self):
        selected = self._get_selected_registers()
        if selected:
            self._read_specific(selected)
        else:
            self._display_status("No registers are selected, nothing to read")

    def _do_reset_selected(self):
        rv = {}
        for r in self._get_selected_registers():
            if r.has_default_value:
                rv[r] = r.default_value

        self._write_specific(rv)

    def _do_reset_all(self):
        rv = {}
        for r in self._registers:
            if r.has_default_value:
                rv[r] = r.default_value

        self._write_specific(rv)

    def _do_read_all(self):
        self._read_specific(self._tree.model().registers)

    def _do_import(self):
        import_registers(parent=self, registers=self._registers)

    def _do_export(self):
        export_registers(parent=self, registers=self._registers)

    def _read_specific(self, registers: typing.List[Register]):
        total_registers_read = None

        def progress_callback(register: Register, current_register_index: int,
                              total_registers: int):
            nonlocal total_registers_read
            total_registers_read = total_registers
            self._display_status(
                f"Reading {register.name!r} "
                f"({current_register_index + 1} of {total_registers})")

        async def executor():
            try:
                _logger.info("Reading registers: %r",
                             [r.name for r in registers])
                mod: Model = self._tree.model()
                await mod.read(registers=registers,
                               progress_callback=progress_callback)
            except asyncio.CancelledError:
                self._display_status(f"Read has been cancelled")
                raise
            except Exception as ex:
                _logger.exception("Register read failed")
                show_error("Read failed", "Could not read registers", repr(ex),
                           self)
                self._display_status(f"Could not read registers: {ex!r}")
            else:
                self._display_status(
                    f"{total_registers_read} registers have been read")

        self._cancel_task()
        self._running_task = asyncio.get_event_loop().create_task(executor())

    def _write_specific(self, register_value_mapping: typing.Dict[Register,
                                                                  typing.Any]):
        total_registers_assigned = None

        def progress_callback(register: Register, current_register_index: int,
                              total_registers: int):
            nonlocal total_registers_assigned
            total_registers_assigned = total_registers
            self._display_status(
                f"Writing {register.name!r} "
                f"({current_register_index + 1} of {total_registers})")

        async def executor():
            try:
                _logger.info(
                    "Writing registers: %r",
                    [r.name for r in register_value_mapping.keys()],
                )
                mod: Model = self._tree.model()
                await mod.write(
                    register_value_mapping=register_value_mapping,
                    progress_callback=progress_callback,
                )
            except asyncio.CancelledError:
                self._display_status(f"Write has been cancelled")
                raise
            except Exception as ex:
                _logger.exception("Register write failed")
                show_error("Write failed", "Could not read registers",
                           repr(ex), self)
                self._display_status(f"Could not write registers: {ex!r}")
            else:
                self._display_status(
                    f"{total_registers_assigned} registers have been written")

        self._cancel_task()
        self._running_task = asyncio.get_event_loop().create_task(executor())

    def _get_selected_registers(self) -> typing.List[Register]:
        selected_indexes: typing.List[
            QModelIndex] = self._tree.selectedIndexes()
        selected_registers = set()
        for si in selected_indexes:
            r = Model.get_register_from_index(si)
            if r is not None:
                selected_registers.add(r)
        # Beware that sets are not sorted, this may lead to weird user experience when watching the registers
        # read in a funny order.
        return list(sorted(selected_registers, key=lambda x: x.name))

    def _cancel_task(self):
        # noinspection PyBroadException
        try:
            self._running_task.cancel()
        except Exception:
            pass
        else:
            _logger.info("A running task had to be cancelled: %r",
                         self._running_task)
        finally:
            self._running_task = None

    def _display_status(self, text=None):
        self._status_display.setText(text)
예제 #9
0
class TreeView(QWidget):
    def __init__(self, table, parent):
        QWidget.__init__(self, parent)
        self.window = parent
        self.tree = QTreeView(self)
        indent = self.tree.indentation()
        self.tree.setIndentation(indent / 2)

        self.model = DataModel(table)
        self.sorter = sorter = FilterModel(self)
        sorter.setSourceModel(self.model)
        self.tree.setModel(sorter)
        for col in range(3, 9):
            self.tree.setItemDelegateForColumn(col, PercentDelegate(self))
        self.tree.header().setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.tree.header().customContextMenuRequested.connect(
            self._on_header_menu)
        self.tree.setSortingEnabled(True)
        self.tree.setAutoExpandDelay(0)
        self.tree.resizeColumnToContents(0)
        self.tree.resizeColumnToContents(NAME_COLUMN)
        self.tree.expand(self.sorter.index(0, 0))
        #self.tree.expandAll()

        self.tree.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.tree.customContextMenuRequested.connect(self._on_tree_menu)

        searchbox = QHBoxLayout()
        self.search = QLineEdit(self)

        searchbox.addWidget(self.search)

        self.search_type = QComboBox(self)
        self.search_type.addItem("Contains", SEARCH_CONTAINS)
        self.search_type.addItem("Exact", SEARCH_EXACT)
        self.search_type.addItem("Reg.Exp", SEARCH_REGEXP)
        searchbox.addWidget(self.search_type)

        btn = QPushButton("&Search", self)
        searchbox.addWidget(btn)
        btn.clicked.connect(self._on_search)

        btn = QPushButton("&Next", self)
        searchbox.addWidget(btn)
        btn.clicked.connect(self._on_search_next)

        filterbox = QHBoxLayout()

        label = QLabel("Time Individual", self)
        filterbox.addWidget(label)
        self.individual_time = QSpinBox(self)
        self.individual_time.setMinimum(0)
        self.individual_time.setMaximum(100)
        self.individual_time.setSuffix(" %")
        filterbox.addWidget(self.individual_time)

        label = QLabel("Alloc Individual", self)
        filterbox.addWidget(label)
        self.individual_alloc = QSpinBox(self)
        self.individual_alloc.setMinimum(0)
        self.individual_alloc.setMaximum(100)
        self.individual_alloc.setSuffix(" %")
        filterbox.addWidget(self.individual_alloc)

        label = QLabel("Time Inherited", self)
        filterbox.addWidget(label)
        self.inherited_time = QSpinBox(self)
        self.inherited_time.setMinimum(0)
        self.inherited_time.setMaximum(100)
        self.inherited_time.setSuffix(" %")
        filterbox.addWidget(self.inherited_time)

        label = QLabel("Alloc Inherited", self)
        filterbox.addWidget(label)
        self.inherited_alloc = QSpinBox(self)
        self.inherited_alloc.setMinimum(0)
        self.inherited_alloc.setMaximum(100)
        self.inherited_alloc.setSuffix(" %")
        filterbox.addWidget(self.inherited_alloc)

        btn = QPushButton("&Filter", self)
        btn.clicked.connect(self._on_filter)
        filterbox.addWidget(btn)
        btn = QPushButton("&Reset", self)
        filterbox.addWidget(btn)
        btn.clicked.connect(self._on_reset_filter)

        vbox = QVBoxLayout()
        vbox.addLayout(searchbox)
        vbox.addLayout(filterbox)
        vbox.addWidget(self.tree)
        self.setLayout(vbox)

        self._search_idxs = None
        self._search_idx_no = 0

    def _expand_to(self, idx):
        idxs = [idx]
        parent = idx
        while parent and parent.isValid():
            parent = self.sorter.parent(parent)
            idxs.append(parent)
        #print(idxs)
        for idx in reversed(idxs[:-1]):
            data = self.sorter.data(idx, QtCore.Qt.DisplayRole)
            #print(data)
            self.tree.expand(idx)

    def _on_search(self):
        text = self.search.text()
        selected = self.tree.selectedIndexes()
        #         if selected:
        #             start = selected[0]
        #         else:
        start = self.sorter.index(0, NAME_COLUMN)
        search_type = self.search_type.currentData()
        if search_type == SEARCH_EXACT:
            method = QtCore.Qt.MatchFixedString
        elif search_type == SEARCH_CONTAINS:
            method = QtCore.Qt.MatchContains
        else:
            method = QtCore.Qt.MatchRegExp

        self._search_idxs = idxs = self.sorter.search(start, text, search_type)
        if idxs:
            self.window.statusBar().showMessage(
                "Found: {} occurence(s)".format(len(idxs)))
            self._search_idx_no = 0
            idx = idxs[0]
            self._locate(idx)
        else:
            self.window.statusBar().showMessage("Not found")

    def _locate(self, idx):
        self.tree.resizeColumnToContents(0)
        self.tree.resizeColumnToContents(NAME_COLUMN)
        self._expand_to(idx)
        self.tree.setCurrentIndex(idx)
        #self.tree.selectionModel().select(idx, QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Current | QItemSelectionModel.Rows)
        #self.tree.scrollTo(idx, QAbstractItemView.PositionAtCenter)

    def _on_search_next(self):
        if self._search_idxs:
            n = len(self._search_idxs)
            self._search_idx_no = (self._search_idx_no + 1) % n
            idx = self._search_idxs[self._search_idx_no]
            self.window.statusBar().showMessage("Occurence {} of {}".format(
                self._search_idx_no, n))
            self._locate(idx)
        else:
            self.window.statusBar().showMessage("No search results")

    def _on_filter(self):
        self.sorter.setFilter(self.search.text(), self.individual_time.value(),
                              self.individual_alloc.value(),
                              self.inherited_time.value(),
                              self.inherited_alloc.value())

    def _on_reset_filter(self):
        self.sorter.reset()

    def _on_header_menu(self, pos):
        menu = make_header_menu(self.tree)
        menu.exec_(self.mapToGlobal(pos))

    def _on_tree_menu(self, pos):
        index = self.tree.indexAt(pos)
        #print("index: {}".format(index))
        if index.isValid():
            record = self.sorter.data(index, QtCore.Qt.UserRole + 1)
            #print("okay?..")
            #print("context: {}".format(record))
            menu = self.window.make_item_menu(self.model, record)
            menu.exec_(self.tree.viewport().mapToGlobal(pos))
예제 #10
0
파일: debug.py 프로젝트: wbsoft/parceqt
class DebugWindow(QMainWindow):
    """A main window to edit text and examine the generated token structure.

    Example::

        from PyQt5.Qt import *
        a=QApplication([])

        from parceqt.debug import DebugWindow
        w = DebugWindow()
        w.resize(1200,900)
        w.show()

        w.set_theme("default", True)

        from parce.lang.css import *
        w.set_root_lexicon(Css.root)
        w.set_text(open("path/to/parce/themes/default.css").read())

    In the debug window you can edit the text at the left and directly at the
    right examine the tree structure. Along the top of the window the path to
    the token at the current cursor position is displayed, from the root
    lexicon upto the token, from which the action is displayed.

    Clicking a button selects the associated range of the context or token in
    the text view. Clicking an item in the tree also selects that range in the
    text.

    Moving the cursor in the text updates the current item in the tree,
    and the displayed ancestor path.

    """

    show_updated_region_enabled = False

    def __init__(self, parent=None):
        super().__init__(parent, windowTitle="parceqt debugger")

        f = self._updated_format = QTextCharFormat()
        c = QColor("palegreen")
        c.setAlpha(64)
        f.setBackground(c)
        f = self._currentline_format = QTextCharFormat()
        f.setProperty(QTextCharFormat.FullWidthSelection, True)

        self._actions = Actions(self)
        self._actions.add_menus(self.menuBar())

        widget = QWidget(self)
        self.setCentralWidget(widget)
        layout = QVBoxLayout(margin=4, spacing=2)
        widget.setLayout(layout)

        top_layout = QHBoxLayout(margin=0, spacing=0)

        self.guessButton = QToolButton(self,
                                       clicked=self.guess_root_lexicon,
                                       toolTip="Guess Language",
                                       icon=self.style().standardIcon(
                                           QStyle.SP_BrowserReload))
        self.lexiconChooser = LexiconChooser(self)
        self.ancestorView = AncestorView(self)
        top_layout.addWidget(self.guessButton)
        top_layout.addWidget(self.lexiconChooser)
        top_layout.addWidget(self.ancestorView)
        top_layout.addStretch(10)
        layout.addLayout(top_layout)
        self.guessButton.setFixedHeight(
            self.lexiconChooser.sizeHint().height())

        splitter = QSplitter(self, orientation=Qt.Horizontal)
        layout.addWidget(splitter, 100)

        self.textEdit = QPlainTextEdit(lineWrapMode=QPlainTextEdit.NoWrap,
                                       cursorWidth=2)
        self.treeView = QTreeView()

        splitter.addWidget(self.textEdit)
        splitter.addWidget(self.treeView)
        splitter.setStretchFactor(0, 3)
        splitter.setStretchFactor(1, 2)

        self.extraSelectionManager = ExtraSelectionManager(self.textEdit)

        self.document = d = self.textEdit.document()
        self.textEdit.setDocument(self.document)

        self.worker = w = parceqt.worker(d)
        self.builder = b = w.builder()
        w.debugging = True

        self.setStatusBar(QStatusBar())
        self.create_model()

        # signal connections
        self.textEdit.viewport().installEventFilter(self)
        self.textEdit.installEventFilter(self)
        self.lexiconChooser.lexicon_changed.connect(
            self.slot_root_lexicon_changed)
        self.ancestorView.node_clicked.connect(self.slot_node_clicked)
        w.started.connect(self.slot_build_started)
        w.tree_updated.connect(self.slot_build_updated)
        self.textEdit.cursorPositionChanged.connect(
            self.slot_cursor_position_changed)
        self.treeView.clicked.connect(self.slot_item_clicked)

        self.textEdit.setFocus()
        self.set_theme()

        # somewhat larger font by default
        font = self.textEdit.font()
        font.setPointSizeF(11)
        self.textEdit.setFont(font)

    def create_model(self):
        """Instantiate a tree model for the tree view."""
        m = self.treeView.model()
        if not m:
            m = parceqt.treemodel.TreeModel(self.builder.root)
            m.connect_debugging_builder(self.builder)
            self.treeView.setModel(m)

    def delete_model(self):
        """Delete the model and remove it from the tree."""
        m = self.treeView.model()
        if m:
            m.disconnect_debugging_builder(self.builder)
            self.treeView.setModel(None)
            m.deleteLater()

    def set_text(self, text):
        """Set the text in the text edit."""
        self.document.setPlainText(text)

    def set_root_lexicon(self, lexicon):
        """Set the root lexicon to use."""
        self.lexiconChooser.set_root_lexicon(lexicon)

    def guess_root_lexicon(self):
        """Again choose the root lexicon based on the text."""
        text = self.document.toPlainText()
        if text:
            self.set_root_lexicon(parce.find(contents=text))

    def open_file(self, filename):
        """Read a file from disk and guess the language."""
        text = read_file(filename)
        root_lexicon = parce.find(filename=filename, contents=text)
        self.set_text(text)
        self.set_root_lexicon(root_lexicon)
        c = self.textEdit.textCursor()
        c.setPosition(0)
        self.textEdit.setTextCursor(c)

    def set_theme(self, theme="default", adjust_widget=True):
        """Set the theme to use for the text edit."""
        if isinstance(theme, str):
            theme = parce.theme_by_name(theme)
        formatter = parceqt.formatter.Formatter(theme) if theme else None
        if adjust_widget:
            if formatter:
                font = formatter.font(self)
                self.textEdit.setPalette(formatter.palette(self))
            else:
                font = QApplication.font(self)
                self.textEdit.setPalette(QApplication.palette(self))
            font.setPointSizeF(self.textEdit.font().pointSizeF())  # keep size
            self.textEdit.setFont(font)
            self.highlight_current_line()
        h = parceqt.highlighter.SyntaxHighlighter.instance(self.worker)
        h.set_formatter(formatter)

    def slot_build_started(self):
        """Called when the tree builder has started a build."""
        self.treeView.setCursor(Qt.BusyCursor)

    def slot_build_updated(self):
        """Called when the tree builder has finished a build."""
        self.treeView.unsetCursor()
        self.slot_cursor_position_changed()
        self.statusBar().showMessage(", ".join(
            lexicon_names(self.builder.lexicons)))
        tree = self.worker.get_root()
        self.lexiconChooser.setToolTip(
            parceqt.treemodel.TreeModel.node_tooltip(tree))
        if self.show_updated_region_enabled:
            self.show_updated_region()

    def slot_cursor_position_changed(self):
        """Called when the text cursor moved."""
        tree = self.worker.get_root()
        if tree:
            pos = self.textEdit.textCursor().position()
            doc = parceqt.document.Document(self.document)
            token = doc.token(pos)
            if token:
                self.ancestorView.set_token_path(token)
                model = self.treeView.model()
                if model:
                    index = model.get_model_index(token)
                    self.treeView.setCurrentIndex(index)
        elif tree is not None:
            self.ancestorView.clear()
        self.highlight_current_line()

    def slot_item_clicked(self, index):
        """Called when a node in the tree view is clicked."""
        tree = self.worker.get_root()
        if tree:
            model = self.treeView.model()
            if model:
                node = self.treeView.model().get_node(index)
                cursor = self.textEdit.textCursor()
                cursor.setPosition(node.end)
                cursor.setPosition(node.pos, QTextCursor.KeepAnchor)
                self.textEdit.setTextCursor(cursor)
        self.textEdit.setFocus()

    def slot_node_clicked(self, node):
        """Called when a button in the ancestor view is clicked."""
        tree = self.worker.get_root()
        if tree and node.root() is tree:
            cursor = self.textEdit.textCursor()
            cursor.setPosition(node.end)
            cursor.setPosition(node.pos, QTextCursor.KeepAnchor)
            self.textEdit.setTextCursor(cursor)
            self.textEdit.setFocus()
            model = self.treeView.model()
            if model:
                index = model.get_model_index(node)
                self.treeView.expand(index)
                self.treeView.setCurrentIndex(index)

    def slot_root_lexicon_changed(self, lexicon):
        """Called when the root lexicon is changed."""
        parceqt.set_root_lexicon(self.document, lexicon)

    def highlight_current_line(self):
        """Highlight the current line."""
        group = QPalette.Active if self.textEdit.hasFocus(
        ) else QPalette.Inactive
        p = self.textEdit.palette()
        color = p.color(group, QPalette.AlternateBase)
        self._currentline_format.setBackground(color)
        if color != p.color(group, QPalette.Base):
            c = self.textEdit.textCursor()
            c.clearSelection()
            self.extraSelectionManager.highlight(self._currentline_format, [c])
        else:
            self.extraSelectionManager.clear(self._currentline_format)

    def show_updated_region(self):
        """Highlight the updated region for 2 seconds."""
        end = self.builder.end
        if end >= self.document.characterCount() - 1:
            end = self.document.characterCount() - 1
            if self.builder.start == 0:
                return
        c = QTextCursor(self.document)
        c.setPosition(end)
        c.setPosition(self.builder.start, QTextCursor.KeepAnchor)
        self.extraSelectionManager.highlight(self._updated_format, [c],
                                             msec=2000)

    def clear_updated_region(self):
        self.extraSelectionManager.clear(self._updated_format)

    def eventFilter(self, obj, ev):
        """Implemented to support Ctrl+wheel zooming and keybfocus handling."""
        if obj == self.textEdit:
            if ev.type() in (QEvent.FocusIn, QEvent.FocusOut):
                self.highlight_current_line()
        else:  # viewport
            if ev.type() == QEvent.Wheel and ev.modifiers(
            ) == Qt.ControlModifier:
                if ev.angleDelta().y() > 0:
                    self.textEdit.zoomIn()
                elif ev.angleDelta().y() < 0:
                    self.textEdit.zoomOut()
                return True
        return False
예제 #11
0
class FileBrowserWidget(QWidget):
    on_open = pyqtSignal(str)

    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.model = QFileSystemModel()
        self.rootFolder = ''
        self.model.setRootPath(self.rootFolder)
        self.tree = QTreeView()
        self.tree.setModel(self.model)

        self.tree.setAnimated(False)
        self.tree.setIndentation(20)
        self.tree.setSortingEnabled(True)
        self.tree.sortByColumn(0, 0)
        self.tree.setColumnWidth(0, 200)
        self.tree.setDragEnabled(True)

        self.tree.setWindowTitle("Dir View")
        self.tree.resize(640, 480)
        self.tree.doubleClicked.connect(self.onDblClick)
        self.tree.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.tree.customContextMenuRequested.connect(
            self.onCustomContextMenuRequested)

        windowLayout = QVBoxLayout()
        windowLayout.addWidget(self.tree)
        windowLayout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(windowLayout)

    def onCustomContextMenuRequested(self, point):
        index = self.tree.indexAt(point)
        selectedFile = None
        selectedFolder = None
        ctx = QMenu("Context menu", self)
        if index.isValid():
            file = self.model.fileInfo(index)
            selectedFile = file.absoluteFilePath()
            selectedFolder = selectedFile if file.isDir(
            ) else file.absolutePath()
            if file.isDir():
                ctx.addAction(
                    "Open in file manager", lambda: QDesktopServices.openUrl(
                        QUrl.fromLocalFile(selectedFile)))
            if not file.isDir():
                for wndTyp, meta in WindowTypes.types:
                    text = 'Open with ' + meta.get('displayName', meta['name'])
                    print(wndTyp, meta)
                    ctx.addAction(
                        QAction(text,
                                self,
                                statusTip=text,
                                triggered=lambda dummy, meta=meta: navigate(
                                    "WINDOW", "Type=" + meta['name'],
                                    "FileName=" + selectedFile)))
                ctx.addSeparator()

        ctx.addAction("Set root folder ...",
                      lambda: self.selectRootFolder(preselect=selectedFolder))
        ctx.exec(self.tree.viewport().mapToGlobal(point))

    def selectRootFolder(self, preselect=None):
        if preselect == None: preselect = self.rootFolder
        dir = QFileDialog.getExistingDirectory(self, "Set root folder",
                                               preselect)
        if dir != None:
            self.setRoot(dir)

    def setRoot(self, dir):
        self.rootFolder = dir
        self.model.setRootPath(dir)
        self.tree.setRootIndex(self.model.index(dir))

    def onDblClick(self, index):
        if index.isValid():
            file = self.model.fileInfo(index)
            if not file.isDir():
                navigate("OPEN", "FileName=" + file.absoluteFilePath())

    def saveState(self):
        if self.tree.currentIndex().isValid():
            info = self.model.fileInfo(self.tree.currentIndex())
            return {"sel": info.absoluteFilePath(), "root": self.rootFolder}

    def restoreState(self, state):
        try:
            self.setRoot(state["root"])
        except:
            pass
        try:
            idx = self.model.index(state["sel"])
            if idx.isValid():
                self.tree.expand(idx)
                self.tree.setCurrentIndex(idx)
                self.tree.scrollTo(idx, QAbstractItemView.PositionAtCenter)
        except:
            pass
예제 #12
0
파일: tdm.py 프로젝트: mechromonger/tdm
class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        self._version = "0.1.11"
        self.setWindowIcon(QIcon("GUI/icons/logo.png"))
        self.setWindowTitle("Tasmota Device Manager {}".format(self._version))

        self.main_splitter = QSplitter()
        self.devices_splitter = QSplitter(Qt.Vertical)

        self.fulltopic_queue = []
        self.settings = QSettings()
        self.setMinimumSize(QSize(1280,800))

        self.device_model = TasmotaDevicesModel()
        self.telemetry_model = TasmotaDevicesTree()
        self.console_model = ConsoleModel()

        self.sorted_console_model = QSortFilterProxyModel()
        self.sorted_console_model.setSourceModel(self.console_model)
        self.sorted_console_model.setFilterKeyColumn(CnsMdl.FRIENDLY_NAME)

        self.setup_mqtt()
        self.setup_telemetry_view()
        self.setup_main_layout()
        self.add_devices_tab()
        self.build_toolbars()
        self.setStatusBar(QStatusBar())

        self.queue_timer = QTimer()
        self.queue_timer.setSingleShot(True)
        self.queue_timer.timeout.connect(self.mqtt_ask_for_fulltopic)

        self.build_cons_ctx_menu()

        self.load_window_state()

    def setup_main_layout(self):
        self.mdi = QMdiArea()
        self.mdi.setActivationOrder(QMdiArea.ActivationHistoryOrder)
        self.mdi.setViewMode(QMdiArea.TabbedView)
        self.mdi.setDocumentMode(True)

        mdi_widget = QWidget()
        mdi_widget.setLayout(VLayout())
        mdi_widget.layout().addWidget(self.mdi)

        self.devices_splitter.addWidget(mdi_widget)

        vl_console = VLayout()
        self.console_view = TableView()
        self.console_view.setModel(self.sorted_console_model)
        self.console_view.setupColumns(columns_console)
        self.console_view.setAlternatingRowColors(True)
        self.console_view.setSortingEnabled(True)
        self.console_view.sortByColumn(CnsMdl.TIMESTAMP, Qt.DescendingOrder)
        self.console_view.verticalHeader().setDefaultSectionSize(20)
        self.console_view.setMinimumHeight(200)
        self.console_view.setContextMenuPolicy(Qt.CustomContextMenu)

        vl_console.addWidget(self.console_view)

        console_widget = QWidget()
        console_widget.setLayout(vl_console)

        self.devices_splitter.addWidget(console_widget)
        self.main_splitter.insertWidget(0, self.devices_splitter)
        self.setCentralWidget(self.main_splitter)
        self.console_view.clicked.connect(self.select_cons_entry)
        self.console_view.doubleClicked.connect(self.view_payload)
        self.console_view.customContextMenuRequested.connect(self.show_cons_ctx_menu)

    def setup_telemetry_view(self):
        tele_widget = QWidget()
        vl_tele = VLayout()
        self.tview = QTreeView()
        self.tview.setMinimumWidth(300)
        self.tview.setModel(self.telemetry_model)
        self.tview.setAlternatingRowColors(True)
        self.tview.setUniformRowHeights(True)
        self.tview.setIndentation(15)
        self.tview.setSizePolicy(QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Minimum))
        self.tview.expandAll()
        self.tview.resizeColumnToContents(0)
        vl_tele.addWidget(self.tview)
        tele_widget.setLayout(vl_tele)
        self.main_splitter.addWidget(tele_widget)

    def setup_mqtt(self):
        self.mqtt = MqttClient()
        self.mqtt.connecting.connect(self.mqtt_connecting)
        self.mqtt.connected.connect(self.mqtt_connected)
        self.mqtt.disconnected.connect(self.mqtt_disconnected)
        self.mqtt.connectError.connect(self.mqtt_connectError)
        self.mqtt.messageSignal.connect(self.mqtt_message)

    def add_devices_tab(self):
        tabDevicesList = DevicesListWidget(self)
        self.mdi.addSubWindow(tabDevicesList)
        tabDevicesList.setWindowState(Qt.WindowMaximized)

    def load_window_state(self):
        wndGeometry = self.settings.value('window_geometry')
        if wndGeometry:
            self.restoreGeometry(wndGeometry)
        spltState = self.settings.value('splitter_state')
        if spltState:
            self.main_splitter.restoreState(spltState)

    def build_toolbars(self):
        main_toolbar = Toolbar(orientation=Qt.Horizontal, iconsize=32, label_position=Qt.ToolButtonIconOnly)
        main_toolbar.setObjectName("main_toolbar")
        self.addToolBar(main_toolbar)

        main_toolbar.addAction(QIcon("./GUI/icons/connections.png"), "Configure MQTT broker", self.setup_broker)
        agBroker = QActionGroup(self)
        agBroker.setExclusive(True)

        self.actConnect = CheckableAction(QIcon("./GUI/icons/connect.png"), "Connect to the broker", agBroker)
        self.actDisconnect = CheckableAction(QIcon("./GUI/icons/disconnect.png"), "Disconnect from broker", agBroker)

        self.actDisconnect.setChecked(True)

        self.actConnect.triggered.connect(self.mqtt_connect)
        self.actDisconnect.triggered.connect(self.mqtt_disconnect)

        main_toolbar.addActions(agBroker.actions())
        main_toolbar.addSeparator()

    def initial_query(self, idx):
        for q in initial_queries:
            topic = "{}status".format(self.device_model.commandTopic(idx))
            self.mqtt.publish(topic, q)
            q = q if q else ''
            self.console_log(topic, "Asked for STATUS {}".format(q), q)

    def setup_broker(self):
        brokers_dlg = BrokerDialog()
        if brokers_dlg.exec_() == QDialog.Accepted and self.mqtt.state == self.mqtt.Connected:
            self.mqtt.disconnect()

    def mqtt_connect(self):
        self.broker_hostname = self.settings.value('hostname', 'localhost')
        self.broker_port = self.settings.value('port', 1883, int)
        self.broker_username = self.settings.value('username')
        self.broker_password = self.settings.value('password')

        self.mqtt.hostname = self.broker_hostname
        self.mqtt.port = self.broker_port

        if self.broker_username:
            self.mqtt.setAuth(self.broker_username, self.broker_password)

        if self.mqtt.state == self.mqtt.Disconnected:
            self.mqtt.connectToHost()

    def mqtt_disconnect(self):
        self.mqtt.disconnectFromHost()

    def mqtt_connecting(self):
        self.statusBar().showMessage("Connecting to broker")

    def mqtt_connected(self):
        self.statusBar().showMessage("Connected to {}:{} as {}".format(self.broker_hostname, self.broker_port, self.broker_username if self.broker_username else '[anonymous]'))

        self.mqtt_subscribe()

        for d in range(self.device_model.rowCount()):
            idx = self.device_model.index(d, 0)
            self.initial_query(idx)

    def mqtt_subscribe(self):
        main_topics = ["+/stat/+", "+/tele/+", "stat/#", "tele/#"]

        for d in range(self.device_model.rowCount()):
            idx = self.device_model.index(d, 0)
            if not self.device_model.isDefaultTemplate(idx):
                main_topics.append(self.device_model.commandTopic(idx))
                main_topics.append(self.device_model.statTopic(idx))

        for t in main_topics:
            self.mqtt.subscribe(t)

    def mqtt_ask_for_fulltopic(self):
        for i in range(len(self.fulltopic_queue)):
            self.mqtt.publish(self.fulltopic_queue.pop(0))

    def mqtt_disconnected(self):
        self.statusBar().showMessage("Disconnected")

    def mqtt_connectError(self, rc):
        reason = {
            1: "Incorrect protocol version",
            2: "Invalid client identifier",
            3: "Server unavailable",
            4: "Bad username or password",
            5: "Not authorized",
        }
        self.statusBar().showMessage("Connection error: {}".format(reason[rc]))
        self.actDisconnect.setChecked(True)

    def mqtt_message(self, topic, msg):
        found = self.device_model.findDevice(topic)
        if found.reply == 'LWT':
            if not msg:
                msg = "offline"

            if found.index.isValid():
                self.console_log(topic, "LWT update: {}".format(msg), msg)
                self.device_model.updateValue(found.index, DevMdl.LWT, msg)

            elif msg == "Online":
                self.console_log(topic, "LWT for unknown device '{}'. Asking for FullTopic.".format(found.topic), msg, False)
                self.fulltopic_queue.append("cmnd/{}/fulltopic".format(found.topic))
                self.fulltopic_queue.append("{}/cmnd/fulltopic".format(found.topic))
                self.queue_timer.start(1500)

        elif found.reply == 'RESULT':
            full_topic = loads(msg).get('FullTopic')
            new_topic = loads(msg).get('Topic')
            template_name = loads(msg).get('NAME')

            if full_topic:
                # TODO: update FullTopic for existing device AFTER the FullTopic changes externally (the message will arrive from new FullTopic)
                if not found.index.isValid():
                    self.console_log(topic, "FullTopic for {}".format(found.topic), msg, False)

                    new_idx = self.device_model.addDevice(found.topic, full_topic, lwt='online')
                    tele_idx = self.telemetry_model.addDevice(TasmotaDevice, found.topic)
                    self.telemetry_model.devices[found.topic] = tele_idx
                    #TODO: add QSortFilterProxyModel to telemetry treeview and sort devices after adding

                    self.initial_query(new_idx)
                    self.console_log(topic, "Added {} with fulltopic {}, querying for STATE".format(found.topic, full_topic), msg)
                    self.tview.expand(tele_idx)
                    self.tview.resizeColumnToContents(0)

            if new_topic:
                if found.index.isValid() and found.topic != new_topic:
                    self.console_log(topic, "New topic for {}".format(found.topic), msg)

                    self.device_model.updateValue(found.index, DevMdl.TOPIC, new_topic)

                    tele_idx = self.telemetry_model.devices.get(found.topic)

                    if tele_idx:
                        self.telemetry_model.setDeviceName(tele_idx, new_topic)
                        self.telemetry_model.devices[new_topic] = self.telemetry_model.devices.pop(found.topic)

            if template_name:
                self.device_model.updateValue(found.index, DevMdl.MODULE, template_name)

        elif found.index.isValid():
            if found.reply == 'STATUS':
                self.console_log(topic, "Received device status", msg)
                payload = loads(msg)['Status']
                self.device_model.updateValue(found.index, DevMdl.FRIENDLY_NAME, payload['FriendlyName'][0])
                self.telemetry_model.setDeviceFriendlyName(self.telemetry_model.devices[found.topic], payload['FriendlyName'][0])
                self.tview.resizeColumnToContents(0)
                module = payload['Module']
                if module == '0':
                    self.mqtt.publish(self.device_model.commandTopic(found.index)+"template")
                else:
                    self.device_model.updateValue(found.index, DevMdl.MODULE, module)

            elif found.reply == 'STATUS1':
                self.console_log(topic, "Received program information", msg)
                payload = loads(msg)['StatusPRM']
                self.device_model.updateValue(found.index, DevMdl.RESTART_REASON, payload['RestartReason'])

            elif found.reply == 'STATUS2':
                self.console_log(topic, "Received firmware information", msg)
                payload = loads(msg)['StatusFWR']
                self.device_model.updateValue(found.index, DevMdl.FIRMWARE, payload['Version'])
                self.device_model.updateValue(found.index, DevMdl.CORE, payload['Core'])

            elif found.reply == 'STATUS3':
                self.console_log(topic, "Received syslog information", msg)
                payload = loads(msg)['StatusLOG']
                self.device_model.updateValue(found.index, DevMdl.TELEPERIOD, payload['TelePeriod'])

            elif found.reply == 'STATUS5':
                self.console_log(topic, "Received network status", msg)
                payload = loads(msg)['StatusNET']
                self.device_model.updateValue(found.index, DevMdl.MAC, payload['Mac'])
                self.device_model.updateValue(found.index, DevMdl.IP, payload['IPAddress'])

            elif found.reply == 'STATUS8':
                self.console_log(topic, "Received telemetry", msg)
                payload = loads(msg)['StatusSNS']
                self.parse_telemetry(found.index, payload)

            elif found.reply == 'STATUS11':
                self.console_log(topic, "Received device state", msg)
                payload = loads(msg)['StatusSTS']
                self.parse_state(found.index, payload)

            elif found.reply == 'SENSOR':
                self.console_log(topic, "Received telemetry", msg)
                payload = loads(msg)
                self.parse_telemetry(found.index, payload)

            elif found.reply == 'STATE':
                self.console_log(topic, "Received device state", msg)
                payload = loads(msg)
                self.parse_state(found.index, payload)

            elif found.reply.startswith('POWER'):
                self.console_log(topic, "Received {} state".format(found.reply), msg)
                payload = {found.reply: msg}
                self.parse_power(found.index, payload)

    def parse_power(self, index, payload):
        old = self.device_model.power(index)
        power = {k: payload[k] for k in payload.keys() if k.startswith("POWER")}
        needs_update = False
        if old:
            for k in old.keys():
                needs_update |= old[k] != power.get(k, old[k])
                if needs_update:
                    break
        else:
            needs_update = True

        if needs_update:
            self.device_model.updateValue(index, DevMdl.POWER, power)

    def parse_state(self, index, payload):
        bssid = payload['Wifi'].get('BSSId')
        if not bssid:
            bssid = payload['Wifi'].get('APMac')
        self.device_model.updateValue(index, DevMdl.BSSID, bssid)
        self.device_model.updateValue(index, DevMdl.SSID, payload['Wifi']['SSId'])
        self.device_model.updateValue(index, DevMdl.CHANNEL, payload['Wifi'].get('Channel'))
        self.device_model.updateValue(index, DevMdl.RSSI, payload['Wifi']['RSSI'])
        self.device_model.updateValue(index, DevMdl.UPTIME, payload['Uptime'])
        self.device_model.updateValue(index, DevMdl.LOADAVG, payload.get('LoadAvg'))

        self.parse_power(index, payload)

        tele_idx = self.telemetry_model.devices.get(self.device_model.topic(index))

        if tele_idx:
            tele_device = self.telemetry_model.getNode(tele_idx)
            self.telemetry_model.setDeviceFriendlyName(tele_idx, self.device_model.friendly_name(index))

            pr = tele_device.provides()
            for k in pr.keys():
                self.telemetry_model.setData(pr[k], payload.get(k))

    def parse_telemetry(self, index, payload):
        device = self.telemetry_model.devices.get(self.device_model.topic(index))
        if device:
            node = self.telemetry_model.getNode(device)
            time = node.provides()['Time']
            if 'Time' in payload:
                self.telemetry_model.setData(time, payload.pop('Time'))

            temp_unit = "C"
            pres_unit = "hPa"

            if 'TempUnit' in payload:
                temp_unit = payload.pop('TempUnit')

            if 'PressureUnit' in payload:
                pres_unit = payload.pop('PressureUnit')

            for sensor in sorted(payload.keys()):
                if sensor == 'DS18x20':
                    for sns_name in payload[sensor].keys():
                        d = node.devices().get(sensor)
                        if not d:
                            d = self.telemetry_model.addDevice(DS18x20, payload[sensor][sns_name]['Type'], device)
                        self.telemetry_model.getNode(d).setTempUnit(temp_unit)
                        payload[sensor][sns_name]['Id'] = payload[sensor][sns_name].pop('Address')

                        pr = self.telemetry_model.getNode(d).provides()
                        for pk in pr.keys():
                            self.telemetry_model.setData(pr[pk], payload[sensor][sns_name].get(pk))
                        self.tview.expand(d)

                elif sensor.startswith('DS18B20'):
                    d = node.devices().get(sensor)
                    if not d:
                        d = self.telemetry_model.addDevice(DS18x20, sensor, device)
                    self.telemetry_model.getNode(d).setTempUnit(temp_unit)
                    pr = self.telemetry_model.getNode(d).provides()
                    for pk in pr.keys():
                        self.telemetry_model.setData(pr[pk], payload[sensor].get(pk))
                    self.tview.expand(d)

                if sensor == 'COUNTER':
                    d = node.devices().get(sensor)
                    if not d:
                        d = self.telemetry_model.addDevice(CounterSns, "Counter", device)
                    pr = self.telemetry_model.getNode(d).provides()
                    for pk in pr.keys():
                        self.telemetry_model.setData(pr[pk], payload[sensor].get(pk))
                    self.tview.expand(d)

                else:
                    d = node.devices().get(sensor)
                    if not d:
                        d = self.telemetry_model.addDevice(sensor_map.get(sensor, Node), sensor, device)
                    pr = self.telemetry_model.getNode(d).provides()
                    if 'Temperature' in pr:
                        self.telemetry_model.getNode(d).setTempUnit(temp_unit)
                    if 'Pressure' in pr or 'SeaPressure' in pr:
                        self.telemetry_model.getNode(d).setPresUnit(pres_unit)
                    for pk in pr.keys():
                        self.telemetry_model.setData(pr[pk], payload[sensor].get(pk))
                    self.tview.expand(d)
        self.tview.resizeColumnToContents(0)

    def console_log(self, topic, description, payload, known=True):
        device = self.device_model.findDevice(topic)
        fname = self.device_model.friendly_name(device.index)
        self.console_model.addEntry(topic, fname, description, payload, known)
        self.console_view.resizeColumnToContents(1)

    def view_payload(self, idx):
        idx = self.sorted_console_model.mapToSource(idx)
        row = idx.row()
        timestamp = self.console_model.data(self.console_model.index(row, CnsMdl.TIMESTAMP))
        topic = self.console_model.data(self.console_model.index(row, CnsMdl.TOPIC))
        payload = self.console_model.data(self.console_model.index(row, CnsMdl.PAYLOAD))

        dlg = PayloadViewDialog(timestamp, topic, payload)
        dlg.exec_()

    def select_cons_entry(self, idx):
        self.cons_idx = idx

    def build_cons_ctx_menu(self):
        self.cons_ctx_menu = QMenu()
        self.cons_ctx_menu.addAction("View payload", lambda: self.view_payload(self.cons_idx))
        self.cons_ctx_menu.addSeparator()
        self.cons_ctx_menu.addAction("Show only this device", lambda: self.cons_set_filter(self.cons_idx))
        self.cons_ctx_menu.addAction("Show all devices", self.cons_set_filter)

    def show_cons_ctx_menu(self, at):
        self.select_cons_entry(self.console_view.indexAt(at))
        self.cons_ctx_menu.popup(self.console_view.viewport().mapToGlobal(at))

    def cons_set_filter(self, idx=None):
        if idx:
            idx = self.sorted_console_model.mapToSource(idx)
            topic = self.console_model.data(self.console_model.index(idx.row(), CnsMdl.FRIENDLY_NAME))
            self.sorted_console_model.setFilterFixedString(topic)
        else:
            self.sorted_console_model.setFilterFixedString("")

    def closeEvent(self, e):
        self.settings.setValue("window_geometry", self.saveGeometry())
        self.settings.setValue("splitter_state", self.main_splitter.saveState())
        self.settings.sync()
        e.accept()
예제 #13
0
class TableViewSelectTypes(QtWidgets.QDialog):
    def setText(self, text):
        self._text = text

        if text in GridTableView.BasicTypes:
            self.tree.expand(self.bacisItem.index())
            for i in range(self.bacisItem.rowCount()):
                childItem = self.bacisItem.child(i, 0)
                if childItem.text() == text:
                    self.tree.setCurrentIndex(childItem.index())

        else:
            self.tree.expand(self.otherItem.index())
            names = text.split('.')
            parentItem = self.otherItem
            for v in names:
                isBreak = False
                for i in range(parentItem.rowCount()):
                    if isBreak == True:
                        continue

                    if parentItem.child(i, 0).text() == v:
                        parentItem = parentItem.child(i, 0)
                        self.tree.expand(parentItem.index())
                        isBreak = True
                        continue

            self.tree.setCurrentIndex(parentItem.index())

    def text(self):
        return self._text

    def __init__(self, *args):
        super(TableViewSelectTypes, self).__init__(args[1])

        self.tableView = args[1]
        self._text = ''

        gMainWindow = GlobalObjs.GetValue('MainWindow')

        self.setModal(True)
        #self.setModel(True)
        self.setWindowTitle('Select Type')

        self.vboxLayout = QtWidgets.QVBoxLayout(self)
        self.tree = QTreeView(self)
        self.model = QStandardItemModel(self)
        self.tree.setHeaderHidden(True)

        self.tree.setModel(self.model)

        self.bacisItem = QStandardItem()
        self.bacisItem.setText('BasicTypes')
        self.bacisItem.setFlags(QtCore.Qt.ItemIsEnabled)
        self.model.appendRow(self.bacisItem)
        self.tree.expand(self.bacisItem.index())

        for v in GridTableView.BasicTypes:
            item = QStandardItem()
            item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable)
            item.setText(v)
            item.setData(True)
            self.bacisItem.appendRow(item)

        if self.tableView.Model.activeColumn != args[2]:
            self.otherItem = QStandardItem()
            self.otherItem.setText('Other')
            self.otherItem.setFlags(QtCore.Qt.ItemIsEnabled)
            self.model.appendRow(self.otherItem)
            self.tree.expand(self.otherItem.index())

            def LoadOtherTypes(item, parent):
                for i in range(0, item.rowCount()):
                    childItem = item.child(i, 0)
                    if childItem.data(Qt.UserRole + 2) == self.tableView.Model:
                        continue

                    itemAdd = QStandardItem()
                    itemAdd.setText(childItem.text())

                    if childItem.data() == 3:
                        itemAdd.setFlags(QtCore.Qt.ItemIsEnabled
                                         | QtCore.Qt.ItemIsSelectable)
                    else:
                        itemAdd.setFlags(QtCore.Qt.ItemIsEnabled)
                        LoadOtherTypes(childItem, itemAdd)

                    parent.appendRow(itemAdd)

            LoadOtherTypes(gMainWindow.TreeRootItem, self.otherItem)

        self.vboxLayout.addWidget(self.tree)

        self.tree.doubleClicked.connect(self.OnDoubleClicked)
        self.tree.setFocus()

        self.show()

    def OnDoubleClicked(self, index):
        item = self.model.itemFromIndex(index)
        if item.flags(
        ) == QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable:
            if item.data() == True:
                self.setText(index.data())
            else:
                names = []
                parent = index
                while True:
                    names.append(parent.data())
                    parent = parent.parent()
                    if parent.isValid() == False or self.otherItem.index(
                    ) == parent:
                        break

                self.setText('.'.join(reversed(names)))

            self.done(QtWidgets.QDialog.Accepted)
예제 #14
0
class ModelGui(QDialog):
    """The gui to start and manipulate the analysis model"""
    ANALYSIS, ID, NUMPOINTS = range(3)

    NO_PARENT_ID = 0xFFFFFFFF

    def __init__(self, analysis_event_queue, select, delete, load):
        super(ModelGui, self).__init__()
        self.analysis_event_queue = analysis_event_queue
        self.select_callback = select
        self.delete_callback = delete
        self.load_callback = load

        self.name = None
        self.label_name = None
        self.meta_name = None
        self.hsne_name = None

        self.title = 'Analysis hierarchy viewer'
        self.left = 10
        self.top = 10
        self.width = 1000
        self.height = 400
        self.init_ui()
        self.id_item = {}
        self.root_id = None

    def init_ui(self):
        """All the ui layout in one place. Note: Part of the ui
        is switchable depending on the selected demo type, see the ui_matrix"""
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)

        self.model = self.create_analysis_model(self)
        self.analysis_model = None
        self.top_scale = None
        self.thumb_size = (40, 40)

        # The analysis tree
        self.tree = QTreeView()
        self.tree.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.tree.header().setSectionResizeMode(QHeaderView.ResizeToContents)
        self.tree.setIconSize(QSize(*self.thumb_size))
        self.tree.setModel(self.model)

        self.tree.setAnimated(False)
        self.tree.setIndentation(20)
        self.tree.setSortingEnabled(True)

        self.tree.resize(640, 480)

        # Control layout
        controlLayout = QVBoxLayout()

        self.preconfigured_group = QGroupBox("Preconfigured")
        preconfigured_layout = QFormLayout(self)
        self.preconfigured_combo = QComboBox(self)
        self.preconfigured_combo.addItem('None', userData=None)
        for config in CONFIGS:
            self.preconfigured_combo.addItem(config.descriptor,
                                             userData=config)
        self.preconfigured_combo.currentIndexChanged[int].connect(
            self.on_preconfigured)
        preconfigured_layout.addRow(QLabel("Load preset demo:"),
                                    self.preconfigured_combo)
        self.preconfigured_group.setLayout(preconfigured_layout)
        controlLayout.addWidget(self.preconfigured_group)

        # Data type settings
        self.demo_type_group = QGroupBox("Data type")
        data_type_layout = QFormLayout(self)
        self.data_type_combo = QComboBox(self)
        self.data_type_combo.addItem("Image is a data point",
                                     DemoType.LABELLED_DEMO)
        self.data_type_combo.addItem("Point and metadata", DemoType.POINT_DEMO)
        self.data_type_combo.addItem("Hyperspectral image",
                                     DemoType.HYPERSPECTRAL_DEMO)
        data_type_layout.addRow(QLabel("Visualization style:"),
                                self.data_type_combo)
        self.data_type_combo.currentIndexChanged[int].connect(
            self.on_demo_style)

        data_button = QPushButton("Data")
        data_button.clicked.connect(self.on_load)
        self.data_label = QLabel("<choose data .npy>")
        data_type_layout.addRow(data_button, self.data_label)

        self.xy_label = QLabel("Image size")
        self.label_x = QLabel("X:")
        self.label_y = QLabel("Y:")
        self.image_x = QLineEdit()
        self.image_x.setValidator(QIntValidator(1, 10000))
        self.image_y = QLineEdit()
        self.image_y.setValidator(QIntValidator(1, 10000))

        self.xy_container = QWidget()
        self.xy_layout = QHBoxLayout()
        self.xy_layout.setContentsMargins(0, 0, 0, 0)
        self.xy_layout.addWidget(self.label_x)
        self.xy_layout.addWidget(self.image_x)
        self.xy_layout.addWidget(self.label_y)
        self.xy_layout.addWidget(self.image_y)
        self.xy_container.setLayout(self.xy_layout)
        data_type_layout.addRow(self.xy_label, self.xy_container)

        self.label_button = QPushButton("Labels")
        self.label_button.clicked.connect(self.on_load_labels)
        self.label_label = QLabel("<optionally choose labels .npy>")
        data_type_layout.addRow(self.label_button, self.label_label)

        self.meta_button = QPushButton("Label/Color metadata")
        self.meta_button.clicked.connect(self.on_load_labelscolors)
        self.meta_label = QLabel("<optionally choose label/color .csv>")
        data_type_layout.addRow(self.meta_button, self.meta_label)
        self.demo_type_group.setLayout(data_type_layout)
        controlLayout.addWidget(self.demo_type_group)

        # Hsne settings
        self.hsne_group = QGroupBox("hSNE settings")
        self.hsne_form_layout = QFormLayout(self)

        hsne_button = QPushButton("Preload")
        hsne_button.clicked.connect(self.on_load_hsne)
        self.hsne_label = QLabel("<optionally choose existing .hsne>")
        self.hsne_form_layout.addRow(hsne_button, self.hsne_label)

        self.scale_spin = QSpinBox(self)
        self.scale_spin.setRange(1, 10)
        self.scale_spin.setValue(4)
        self.hsne_form_layout.addRow(QLabel("Scales:"), self.scale_spin)

        self.hsne_group.setLayout(self.hsne_form_layout)
        controlLayout.addWidget(self.hsne_group)

        # Embedding settings
        self.embedding_group = QGroupBox("Embedding settings")
        embed_form_layout = QFormLayout(self)
        self.iter_spin = QSpinBox(self)
        self.iter_spin.setRange(350, 1000)
        self.iter_spin.setSingleStep(5)
        self.iter_spin.setValue(500)
        embed_form_layout.addRow(QLabel("Iterations:"), self.iter_spin)
        self.embedding_group.setLayout(embed_form_layout)

        controlLayout.addWidget(self.embedding_group)

        clear_start_layout = QHBoxLayout()
        self.start_button = QPushButton("Start")
        self.start_button.clicked.connect(self.on_start)
        self.start_button.setDisabled(True)
        self.clear_button = QPushButton("Clear")
        self.clear_button.clicked.connect(self.on_clear)

        clear_start_layout.addWidget(self.start_button)
        clear_start_layout.addWidget(self.clear_button)

        controlLayout.addLayout(clear_start_layout)

        self.delete_button = QPushButton("Delete selected")
        self.delete_button.clicked.connect(self.on_delete)

        # Selection
        self.tree.clicked.connect(self.on_selected)

        # Layout
        main_layout = QGridLayout()
        # row, col, rowSpan, colSpan
        main_layout.addWidget(self.tree, 0, 0, 10, 5)
        main_layout.addLayout(controlLayout, 0, 5, 10, 3)

        main_layout.addWidget(self.delete_button, 11, 2, 1, 2)
        self.setLayout(main_layout)
        self.counter = 0
        self.event_timer = QTimer(self)
        self.event_timer.start(500)
        self.event_timer.timeout.connect(self.update_tree)

        # Dynamic UI matrix
        # According to DemoType True widgets are shown False widgets hidden
        self.ui_matrix = {
            DemoType.LABELLED_DEMO: {
                True: [
                    self.label_button, self.label_label, self.xy_container,
                    self.xy_label
                ],
                False: [self.meta_button, self.meta_label]
            },
            DemoType.POINT_DEMO: {
                True: [self.meta_button, self.meta_label],
                False: [
                    self.label_button, self.label_label, self.xy_container,
                    self.xy_label
                ]
            },
            DemoType.HYPERSPECTRAL_DEMO: {
                True: [self.xy_container, self.xy_label],
                False: [
                    self.label_button, self.label_label, self.meta_button,
                    self.meta_label
                ]
            }
        }

        self.show()
        self.on_demo_style(0)

    def set_analysis_model(self, analysis_model):
        # TODO empty event queue
        self.analysis_model = analysis_model
        self.top_scale = self.analysis_model.top_scale_id

    @property
    def iterations(self):
        return self.iter_spin.value()

    @property
    def scales(self):
        return self.scale_spin.value()

    @property
    def demo_type(self):
        return self.data_type_combo.currentData()

    @property
    def im_size_x(self):
        return int(self.image_x.text())

    @property
    def im_size_y(self):
        return int(self.image_y.text())

    @pyqtSlot(int)
    def on_preconfigured(self, index):
        config = self.preconfigured_combo.itemData(index)
        self.on_clear()
        if config is None:
            return
        if type(config).__name__ == "LabelledImage":
            self.data_type_combo.setCurrentIndex(0)
            self.name = config.data.data_file
            self.data_label.setText(config.data.data_file)
            self.label_name = config.data.label_file
            self.label_label.setText(config.data.label_file)
            if config.hsne.hsne_file != "":
                self.hsne_name = config.hsne.hsne_file
                self.__set_scale_from_hsne_file()
                self.scale_spin.setDisabled(True)
            elif config.hsne.scales > 0:
                self.scale_spin.setValue(config.hsne.scales)
            if config.image.dim_x > 0 and config.image.dim_y > 0:
                self.image_x.setText(str(config.image.dim_x))
                self.image_y.setText(str(config.image.dim_y))

        elif type(config).__name__ == "PointMeta":
            self.data_type_combo.setCurrentIndex(1)
            self.name = config.data.data_file
            self.data_label.setText(config.data.data_file)
            self.meta_name = config.data.meta_file
            self.meta_label.setText(config.data.meta_file)
            if config.hsne.hsne_file != "":
                self.hsne_name = config.hsne.hsne_file
                self.__set_scale_from_hsne_file()
                self.scale_spin.setDisabled(True)
            elif config.hsne.scales > 0:
                self.scale_spin.setValue(config.hsne.scales)

        elif type(config).__name__ == "HyperspectralImage":
            self.data_type_combo.setCurrentIndex(2)
            self.name = config.data.data_file
            self.data_label.setText(config.data.data_file)
            if config.hsne.hsne_file != "":
                self.hsne_name = config.hsne.hsne_file
                self.__set_scale_from_hsne_file()
                self.scale_spin.setDisabled(True)
            elif config.hsne.scales > 0:
                self.scale_spin.setValue(config.hsne.scales)
            if config.image.dim_x > 0 and config.image.dim_y > 0:
                self.image_x.setText(str(config.image.dim_x))
                self.image_y.setText(str(config.image.dim_y))

        self.start_button.setEnabled(True)

    @pyqtSlot(int)
    def on_demo_style(self, index):
        # set the visibility of the widgets according to the
        # type of demo being given
        for state in [False, True]:
            for widget in self.ui_matrix[self.demo_type][state]:
                widget.setVisible(state)

    @pyqtSlot()
    def on_load(self):
        workdir = os.path.dirname(os.path.abspath(__file__))
        result = QFileDialog.getOpenFileName(
            self,
            'Open a numpy file where each row is a data point and columns are dimensions',
            workdir, "Numpy files (*.npy)")
        if result[0]:
            self.hsne_label.setText("")
            self.hsne_name = None
            self.scale_spin.setEnabled(True)
            self.name = result[0]
            # print(f"Selected: {self.name}")
            self.start_button.setEnabled(True)
            if (self.demo_type == DemoType.LABELLED_DEMO
                    or self.demo_type == DemoType.HYPERSPECTRAL_DEMO):
                # partial data load (in memory) to read the shape
                the_data = np.load(self.name, mmap_mode='r')
                image_flat_size = the_data.shape[1]
                if self.demo_type == DemoType.HYPERSPECTRAL_DEMO:
                    image_flat_size = the_data.shape[0]
                xsize = int(math.sqrt(image_flat_size))
                ysize = int(image_flat_size / xsize)
                self.image_x.setText(str(xsize))
                self.image_y.setText(str(ysize))
            self.data_label.setText(str(Path(self.name).name))

    def __set_scale_from_hsne_file(self):
        scale_value = nptsne.HSne.read_num_scales(self.hsne_name)
        self.hsne_label.setText(str(Path(self.hsne_name).name))
        self.scale_spin.setValue(scale_value)
        self.scale_spin.setDisabled(True)

    @pyqtSlot()
    def on_load_hsne(self):
        workdir = os.path.dirname(os.path.abspath(__file__))
        result = QFileDialog.getOpenFileName(
            self, 'Open a pre-calculated hSNE analysis file .hsne', workdir,
            "hSNE files (*.hsne)")
        if result[0]:
            self.hsne_name = result[0]
            # print(f"Selected: {self.name}")
            self.__set_scale_from_hsne_file()

    @pyqtSlot()
    def on_load_labels(self):
        workdir = os.path.dirname(os.path.abspath(__file__))
        result = QFileDialog.getOpenFileName(
            self, 'Open a numpy file where each row is an integer label',
            workdir, "Numpy files (*.npy)")
        if result[0]:
            self.label_name = result[0]
            self.label_label.setText(Path(self.label_name).name)

    @pyqtSlot()
    def on_load_labelscolors(self):
        workdir = os.path.dirname(os.path.abspath(__file__))
        result = QFileDialog.getOpenFileName(
            self,
            'Open a CSV file with header where the columns pairs of Label, #COLOR_',
            workdir, "CSV files (*.csv)")

        if result[0]:
            self.meta_name = result[0]
            self.meta_label.setText(Path(self.meta_name).name)

    @pyqtSlot()
    def on_start(self):
        self.load_callback(self.name, self.label_name, self.meta_name,
                           self.hsne_name)

    @pyqtSlot()
    def on_selected(self):
        analysis_id = self._get_selected_id()
        if analysis_id:
            self.select_callback(int(analysis_id))

    @pyqtSlot()
    def on_delete(self):
        analysis_id = self._get_selected_id()
        if analysis_id:
            self.delete_callback([int(analysis_id)])

    @pyqtSlot()
    def on_clear(self):
        if not self.root_id is None:
            self.select_callback(int(self.root_id))
        self.clear()
        self.name = None
        self.data_label.setText("<choose data .npy>")
        self.label_name = None
        self.label_label.setText("<optionally choose labels .npy>")
        self.meta_name = None
        self.meta_label.setText("<optionally choose label/color .csv>")
        self.scale_spin.setValue(4)
        self.scale_spin.setEnabled(True)
        self.start_button.setDisabled(True)
        self.hsne_name = None
        self.hsne_label.setText("<optionally choose existing .hsne>")
        self.image_x.setText("")
        self.image_y.setText("")

    def _get_selected_id(self):
        index = self.tree.currentIndex()
        if index is None:
            return None
        return self.model.itemData(index.siblingAtColumn(self.ID))[0]

    def create_analysis_model(self, parent):
        model = QStandardItemModel(0, 3, parent)
        model.setHeaderData(self.ANALYSIS, Qt.Horizontal, "Analysis")
        model.setHeaderData(self.ID, Qt.Horizontal, "Id")
        model.setHeaderData(self.NUMPOINTS, Qt.Horizontal, "#Points")
        return model

    def add_test_analysis(self):
        parent_id = ModelGui.NO_PARENT_ID
        if self.counter > 0:
            parent_id = self.counter - 1
        self.add_analysis(self.counter, f"{self.counter} Blah blah blah",
                          parent_id, 150)
        self.counter = self.counter + 1

    def update_tree(self):
        # Update tree based on queued events
        while True:
            event = {}
            try:
                event = self.analysis_event_queue.get_nowait()
            except queue.Empty:
                break

            if event['event'] == AnalysisEvent.ADDED:
                self.add_analysis(event['id'], event['name'],
                                  event['parent_id'],
                                  event['number_of_points'])
                continue
            if event['event'] == AnalysisEvent.FINISHED:
                self.finish_analysis(event['id'], event['name'],
                                     event['image_buf'])
                continue
            if event['event'] == AnalysisEvent.REMOVED:
                self.remove_analysis(event['id'])

    def add_analysis(self, analysis_id, name, parent_id, numpoints):
        im = ImageQt.ImageQt(Image.new('RGB', self.thumb_size, (100, 0, 200)))
        item = QStandardItem(QIcon(QPixmap.fromImage(im)), name)
        # Need to persist the thumbnails otherwise the ImageQT will get garbage
        # collected along with the memory
        item.__thumb = im
        if parent_id == ModelGui.NO_PARENT_ID:
            #print("Adding root")
            self.clear()
            self.model.insertRow(0, [
                item,
                QStandardItem(str(analysis_id)),
                QStandardItem(str(numpoints))
            ])
            self.root_id = analysis_id
            self.id_item[analysis_id] = item
        else:
            #print("Adding child")
            parent = self.find_analysis_item(parent_id)
            if parent is not None:
                parent.appendRow([
                    item,
                    QStandardItem(str(analysis_id)),
                    QStandardItem(str(numpoints))
                ])
                self.id_item[analysis_id] = item
                self.tree.expand(parent.index())

    def remove_analysis(self, analysis_id):
        if analysis_id == self.root_id:
            self.clear()
            return
        item = self.find_analysis_item(analysis_id)
        if item:
            if item.parent:
                try:
                    item.parent().removeRow(item.row())
                except RuntimeError:
                    # TODO fix bug that means this is being deleted twice
                    pass

            del self.id_item[analysis_id]

    def finish_analysis(self, analysis_id, name, image_buf):
        print("finished ", analysis_id)
        img = PIL.Image.open(image_buf)
        thumbnail = img.resize(self.thumb_size, PIL.Image.ANTIALIAS)
        # thumbnail.show()
        im = ImageQt.ImageQt(thumbnail)
        item = self.find_analysis_item(analysis_id)

        item.setIcon(QIcon(QPixmap.fromImage(im)))
        item.__thumb = im

    def find_analysis_item(self, analysis_id):
        """ Get the item using the numeric analysis_id """
        return self.id_item.get(analysis_id, None)

    def clear(self):
        print('Clear model content')
        if self.model is not None:
            # print('Remove rows')
            self.model.removeRows(0, self.model.rowCount())
        # print('Reset bookkeeping')
        self.id_item = {}
        self.root_id = None
예제 #15
0
class TapeWidget(QWidget):
    def __init__(self, parent = None):
        super().__init__(parent)

        self._main_layout    = QVBoxLayout(self)
        self._search_box     = QLineEdit(self)
        self._button_layout  = QHBoxLayout()

        self._view = QTreeView()
        self._view.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)
        self._view.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self._view.setHeaderHidden(True)

        self._add_note_button = QPushButton(self)
        self._add_note_button.setText("New note")

        self._add_child_button = QPushButton(self)
        self._add_child_button.setText("New child")

        self._add_sibling_button = QPushButton(self)
        self._add_sibling_button.setText("New sibling")

        self._delete_note_button = QPushButton(self)
        self._delete_note_button.setText("Delete note")

        self._button_layout.addWidget(self._add_note_button)
        self._button_layout.addWidget(self._add_sibling_button)
        self._button_layout.addWidget(self._add_child_button)
        self._button_layout.addWidget(self._delete_note_button)
        self._button_layout.addStretch()

        self._main_layout.addWidget(self._search_box)
        self._main_layout.addLayout(self._button_layout)
        self._main_layout.addWidget(self._view)

        self._tape_filter_proxy_model = TapeFilterProxyModel()
        self._note_delegate           = NoteDelegate()

        self._tape_model = QStandardItemModel()
        self.set_model(self._tape_model)
        self._view.setItemDelegate(self._note_delegate)
        self._view.setModel(self._tape_filter_proxy_model)

        self._add_note_button.clicked.connect(lambda checked: self.add_and_focus_note())
        self._add_sibling_button.clicked.connect(self._new_sibling_handler)
        self._add_child_button.clicked.connect(self._new_child_handler)
        self._delete_note_button.clicked.connect(self.delete_selected_notes)
        self._search_box.textChanged.connect(self._tape_filter_proxy_model.setFilterFixedString)

    def model(self):
        """ Returns the model that contains all notes managed by the tape.

            The model should be treated as read-only. You can only modify it indirectly through
            the methods provided by TapeWidget. """

        return self._tape_model

    def proxy_model(self):
        """ Returns the model that contains notes matching current filter.

            The model should be treated as read-only. You can only modify it indirectly through
            the methods provided by TapeWidget. """

        return self._tape_filter_proxy_model

    def set_model(self, model):
        assert (
            len(set([item_to_id(item) for item in all_items(model) if item_to_id(item) != None])) ==
            len(    [item_to_id(item) for item in all_items(model) if item_to_id(item) != None])
        )

        # NOTE: If there's an exception in setSourceModel(), we can hope that the source model
        # remains unchanged. That's why we assing to _tape_model only if that instruction succeeds.
        self._tape_filter_proxy_model.setSourceModel(model)
        self._tape_model = model

    def notes(self):
        return all_notes(self._tape_model)

    def assign_ids(self):
        assign_note_ids(self._tape_model)

    def create_empty_note(self):
        return Note(
            body       = "",
            tags       = [],
            created_at = datetime.utcnow()
        )

    def add_note(self, note = None, parent_index = None):
        # NOTE: Remember to use indexes from _tape_model, not _tape_filter_proxy_model here
        assert parent_index == None or self._tape_model.itemFromIndex(parent_index) != None and parent_index.isValid()

        root_item = self._tape_model.invisibleRootItem()
        if parent_index == None:
            parent_item = root_item
        else:
            parent_item = self._tape_model.itemFromIndex(parent_index)

        if note != None:
            assert note not in self.notes()
        else:
            note = self.create_empty_note()

        item = QStandardItem()
        set_item_note(item, note)
        parent_item.appendRow(item)

    def add_and_focus_note(self, parent_proxy_index = None):
        if parent_proxy_index != None:
            parent_index = self._tape_filter_proxy_model.mapToSource(parent_proxy_index)
        else:
            parent_index = None

        self.add_note(parent_index = parent_index)

        if parent_proxy_index != None:
            self._view.expand(parent_proxy_index)

            parent_item = self._tape_model.itemFromIndex(parent_index)
        else:
            parent_item = self._tape_model.invisibleRootItem()

        # NOTE: It's likely that the new note does not match the filter and won't not be present
        # in the proxy model. We want to select it and focus on it so the filter must be cleared.
        # And it must be cleared before taking the index in the proxy because changing the filter
        # may change the set of notes present in the proxy and invalidate the index.
        self.set_filter('')

        new_note_index       = parent_item.child(parent_item.rowCount() - 1).index()
        new_note_proxy_index = self._tape_filter_proxy_model.mapFromSource(new_note_index)

        self.clear_selection()
        self.set_note_selection(new_note_proxy_index, True)
        self._view.scrollTo(new_note_proxy_index)

    def remove_notes(self, indexes):
        remove_items(self._tape_model, indexes)

    def clear(self):
        self._tape_model.clear()

    def set_filter(self, text):
        # NOTE: This triggers textChanged() signal which applies the filter
        self._search_box.setText(text)

    def get_filter(self):
        return self._search_box.text()

    def selected_proxy_indexes(self):
        return self._view.selectedIndexes()

    def selected_indexes(self):
        return [self._tape_filter_proxy_model.mapToSource(proxy_index) for proxy_index in self.selected_proxy_indexes()]

    def set_note_selection(self, proxy_index, select):
        assert proxy_index != None and proxy_index.isValid()
        assert self._tape_model.itemFromIndex(self._tape_filter_proxy_model.mapToSource(proxy_index)) != None

        self._view.selectionModel().select(
            QItemSelection(proxy_index, proxy_index),
            QItemSelectionModel.Select if select else QItemSelectionModel.Deselect
        )

    def clear_selection(self):
        self._view.selectionModel().clear()

    def delete_selected_notes(self):
        self.remove_notes(self.selected_indexes())

    def _new_sibling_handler(self):
        selected_proxy_indexes = self._view.selectedIndexes()
        if len(selected_proxy_indexes) > 1:
            self.clear_selection()
            selected_proxy_indexes = []

        if len(selected_proxy_indexes) == 0 or selected_proxy_indexes[0].parent() == QModelIndex():
            self.add_and_focus_note()
        else:
            self.add_and_focus_note(selected_proxy_indexes[0].parent())

    def add_child_to_selected_element(self):
        selected_proxy_indexes = self._view.selectedIndexes()
        if len(selected_proxy_indexes) != 1:
            return False
        else:
            self.add_and_focus_note(selected_proxy_indexes[0])
            return True

    def _new_child_handler(self):
        added = self.add_child_to_selected_element()
        if not added:
            QMessageBox.warning(self, "Can't add note", "To be able to add a new child note select exactly one parent")