def testRefCount(self):
        model = QStandardItemModel(5, 5)
        items = []
        for r in range(5):
            row = []
            for c in range(5):
                row.append(QStandardItem("%d,%d" % (r, c)))
                self.assertEqual(sys.getrefcount(row[c]), 2)

            model.insertRow(r, row)

            for c in range(5):
                ref_after = sys.getrefcount(row[c])
                # check if the ref count was incremented after insertRow
                self.assertEqual(ref_after, 3)

            items.append(row)
            row = None

        for r in range(3):
            my_row = model.takeRow(0)
            my_row = None
            for c in range(5):
                # only rest 1 reference
                self.assertEqual(sys.getrefcount(items[r][c]), 2)

        my_i = model.item(0, 0)
        # ref(my_i) + parent_ref + items list ref
        self.assertEqual(sys.getrefcount(my_i), 4)

        model.clear()
        # ref(my_i)
        self.assertEqual(sys.getrefcount(my_i), 3)
    def testRefCount(self):
        model = QStandardItemModel(5, 5)
        items = []
        for r in range(5):
            row = []
            for c in range(5):
                row.append(QStandardItem("%d,%d" % (r,c)) )
                self.assertEqual(sys.getrefcount(row[c]), 2)

            model.insertRow(r, row)

            for c in range(5):
                ref_after = sys.getrefcount(row[c])
                # check if the ref count was incremented after insertRow
                self.assertEqual(ref_after, 3)

            items.append(row)
            row = None

        for r in range(3):
            my_row = model.takeRow(0)
            my_row = None
            for c in range(5):
                # only rest 1 reference
                self.assertEqual(sys.getrefcount(items[r][c]), 2)

        my_i = model.item(0,0)
        # ref(my_i) + parent_ref + items list ref
        self.assertEqual(sys.getrefcount(my_i), 4)

        model.clear()
        # ref(my_i)
        self.assertEqual(sys.getrefcount(my_i), 3)
예제 #3
0
class FileListWidget(QWidget):
    def __init__(self) -> None:
        QWidget.__init__(self)

        # self.index stores the index of the latest item which is clicked
        self.index = None

        self.model = QStandardItemModel()

        file_view = QListView()
        file_view.setModel(self.model)
        file_view.setEditTriggers(QAbstractItemView.NoEditTriggers)
        file_view.clicked.connect(self.onClicked)
        file_view.doubleClicked.connect(self.onDoubleClicked)

        open_file_button = QPushButton('Open')
        open_file_button.clicked.connect(self.onOpenFile)

        preview_file_button = QPushButton('Preview')
        preview_file_button.clicked.connect(self.onPreviewFile)

        layout = QGridLayout()
        layout.addWidget(file_view, 0, 0, 1, 2)
        layout.addWidget(open_file_button, 1, 0, 1, 1)
        layout.addWidget(preview_file_button, 1, 1, 1, 1)
        layout.setMargin(0)
        self.setLayout(layout)

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

    def appendRow(self, item: QStandardItem):
        self.model.appendRow(item)

    def onDoubleClicked(self, index: QModelIndex):
        # Update self.index and open the file in browser
        self.index = index
        item = self.model.itemFromIndex(self.index)
        url = item.data(-1)
        QDesktopServices.openUrl(url)

    def onClicked(self, index: QModelIndex):
        # Update self.index
        self.index = index

    def onOpenFile(self):
        # Open file in browser
        if self.index is not None:
            item = self.model.itemFromIndex(self.index)
            url = item.data(-1)
            QDesktopServices.openUrl(url)

    def onPreviewFile(self):
        # User Office 365 Api to preview word/powerpoint/excel documents in browser
        if self.index is not None:
            item = self.model.itemFromIndex(self.index)
            url = 'http://view.officeapps.live.com/op/view.aspx?src=' + item.data(
                -1)
            print(url)
            QDesktopServices.openUrl(url)
예제 #4
0
class QCampaignList(QListView):
    CampaignRole = Qt.UserRole

    def __init__(self, campaigns: list[Campaign],
                 show_incompatible: bool) -> None:
        super(QCampaignList, self).__init__()
        self.campaign_model = QStandardItemModel(self)
        self.setModel(self.campaign_model)
        self.setMinimumWidth(250)
        self.setMinimumHeight(350)
        self.campaigns = campaigns
        self.setSelectionBehavior(QAbstractItemView.SelectItems)
        self.setup_content(show_incompatible)

    @property
    def selected_campaign(self) -> Campaign:
        return self.currentIndex().data(QCampaignList.CampaignRole)

    def setup_content(self, show_incompatible: bool) -> None:
        self.selectionModel().blockSignals(True)
        try:
            self.campaign_model.clear()
            for campaign in self.campaigns:
                if show_incompatible or campaign.is_compatible:
                    item = QCampaignItem(campaign)
                    self.campaign_model.appendRow(item)
        finally:
            self.selectionModel().blockSignals(False)

        self.selectionModel().setCurrentIndex(
            self.campaign_model.index(0, 0, QModelIndex()),
            QItemSelectionModel.Select)
예제 #5
0
class QInfoList(QListView):
    def __init__(self, game: Game):
        super(QInfoList, self).__init__()
        self.model = QStandardItemModel(self)
        self.setModel(self.model)
        self.game = game
        self.update_list()

        self.selectionModel().setCurrentIndex(self.indexAt(QPoint(1, 1)),
                                              QItemSelectionModel.Select)
        self.selectionModel().selectionChanged.connect(
            self.on_selected_info_changed)

    def on_selected_info_changed(self):
        index = self.selectionModel().currentIndex().row()

    def update_list(self):
        self.model.clear()
        if self.game is not None:
            for i, info in enumerate(reversed(self.game.informations)):
                self.model.appendRow(QInfoItem(info))
            self.selectionModel().setCurrentIndex(self.indexAt(QPoint(1, 1)),
                                                  QItemSelectionModel.Select)

    def setGame(self, game):
        self.game = game
        self.update_list()
예제 #6
0
class QFlightWaypointList(QTableView):
    def __init__(self, flight: Flight):
        super(QFlightWaypointList, self).__init__()
        self.model = QStandardItemModel(self)
        self.setModel(self.model)
        self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.model.setHorizontalHeaderLabels(["Name", "Alt"])

        header = self.horizontalHeader()
        header.setSectionResizeMode(0, QHeaderView.ResizeToContents)

        self.flight = flight

        if len(self.flight.points) > 0:
            self.selectedPoint = self.flight.points[0]
        self.update_list()

        self.selectionModel().setCurrentIndex(self.indexAt(QPoint(1, 1)),
                                              QItemSelectionModel.Select)
        self.selectionModel().selectionChanged.connect(
            self.on_waypoint_selected_changed)

    def on_waypoint_selected_changed(self):
        index = self.selectionModel().currentIndex().row()

    def update_list(self):
        self.model.clear()
        self.model.setHorizontalHeaderLabels(["Name", "Alt"])
        takeoff = FlightWaypoint(self.flight.from_cp.position.x,
                                 self.flight.from_cp.position.y, 0)
        takeoff.description = "Take Off"
        takeoff.name = takeoff.pretty_name = "Take Off from " + self.flight.from_cp.name
        self.model.appendRow(QWaypointItem(takeoff, 0))
        item = QStandardItem("0 feet AGL")
        item.setEditable(False)
        self.model.setItem(0, 1, item)
        for i, point in enumerate(self.flight.points):
            self.model.insertRow(self.model.rowCount())
            self.model.setItem(self.model.rowCount() - 1, 0,
                               QWaypointItem(point, i + 1))
            item = QStandardItem(
                str(meter_to_feet(point.alt)) + " ft " +
                str(["AGL" if point.alt_type == "RADIO" else "MSL"][0]))
            item.setEditable(False)
            self.model.setItem(self.model.rowCount() - 1, 1, item)
        self.selectionModel().setCurrentIndex(self.indexAt(QPoint(1, 1)),
                                              QItemSelectionModel.Select)
예제 #7
0
class Translator(QObject):
    language_changed = Signal(name="languageChanged")

    def __init__(self, engine, parent=None):
        super().__init__(parent)
        self._engine = engine
        self._languages_model = QStandardItemModel()
        self.load_translations()
        self._translator = QTranslator()

    @Slot(str)
    def set_language(self, language):
        if language != "Default":
            trans_dir = QDir(os.fspath(TRANSLATIONS_DIR))
            filename = trans_dir.filePath(f"i18n_{language}.qm")
            if not self._translator.load(filename):
                print("Failed")
            QGuiApplication.installTranslator(self._translator)
        else:
            QGuiApplication.removeTranslator(self._translator)
        self._engine.retranslate()

    def languages_model(self):
        return self._languages_model

    languages = Property(QObject, fget=languages_model, constant=True)

    def load_translations(self):
        self._languages_model.clear()
        item = QStandardItem("Default")
        self._languages_model.appendRow(item)
        trans_dir = QDir(os.fspath(TRANSLATIONS_DIR))
        for filename in trans_dir.entryList(["*.qm"], QDir.Files, QDir.Name):
            language = re.search(r"i18n_(.*?)\.qm", filename).group(1)
            item = QStandardItem(language)
            self._languages_model.appendRow(item)
예제 #8
0
class MainWindowUi(QMainWindow):
    """sets up ui properties of MainWindowUi class"""
    def __init__(self) -> None:
        """inits MainWindow class

        configuring parameters of MainWindow class and inherits from QtWidget.QMainWindow
        loads .ui file sets up file and directory path vars, inits click events(menuebar, coboboxes, btns) and
        shows gui the first time

        Returns:
            None"""
        super(MainWindowUi, self).__init__()
        self.setWindowTitle("It_Hilfe")
        self.resize(820, 450)
        self.setWindowIcon(QIcon("./data/favicon2.png"))
        self.setMinimumSize(700, 250)

        self.file_path = None
        self.dir = None
        self.last_open_file_path = None
        self.last_open_file_dir = None
        self.initial_theme = None
        self.registered_devices = {}
        self.user_config_file = "./data/user_config.json"

        # setup stackedwidget
        self.stacked_widget = QStackedWidget()
        self.setCentralWidget(self.stacked_widget)
        self.setup_menubar()
        self.setup_p_view()
        self.setup_p_register()
        self.setup_p_create()
        self.setup_p_preferences()
        self.setup_signals()

        self.font = QFont()
        self.font.setPointSize(9)

        self.validate(self.set_user_preferences,
                      file_path=self.user_config_file,
                      schema=validate_json.ItHilfeUserPreferencesSchema,
                      forbidden=[""])

        # setup statusbar
        self.statusbar = self.statusBar()

        self.stacked_widget.setCurrentWidget(self.p_view)

    def setup_menubar(self) -> None:
        """inits menubar

        Returns:
            None"""
        self.menu_Bar = self.menuBar()

        menu_file = self.menu_Bar.addMenu("file")
        self.action_open = QAction("open")
        self.action_save = QAction("save")
        self.action_new = QAction("new")
        self.action_print = QAction("print")
        self.action_preferences = QAction("preferences")
        self.action_hide_menu_bar = QAction("hide menubar")
        self.action_print.setShortcut(QKeySequence("Ctrl+p"))
        self.action_open.setShortcut(QKeySequence("Ctrl+o"))
        self.action_save.setShortcut(QKeySequence("Ctrl+s"))
        self.action_hide_menu_bar.setShortcut(QKeySequence("Ctrl+h"))
        self.action_hide_menu_bar.setIcon(QIcon("./data/show_hide.ico"))
        self.action_print.setIcon(QIcon("./data/print2.ico"))
        self.action_open.setIcon(QIcon("./data/open.ico"))
        self.action_save.setIcon(QIcon("./data/save.ico"))
        self.action_new.setIcon(QIcon("./data/newfile.ico"))
        self.action_preferences.setIcon(QIcon("./data/preferences.ico"))

        menu_file.addAction(self.action_open)
        menu_file.addAction(self.action_save)
        menu_file.addAction(self.action_new)
        menu_file.addAction(self.action_print)
        menu_file.addAction(self.action_preferences)

        menu_edit = self.menu_Bar.addMenu("edit")
        self.action_register = QAction("register")
        self.action_register.setShortcut(QKeySequence("Ctrl+n"))
        self.action_register.setIcon(QIcon("./data/register.ico"))

        menu_edit.addAction(self.action_register)

        menu_view = self.menu_Bar.addMenu("view")
        menu_view.addAction(self.action_hide_menu_bar)

    def setup_p_view(self) -> None:
        """inits stacked widget page widget

        Returns:
            None"""
        self.p_view = QtWidgets.QWidget()
        self.stacked_widget.addWidget(self.p_view)

        self.model = QStandardItemModel(self.p_view)
        self.model.setHorizontalHeaderLabels(labels)

        self.filters = []
        source_model = self.model
        for filter_num in range(7):
            filter = QSortFilterProxyModel()
            filter.setSourceModel(source_model)
            filter.setFilterKeyColumn(filter_num)
            source_model = filter
            self.filters.append(filter)

        delegate = ComboDelegate()
        self.table = QtWidgets.QTableView(self.p_view)
        self.table.setModel(self.filters[-1])
        self.table.setItemDelegateForColumn(2, delegate)
        self.table.horizontalHeader().setStretchLastSection(True)
        self.table.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        self.header = FilterHeader(self.table)
        self.header.set_filter_boxes()
        self.header.setMaximumHeight(50)
        self.table.setHorizontalHeader(self.header)

        self.bt_burger = QPushButton(self.p_view)
        self.bt_burger.setIcon(QIcon("./data/menu2.svg"))
        self.bt_burger.setIconSize(QSize(30, 30))
        self.bt_burger.setToolTip('slide out description')
        l_burger = QLabel("menu", self.p_view)

        self.bt_register_new = QPushButton(self.p_view)
        self.bt_register_new.setIcon(QIcon("./data/add.ico"))
        self.bt_register_new.setIconSize(QSize(30, 30))
        self.bt_register_new.setToolTip("register new")
        l_register_new = QLabel("register new", self.p_view)

        self.bt_delete_column = QPushButton(self.p_view)
        self.bt_delete_column.setIcon(QIcon("./data/remove.ico"))
        self.bt_delete_column.setIconSize(QSize(30, 30))
        self.bt_delete_column.setToolTip(
            "delete columns with min 1 cell selected")
        l_delete = QLabel("delete column", self.p_view)

        self.bt_hide_show_filter = QPushButton(self.p_view)
        self.bt_hide_show_filter.setIcon(QIcon("./data/show_hide.ico"))
        self.bt_hide_show_filter.setIconSize(QSize(30, 30))
        self.bt_hide_show_filter.setToolTip("hide/show filter input")
        l_hide_show = QLabel("hide/show", self.p_view)

        self.left_btn_frame = QFrame(self.p_view)
        self.left_btn_frame.setMaximumWidth(40)
        self.left_btn_frame.setContentsMargins(0, 0, 0, 0)

        self.left_menu_frame = QFrame(self.p_view)
        self.left_menu_frame.setMaximumWidth(0)
        self.left_menu_frame.setContentsMargins(0, 0, 0, 0)

        p_view_layout2 = QtWidgets.QVBoxLayout(self.left_btn_frame)
        p_view_layout2.addWidget(self.bt_burger)
        p_view_layout2.addWidget(self.bt_register_new)
        p_view_layout2.addWidget(self.bt_delete_column)
        p_view_layout2.addWidget(self.bt_hide_show_filter)
        p_view_layout2.setAlignment(Qt.AlignTop)
        p_view_layout2.setContentsMargins(0, 0, 0, 0)

        self.p_view_layout3 = QtWidgets.QVBoxLayout(self.left_menu_frame)
        self.p_view_layout3.addWidget(l_burger)
        self.p_view_layout3.addWidget(l_register_new)
        self.p_view_layout3.addWidget(l_delete)
        self.p_view_layout3.addWidget(l_hide_show)
        self.p_view_layout3.setAlignment(Qt.AlignTop | Qt.AlignCenter)
        self.p_view_layout3.setContentsMargins(0, 0, 0, 0)
        self.p_view_layout3.setSpacing(25)

        p_view_layout = QHBoxLayout(self.p_view)
        p_view_layout.setContentsMargins(0, 0, 0, 0)
        p_view_layout.addWidget(self.left_btn_frame)
        p_view_layout.addWidget(self.left_menu_frame)
        p_view_layout.addWidget(self.table)
        self.p_view.setLayout(p_view_layout)

        self.p_view.addAction(self.action_open)
        self.p_view.addAction(self.action_save)
        self.p_view.addAction(self.action_new)
        self.p_view.addAction(self.action_print)
        self.p_view.addAction(self.action_register)
        self.p_view.addAction(self.action_hide_menu_bar)

    def setup_p_register(self) -> None:
        """inits stacked widget page widgets

        Returns:
            None"""

        self.p_register = QtWidgets.QWidget()
        self.stacked_widget.addWidget(self.p_register)

        l_user = QtWidgets.QLabel("Username", self.p_register)
        self.in_username = QtWidgets.QLineEdit(self.p_register)
        l_devicename = QtWidgets.QLabel("Devicename", self.p_register)
        self.in_devicename = QtWidgets.QLineEdit(self.p_register)
        l_devicetype = QtWidgets.QLabel("DeviceType", self.p_register)
        self.in_combobox_devicetype = QtWidgets.QComboBox(self.p_register)
        l_os = QtWidgets.QLabel("OS", self.p_register)
        self.in_combobox_os = QtWidgets.QComboBox(self.p_register)
        l_comment = QtWidgets.QLabel("Comment", self.p_register)
        self.in_comment = QtWidgets.QTextEdit(self.p_register)
        self.bt_enter_register = QPushButton("register", self.p_register)
        self.bt_cancel_register = QPushButton("cancel", self.p_register)

        p_register_layout = QtWidgets.QVBoxLayout(self.p_register)
        p_register_layout.addWidget(l_user)
        p_register_layout.addWidget(self.in_username)
        p_register_layout.addWidget(l_devicename)
        p_register_layout.addWidget(self.in_devicename)
        p_register_layout.addWidget(l_devicetype)
        p_register_layout.addWidget(self.in_combobox_devicetype)
        p_register_layout.addWidget(l_os)
        p_register_layout.addWidget(self.in_combobox_os)
        p_register_layout.addWidget(l_comment)
        p_register_layout.addWidget(self.in_comment)
        p_register_layout.addWidget(self.bt_enter_register)
        p_register_layout.addWidget(self.bt_cancel_register)

    def setup_p_create(self) -> None:
        """inits stacked widget page widget

        Returns:
            None"""

        self.p_create = QtWidgets.QWidget()
        self.stacked_widget.addWidget(self.p_create)

        l_new_filepath = QtWidgets.QLabel("new filepath", self.p_create)
        self.bt_mod_new_path = QPushButton("mod filepath", self.p_create)
        self.in_new_filepath = QtWidgets.QLineEdit(self.p_create)
        l_new_filename = QtWidgets.QLabel("new filename", self.p_create)
        self.in_new_filename = QtWidgets.QLineEdit(self.p_create)
        self.bt_create = QPushButton("create", self.p_create)
        self.bt_cancel_create = QPushButton("cancel", self.p_create)

        p_create_layout = QtWidgets.QVBoxLayout(self.p_create)
        p_create_layout.addWidget(l_new_filepath)
        p_create_layout.addWidget(self.in_new_filepath)
        p_create_layout.addWidget(l_new_filename)
        p_create_layout.addWidget(self.in_new_filename)
        p_create_layout.addStretch(100)
        p_create_layout.addWidget(self.bt_mod_new_path)
        p_create_layout.addWidget(self.bt_create)
        p_create_layout.addWidget(self.bt_cancel_create)

    def setup_p_preferences(self) -> None:
        """inits setup_p_preferences stacked widget page widget

            Returns:
                None"""

        self.p_preferences = QWidget()
        self.p_preferences.resize(500, 250)
        self.p_preferences.setWindowTitle("preferences")
        self.list_Widget = QListWidget(self.p_preferences)
        self.list_Widget.addItems(["appearance", "about"])
        self.list_Widget.setMaximumWidth(100)

        self.stacked_widget_preferences = QStackedWidget(self.p_preferences)

        # setup appearance
        self.apperence_widget = QWidget()
        self.stacked_widget_preferences.addWidget(self.apperence_widget)
        self.in_combo_themes = QComboBox(self.apperence_widget)
        self.in_combo_themes.addItems(["dark_theme", "light_theme"])

        self.in_combo_theme_initial = QComboBox(self.apperence_widget)
        self.in_combo_theme_initial.addItems(["dark_theme", "light_theme"])

        self.text_size_slider = QSlider(QtCore.Qt.Orientation.Horizontal,
                                        self.apperence_widget)
        self.text_size_slider.setTickPosition(QSlider.TickPosition.TicksAbove)
        self.text_size_slider.setMaximum(15)
        self.text_size_slider.setMinimum(8)

        stacked_widget_preferences_layout = QGridLayout(self.apperence_widget)
        stacked_widget_preferences_layout.setAlignment(QtCore.Qt.AlignTop)
        stacked_widget_preferences_layout.addWidget(QLabel("theme"), 0, 0)
        stacked_widget_preferences_layout.addWidget(self.in_combo_themes, 0, 1)
        stacked_widget_preferences_layout.addWidget(QLabel("initial theme"), 1,
                                                    0)
        stacked_widget_preferences_layout.addWidget(
            self.in_combo_theme_initial, 1, 1)
        stacked_widget_preferences_layout.addWidget(QLabel("Fontsize"), 2, 0)
        stacked_widget_preferences_layout.addWidget(self.text_size_slider, 2,
                                                    1)

        self.about_widget = QWidget()
        self.stacked_widget_preferences.addWidget(self.about_widget)

        about_text_edit = QTextEdit(self.about_widget)
        about_text_edit.setText(
            "developed by Maurice Jarck\nwith kind support from Shuai Lou\n07.2020-04.2021"
        )
        about_text_edit.setEnabled(False)
        stacked_widget_about_layout = QGridLayout(self.about_widget)
        stacked_widget_about_layout.addWidget(about_text_edit)

        p_apperance_layout = QHBoxLayout(self.p_preferences)
        p_apperance_layout.addWidget(self.list_Widget)
        p_apperance_layout.addWidget(self.stacked_widget_preferences)

    def setup_signals(self) -> None:
        """connects signals

        Returns:
            None"""

        # header
        for filter, editor in zip(self.filters, self.header.editors):
            editor.textChanged.connect(filter.setFilterRegExp)

        # line edit
        self.in_new_filename.returnPressed.connect(lambda: self.validate(
            self.new,
            line_edit_list=[self.in_new_filepath, self.in_new_filename],
            data=False))

        # comboboxes
        self.in_combobox_devicetype.addItems(
            ["choose here"] + [x.__name__ for x in valid_devices])
        self.in_combobox_devicetype.currentIndexChanged.connect(
            lambda: self.update_combobox(
                self.in_combobox_os, valid_devices[
                    self.in_combobox_devicetype.currentIndex() - 1].expected_OS
            ))
        self.in_combo_themes.currentIndexChanged.connect(
            lambda: self.change_theme(self.in_combo_themes.currentText()))
        self.in_combo_theme_initial.currentTextChanged.connect(lambda: setattr(
            self, "initial_theme", self.in_combo_theme_initial.currentText()))
        # btns
        self.bt_delete_column.clicked.connect(self.delete)
        # self.bt_hide_show_filter.clicked.connect(lambda: self.toggle_hide_show_ani(37, 47, "height", self.header, b"maximumHeight"))
        self.bt_hide_show_filter.clicked.connect(self.header.hide_show)
        # self.bt_hide_show_filter.clicked.connect(lambda: self.toggle_hide_show_ani(30, 44, "height", self.header, b"maximumHeight"))
        self.bt_register_new.clicked.connect(
            lambda: self.stacked_widget.setCurrentWidget(self.p_register))
        self.bt_enter_register.clicked.connect(lambda: self.validate(
            self.register,
            line_edit_list=[self.in_username, self.in_devicename],
            combo_box_list=[self.in_combobox_devicetype, self.in_combobox_os],
            forbidden=list(self.registered_devices.keys()),
            checkfname=True))
        self.bt_create.clicked.connect(lambda: self.validate(
            self.new,
            line_edit_list=[self.in_new_filepath, self.in_new_filename],
            data=False))
        self.bt_mod_new_path.clicked.connect(lambda: self.new(True))
        self.bt_burger.clicked.connect(lambda: self.toggle_hide_show_ani(
            0,
            66,
            "width",
            self.left_menu_frame,
            b"maximumWidth",
        ))
        # menu bar
        self.action_register.triggered.connect(
            lambda: self.stacked_widget.setCurrentWidget(self.p_register))
        self.action_open.triggered.connect(self.get_open_file_path)
        self.action_save.triggered.connect(self.save)
        self.action_new.triggered.connect(lambda: self.new(True))
        self.action_print.triggered.connect(
            lambda: self.validate(self.print, data=False, checkfname=True))
        self.action_hide_menu_bar.triggered.connect(
            lambda: self.toggle_hide_show(self.menu_Bar))
        self.action_preferences.triggered.connect(self.p_preferences.show)
        # cancel
        self.bt_cancel_register.clicked.connect(lambda: self.cancel([
            self.in_username, self.in_devicename, self.in_combobox_os, self.
            in_comment
        ]))

        # list widget
        self.list_Widget.currentRowChanged.connect(
            lambda: self.stacked_widget_preferences.setCurrentIndex(
                self.list_Widget.currentIndex().row()))

        # slider
        self.text_size_slider.sliderMoved.connect(
            lambda: self.change_font_size(self.text_size_slider.value()))
        # self.text_size_slider.sliderMoved.connect(lambda: print(self.text_size_slider.value()))

    def change_theme(self, theme) -> None:
        """changes theme according to combobox selection

        Returns:
            None"""

        with open(f"./data/{theme}.css", "r") as file:
            stylesheed = " ".join(file.readlines())
            self.setStyleSheet(stylesheed)
            self.p_preferences.setStyleSheet(stylesheed)

        if self.in_combo_themes.currentText() == "dark_theme":

            self.left_btn_frame.setStyleSheet(
                u"background: #455364; border: 0px solid;")
            self.p_view_layout3.setSpacing(30)

        else:
            self.left_btn_frame.setStyleSheet(
                u"background: #ADADAD; border: 0px solid;")
            self.p_view_layout3.setSpacing(25)

        return self.in_combo_themes.currentText()

    def toggle_hide_show_ani(self, collapsed_val: int, expanded_val: int,
                             actual: str, to_animate, property: bytes):
        """interpolates over a defined range of vales and sets it to a given property of a given widget"""
        if getattr(to_animate, actual)() == expanded_val:
            destination = collapsed_val
        else:
            destination = expanded_val
        print(getattr(to_animate, actual)(), destination)
        self.ani = QPropertyAnimation(to_animate, property)
        self.ani.setDuration(300)
        self.ani.setStartValue(getattr(to_animate, actual)())
        self.ani.setEndValue(destination)
        self.ani.setEasingCurve(QEasingCurve.Linear)
        self.ani.start()

    def toggle_hide_show(self, widget: QWidget) -> None:
        """toggles visibiliy of a given widget
        Arg:
            widget: widget which is aimed to be hidden or shown
        Returs:
            None"""

        if widget.isVisible():
            widget.hide()
        else:
            widget.show()

    def reopen_last_file(self) -> None:
        """asks for reopening of the last opened file"""
        if self.last_open_file_path != "" or self.last_open_file_path is not None:
            reopen_dialog = QMessageBox.question(
                self.p_view, "reopen last file?",
                "Do you want to reopen the last edited file?",
                QMessageBox.Yes | QMessageBox.No)
            if reopen_dialog == QMessageBox.Yes:
                self.file_path = self.last_open_file_path
                self.load()

    def change_font_size(self, size: int) -> None:
        """changes all font sizes"""
        self.font.setPointSize(size)
        self.menu_Bar.setFont(self.font)
        self.header.setFont(self.font)
        self.table.setFont(self.font)
        self.p_preferences.setFont(self.font)

    def set_user_preferences(self) -> None:
        """Reads user_config file and sets its propertys"""
        with open(self.user_config_file, "r") as config_file:
            data = dict(json.load(config_file))

            self.last_open_file_path = data["last_open_file_path"]
            self.initial_theme = data['initial_theme']
            self.change_font_size(data['font_size'])
            self.text_size_slider.setValue(data['font_size'])
            self.in_combo_theme_initial.setCurrentText(self.initial_theme)
            self.in_combo_themes.setCurrentText(self.initial_theme)

            with open(f"./data/{self.initial_theme}.css") as file:
                style_sheed = " ".join(file.readlines())
                self.setStyleSheet(style_sheed)
                self.p_preferences.setStyleSheet(style_sheed)

            self.bt_burger.setStyleSheet(
                "border: 0px solid; background: transparent;")
            self.bt_register_new.setStyleSheet(
                "border: 0px solid; background: transparent;")
            self.bt_delete_column.setStyleSheet(
                "border: 0px solid; background: transparent;")
            self.bt_hide_show_filter.setStyleSheet(
                "border: 0px solid; background: transparent;")
            self.left_menu_frame.setStyleSheet(u" border: 0px solid;")

            if self.initial_theme == "dark_theme":
                self.left_btn_frame.setStyleSheet(
                    u"background: #455364; border: 0px solid;")

            else:
                self.left_btn_frame.setStyleSheet(
                    u"background: #ADADAD; border: 0px solid;")

    def cancel(self, widgets: list) -> None:
        """click event for all cancel buttons

        shows fist page in stacked widget and clears all widgets in widgets

        Args:
               widgets: defines list containing widgets to clear, only widgets with method .clear() are possible

        Returns:
            None"""
        for widget in widgets:
            widget.clear()
        self.stacked_widget.setCurrentWidget(self.p_view)

    def update_combobox(self, box, data: list) -> None:
        """ clears combo box

        updates combobox so that old content not needed any more isnt displayed and adds 'choose here' dummy
        to ensure an index change will be made (updating next box depends on index change)
        Args:
            box: instance of pyqt5.QtWidgets.qComboBox
            data: data supposed to be inserted into combobox
        Returns:
            None"""

        box.clear()
        box.addItems(["choose here"] + data)

    def validate(self,
                 command,
                 file_path: str = None,
                 schema=None,
                 line_edit_list: list = None,
                 combo_box_list: list = None,
                 data=None,
                 forbidden: list = None,
                 checkfname: bool = None) -> None:
        """validates user input

        Args:
            command: function to be called after vailidation process if finished
            line_edit_list: contents pyqt5.QtWidgets.QlineEdit instances to be checked if empty or current text in forbidden or not in allowed
            combo_box_list: contents pyqt5.QtWidgets.qComboBox instances to be checked if nothing selected
            data: data to be passed into command function if needed
            forbidden: houses keys which are not allowed to be entered
            checkfname: check weather an file path exists or not

        Returns:
            None"""

        fails = 0
        if line_edit_list is not None:
            for x in line_edit_list:
                if x.text() == "":
                    x.setText("fill all fields")
                    fails += 1
                if forbidden is not None and x.text() in forbidden:
                    x.setText("in forbidden!!")
                    fails += 1
        if combo_box_list is not None:
            for combobox in combo_box_list:
                if combobox.currentText() == "":
                    self.statusbar.showMessage("all comboboxes must be filled")
                    fails += 1
        if checkfname is True and self.file_path is None:
            self.statusbar.showMessage(
                "no file path specified, visit Ctrl+o or menuebar/edit/open to fix"
            )
            fails += 1

        if file_path is not None:
            if forbidden is not None and file_path in forbidden:
                fails += 1
                self.statusbar.showMessage("select a file to continue")
            else:
                try:
                    validate_json.validate(file_path, schema)
                except ValidationError as e:
                    self.msg_box = QtWidgets.QMessageBox.critical(
                        self, "validation failed",
                        f"Invalid Json file, problem in: {e.messages}")
                    fails += 1
        if fails == 0:
            if data is None:
                command()
            else:
                command(data)
        else:
            message = f"problem\ncommand: {command.__name__}\nfails: {fails}"
            print(message)
            return message

    def register(self) -> None:
        """registers a new device and saves

        Returns:
            None"""
        logic.register(devname=self.in_devicename.text(),
                       devtype=[
                           device for device in valid_devices
                           if device.__name__ ==
                           self.in_combobox_devicetype.currentText()
                       ].pop(),
                       username=self.in_username.text(),
                       os=self.in_combobox_os.currentText(),
                       comment=self.in_comment.toPlainText(),
                       datetime=str(datetime.datetime.now()),
                       registered_devices=self.registered_devices)

        new_values = [
            self.in_devicename.text(),
            self.in_username.text(),
            self.in_combobox_os.currentText(),
            [
                device.__name__ for device in valid_devices if device.__name__
                == self.in_combobox_devicetype.currentText()
            ].pop(),
            self.in_comment.toPlainText(),
            str(datetime.datetime.now())
        ]
        row = [QStandardItem(str(item)) for item in new_values]
        self.model.appendRow(row)

        self.stacked_widget.setCurrentWidget(self.p_view)
        self.in_devicename.clear()
        self.in_username.clear()
        self.in_combobox_os.clear()
        self.in_comment.clear()
        self.save()

    def delete(self) -> None:
        """deletes all rows associated with min 1 slected cell
        Returns:
            None"""
        rows = sorted(set(index.row()
                          for index in self.table.selectedIndexes()),
                      reverse=True)
        qb = QMessageBox()
        answ = qb.question(self, 'delete rows',
                           f"Are you sure to delete {rows} rows?",
                           qb.Yes | qb.No)

        if answ == qb.Yes:
            for row in rows:
                self.registered_devices.pop(
                    str(self.model.index(row, 0).data()))
                self.model.removeRow(row)
            qb.information(self, 'notification', f"deleted {rows} row")
        else:
            qb.information(self, 'notification', "Nothing Changed")
        self.save()

    def get_open_file_path(self) -> None:
        """gets file-path and set it to self.file_path, extra step for json validation

        Returns:
            None"""

        self.file_path = \
            QFileDialog.getOpenFileName(self, "open file", f"{self.last_open_file_dir or 'c://'}",
                                        "json files (*json)")[0]
        self.validate(command=self.load,
                      file_path=self.file_path,
                      schema=validate_json.ItHilfeDataSchema,
                      forbidden=[""])

    def load(self) -> None:
        """opens json file and loads its content into registered devices

        Returns:
            None"""

        self.model.clear()
        self.registered_devices.clear()
        with open(self.file_path, "r") as file:
            data = dict(json.load(file))
            devices = data["devices"].values()
            self.last_open_file_dir = data["last_open_file_dir"]
            for value in devices:
                row = []
                for i, item in enumerate(value):
                    cell = QStandardItem(str(item))
                    row.append(cell)
                    if i == 0 or i == 3 or i == 5:
                        cell.setEditable(False)
                self.model.appendRow(row)

                new = [x for x in valid_devices
                       if x.__name__ == value[3]].pop(0)(value[0], value[1],
                                                         value[4], value[5])
                new.OS = value[2]
                self.registered_devices[value[0]] = new

        self.model.setHorizontalHeaderLabels(labels)
        self.statusbar.showMessage("")

        # auto complete
        for a in range(len(self.header.editors)):
            completer = QCompleter([
                self.model.data(self.model.index(x, a))
                for x in range(self.model.rowCount())
            ])
            completer.setCompletionMode(QCompleter.InlineCompletion)
            self.header.editors[a].setCompleter(completer)

    def save(self) -> None:
        """saves content fo self.registered_devices into specified json file

        Returns:
            None"""
        if not self.file_path:
            self.statusbar.showMessage(
                "no file path set all changes get lost if closed")
        else:
            with open(
                    self.file_path,
                    'w',
            ) as file:
                devices = {
                    k: [
                        v.name, v.user, v.OS, v.__class__.__name__, v.comment,
                        v.datetime
                    ]
                    for (k, v) in enumerate(self.registered_devices.values())
                }
                last_open_file_dir = "/".join(self.file_path.split("/")[:-1])
                resulting_dict = {
                    "devices": devices,
                    "last_open_file_dir": last_open_file_dir
                }
                json.dump(resulting_dict, file)
                self.statusbar.showMessage("saved file")

        with open(self.user_config_file, "w") as user_preferences_file:
            json.dump(
                {
                    "last_open_file_path": self.last_open_file_path,
                    "initial_theme": self.initial_theme,
                    "font_size": self.text_size_slider.value()
                }, user_preferences_file)

    def new(self, stage: bool, test: bool = False) -> None:
        """creates new csv file to save into

        stage is True: set filepath
        stage is False: set new name, save
        Args:
            stage: determines a which stage to execute this function

        Returns:
            None"""

        if stage is True:
            if not test:
                self.dir = QFileDialog.getExistingDirectory(
                    self, "select a folder", "c://")
            self.stacked_widget.setCurrentWidget(self.p_create)
            self.in_new_filepath.setText(self.dir)
            self.registered_devices.clear()

        else:
            self.file_path = self.dir + f"/{self.in_new_filename.text()}.json"
            self.save()
            self.stacked_widget.setCurrentWidget(self.p_view)

    def print(self, test: bool) -> None:
        """setup and preview pViewTable for paper printing

        Returns:
            None"""

        with open(self.file_path) as f:
            self.data = json.dumps(dict(json.load(f)),
                                   sort_keys=True,
                                   indent=6,
                                   separators=(".", "="))
        self.document = QtWidgets.QTextEdit()
        self.document.setText(self.data)

        if not test:
            printer = QPrinter()
            previewDialog = QPrintPreviewDialog(printer, self)
            previewDialog.paintRequested.connect(
                lambda: self.document.print_(printer))
            previewDialog.exec_()
예제 #9
0
class TypingExercise():
    def __init__(self, main_window, word_deck, parent=None):
        super(TypingExercise, self).__init__()
        self.win = main_window
        self.wordDeck = word_deck
        self.model = QStandardItemModel(
        )  # Model used for displaying review session data
        self.missedWordSet = set(
        )  # Set used to keep track of all missed words
        self.reviewSet = set()
        self.startTime = datetime.datetime.now()
        print("Started session at: ", self.startTime)

    def resetUi(self):
        #self.mainWindow.ui.progressBar_typing.reset()
        self.setStatLabels()
        self.win.ui.pushButton_enter.setEnabled(True)
        self.win.ui.pushButton_enter.clicked.connect(self.submitAnswer)
        self.win.ui.pushButton_notSure_Skip.clicked.connect(self.nextWord)
        self.win.ui.pushButton_notSure_Skip.hide()
        self.win.ui.lineEdit_answer.textEdited['QString'].connect(
            lambda: self.win.ui.pushButton_enter.setText("Enter"))

    def setStatLabels(self):
        cn = self.wordDeck.cardNum
        timesCorrect = self.wordDeck.studyList[cn].timesCorrect
        timesMissed = str(self.wordDeck.studyList[cn].timesAttempted -
                          timesCorrect)
        self.win.ui.label_typingCorrect.setText("Times Correct: " +
                                                str(timesCorrect))
        self.win.ui.label_typingMissed.setText("Times Missed: " +
                                               str(timesMissed))

    def buildAnswerList(self, input_str):
        '''This function will build a list of answers from the database string'''
        tempAnsList = []
        ansList = []
        # Check if there are multiple definitions separated by semicolon
        if ';' in input_str:
            tempAnsList = input_str.split(';')
        # If not, add the single string to a list
        else:
            tempAnsList.append(input_str)

        for ans in tempAnsList:
            # if '(' in ans:
            #     # optionalAns = ans.replace(")", "").split("(")
            #     # ansList.append(self.sanitizeInput(optionalAns[0]))
            #     # ansList.append(self.sanitizeInput(optionalAns[1]))
            pos = ans.find('(')
            if pos != -1:
                ans = ans[pos:]
            a = self.sanitizeInput(ans)
            if len(a) > 0 and a != " ":
                ansList.append(a)
        return ansList

    def compareAnswerLists(self, user_input_list: list, answer_list: list):
        for u in user_input_list:
            if u in answer_list:
                return True
        return False

    def submitAnswer(self):
        currWordData = self.wordDeck.studyList[self.wordDeck.cardNum]
        userInput = self.win.ui.lineEdit_answer.text()
        answer = self.wordDeck.studyList[self.wordDeck.cardNum].definition
        userInputList = self.buildAnswerList(userInput)
        answerList = self.buildAnswerList(answer)
        print("user input:", userInputList, " answer:", answerList)
        # Check answer list against user input list
        if self.compareAnswerLists(userInputList, answerList):
            self.proceedUi()
            print("Current word data: ", currWordData)
            self.reviewSet.add(currWordData)
        else:
            self.pauseUi(answerList)
            print("Current word data: ", currWordData)
            self.missedWordSet.add(currWordData)

    def pauseUi(self, answer_list: list):

        cn = self.wordDeck.cardNum
        self.wordDeck.studyList[cn].timesAttempted += 1
        self.setStatLabels()

        self.wordDeck.missedWordList.append(
            self.wordDeck.studyList[cn])  # Add to incorrect list
        # how to prevent attempted from increasing on "enter it correctly" call
        self.win.ui.lineEdit_answer.textChanged.connect(
            lambda: self.unpauseUi(answer_list))
        percentLabelStr = self.wordDeck.calcPercentageCorrect()
        answerLabelStr = self.wordDeck.studyList[cn].definition

        self.win.ui.label_typingFractionCorrect.setText("%" +
                                                        str(percentLabelStr))
        #self.mainWindow.ui.pushButton_enter.setText("Enter")

        self.win.ui.pushButton_notSure_Skip.show()
        self.win.ui.lineEdit_answer.clear()

        self.win.ui.label_typingWord.setText("Oops! Correct answer is:\n" +
                                             answerLabelStr)
        self.win.ui.pushButton_notSure_Skip.setText("I was right")
        self.win.ui.pushButton_notSure_Skip.clicked.disconnect()
        self.win.ui.pushButton_notSure_Skip.clicked.connect(self.wasRight)
        self.win.ui.lineEdit_answer.setPlaceholderText(
            "Enter the correct answer")

    def wasRight(self):
        cn = self.wordDeck.cardNum
        print(self.wordDeck.studyList[cn])
        self.wordDeck.studyList[cn].timesAttempted -= 1
        print(self.wordDeck.studyList[cn])
        self.nextWord()

    def unpauseUi(self, answer_list: str):
        userInputList = self.sanitizeInput(self.win.ui.lineEdit_answer.text())
        if userInputList in answer_list:
            print("ui unpauesd")
            self.win.ui.pushButton_enter.setText("Enter")
            self.win.ui.pushButton_enter.setEnabled(True)
            # self.mainWindow.ui.lineEdit_answer.textChanged.disconnect()
            self.win.ui.pushButton_enter.clicked.disconnect()
            self.win.ui.pushButton_enter.clicked.connect(self.nextWord)
        else:
            self.win.ui.pushButton_enter.setEnabled(False)

    def proceedUi(self):
        cn = self.wordDeck.cardNum
        answer = self.wordDeck.studyList[cn].definition
        self.wordDeck.studyList[cn].timesCorrect += 1
        self.wordDeck.studyList[cn].timesAttempted += 1
        self.setStatLabels()
        percent = self.wordDeck.calcPercentageCorrect()
        self.win.ui.lineEdit_answer.clear()
        self.win.ui.label_typingFractionCorrect.setText("%" + str(percent))
        self.win.ui.label_typingWord.setText("Correct!\n" + answer)
        self.win.ui.pushButton_enter.setText("Continue")

        self.win.ui.lineEdit_answer.setPlaceholderText(
            "Press Enter to continue")
        self.win.ui.lineEdit_answer.setDisabled(True)
        self.win.ui.pushButton_enter.clicked.disconnect()
        self.win.ui.pushButton_enter.clicked.connect(self.nextWord)

    def intermission(self):
        ''' This function will serve as a breakpoint in the typing module where the user can review their progress
        and the program will make a call to save session data into the database. '''
        review = ReviewDialog(self.win)
        combinedSet = self.missedWordSet | self.reviewSet
        self.model.clear()
        for i in combinedSet:
            # IS_STARRED, WORD, DEFINITION, PRONUN, TC, TM
            cardNum = QStandardItem(str(i.cardNum))
            timesMissed = i.timesAttempted - i.timesCorrect
            isStarred = QStandardItem(str(i.isStarred))
            #isStarred.itemChanged.connect(lambda: self.changeStar(isStarred))
            vocabulary = QStandardItem(i.vocabulary)
            definition = QStandardItem(i.definition)
            pronunciation = QStandardItem(i.pronunciation)
            timesCorrect = QStandardItem(str(i.timesCorrect))
            timesMissed = QStandardItem(str(timesMissed))
            row = [
                cardNum, isStarred, vocabulary, definition, pronunciation,
                timesCorrect, timesMissed
            ]
            print(i.timesAttempted, i.timesCorrect)
            self.model.appendRow(row)
        self.n = StarDelegate(review.rd.tableView)
        headers = [
            "#", "тнР", "Vocabulary", "Definition", "Pronunciation",
            "# Correct", "# Missed"
        ]
        self.model.sort(5)
        review.rd.tableView.setItemDelegateForColumn(1, self.n)
        self.model.setHorizontalHeaderLabels(headers)
        review.rd.tableView.setColumnWidth(0, 40)
        review.rd.tableView.setModel(self.model)
        review.rd.tableView.sortByColumn(5)
        review.show()

    def changeStar(self, index, isStarred: QStandardItem):

        if isStarred.text() == 0:
            isStarred.setText(1)
        elif isStarred.text() == 1:
            isStarred.setText(0)

    def updateSession(self):
        pass
        # Read from model
        # for n in range (0, len(self.model.rowCount)):
        #     if self.model.item(n, 1).text() != :
        DATABASE_PATH = '../data/vocab2.db'
        database = SqlTools(self.DATABASE_PATH)
        combinedSet = self.missedWordSet | self.reviewSet
        rows = (self.startTime, self.win.nameOfCurrentDeck)
        database.insertSession(rows)

        database.close()

    def nextWord(self):
        self.win.ui.progressBar_typing.setValue(self.wordDeck.cardNum + 1)
        self.win.ui.label_typingFractionCorrect.clear()
        print(self.wordDeck.cardNum + 1, " of ", len(self.wordDeck.studyList))
        if self.wordDeck.cardNum == len(self.wordDeck.studyList):
            print("END GAME")
            return
        elif self.wordDeck.cardNum in self.wordDeck.summaryIndexList:
            print("OVERVIEW")
            print("Here are the words most commonly missed:")
            self.intermission()
            for i in self.missedWordSet:
                timesMissed = i.timesAttempted - i.timesCorrect
                print(i, timesMissed)

        self.wordDeck.cardNum += 1
        self.setStatLabels()
        self.win.ui.lineEdit_answer.setEnabled(True)
        self.win.ui.pushButton_notSure_Skip.hide()
        self.win.ui.lineEdit_answer.setFocus()
        self.win.ui.lineEdit_answer.clear()
        self.win.ui.lineEdit_answer.setPlaceholderText("Enter your answer")
        self.win.ui.pushButton_enter.setText("Don't Know")
        self.win.ui.label_typingWord.setText(
            self.wordDeck.studyList[self.wordDeck.cardNum].vocabulary)
        self.win.ui.pushButton_enter.clicked.disconnect()
        self.win.ui.pushButton_enter.clicked.connect(self.submitAnswer)
        try:
            self.win.ui.lineEdit_answer.textChanged.disconnect()
        except RuntimeError:
            print("didnt have connection?")
        self.win.ui.pushButton_enter.setEnabled(True)

    def sanitizeInput(self, input_str: str):
        punct = '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ '
        return input_str.translate({ord(ch): '' for ch in punct}).lower()
예제 #10
0
class exergyAnalysisWindow(QObject):
    def __init__(self, dataPoints=[], blankDataLen=0, parent=None):
        super(exergyAnalysisWindow,self).__init__(parent)
        ui_file = QFile('exergyAnalysis/exergyAnalysis.ui')
        ui_file.open(QFile.ReadOnly)
        
        loader = QUiLoader()
        self.window = loader.load(ui_file) # self.window is ui
        
        self.tagNameDict=defaultdict(list)
        self.dataPoints = dataPoints   # list of all entered data in form of ditionary
        self.tagName=["Process Parameters","Biomass Composition","Biochar Composition", "Bio-Oil Composition","Flue-gas Composition","Flue gas property Composition"]
        self.noOfAddedData = len(self.dataPoints)#0  # no of added data, 1 for 1 data
        self.sendData=defaultdict(list)
        self.receivedData=defaultdict(list)
        
        self.createwidgets()
        self.createTagNameDict()
        
        self.updateDataPointsFromProcessParameters(blankLength = blankDataLen)
        
        ui_file.close()
        
    def showWindow(self):
        #self.window.show()
        self.window.showMaximized()
        self.updateTreeView()
    def closeWindow(self):
        self.window.close()
        
    def updateDataPointsFromProcessParameters(self, blankLength=0):
        tempdict=defaultdict(list)
        for i in range(self.noOfAddedData-blankLength, self.noOfAddedData, 1):
            pp_arr = self.dataPoints[i].copy()
            tempdict.clear()
            tempdict[self.tagName[0]] = pp_arr
                 
            tempdict[self.tagName[1]] = [""for k in range(len( self.tagNameDict[self.tagName[1]]))]
            
            tempdict[self.tagName[2]] = [""for k in range(len( self.tagNameDict[self.tagName[2]]))]
            tempdict[self.tagName[3]] = [""for k in range(len( self.tagNameDict[self.tagName[3]]))]
            tempdict[self.tagName[4]] = [""for k in range(len( self.tagNameDict[self.tagName[4]]))]
            tempdict[self.tagName[5]] = [""for k in range(len( self.tagNameDict[self.tagName[5]]))] 
            
            self.dataPoints[i]=tempdict.copy()
                 
    def createTagNameDict(self):
        self.tagNameDict[self.tagName[0]].append("Product System")
        self.tagNameDict[self.tagName[0]].append("Feed Type")
        self.tagNameDict[self.tagName[0]].append("Feed Size(mm)")
        self.tagNameDict[self.tagName[0]].append("Feed Amaount(kg)")
        self.tagNameDict[self.tagName[0]].append("Temp(C)")
        self.tagNameDict[self.tagName[0]].append("Residense Time(min) ")
        self.tagNameDict[self.tagName[0]].append("Heating rate(C/min)")
        
        self.tagNameDict[self.tagName[1]].append("Carbon (kg)")
        self.tagNameDict[self.tagName[1]].append("Hydrogen (kg)")
        self.tagNameDict[self.tagName[1]].append("Oxygen (kg)")
        self.tagNameDict[self.tagName[1]].append("Nitogen (kg)")
        self.tagNameDict[self.tagName[1]].append("Sulpher (kg)")
        self.tagNameDict[self.tagName[1]].append("Phosphrous (kg)")
        
        self.tagNameDict[self.tagName[2]].append("Carbon (kg)")
        self.tagNameDict[self.tagName[2]].append("Hydrogen (kg)")
        self.tagNameDict[self.tagName[2]].append("Oxygen (kg)")
        self.tagNameDict[self.tagName[2]].append("Nitogen (kg)")
        self.tagNameDict[self.tagName[2]].append("Sulpher (kg)")
        self.tagNameDict[self.tagName[2]].append("Phosphrous (kg)")
        
        self.tagNameDict[self.tagName[3]].append("Carbon (kg)")
        self.tagNameDict[self.tagName[3]].append("Hydrogen (kg)")
        self.tagNameDict[self.tagName[3]].append("Oxygen (kg)")
        self.tagNameDict[self.tagName[3]].append("Nitogen (kg)")
        self.tagNameDict[self.tagName[3]].append("Sulpher (kg)")
        self.tagNameDict[self.tagName[3]].append("Phosphrous (kg)")
              
        self.tagNameDict[self.tagName[4]].append("Carbon dioxide (kg)")
        self.tagNameDict[self.tagName[4]].append("Methane (kg)")
        self.tagNameDict[self.tagName[4]].append("Carbon mono-oxide (kg)")
        self.tagNameDict[self.tagName[4]].append("Oxides of Sulpher (kg)")
        self.tagNameDict[self.tagName[4]].append("Oxides of Nitrogen (kg)")
        self.tagNameDict[self.tagName[4]].append("Oxides of Phosphrous (kg)")
        self.tagNameDict[self.tagName[4]].append("Pyrolysis (Process) Energy (MJ)")
        
        self.tagNameDict[self.tagName[5]].append("Process temp [T] (K)")
        self.tagNameDict[self.tagName[5]].append("Ambient temp [To] (K)")
        self.tagNameDict[self.tagName[5]].append("Enthalpy at process temp [h] (J)")
        self.tagNameDict[self.tagName[5]].append("Enthalpy at ambient temp [ho] (J)")
        self.tagNameDict[self.tagName[5]].append("Entropy at process temp [S] (J/K)")
        self.tagNameDict[self.tagName[5]].append("Entropy at ambient temp [So] (J/K)")
        
        
    def createDataDictForEntry(self,addEditIndex=0):
        self.sendData.clear()
        self.sendData = self.dataPoints[addEditIndex-1].copy()  # addEditIndex start from 1 where as data index from o

        
    def createwidgets(self):
        self.sampleDataNo_CB = self.window.comboBox
        self.addEditButton_PB = self.window.pushButton
        self.dataTreeView = self.window.treeView
        self.run_PB = self.window.pushButton_2
        
        self.sampleDataNo_CB.addItems([str(i) for i in range(1,self.noOfAddedData+1)]) 
        
        self.addEditButton_PB.clicked.connect(self.addEditData)
    
        self.model = QStandardItemModel()
        self.dataTreeView.setModel(self.model)
        self.dataTreeView.setUniformRowHeights(True)
            
    def updateTreeView(self):
        self.model.clear()
        self.model.setHorizontalHeaderLabels(self.tagName)
        
        for i in range(self.noOfAddedData):
            parent1 = QStandardItem('Samle No {}.'.format(i+1))  # sample no 1,2,3,----
            showThisData = self.dataPoints[i]
            maximumIteam = max(len(self.tagNameDict[self.tagName[0]]),len(self.tagNameDict[self.tagName[1]]),len(self.tagNameDict[self.tagName[2]]))

            for j in range(maximumIteam):
                currentRow=[]
                for tn in self.tagName:
                    if(j<len(self.tagNameDict[tn])):  # because 0 to len-1
                        x=str(self.tagNameDict[tn][j])
                        y=str(showThisData[tn][j])
                        child =QStandardItem((x+': {}').format(y))
                    else:
                        child =QStandardItem("")                        
                    currentRow.append(child)
                parent1.appendRow(currentRow)
            self.model.appendRow(parent1)
        
    def addEditData(self):
        addEditIndex = int(self.sampleDataNo_CB.currentText())
        self.createDataDictForEntry(addEditIndex)   
        ######## launch add data dialog and send current data##########
        exergyAnalysisDialogWindow = exergyAnalysisDialog(self.sendData, editData=1)  # only data can be edited by filling blank spots
        
        def getValue():
            exergyAnalysisDialogWindow.fetchData()
            self.receivedData = exergyAnalysisDialogWindow.data.copy()                              
            self.dataPoints[addEditIndex -1] = self.receivedData # -1 because list starts from 0             
            self.updateTreeView()
            
        exergyAnalysisDialogWindow.okButton.clicked.connect(getValue)
        exergyAnalysisDialogWindow.showWindow()
            
        
            
            
        
예제 #11
0
class LibraryTree(QObject):
    def __init__(self, app, window):
        QObject.__init__(self)
        self.app = app
        self.library = app.library
        self.view = window.lib_tree
        self.and_btn = window.tags_and_btn
        self.or_btn = window.tags_or_btn
        self.not_btn = window.tags_not_btn
        self.items = {}
        self.model = QStandardItemModel()
        self.model.setColumnCount(2)
        self.view.setHeaderHidden(True)
        self.view.setModel(self.model)
        self.view.sortByColumn(0, Qt.AscendingOrder)
        self.view.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.view.expanded.connect(self.resize_columns)
        self.view.collapsed.connect(self.resize_columns)
        self.reload()
        selection_model = self.view.selectionModel()
        selection_model.select(self.items["/"][0].index(),
                               QItemSelectionModel.Select)
        selection_model.selectionChanged.connect(self.selection_changed)

    def reload(self):
        self.model.clear()
        self.items = {}
        for tag, count in sorted(self.library.get_tags()):
            logger.debug("Creating item(s) for tag %r", tag)
            parent_obj = self.model.invisibleRootItem()
            if tag.startswith("/") and tag != "/":
                parent = tag.rsplit("/", 1)[0]
                if not parent:
                    parent = "/"
                item, c_item = self.items[parent]
                parent_obj = item
            item, c_item = self.create_items(tag, count)
            self.items[tag] = (item, c_item)
            parent_obj.appendRow([item, c_item])
        self.resize_columns()

    def create_items(self, name, count):
        if name.startswith("/"):
            if name == "/":
                short_name = "all"
            else:
                short_name = name.rsplit("/", 1)[1]
            icon = QIcon.fromTheme("folder")
        else:
            short_name = name
            icon = QIcon.fromTheme("tag")
        item = QStandardItem(icon, short_name)
        item.setToolTip(name)
        item.setData(name)
        c_item = QStandardItem(str(count))
        c_item.setTextAlignment(Qt.AlignRight)
        return item, c_item

    def get_current_conditions(self):
        result = []
        if self.not_btn.isChecked():
            cond = TagExcludeQuery
        elif self.or_btn.isChecked():
            cond = TagIncludeQuery
        else:
            cond = TagRequireQuery
        for index in self.view.selectedIndexes():
            if index.column() > 0:
                continue
            item = self.model.itemFromIndex(index)
            logger.debug("Selected item: %r", item)
            tag = item.data()
            result.append(cond(tag))
        return result

    @Slot()
    def resize_columns(self, index=None):
        self.view.resizeColumnToContents(0)
        self.view.resizeColumnToContents(1)

    @Slot(QItemSelection)
    def selection_changed(self, selection):
        logger.debug("selection changed")
예제 #12
0
class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()

        # 声明工具对象
        self._sniffer_tool = None
        self._scan_tool = None

        # 初始化嗅探IP表格数据源
        self._table_model = QStandardItemModel()
        self._scan_table_model = QStandardItemModel()

        # 声明嗅探列表刷新线程
        self._table_refresh_thread = None

        # 加载UI
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        # 使窗口居中
        self.center()

        # 连接信号和槽
        self.ui.action_about.triggered.connect(self.about)
        self.ui.local_refresh_button.clicked.connect(
            self.load_local_net_config)
        self.ui.sniffer_start_button.clicked.connect(self.start_sniffer_thread)
        self.ui.sniffer_stop_button.clicked.connect(self.stop_sniffer_thread)
        self.ui.scan_start_button.clicked.connect(self.start_scan_thread)
        self.ui.scan_stop_button.clicked.connect(self.stop_scan_thread)

        # 初始化控件状态
        self.ui.sniffer_stop_button.setEnabled(False)
        self.ui.scan_stop_button.setEnabled(False)

        # 初始化完成
        self.statusBar().showMessage('准备就绪。')

    def center(self):
        """
        使窗口居中
        """
        screen_g = QDesktopWidget().screenGeometry()
        window_g = self.geometry()
        left = int((screen_g.width() - window_g.width()) / 2)
        up = int((screen_g.height() - window_g.height()) / 2)
        self.move(left, up)

    def about(self):
        """
        显示软件关于信息
        """
        QMessageBox.information(
            self, '关于Net Listener', '本软件用来进行网络嗅探、端口扫描,\
以发现局域网中的主机或任何主机的端口开放情况,使用全部功能需要使用ROOT权限运行', QMessageBox.Yes, QMessageBox.Yes)

    def load_local_net_config(self):
        """
        程序被第一次打开,加载本地网络配置
        """
        self.ui.device_tip_label.setHidden(True)
        self.ui.local_refresh_button.setEnabled(False)
        self.statusBar().clearMessage()
        self.statusBar().showMessage('正在读取网络配置……')
        tool = LocalhostNetInfoTool()
        tool.start_local_info_thread(self.ui.device_info_label,
                                     self.ui.scroll_area_widget_contents,
                                     self.statusBar(),
                                     self.ui.local_refresh_button)

    def start_sniffer_thread(self):
        """
        开启嗅探线程
        """
        # 测试当前是否是root用户
        if not OtherToolFunctionSet.test_root_permission():
            user_input = QMessageBox.information(
                self, '需要ROOT权限', '请以ROOT权限重启程序以运行嗅探功能。',
                QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
            if user_input == QMessageBox.Yes:
                # 退出应用程序
                QCoreApplication.instance().quit()
            else:
                return

        # 开启网卡混杂模式
        if_turn_promisc_on = QMessageBox.question(
            self, '需要开启网卡混杂模式', '嗅探功能需要开启网卡混杂模式,是否开启?',
            QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
        if if_turn_promisc_on == QMessageBox.Yes:
            if not OtherToolFunctionSet.turn_net_card_promisc(True):
                QMessageBox.information(self, '开启结果', '开启混杂模式失败。',
                                        QMessageBox.Yes, QMessageBox.Yes)
                return
        else:
            self.statusBar().showMessage('停止嗅探,需要开启网卡混杂模式。')
            return

        self.ui.sniffer_tip_label.setHidden(True)

        # 设置表格数据源
        self._table_model.clear()
        self.ui.sniffer_table.setModel(self._table_model)
        self._table_model.setHorizontalHeaderLabels(['IP', 'MAC'])
        self._table_model.setColumnCount(2)

        # 开启嗅探线程
        self._sniffer_tool = NetSnifferTool()
        self._sniffer_tool.start_sniffer_thread(self._table_model,
                                                self.ui.sniffer_table,
                                                self.statusBar(),
                                                self.ui.sniffer_start_button,
                                                self.ui.sniffer_stop_button)

        # 修改控件状态
        self.ui.sniffer_stop_button.setEnabled(True)
        self.ui.sniffer_start_button.setEnabled(False)

    def stop_sniffer_thread(self):
        """
        关闭嗅探线程
        """
        # 关闭嗅探线程
        if self._sniffer_tool is not None:
            self._sniffer_tool.stop_sniffer_thread()
            self._sniffer_tool = None

            self.statusBar().showMessage('正在停止……')

    def start_scan_thread(self):
        """
        扫描给定IP的给定端口
        """
        target_ip = self.ui.scan_target_ip_edit.text()
        target_port_start = self.ui.scan_start_port_edit.text()
        target_port_stop = self.ui.scan_stop_port_edit.text()
        scan_timeout = self.ui.scan_timeout_edit.text()

        self._scan_tool = PortScanningTool(target_ip, target_port_start,
                                           target_port_stop, scan_timeout,
                                           self.statusBar())

        # 启动扫描线程
        self.statusBar().showMessage('正在扫描……')
        print('启动端口扫描:')

        # 设置表格模型
        self.ui.scan_result_table.setModel(self._scan_table_model)
        self._scan_table_model.clear()
        self._scan_table_model.setHorizontalHeaderLabels(['端口', '状态'])
        self._scan_table_model.setColumnCount(2)

        # 开始扫描
        self._scan_tool.start_scan_thread(self._scan_table_model,
                                          self.ui.scan_result_table,
                                          self.ui.scan_start_button,
                                          self.ui.scan_stop_button)

        # 修改控件状态
        self.ui.scan_stop_button.setEnabled(True)
        self.ui.scan_start_button.setEnabled(False)

    def stop_scan_thread(self):
        """
        停止端口扫描线程
        """
        if self._scan_tool is not None:
            self._scan_tool.stop_scan_thread()
            self._scan_tool = None

            self.statusBar().showMessage('正在停止……')

    def closeEvent(self, event: QCloseEvent):
        """
        窗口关闭回调,用于关闭工作线程。
        :param event: 关闭事件
        """
        # 关闭嗅探线程
        self.stop_sniffer_thread()
        # 关闭端口扫描线程
        self.stop_scan_thread()
예제 #13
0
class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        # Instantiate widgets and models
        self.toolBar = ToolBar()
        self.addToolBar(self.toolBar)

        self.dirModel = TableModel(dict())
        self.imageModel = QStandardItemModel()

        self.ui.dockWidget.setTitleBarWidget(QtWidgets.QWidget())

        self.statusBarLabel = QtWidgets.QLabel()
        self.statusBar().addWidget(self.statusBarLabel)

        self.cmapMenu = QtWidgets.QMenu()

        self.in_directory = None

        # Cmap menu
        Config.cmap = 'bone'
        self.create_cmap_menu()

        # Toolbar
        self.toolBar.refreshButton.triggered.connect(self.create_dir_model)
        self.toolBar.saveButton.triggered.connect(self.export_df)
        self.toolBar.loadButton.triggered.connect(self.load_df)

        # Directory list view
        self.ui.dirView.clicked.connect(self.change_image_grid)

        # Image view
        image_size = QtCore.QSize(400, 400)  # Set icon sizing here
        grid_size = QtCore.QSize(450, 450)

        self.ui.imageView.setModel(self.imageModel)
        self.ui.imageView.setViewMode(QtWidgets.QListView.IconMode)
        self.ui.imageView.setIconSize(image_size)
        self.ui.imageView.setGridSize(grid_size)
        self.ui.imageView.clicked.connect(self.image_selected)

        # Final touches
        self.update_statusbar()

    ################################################################

    def create_cmap_menu(self):
        self.cmapMenu.clear()
        for cmap_type, cmaps in Config.cmaps.items():
            thisMenu = QtWidgets.QMenu(cmap_type)
            self.cmapMenu.addMenu(thisMenu)

            for cmap in cmaps:
                thisAction = QtWidgets.QAction(cmap, self.cmapMenu)
                thisAction.triggered.connect(self.update_cmap)
                thisMenu.addAction(thisAction)

        self.toolBar.cmapButton.setMenu(self.cmapMenu)

    def update_cmap(self, args):
        Config.cmap = self.sender().text()
        self.change_image_grid(self.ui.dirView.currentIndex())
        self.update_statusbar()

    ################################################################

    def update_statusbar(self):
        self.statusBarLabel.setText(
            f'Colormap: {Config.cmap} | Directories loaded: {self.dirModel.rowCount()}'
        )

    ################################################################

    def create_dir_model(self):
        self.in_directory = QtWidgets.QFileDialog.getExistingDirectory(
            self, 'Study Directory', os.path.expanduser('~'))

        dict_table = {}
        for root, _, files in os.walk(self.in_directory):
            if not files:
                continue

            dir_name = root.split(os.sep)[-1]
            total_files = len(files)
            selected = pd.NA

            dict_table[dir_name] = {
                'TOTAL FILES': total_files,
                'SELECTED': selected
            }

        self.dirModel = TableModel(dict_table)
        self.ui.dirView.setModel(self.dirModel)
        self.ui.dirView.horizontalHeader().setSectionResizeMode(
            QtWidgets.QHeaderView.Stretch)
        self.update_statusbar()

        self.toolBar.cmapButton.setEnabled(True)
        self.toolBar.saveButton.setEnabled(True)
        self.toolBar.loadButton.setEnabled(True)

    ################################################################

    def change_image_grid(self, index):
        self.imageModel.clear()

        # Get dir path from the index data itself
        directory = self.dirModel.list_keys[index.row()]
        dir_path = os.path.join(self.in_directory, directory)

        previously_checked = self.dirModel.dict_studies[directory]['SELECTED']

        # Parse arrays within the directory
        image_list = [
            os.path.join(dir_path, i) for i in os.listdir(dir_path)
            if i.endswith('.bmeii')
        ]
        loaded_images = loader(image_list)

        for img_path in loaded_images:
            img_pixmap = QPixmap.fromImage(QImage(img_path))
            filename = os.path.basename(img_path).replace('.png', '')

            imageItem = QStandardItem()
            imageItem.setIcon(img_pixmap)
            imageItem.setText(filename)
            imageItem.setEditable(False)

            imageItem.setCheckable(True)
            if not pd.isnull(previously_checked):
                if filename in previously_checked:
                    imageItem.setCheckState(QtCore.Qt.CheckState.Checked)

            self.imageModel.appendRow(imageItem)

    def image_selected(self, index):
        current_row = self.ui.dirView.currentIndex().row()
        current_dir = self.dirModel.list_keys[current_row]

        images_selected = []

        for row in range(self.imageModel.rowCount()):
            item = self.imageModel.item(row)
            if item.checkState() == QtCore.Qt.CheckState.Checked:
                item_text = item.text()
                images_selected.append(item_text)

        if images_selected == []:
            self.dirModel.dict_studies[current_dir]['SELECTED'] = pd.NA
        else:
            self.dirModel.dict_studies[current_dir][
                'SELECTED'] = images_selected

        # TODO
        # self.dirModel.dataChanged.emit()

    ################################################################

    def export_df(self):
        rows = []
        for directory, values in self.dirModel.dict_studies.items():
            files = values['SELECTED']
            rows.append((directory, files))

        df_studies = pd.DataFrame(rows, columns=['DIRECTORY', 'FILES'])
        df_studies.to_csv(
            os.path.join(os.path.expanduser('~'), 'SelectedFiles.csv'))

    def load_df(self):
        in_csv = QtWidgets.QFileDialog.getOpenFileName(
            self, 'Load exported file list', os.path.expanduser('~'))

        try:
            df = pd.read_csv(in_csv[0])
            self.dirModel.update_from_loaded(df)
        except KeyboardInterrupt:
            self.statusBarLabel.setText(
                'No file selected / Error loading saved list')
            return
예제 #14
0
class CtrlrModel(QObject):
    """
    Controller model.
    """
    def __init__(self, appLogger: object) -> None:
        """
        Constructor.

        Params:
            appLogger:      The application logger.
        """
        QObject.__init__(self)
        self._appLogger = appLogger
        self._logger = appLogger.getLogger('CTRL_MODEL')
        self._logger.info('initializing...')
        self._controllers = {'active': None, 'list': []}
        self.model = QStandardItemModel(0, 1)
        Controller.initFramework()
        self.updateCtrlrList()
        self._logger.info('initialized')

    def _listCurrentCtrlrs(self) -> tuple:
        """
        Get the list of current controller names.

        Return:
            The list of names of current controllers.
        """
        currentNames = []
        for ctrlr in self._controllers['list']:
            currentNames.append(ctrlr.getName())
        return tuple(currentNames)

    def _filterAddedCtrlrs(self, newList: tuple) -> tuple:
        """
        Filter the added controllers.

        Params:
            newList:        The new list of controllers.

        Return:
            The list of controllers to add.
        """
        currentList = map(lambda ctrlr: ctrlr.getName(),
                          self._controllers['list'])
        addedCtrlrs = filter(lambda newCtrlr: newCtrlr not in currentList,
                             newList)
        return tuple(addedCtrlrs)

    def _filterRemovedCtrlrs(self, newList: tuple) -> tuple:
        """
        Filter the controllers to be removed.

        Params:
            newList:        The new list of controllers.

        Return:
            The list of controllers to remove.
        """
        currentList = map(lambda ctrlr: ctrlr.getName(),
                          self._controllers['list'])
        removedCtrls = filter(lambda oldCtrlr: oldCtrlr not in newList,
                              currentList)
        return tuple(removedCtrls)

    def _addControllers(self, availableCtrlrs: dict, addList: tuple) -> None:
        """
        Add the new controllers.

        Params:
            availableCtrlrs:    The available controllers.
            addList:            The list of controllers to add.
        """
        for ctrlr in addList:
            self._controllers['list'].append(
                Controller(self._appLogger, availableCtrlrs[ctrlr], ctrlr))

    def _removeControllers(self, removeList: tuple) -> None:
        """
        Removed the old controllers.

        Params:
            removeList:         The list of controllers to remove.
        """
        if self._controllers['active'] and \
                self._controllers['active'].getName() in removeList:
            self._controllers['active'] = None
        for ctrlr in self._controllers['list']:
            if ctrlr.getName() in removeList:
                self._controllers['list'].remove(ctrlr)

    def _updateModel(self) -> None:
        """
        Update the controller combobox model.
        """
        self.model.clear()
        for ctrlr in self._controllers['list']:
            item = QStandardItem(ctrlr.getName())
            self.model.appendRow(item)

    def updateCtrlrList(self) -> None:
        """
        Update the controller list.
        """
        self._logger.info('updating controller list...')
        connectedCtrlrs = Controller.listControllers()
        addedCtrls = self._filterAddedCtrlrs(tuple(connectedCtrlrs))
        self._addControllers(connectedCtrlrs, addedCtrls)
        removedCtrlrs = self._filterRemovedCtrlrs(tuple(connectedCtrlrs))
        self._removeControllers(removedCtrlrs)
        self._updateModel()
        self._logger.info('controller list updated')
예제 #15
0
class QFlightWaypointList(QTableView):
    def __init__(self, package: Package, flight: Flight):
        super().__init__()
        self.package = package
        self.flight = flight

        self.model = QStandardItemModel(self)
        self.setModel(self.model)
        self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.model.setHorizontalHeaderLabels(["Name", "Alt", "TOT/DEPART"])

        header = self.horizontalHeader()
        header.setSectionResizeMode(0, QHeaderView.ResizeToContents)

        if len(self.flight.points) > 0:
            self.selectedPoint = self.flight.points[0]
        self.update_list()

        self.selectionModel().setCurrentIndex(self.indexAt(QPoint(1, 1)),
                                              QItemSelectionModel.Select)

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

        self.model.setHorizontalHeaderLabels(["Name", "Alt", "TOT/DEPART"])

        waypoints = self.flight.flight_plan.waypoints
        for row, waypoint in enumerate(waypoints):
            self.add_waypoint_row(row, self.flight, waypoint)
        self.selectionModel().setCurrentIndex(self.indexAt(QPoint(1, 1)),
                                              QItemSelectionModel.Select)

    def add_waypoint_row(self, row: int, flight: Flight,
                         waypoint: FlightWaypoint) -> None:
        self.model.insertRow(self.model.rowCount())

        self.model.setItem(row, 0, QWaypointItem(waypoint, row))

        altitude = meter_to_feet(waypoint.alt)
        altitude_type = "AGL" if waypoint.alt_type == "RADIO" else "MSL"
        altitude_item = QStandardItem(f"{altitude} ft {altitude_type}")
        altitude_item.setEditable(False)
        self.model.setItem(row, 1, altitude_item)

        tot = self.tot_text(flight, waypoint)
        tot_item = QStandardItem(tot)
        tot_item.setEditable(False)
        self.model.setItem(row, 2, tot_item)

    def tot_text(self, flight: Flight, waypoint: FlightWaypoint) -> str:
        if waypoint.waypoint_type == FlightWaypointType.TAKEOFF:
            return self.takeoff_text(flight)
        prefix = ""
        time = flight.flight_plan.tot_for_waypoint(waypoint)
        if time is None:
            prefix = "Depart "
            time = flight.flight_plan.depart_time_for_waypoint(waypoint)
        if time is None:
            return ""
        time = timedelta(seconds=int(time.total_seconds()))
        return f"{prefix}T+{time}"

    @staticmethod
    def takeoff_text(flight: Flight) -> str:
        takeoff_time = flight.flight_plan.takeoff_time()
        # Handle custom flight plans where we can't estimate the takeoff time.
        if takeoff_time is None:
            takeoff_time = timedelta()
        start_time = timedelta(seconds=int(takeoff_time.total_seconds()))
        return f"T+{start_time}"
예제 #16
0
class DesktopTimelogDialog(
        QtWidgets.QWidget,
        ui_sg_desktop_timelog_dialog.Ui_ShotgunDesktopTimelogDialog):
    def __init__(self, project_name, user_name, parent=None):
        super(DesktopTimelogDialog, self).__init__(parent)
        self.setupUi(self)
        self.timer = QtCore.QTimer()
        self.treeViewAsset.expanded.connect(self.asset_expanded)
        self.treeViewShot.expanded.connect(self.shot_expanded)
        self.treeViewSequence.expanded.connect(self.sequence_expanded)
        self.treeViewAsset.clicked.connect(self.treeview_single_clicked)
        self.treeViewShot.clicked.connect(self.treeview_single_clicked)
        self.treeViewSequence.clicked.connect(self.treeview_single_clicked)
        self.treeViewMyTask.clicked.connect(self.treeview_single_clicked)
        self.timer.timeout.connect(self.disable_double_clicked)
        self.tabWidgetList.currentChanged.connect(self.tab_widget_list_index)
        self.tabWidgetList.setCurrentIndex(0)

        self.tableWidgetTimelog.setColumnWidth(0, 90)
        self.tableWidgetTimelog.setColumnWidth(1, 100)
        self.tableWidgetTimelog.setColumnWidth(2, 70)
        self.tableWidgetTimelog.setColumnWidth(3, 300)
        self.tableWidgetTimelog.setColumnWidth(4, 30)
        self.tableWidgetTimelog.setSelectionBehavior(
            QtWidgets.QAbstractItemView.SelectRows)
        self.tableWidgetTimelog.setSelectionMode(
            QtWidgets.QAbstractItemView.SingleSelection)
        self.tableWidgetTimelog.setCornerButtonEnabled(True)
        self.tableWidgetTimelog.horizontalHeader().setSectionsClickable(False)
        self.tableWidgetTimelog.verticalHeader().setVisible(False)
        self.tableWidgetTimelog.horizontalHeader().setVisible(True)
        self.tableWidgetTimelog.setFrameShape(QtWidgets.QFrame.StyledPanel)

        self.tableWidgetTimelog.setContextMenuPolicy(
            QtCore.Qt.CustomContextMenu)
        self.tableWidgetTimelog.customContextMenuRequested[
            QtCore.QPoint].connect(self.right_context_menu)

        self.pushButtonSubmit.setEnabled(False)
        self.dateEditDate.setDate(QtCore.QDate.currentDate())

        validator = QDoubleValidator(0, 999, 1, self)
        validator.setNotation(QDoubleValidator.StandardNotation)
        self.lineEditDuration.setValidator(validator)

        self.project_name = project_name
        self.user_name = user_name
        self.sg = sg_publish.ShotgunPublish()
        self.project_entity = self.sg.get_project(self.project_name)

        # init source model
        self.source_asset_model = QStandardItemModel(self.treeViewAsset)
        self.source_shot_model = QStandardItemModel(self.treeViewShot)
        self.source_sequence_model = QStandardItemModel(self.treeViewSequence)
        self.source_mytask_model = QStandardItemModel(self.treeViewMyTask)

        # init treeview ui data
        self.tab_asset_ui()
        self.tab_shot_ui()
        self.tab_sequence_ui()
        self.tab_mytask_ui()

        self._project_level_info = []
        self._tab_index = self.tabWidgetList.currentIndex()

    @QtCore.Slot(int)
    def tab_widget_list_index(self, index):
        '''
        tabWidget changed slot
        :param index: tabWidget index
        :return:
        '''
        self._tab_index = index

    @QtCore.Slot()
    def search_asset_slot(self):
        '''
        search asset slot function
        :return:
        '''
        search_text = self.lineEditAssetSearch.text()
        if not search_text:
            self.tab_asset_init()
        else:
            self.tab_asset_search_init()
            self.search_asset(self.source_asset_model, search_text)

    @QtCore.Slot()
    def search_shot_slot(self):
        '''
        search shot slot function
        :return:
        '''
        search_text = self.lineEditShotSearch.text()
        if not search_text:
            self.tab_shot_init()
        else:
            self.tab_shot_search_init()
            self.search_shot(self.source_shot_model, search_text)

    @QtCore.Slot()
    def search_sequence_slot(self):
        '''
        search sequence slot function
        :return:
        '''
        search_text = self.lineEditSequenceSearch.text()
        if not search_text:
            self.tab_sequence_init()
        else:
            self.tab_sequence_search_init()
            self.search_sequence(self.source_sequence_model, search_text)

    @QtCore.Slot()
    def search_mytask_slot(self):
        '''
        search mytask slot function
        :return:
        '''
        search_text = self.lineEditMyTaskSearch.text()
        if not search_text:
            self.tab_mytask_init()
        else:
            self.tab_mytask_init()
            self.search_mytask(self.source_mytask_model, search_text)

    def search_mytask(self, source_model, search_text):
        '''
        search mytask
        :param source_model: source model
        :param search_text: text
        :return:
        '''
        mytask_type_item_temp_list = []
        for mytask_type_index in range(source_model.rowCount()):
            mytask_type_item = source_model.item(mytask_type_index, 0)
            mytask_type_item_temp = QStandardItem(mytask_type_item.text())
            mytask_type_item_temp.setEditable(False)
            for mytask_name_index in range(mytask_type_item.rowCount()):
                mytask_name_item = mytask_type_item.child(mytask_name_index, 0)
                mytask_name = mytask_name_item.text()
                if search_text in mytask_name:
                    mytask_name_item_temp = mytask_name_item.clone()
                    if mytask_name_item.hasChildren():
                        for mytask_name_index in range(
                                mytask_name_item.rowCount()):
                            mytask_step_item = mytask_name_item.child(
                                mytask_name_index, 0)
                            mytask_step_item_temp = mytask_step_item.clone()
                            if mytask_step_item.hasChildren():
                                for mytask_step_index in range(
                                        mytask_step_item.rowCount()):
                                    mytask_step_task_item = mytask_step_item.child(
                                        mytask_step_index, 0)
                                    mytask_step_task_item_temp = mytask_step_task_item.clone(
                                    )
                                    mytask_step_item_temp.appendRow(
                                        mytask_step_task_item_temp)
                            mytask_name_item_temp.appendRow(
                                mytask_step_item_temp)
                    mytask_name_item_temp.setEditable(False)
                    mytask_type_item_temp.appendRow(mytask_name_item_temp)
            if mytask_type_item_temp.hasChildren():
                mytask_type_item_temp_list.append(mytask_type_item_temp)
        source_model.clear()
        for item in mytask_type_item_temp_list:
            source_model.appendRow(item)

    def search_sequence(self, source_model, search_text):
        '''
        search sequence
        :param source_model: source model
        :param search_text: text
        :return:
        '''
        sequence_item_temp_list = []
        for sequence_index in range(source_model.rowCount()):
            sequence_item = source_model.item(sequence_index, 0)
            sequence_name = sequence_item.text()
            if search_text in sequence_name:
                sequence_name_item_temp = QStandardItem(sequence_name)
                retrieving = QStandardItem("Retrieving Tasks...")
                retrieving.setEditable(False)
                sequence_name_item_temp.appendRow(retrieving)
                sequence_name_item_temp.setEditable(False)
                sequence_item_temp_list.append(sequence_name_item_temp)
        source_model.clear()
        for item in sequence_item_temp_list:
            source_model.appendRow(item)

    def search_shot(self, source_model, search_text):
        '''
        search shot
        :param source_model: source model
        :param search_text: text
        :return:
        '''
        sequence_item_temp_list = []
        for sequence_index in range(source_model.rowCount()):
            sequence_item = source_model.item(sequence_index, 0)
            sequence_item_temp = QStandardItem(sequence_item.text())
            sequence_item_temp.setEditable(False)
            for shot_name_index in range(sequence_item.rowCount()):
                shot_name_item = sequence_item.child(shot_name_index, 0)
                shot_name = shot_name_item.text()
                if search_text in shot_name:
                    shot_name_item_temp = shot_name_item.clone()
                    retrieving = QStandardItem("Retrieving Tasks...")
                    retrieving.setEditable(False)
                    shot_name_item_temp.appendRow(retrieving)
                    shot_name_item_temp.setEditable(False)
                    sequence_item_temp.appendRow(shot_name_item_temp)
            if sequence_item_temp.hasChildren():
                sequence_item_temp_list.append(sequence_item_temp)
        source_model.clear()
        for item in sequence_item_temp_list:
            source_model.appendRow(item)

    def search_asset(self, source_model, search_text):
        '''
        search asset
        :param source_model: source model
        :param search_text: text
        :return:
        '''
        asset_type_item_temp_list = []
        for asset_type_index in range(source_model.rowCount()):
            asset_type_item = source_model.item(asset_type_index, 0)
            asset_type_item_temp = QStandardItem(asset_type_item.text())
            asset_type_item_temp.setEditable(False)
            for asset_name_index in range(asset_type_item.rowCount()):
                asset_name_item = asset_type_item.child(asset_name_index, 0)
                asset_name = asset_name_item.text()
                if search_text in asset_name:
                    asset_name_item_temp = asset_name_item.clone()
                    retrieving = QStandardItem("Retrieving Tasks...")
                    retrieving.setEditable(False)
                    asset_name_item_temp.appendRow(retrieving)
                    asset_name_item_temp.setEditable(False)
                    asset_type_item_temp.appendRow(asset_name_item_temp)
            if asset_type_item_temp.hasChildren():
                asset_type_item_temp_list.append(asset_type_item_temp)
        source_model.clear()
        for item in asset_type_item_temp_list:
            source_model.appendRow(item)

    def tab_asset_ui(self):
        '''
        tab asset ui
        :return:
        '''
        self.tab_asset_init()
        self.treeViewAsset.setModel(self.source_asset_model)
        self.treeViewAsset.setHeaderHidden(True)

    def tab_shot_ui(self):
        '''
        tab shot ui
        :return:
        '''
        self.tab_shot_init()
        self.treeViewShot.setModel(self.source_shot_model)
        self.treeViewShot.setHeaderHidden(True)

    def tab_sequence_ui(self):
        '''
        tab sequence ui
        :return:
        '''
        self.tab_sequence_init()
        self.treeViewSequence.setModel(self.source_sequence_model)
        self.treeViewSequence.setHeaderHidden(True)

    def tab_mytask_ui(self):
        '''
        tab mytask ui
        :return:
        '''
        self.tab_mytask_init()
        self.treeViewMyTask.setModel(self.source_mytask_model)
        self.treeViewMyTask.setHeaderHidden(True)

    def tab_asset_init(self):
        '''
        init asset list in treeView
        :return:
        '''
        self.source_asset_model.clear()

        asset_type_list = [
            'Chr', 'Env', 'Prp', 'Flg', 'Crd', 'Scn', 'Uif', 'Asb'
        ]

        for asset_type in asset_type_list:
            asset_type_item = QStandardItem(asset_type)
            asset_type_item.setEditable(False)
            # append the next level item
            retrieving = QStandardItem("Retrieving Tasks...")
            retrieving.setEditable(False)
            asset_type_item.appendRow(retrieving)

            self.source_asset_model.appendRow(asset_type_item)

    def tab_shot_init(self):
        '''
        init shot list in treeView
        :return:
        '''
        self.source_shot_model.clear()
        sequence_entity_list = self.sg.get_sequence_list(self.project_name)
        if sequence_entity_list:
            for sequence_entity in sequence_entity_list:
                sequence_name = sequence_entity['code']
                sequence_name_item = QStandardItem(sequence_name)
                sequence_name_item.setEditable(False)

                retrieving = QStandardItem("Retrieving Tasks...")
                retrieving.setEditable(False)
                sequence_name_item.appendRow(retrieving)

                self.source_shot_model.appendRow(sequence_name_item)

    def tab_sequence_init(self):
        '''
        init sequence list in treeView
        :return:
        '''
        self.source_sequence_model.clear()
        sequence_entity_list = self.sg.get_sequence_list(self.project_name)
        if sequence_entity_list:
            for sequence_entity in sequence_entity_list:
                sequence_name = sequence_entity['code']
                sequence_name_item = QStandardItem(sequence_name)
                sequence_name_item.setEditable(False)
                # append the next level item
                retrieving = QStandardItem("Retrieving Tasks...")
                retrieving.setEditable(False)
                sequence_name_item.appendRow(retrieving)
                self.source_sequence_model.appendRow(sequence_name_item)

    def tab_mytask_init(self):
        '''
        init mytask list in treeView
        :return:
        '''
        self.source_mytask_model.clear()
        mytask_entity_list = self.sg.get_mytask_list(self.project_name,
                                                     self.user_name)
        mytask_type_sequence_item = QStandardItem("Sequence")
        mytask_type_asset_item = QStandardItem("Asset")
        mytask_type_shot_item = QStandardItem("Shot")
        mytask_type_sequence_item.setEditable(False)
        mytask_type_asset_item.setEditable(False)
        mytask_type_shot_item.setEditable(False)

        mytask_link_type_list_item = [
            mytask_type_sequence_item, mytask_type_asset_item,
            mytask_type_shot_item
        ]

        mytask_link_name_set = set()
        if mytask_entity_list:
            # My God! It's really too complicated!!!!!!!!
            self.source_mytask_model.appendColumn(mytask_link_type_list_item)
            for mytask_entity in mytask_entity_list:
                mytask_name = mytask_entity['content']
                mytask_link_name = mytask_entity['entity']['name']
                mytask_link_type = mytask_entity['entity']['type']
                mytask_step_name = mytask_entity['step']['name']

                if mytask_link_name in mytask_link_name_set:
                    for mytask_index in range(
                            self.source_mytask_model.rowCount()):
                        mytask_link_type_item = self.source_mytask_model.item(
                            mytask_index, 0)
                        if mytask_link_type == mytask_link_type_item.text():
                            for mytask_link_type_index in range(
                                    mytask_link_type_item.rowCount()):
                                mytask_link_name_item = mytask_link_type_item.child(
                                    mytask_link_type_index, 0)
                                if mytask_link_name == mytask_link_name_item.text(
                                ):
                                    found = False
                                    for mytask_link_name_index in range(
                                            mytask_link_name_item.rowCount()):
                                        mytask_step_name_item = mytask_link_name_item.child(
                                            mytask_link_name_index, 0)
                                        if mytask_step_name.decode(
                                                'utf-8'
                                        ) == mytask_step_name_item.text():
                                            found = True
                                            mytask_name_item = QStandardItem(
                                                mytask_name)
                                            mytask_name_item.setEditable(False)
                                            mytask_step_name_item.appendRow(
                                                mytask_name_item)
                                    if not found:
                                        mytask_name_item = QStandardItem(
                                            mytask_name)
                                        mytask_name_item.setEditable(False)
                                        mytask_step_name_item = QStandardItem(
                                            mytask_step_name)
                                        mytask_step_name_item.setEditable(
                                            False)
                                        mytask_step_name_item.appendRow(
                                            mytask_name_item)
                                        mytask_link_name_item.appendRow(
                                            mytask_step_name_item)
                else:
                    mytask_name_item = QStandardItem(mytask_name)
                    mytask_name_item.setEditable(False)
                    mytask_step_name_item = QStandardItem(mytask_step_name)
                    mytask_step_name_item.setEditable(False)
                    mytask_step_name_item.appendRow(mytask_name_item)
                    mytask_link_name_item = QStandardItem(mytask_link_name)
                    mytask_link_name_item.setEditable(False)
                    mytask_link_name_item.appendRow(mytask_step_name_item)
                    for mytask_link_type_item in mytask_link_type_list_item:
                        if mytask_link_type_item.text() == mytask_link_type:
                            mytask_link_type_item.appendRow(
                                mytask_link_name_item)

                mytask_link_name_set.add(mytask_link_name)

    def tab_asset_search_init(self):
        '''
        init asset list in treeView(Only for search!)
        :return:
        '''
        self.source_asset_model.clear()

        asset_type_list = [
            'Chr', 'Env', 'Prp', 'Flg', 'Crd', 'Scn', 'Uif', 'Asb'
        ]

        for asset_type in asset_type_list:
            asset_type_item = QStandardItem(asset_type)
            asset_type_item.setEditable(False)

            asset_entity_list = self.sg.get_asset_list(self.project_name,
                                                       asset_type)
            if asset_entity_list:
                for asset_entity in asset_entity_list:
                    asset_name = asset_entity['code']
                    asset_name_chinesename = asset_entity['sg_chinesename']
                    if asset_name_chinesename:
                        asset_name = asset_name + "|" + asset_name_chinesename
                    asset_name_item = QStandardItem(asset_name)
                    asset_name_item.setEditable(False)

                    # append the next level item
                    retrieving = QStandardItem("Retrieving Tasks...")
                    retrieving.setEditable(False)
                    asset_name_item.appendRow(retrieving)

                    asset_type_item.appendRow(asset_name_item)

            self.source_asset_model.appendRow(asset_type_item)

    def tab_shot_search_init(self):
        '''
        init shot list in treeView(Only for search!)
        :return:
        '''
        self.source_shot_model.clear()
        sequence_entity_list = self.sg.get_sequence_list(self.project_name)
        if sequence_entity_list:
            for sequence_entity in sequence_entity_list:
                sequence_name = sequence_entity['code']
                sequence_name_item = QStandardItem(sequence_name)
                sequence_name_item.setEditable(False)

                # find the shot below sequence
                shot_entity_list = self.sg.get_sequence_shot_list(
                    self.project_name, sequence_name)
                if shot_entity_list:
                    for shot_entity in shot_entity_list:
                        shot_name = shot_entity['code']
                        shot_name_item = QStandardItem(shot_name)
                        shot_name_item.setEditable(False)

                        # append the next level item
                        retrieving = QStandardItem("Retrieving Tasks...")
                        retrieving.setEditable(False)
                        shot_name_item.appendRow(retrieving)

                        sequence_name_item.appendRow(shot_name_item)

                self.source_shot_model.appendRow(sequence_name_item)

    def tab_sequence_search_init(self):
        '''
        init sequence list in treeView(Only for search!)
        :return:
        '''
        self.source_sequence_model.clear()
        sequence_entity_list = self.sg.get_sequence_list(self.project_name)
        if sequence_entity_list:
            for sequence_entity in sequence_entity_list:
                sequence_name = sequence_entity['code']
                sequence_name_item = QStandardItem(sequence_name)
                sequence_name_item.setEditable(False)
                self.source_sequence_model.appendRow(sequence_name_item)

    @QtCore.Slot(int)
    def asset_expanded(self, index):
        '''
        asset treeView expanded
        :param index: index of the model
        :return:
        '''
        item = self.source_asset_model.itemFromIndex(index)
        asset_level_struct_item = []
        self.get_upper_level_item(self.source_asset_model,
                                  asset_level_struct_item, index)

        item_level = len(asset_level_struct_item)

        if item_level == 1:
            search_text = self.lineEditAssetSearch.text()
            if search_text:
                return
            item.removeRows(0, item.rowCount())
            # this is the top level
            _asset_type = asset_level_struct_item[0]
            asset_entity_list = self.sg.get_asset_list(self.project_name,
                                                       _asset_type)
            if asset_entity_list:
                for asset_entity in asset_entity_list:
                    asset_name = asset_entity['code']
                    asset_name_chinesename = asset_entity['sg_chinesename']
                    if asset_name_chinesename:
                        asset_name = asset_name + "|" + asset_name_chinesename
                    asset_name_item = QStandardItem(asset_name)
                    asset_name_item.setEditable(False)

                    # append the next level item
                    retrieving = QStandardItem("Retrieving Tasks...")
                    retrieving.setEditable(False)
                    asset_name_item.appendRow(retrieving)

                    item.appendRow(asset_name_item)
            else:
                # if this level not item then show Retrieving Task...
                retrieving = QStandardItem("Retrieving Tasks...")
                retrieving.setEditable(False)
                item.appendRow(retrieving)

        elif item_level == 2:
            item.removeRows(0, item.rowCount())
            asset_name = asset_level_struct_item[1]
            asset_task_list = self.sg.get_asset_task_list(
                self.project_name, asset_name)
            asset_step_name_set = set()
            if asset_task_list:
                for asset_task in asset_task_list:
                    asset_task_step = asset_task['step']
                    if asset_task_step:
                        asset_task_step_name = asset_task_step['name']
                        # remove all duplicate name in step by using set()
                        asset_step_name_set.add(asset_task_step_name)
                for step_name in asset_step_name_set:
                    asset_task_step_name_item = QStandardItem(step_name)
                    asset_task_step_name_item.setEditable(False)

                    # append the next level item
                    retrieving = QStandardItem("Retrieving Tasks...")
                    retrieving.setEditable(False)
                    asset_task_step_name_item.appendRow(retrieving)

                    item.appendRow(asset_task_step_name_item)
                    asset_level_struct_item.append(step_name)

        elif item_level == 3:
            item.removeRows(0, item.rowCount())
            asset_name = asset_level_struct_item[1]
            asset_step_name = asset_level_struct_item[2]
            asset_task_list = self.sg.get_asset_task_list(
                self.project_name, asset_name, asset_step_name)
            if asset_task_list:
                for asset_task in asset_task_list:
                    asset_task_name = asset_task['content']
                    asset_task_name_item = QStandardItem(asset_task_name)
                    asset_task_name_item.setEditable(False)

                    item.appendRow(asset_task_name_item)

    @QtCore.Slot(int)
    def shot_expanded(self, index):
        '''
        shot treeView expanded
        :param index: index of the model
        :return:
        '''
        # dynamic load data from shotgun server database
        item = self.source_shot_model.itemFromIndex(index)
        shot_level_struct_item = []
        self.get_upper_level_item(self.source_shot_model,
                                  shot_level_struct_item, index)
        item_level = len(shot_level_struct_item)
        # search item from shotgun server
        if item_level == 1:
            search_text = self.lineEditShotSearch.text()
            if search_text:
                return
            item.removeRows(0, item.rowCount())
            # this is the top level
            sequence_name = shot_level_struct_item[0]
            # find the shot below sequence
            shot_entity_list = self.sg.get_sequence_shot_list(
                self.project_name, sequence_name)
            if shot_entity_list:
                for shot_entity in shot_entity_list:
                    shot_name = shot_entity['code']
                    shot_name_item = QStandardItem(shot_name)
                    shot_name_item.setEditable(False)

                    # append the next level item
                    retrieving = QStandardItem("Retrieving Tasks...")
                    retrieving.setEditable(False)
                    shot_name_item.appendRow(retrieving)

                    item.appendRow(shot_name_item)
            else:
                # if this level not item then show Retrieving Task...
                retrieving = QStandardItem("Retrieving Tasks...")
                retrieving.setEditable(False)
                item.appendRow(retrieving)

        elif item_level == 2:
            item.removeRows(0, item.rowCount())
            sequence_name = shot_level_struct_item[0]
            shot_name = shot_level_struct_item[1]
            shot_task_list = self.sg.get_sequence_shot_task_list(
                self.project_name, sequence_name, shot_name)
            shot_step_name_set = set()
            if shot_task_list:
                for shot_task in shot_task_list:
                    shot_task_step = shot_task['step']
                    if shot_task_step:
                        shot_task_step_name = shot_task_step['name']
                        # remove all duplicate name in step by using set()
                        shot_step_name_set.add(shot_task_step_name)
                for step_name in shot_step_name_set:
                    shot_task_step_name_item = QStandardItem(step_name)
                    shot_task_step_name_item.setEditable(False)

                    # append the next level item
                    retrieving = QStandardItem("Retrieving Tasks...")
                    retrieving.setEditable(False)
                    shot_task_step_name_item.appendRow(retrieving)

                    item.appendRow(shot_task_step_name_item)
                    shot_level_struct_item.append(step_name)

        elif item_level == 3:
            item.removeRows(0, item.rowCount())
            sequence_name = shot_level_struct_item[0]
            shot_name = shot_level_struct_item[1]
            shot_step_name = shot_level_struct_item[2]
            shot_task_list = self.sg.get_sequence_shot_task_list(
                self.project_name, sequence_name, shot_name, shot_step_name)
            if shot_task_list:
                for shot_task in shot_task_list:
                    shot_task_name = shot_task['content']
                    shot_task_name_item = QStandardItem(shot_task_name)
                    shot_task_name_item.setEditable(False)

                    item.appendRow(shot_task_name_item)

    @QtCore.Slot(int)
    def sequence_expanded(self, index):
        '''
        sequence treeView expanded
        :param index: index of the model
        :return:
        '''
        # dynamic load data from shotgun server database
        item = self.source_sequence_model.itemFromIndex(index)
        item.removeRows(0, item.rowCount())
        sequence_level_struct_item = []
        self.get_upper_level_item(self.source_sequence_model,
                                  sequence_level_struct_item, index)
        item_level = len(sequence_level_struct_item)
        # search item from shotgun server
        if item_level == 1:
            # this is the top level
            sequence_name = sequence_level_struct_item[0]
            # find the step below sequence
            sequence_task_list = self.sg.get_sequence_task_list(
                self.project_name, sequence_name)
            sequence_step_name_set = set()
            if sequence_task_list:
                for sequence_task in sequence_task_list:
                    sequence_task_step = sequence_task['step']
                    if sequence_task_step:
                        sequence_task_step_name = sequence_task_step['name']
                        # remove all duplicate name in step by using set()
                        sequence_step_name_set.add(sequence_task_step_name)
                for step_name in sequence_step_name_set:
                    sequence_task_step_name_item = QStandardItem(step_name)
                    sequence_task_step_name_item.setEditable(False)

                    # append the next level item
                    retrieving = QStandardItem("Retrieving Tasks...")
                    retrieving.setEditable(False)
                    sequence_task_step_name_item.appendRow(retrieving)

                    item.appendRow(sequence_task_step_name_item)
                    sequence_level_struct_item.append(step_name)

        elif item_level == 2:
            sequence_name = sequence_level_struct_item[0]
            sequence_step_name = sequence_level_struct_item[1]
            sequence_task_list = self.sg.get_sequence_task_list(
                self.project_name, sequence_name, sequence_step_name)
            if sequence_task_list:
                for sequence_task in sequence_task_list:
                    sequence_task_name = sequence_task['content']
                    sequence_task_name_item = QStandardItem(sequence_task_name)
                    sequence_task_name_item.setEditable(False)

                    item.appendRow(sequence_task_name_item)

    def treeview_single_clicked(self, index):
        '''
        treeView single clicked
        :param index: index of model
        :return:
        '''
        if not self.timer.isActive():
            self.timer.start(500)
            self.listwidget_show_item_info(index)

    @QtCore.Slot()
    def disable_double_clicked(self):
        '''
        disable double clicked
        :return:
        '''
        self.timer.stop()

    @QtCore.Slot()
    def listwidget_show_item_info(self, index):
        '''
        this function process the published file to show in listWidget
        :param index: index of model
        :return:
        '''
        tab_index = self._tab_index
        project_level_info = []
        timelog_list = []
        self._project_level_info = []
        if tab_index == 0:
            # mytask table
            project_level_info, timelog_list = self.get_mytask_timelog_list(
                self.source_mytask_model, index)

        elif tab_index == 1:
            # asset table
            project_level_info, timelog_list = self.get_asset_timelog_list(
                self.source_asset_model, index)

        elif tab_index == 2:
            # shot table
            project_level_info, timelog_list = self.get_shot_timelog_list(
                self.source_shot_model, index)

        elif tab_index == 3:
            # sequence table
            project_level_info, timelog_list = self.get_sequence_timelog_list(
                self.source_sequence_model, index)

        if project_level_info:
            self.tableWidgetTimelog.setRowCount(0)
            self._project_level_info = project_level_info
            self.pushButtonSubmit.setEnabled(True)
        else:
            self.pushButtonSubmit.setEnabled(False)

        if timelog_list:
            self.show_timelog_info(timelog_list)

    def get_sequence_timelog_list(self, source_model, index):
        '''
        get the sequence published file from shotgun
        :param source_model: the source model
        :param index: the index of source model
        :return: file info list
        '''
        project_level_struct_item = []
        timelog_list = []
        project_level_info = []
        self.get_upper_level_item(source_model, project_level_struct_item,
                                  index)
        item_level = len(project_level_struct_item)
        if item_level >= 3:
            # it is 3 level in the treeview widget
            _sequence_name = project_level_struct_item[0]
            _sequence_step_name = project_level_struct_item[1]
            _sequence_step_task_name = project_level_struct_item[2]
            project_level_info = project_level_struct_item

            dict_data = sg_base_find.create_project_struct(
                self.project_name,
                self.user_name,
                "sequence",
                sequence_name=_sequence_name,
                sequence_step_name=_sequence_step_name,
                sequence_step_task_name=_sequence_step_task_name)
            timelog_list = self.sg.get_time_log(dict_data)
        return project_level_info, timelog_list

    def get_shot_timelog_list(self, source_model, index):
        '''
        get the shot published file from shotgun
        :param source_model: the source model
        :param index: the index of source model
        :return: file info list
        '''
        project_level_struct_item = []
        project_level_info = []
        timelog_list = []
        self.get_upper_level_item(source_model, project_level_struct_item,
                                  index)
        item_level = len(project_level_struct_item)
        if item_level >= 4:
            # it is 4 level in the treeview widget
            _sequence_name = project_level_struct_item[0]
            _sequence_shot_name = project_level_struct_item[1]
            _sequence_shot_step_name = project_level_struct_item[2]
            _sequence_shot_step_task_name = project_level_struct_item[3]
            project_level_info = project_level_struct_item

            dict_data = sg_base_find.create_project_struct(
                self.project_name,
                self.user_name,
                "shot",
                sequence_name=_sequence_name,
                sequence_shot_name=_sequence_shot_name,
                sequence_shot_step_name=_sequence_shot_step_name,
                sequence_shot_step_task_name=_sequence_shot_step_task_name)
            timelog_list = self.sg.get_time_log(dict_data)
        return project_level_info, timelog_list

    def get_asset_timelog_list(self, source_model, index):
        '''
        get the asset published file from shotgun
        :param source_model: the source model
        :param index: the index of source model
        :return: file info list
        '''
        project_level_struct_item = []
        project_level_info = []
        timelog_list = []
        self.get_upper_level_item(source_model, project_level_struct_item,
                                  index)
        item_level = len(project_level_struct_item)
        if item_level >= 4:
            # it is 4 level in the treeview widget
            _asset_name = project_level_struct_item[1]
            _asset_step_name = project_level_struct_item[2]
            _asset_step_task_name = project_level_struct_item[3]
            project_level_info = project_level_struct_item

            dict_data = sg_base_find.create_project_struct(
                self.project_name,
                self.user_name,
                "asset",
                asset_name=_asset_name,
                asset_step_name=_asset_step_name,
                asset_step_task_name=_asset_step_task_name)
            timelog_list = self.sg.get_time_log(dict_data)

        return project_level_info, timelog_list

    def get_mytask_timelog_list(self, source_model, index):
        '''
        get the mytask published file from shotgun
        :param source_model: the source model
        :param index: the index of source model
        :return: file info list
        '''
        project_level_struct_item = []
        project_level_info = []
        timelog_list = []
        self.get_upper_level_item(source_model, project_level_struct_item,
                                  index)
        item_level = len(project_level_struct_item)
        if item_level >= 4:
            # it is 4 level in the treeview widget
            mytask_type = project_level_struct_item[0]
            mytask_name = project_level_struct_item[1]
            mytask_step_name = project_level_struct_item[2]
            mytask_step_task_name = project_level_struct_item[3]
            project_level_info = project_level_struct_item

            if mytask_type == 'Asset':
                dict_data = sg_base_find.create_project_struct(
                    self.project_name,
                    self.user_name,
                    "asset",
                    asset_name=mytask_name,
                    asset_step_name=mytask_step_name,
                    asset_step_task_name=mytask_step_task_name)
                timelog_list = self.sg.get_time_log(dict_data)
            elif mytask_type == 'Shot':
                sequence_name = mytask_name.split("_")[0]
                dict_data = sg_base_find.create_project_struct(
                    self.project_name,
                    self.user_name,
                    "shot",
                    sequence_name=sequence_name,
                    sequence_shot_name=mytask_name,
                    sequence_shot_step_name=mytask_step_name,
                    sequence_shot_step_task_name=mytask_step_task_name)
                timelog_list = self.sg.get_time_log(dict_data)
            elif mytask_type == 'Sequence':
                dict_data = sg_base_find.create_project_struct(
                    self.project_name,
                    self.user_name,
                    "sequence",
                    sequence_name=mytask_name,
                    sequence_step_name=mytask_step_name,
                    sequence_step_task_name=mytask_step_task_name)
                timelog_list = self.sg.get_time_log(dict_data)

        return project_level_info, timelog_list

    # get item list for treeView***
    def get_upper_level_item(self, source_model, level_struct_item, index):
        '''
        get the upper item name from treeView
        :param source_model: the source model
        :param level_struct_item: item list
        :param index: the index of source model
        '''
        item = source_model.itemFromIndex(index)
        if item.parent():
            self.get_upper_level_item(source_model, level_struct_item,
                                      item.parent().index())
        text = item.text()
        # strip chinese name if source_model is asset
        text = text.split("|")[0]
        level_struct_item.append(text)

    @QtCore.Slot()
    def submit_slot(self):
        date = self.dateEditDate.date().toString(QtCore.Qt.ISODate)
        duration_temp = self.lineEditDuration.text()
        if not duration_temp:
            QMessageBox.warning(self,
                                "Error",
                                u'Please input duration',
                                buttons=QMessageBox.Ok,
                                defaultButton=QMessageBox.Ok)
            return
        duration = float(duration_temp) * 60
        description = self.lineEditDescription.text()
        dict_data = self.get_dict_data()
        result = self.sg.create_time_log(date, duration, dict_data,
                                         description)
        self.update_timelog_info()

    def get_dict_data(self):
        project_level_struct_item = self._project_level_info
        dict_data = {}
        tab_index = self._tab_index
        if tab_index == 0:
            mytask_type = project_level_struct_item[0]
            mytask_name = project_level_struct_item[1]
            mytask_step_name = project_level_struct_item[2]
            mytask_step_task_name = project_level_struct_item[3]

            if mytask_type == 'Asset':
                dict_data = sg_base_find.create_project_struct(
                    self.project_name,
                    self.user_name,
                    "asset",
                    asset_name=mytask_name,
                    asset_step_name=mytask_step_name,
                    asset_step_task_name=mytask_step_task_name)

            elif mytask_type == 'Shot':
                sequence_name = mytask_name.split("_")[0]
                dict_data = sg_base_find.create_project_struct(
                    self.project_name,
                    self.user_name,
                    "shot",
                    sequence_name=sequence_name,
                    sequence_shot_name=mytask_name,
                    sequence_shot_step_name=mytask_step_name,
                    sequence_shot_step_task_name=mytask_step_task_name)

            elif mytask_type == 'Sequence':
                dict_data = sg_base_find.create_project_struct(
                    self.project_name,
                    self.user_name,
                    "sequence",
                    sequence_name=mytask_name,
                    sequence_step_name=mytask_step_name,
                    sequence_step_task_name=mytask_step_task_name)
        elif tab_index == 1:
            _asset_name = project_level_struct_item[1]
            _asset_step_name = project_level_struct_item[2]
            _asset_step_task_name = project_level_struct_item[3]

            dict_data = sg_base_find.create_project_struct(
                self.project_name,
                self.user_name,
                "asset",
                asset_name=_asset_name,
                asset_step_name=_asset_step_name,
                asset_step_task_name=_asset_step_task_name)
        elif tab_index == 2:
            _sequence_name = project_level_struct_item[0]
            _sequence_shot_name = project_level_struct_item[1]
            _sequence_shot_step_name = project_level_struct_item[2]
            _sequence_shot_step_task_name = project_level_struct_item[3]

            dict_data = sg_base_find.create_project_struct(
                self.project_name,
                self.user_name,
                "shot",
                sequence_name=_sequence_name,
                sequence_shot_name=_sequence_shot_name,
                sequence_shot_step_name=_sequence_shot_step_name,
                sequence_shot_step_task_name=_sequence_shot_step_task_name)
        elif tab_index == 3:
            _sequence_name = project_level_struct_item[0]
            _sequence_step_name = project_level_struct_item[1]
            _sequence_step_task_name = project_level_struct_item[2]

            dict_data = sg_base_find.create_project_struct(
                self.project_name,
                self.user_name,
                "sequence",
                sequence_name=_sequence_name,
                sequence_step_name=_sequence_step_name,
                sequence_step_task_name=_sequence_step_task_name)
        return dict_data

    @QtCore.Slot(QPoint)
    def right_context_menu(self, pos):
        '''
        this is the right context menu for listwidget item which you selected
        it will call the function below
        '''
        pop_menu = QMenu()
        if self.tableWidgetTimelog.itemAt(pos):
            self.show_right_context_menu(pop_menu)
        pop_menu.exec_(QCursor.pos())

    def show_right_context_menu(self, pop_menu):
        pop_menu.addAction(
            QAction(u'delete', self, triggered=self.right_menu_process))

    def right_menu_process(self):
        current_row = self.tableWidgetTimelog.currentRow()
        timelog_id = self.tableWidgetTimelog.item(current_row, 4).text()
        self.sg.delete("TimeLog", int(timelog_id))
        self.update_timelog_info()

    def show_timelog_info(self, timelog_list):
        self.tableWidgetTimelog.setRowCount(len(timelog_list))
        row = -1
        for timelog in timelog_list:
            row += 1
            user = timelog['user']['name']
            date = timelog['date']
            duration = str(timelog['duration'] / 60.0)
            description = timelog['description']
            timelog_id = str(timelog['id'])
            timelog_info = [user, date, duration, description, timelog_id]
            self.update_table_item(row, timelog_info)

    def update_table_item(self, row, timelog_info):
        column = -1
        for timelog in timelog_info:
            column += 1
            item = QTableWidgetItem()
            item.setFlags(item.flags() & ~QtCore.Qt.ItemIsEditable)
            if column == 2:
                timelog += " hour"
            item.setText(timelog)
            if not column == 3:
                item.setTextAlignment(QtCore.Qt.AlignRight
                                      | QtCore.Qt.AlignVCenter)
            self.tableWidgetTimelog.setItem(row, column, item)

    def update_timelog_info(self):
        dict_data = self.get_dict_data()
        timelog_list = self.sg.get_time_log(dict_data)
        self.show_timelog_info(timelog_list)
예제 #17
0
class FieldsPage(QWizardPage):
    """Allow user to skip too import some fields 

    """

    MANDATORY_FIELDS = ["chr", "pos", "ref", "alt", "gt"]

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

        self.setTitle(self.tr("Fields"))
        self.setSubTitle(self.tr("Select fields you want to import"))
        self.help_text = QLabel(self.tr("Check fields you want to import "))
        self.select_button = QPushButton(self.tr("(Un)Select all"))
        self.view = QTableView()
        self.model = QStandardItemModel()
        self.view.setModel(self.model)
        self.view.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.view.setSelectionMode(QAbstractItemView.SingleSelection)
        self.view.setAlternatingRowColors(True)

        # main layout
        main_layout = QHBoxLayout()
        main_layout.addWidget(self.view)

        self.setLayout(main_layout)

    def initializePage(self):
        """ overload """

        # Load fields
        self.model.clear()
        self.model.setColumnCount(4)
        self.model.setHorizontalHeaderLabels(
            ["name", "category", "description", "type"])

        # Open variant file of the project and read its headers
        filename = self.field("filename")

        with create_reader(filename) as reader:

            for field in reader.get_fields():

                name_item = QStandardItem(field["name"])
                cat_item = QStandardItem(field["category"])
                desc_item = QStandardItem(field["description"])
                type_item = QStandardItem(field["type"])

                name_item.setCheckable(True)
                name_item.setCheckState(Qt.Checked)
                name_item.setData(field)

                line = [name_item, cat_item, desc_item, type_item]

                for item in line:
                    item.setEditable(False)
                    item.setEnabled(not field["name"] in self.MANDATORY_FIELDS)

                self.model.appendRow(line)

        self.view.horizontalHeader().setSectionResizeMode(
            2, QHeaderView.Stretch)

    def validatePage(self):
        """ override """

        # Loop over fields a create a ignored fields set e.g (("qual","variants"))
        ignored_fields = set()
        for row in range(self.model.rowCount()):
            item = self.model.item(row, 0)
            if item.checkState() == Qt.Unchecked:
                field = item.data()
                ignored_fields.add((field["name"], field["category"]))

        self.wizard().config["ignored_fields"] = ignored_fields

        return True
예제 #18
0
class View(ProjectItem):
    def __init__(self, toolbox, project, logger, name, description, x, y):
        """
        View class.

        Args:
            toolbox (ToolboxUI): a toolbox instance
            project (SpineToolboxProject): the project this item belongs to
            logger (LoggerInterface): a logger instance
            name (str): Object name
            description (str): Object description
            x (float): Initial X coordinate of item icon
            y (float): Initial Y coordinate of item icon
        """
        super().__init__(name, description, x, y, project, logger)
        self._references = dict()
        self.reference_model = QStandardItemModel()  # References to databases
        self._spine_ref_icon = QIcon(QPixmap(":/icons/Spine_db_ref_icon.png"))

    @staticmethod
    def item_type():
        """See base class."""
        return ItemInfo.item_type()

    @staticmethod
    def item_category():
        """See base class."""
        return ItemInfo.item_category()

    def execution_item(self):
        """Creates project item's execution counterpart."""
        return ExecutableItem(self.name, self._logger)

    def make_signal_handler_dict(self):
        """Returns a dictionary of all shared signals and their handlers.
        This is to enable simpler connecting and disconnecting."""
        s = super().make_signal_handler_dict()
        s[self._properties_ui.toolButton_view_open_dir.
          clicked] = lambda checked=False: self.open_directory()
        s[self._properties_ui.pushButton_view_open_ds_form.
          clicked] = self.open_view
        return s

    def restore_selections(self):
        """Restore selections into shared widgets when this project item is selected."""
        self._properties_ui.label_view_name.setText(self.name)
        self._properties_ui.treeView_view.setModel(self.reference_model)

    def save_selections(self):
        """Save selections in shared widgets for this project item into instance variables."""
        self._properties_ui.treeView_view.setModel(None)

    @Slot(bool)
    def open_view(self, checked=False):
        """Opens references in a view window.
        """
        indexes = self._selected_indexes()
        db_url_codenames = self._db_url_codenames(indexes)
        if not db_url_codenames:
            return
        self._project.db_mngr.show_data_store_form(db_url_codenames,
                                                   self._logger)

    def populate_reference_list(self):
        """Populates reference list."""
        self.reference_model.clear()
        self.reference_model.setHorizontalHeaderItem(
            0, QStandardItem("References"))  # Add header
        for db in sorted(self._references, reverse=True):
            qitem = QStandardItem(db)
            qitem.setFlags(~Qt.ItemIsEditable)
            qitem.setData(self._spine_ref_icon, Qt.DecorationRole)
            self.reference_model.appendRow(qitem)

    def update_name_label(self):
        """Update View tab name label. Used only when renaming project items."""
        self._properties_ui.label_view_name.setText(self.name)

    def execute_forward(self, resources):
        """see base class"""
        self._update_references_list(resources)
        return True

    def _do_handle_dag_changed(self, resources):
        """Update the list of references that this item is viewing."""
        self._update_references_list(resources)

    def _update_references_list(self, resources_upstream):
        """Updates the references list with resources upstream.

        Args:
            resources_upstream (list): ProjectItemResource instances
        """
        self._references.clear()
        for resource in resources_upstream:
            if resource.type_ == "database" and resource.scheme == "sqlite":
                url = make_url(resource.url)
                self._references[url.database] = (url, resource.provider.name)
            elif resource.type_ == "file":
                filepath = resource.path
                if os.path.splitext(filepath)[1] == '.sqlite':
                    url = URL("sqlite", database=filepath)
                    self._references[url.database] = (url,
                                                      resource.provider.name)
        self.populate_reference_list()

    def _selected_indexes(self):
        """Returns selected indexes."""
        selection_model = self._properties_ui.treeView_view.selectionModel()
        if not selection_model.hasSelection():
            self._properties_ui.treeView_view.selectAll()
        return self._properties_ui.treeView_view.selectionModel().selectedRows(
        )

    def _db_url_codenames(self, indexes):
        """Returns a dict mapping url to provider's name for given indexes in the reference model."""
        return dict(self._references[index.data(Qt.DisplayRole)]
                    for index in indexes)

    def notify_destination(self, source_item):
        """See base class."""
        if source_item.item_type() == "Tool":
            self._logger.msg.emit(
                "Link established. You can visualize the ouput from Tool "
                f"<b>{source_item.name}</b> in View <b>{self.name}</b>.")
        elif source_item.item_type() == "Data Store":
            self._logger.msg.emit(
                "Link established. You can visualize Data Store "
                f"<b>{source_item.name}</b> in View <b>{self.name}</b>.")
        else:
            super().notify_destination(source_item)

    @staticmethod
    def default_name_prefix():
        """see base class"""
        return "View"
예제 #19
0
class MainWindow(QMainWindow, Ui_MainWindow):

    _TV_FOLDERS_ITEM_MAP = {}

    # signals
    _dir_load_start = Signal(object)
    _dir_watcher_start = Signal()

    _loader_load_scandir = Signal(int, int, int, object)

    _is_watcher_running = False
    _update_mgr = None

    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setupUi(self)

        self.actiongrp_thumbs_size = QtWidgets.QActionGroup(self)
        self.actiongrp_thumbs_size.addAction(self.action_small_thumbs)
        self.actiongrp_thumbs_size.addAction(self.action_normal_thumbs)

        self.actiongrp_thumbs_caption = QtWidgets.QActionGroup(self)
        self.actiongrp_thumbs_caption.addAction(self.action_caption_none)
        self.actiongrp_thumbs_caption.addAction(self.action_caption_filename)
        self.action_caption_none.setChecked(True)

        # threads
        self._dir_watcher_thread = QThread()
        self._img_loader_thread = QThread()

        # helpers
        self._meta_files_mgr = MetaFilesManager()
        self._meta_files_mgr.connect()
        self._watch = Watcher()
        self._img_loader = ImageLoader()

        self.listView_thumbs = ThumbsListView(self.frame_thumbs)
        self.listView_thumbs.setFrameShape(QtWidgets.QFrame.NoFrame)
        self.vlayout_frame_thumbs.addWidget(self.listView_thumbs)

        # connections
        self._setup_connections()

        # Populates the folder list
        self._setup_scan_dir_list_model()

        # Properties widget
        self.vlayout_properties = QtWidgets.QVBoxLayout(
            self.toolbox_metadata_properties)
        self.vlayout_properties.setContentsMargins(0, 0, 0, 0)
        self.vlayout_properties.setSpacing(0)
        self.properties_widget = PropertiesWidget(
            self.toolbox_metadata_properties)
        self.vlayout_properties.addWidget(self.properties_widget)

        self.statusBar().showMessage("Ready")

        # settings
        self.hslider_thumb_size.setValue(
            settings.get(SettingType.UI_THUMBS_SIZE, 128, 'int'))

        thumb_caption_type = settings.get(
            SettingType.UI_THUMBS_CAPTION_DISPLAY_MODE,
            Thumb_Caption_Type.NoCaption.name)
        if thumb_caption_type == Thumb_Caption_Type.FileName.name:
            self.action_caption_filename.setChecked(True)

        if settings.get(SettingType.UI_METADATA_SHOW_PROPS, False, 'bool'):
            self.toolBox_metadata.setCurrentIndex(0)
            self.toolbutton_properties.setChecked(True)
            self.toolbutton_tags.setChecked(False)
            self.action_properties.setChecked(True)
            self.action_tags.setChecked(False)
            self.frame_metadata.show()
        elif settings.get(SettingType.UI_METADATA_SHOW_TAGS, False, 'bool'):
            self.toolBox_metadata.setCurrentIndex(1)
            self.toolbutton_properties.setChecked(False)
            self.toolbutton_tags.setChecked(True)
            self.action_properties.setChecked(False)
            self.action_tags.setChecked(True)
            self.frame_metadata.show()
        else:
            self.frame_metadata.hide()

        # Set event filters
        self.btn_slideshow.installEventFilter(self)

        self._curr_img_serial = 1

    def resizeEvent(self, event):
        if event.spontaneous():
            # Begin loading the currently selected dir
            self._make_default_dir_list_selection()
            cur_sel_ids = self.get_current_selection_ids()
            if 'sd_id' in cur_sel_ids:
                self._load_dir_images(cur_sel_ids['sd_id'])
            # Start the dir watcher thread
            self.init_watch_thread()
            # Start the loader thread
            self.init_loader_thread()

    def closeEvent(self, event):
        LOGGER.debug('Shutting down gracefully....')
        self._meta_files_mgr.disconnect()
        settings.persist_to_disk()

        self._dir_watcher_thread.quit()
        self._dir_watcher_thread.wait()

        self._img_loader_thread.quit()
        self._img_loader_thread.wait()

    def _make_default_dir_list_selection(self):
        folders_index = self._dirs_list_model.indexFromItem(
            self._TV_FOLDERS_ITEM_MAP[0])
        if self._dirs_list_model.rowCount(folders_index) > 0:
            self._dirs_list_selection_model.select(
                self._dirs_list_model.index(0, 0).child(0, 0),
                QItemSelectionModel.Select | QItemSelectionModel.Rows)

    def _setup_connections(self):
        # Menu
        # File
        self.action_add_folder.triggered.connect(
            self.action_folder_manager_clicked)
        self.action_rescan.triggered.connect(self._run_watcher)
        self.action_file_locate.triggered.connect(
            self.handle_action_file_locate_triggered)
        self.action_exit.triggered.connect(self.action_exit_clicked)
        # View
        self.action_small_thumbs.triggered.connect(
            self.handle_action_small_thumbs_triggered)
        self.action_normal_thumbs.triggered.connect(
            self.handle_action_normal_thumbs_triggered)
        self.action_properties.triggered.connect(
            self.action_properties_clicked)
        self.action_tags.triggered.connect(self.action_tags_clicked)
        self.action_slideshow.triggered.connect(self.start_slideshow)
        self.action_caption_none.triggered.connect(
            self.handle_action_thumbnail_caption_none_triggered)
        self.action_caption_filename.triggered.connect(
            self.handle_action_thumbnail_caption_filename_triggered)
        # Folder
        self.action_folder_slideshow.triggered.connect(self.start_slideshow)
        self.action_folder_locate.triggered.connect(
            self.handle_action_folder_locate_triggered)
        # Picture
        self.action_picture_properties.triggered.connect(
            self.show_image_properties)
        # Tools
        self.action_settings.triggered.connect(
            self.handle_action_settings_triggered)
        self.action_folder_manager.triggered.connect(
            self.action_folder_manager_clicked)
        # Help
        self.action_check_updates.triggered.connect(
            self._handle_check_for_updates_clicked)

        # Btns
        self.btn_slideshow.clicked.connect(self.start_slideshow)
        self.hslider_thumb_size.valueChanged.connect(
            self.on_hslider_thumb_size_value_changed)

        # Watcher
        self._dir_watcher_start.connect(self._watch.watch_all)
        self._watch.new_img_found.connect(self.on_new_img_found)
        self._watch.watch_all_done.connect(self.on_watch_all_done)
        self._watch.dir_added_or_updated.connect(self.on_dir_added_or_updated)
        self._watch.dir_empty_or_deleted.connect(self.on_dir_empty_deleted)
        self._watch.watch_empty_or_deleted_done.connect(
            self.on_watch_dir_empty_deleted_done)

        # Loader
        self._loader_load_scandir.connect(self._img_loader.load_scandir)
        # self._loader_load_scanimg.connect(self._img_loader.load_scanimg)
        self._img_loader.load_scan_dir_info_success.connect(
            self._handle_load_scan_dir_info_success)
        # self._img_loader.load_images_success.connect(
        # self._handle_load_images_sucess)
        self._img_loader.load_images_success.connect(
            self.listView_thumbs.render_thumbs)

        # Tree View
        self.treeView_scandirs.clicked.connect(
            self.on_scan_dir_treeView_clicked)

        # Thumbs view
        self.listView_thumbs.clicked.connect(self.on_thumb_clicked)
        self.listView_thumbs.empty_area_clicked.connect(
            self.on_thumb_listview_empty_area_clicked)
        self.listView_thumbs.load_dir_images_for_scroll_up.connect(
            self.on_load_dir_images_for_scroll_up)
        self.listView_thumbs.load_dir_images_for_scroll_down.connect(
            self.on_load_dir_images_for_scroll_down)

        self.buttonGroup_metadata.buttonClicked.connect(
            self.on_buttongroup_metadata_clicked)

        self.txtbox_search.textEdited.connect(self.handle_search)

    def init_watch_thread(self):
        self._watch.moveToThread(self._dir_watcher_thread)
        self._dir_watcher_thread.start()
        self._run_watcher()
        LOGGER.debug('Watcher thread started.')

    def init_loader_thread(self):
        self._img_loader.moveToThread(self._img_loader_thread)
        self._img_loader_thread.start()
        LOGGER.debug('Loader thread started.')

    def _run_watcher(self):
        self._is_watcher_running = True
        self.action_rescan.setEnabled(False)
        self._dir_watcher_start.emit()

    def _populate_dirs_tree_view(self, parent_key, folders):
        parent_item = self._TV_FOLDERS_ITEM_MAP[parent_key]
        if folders:
            for idx, dir in enumerate(folders):
                item_title = "%s(%s)" % (dir['name'], dir['img_count'])
                item = QStandardItem(item_title)
                item.setData(dir['id'], QtCore.Qt.UserRole + 1)
                item.setSizeHint(QSize(item.sizeHint().width(), 24))
                item.setIcon(QIcon(':/images/icon_folder'))
                parent_item.appendRow(item)
                if parent_key == 0:
                    self._TV_FOLDERS_ITEM_MAP[dir['id']] = item
        self.treeView_scandirs.expandAll()

    def _setup_scan_dir_list_model(self):
        self._dirs_list_model = QStandardItemModel()
        self._dirs_list_selection_model = QItemSelectionModel(
            self._dirs_list_model)
        self._thumbs_view_model = QStandardItemModel()
        self._thumbs_selection_model = QItemSelectionModel(
            self._thumbs_view_model)

        self._dirs_list_model.setColumnCount(1)
        # self._dirs_list_model.setRowCount(len(scan_dirs))

        self._root_tree_item = self._dirs_list_model.invisibleRootItem()
        # FOLDERS item
        folder_item = QStandardItem("Folders")
        folder_item_font = QFont()
        folder_item_font.setBold(True)
        folder_item.setFont(folder_item_font)
        folder_item.setSizeHint(QSize(folder_item.sizeHint().width(), 24))
        self._root_tree_item.appendRow(folder_item)
        self._TV_FOLDERS_ITEM_MAP[0] = folder_item

        self.treeView_scandirs.setModel(self._dirs_list_model)
        self.treeView_scandirs.setSelectionModel(
            self._dirs_list_selection_model)
        # self.treeView_scandirs.setRootIsDecorated(False)

        self.listView_thumbs.setModel(self._thumbs_view_model)
        self.listView_thumbs.setSelectionModel(self._thumbs_selection_model)

        scan_dirs = self._meta_files_mgr.get_scan_dirs()
        self._populate_dirs_tree_view(0, scan_dirs)

    def _repopulate_scan_dir_list_model(self):
        self._clear_folders_tree_view()
        scan_dirs = self._meta_files_mgr.get_scan_dirs()
        self._populate_dirs_tree_view(0, scan_dirs)

    def _populate_search_tree_view(self, results):
        if 'search' not in self._TV_FOLDERS_ITEM_MAP:
            self._root_tree_item = self._dirs_list_model.invisibleRootItem()
            # FOLDERS item
            search_item = QStandardItem("Search")
            search_item_font = QFont()
            search_item_font.setBold(True)
            search_item.setFont(search_item_font)
            search_item.setSizeHint(QSize(search_item.sizeHint().width(), 24))
            self._root_tree_item.insertRow(0, search_item)
            self._TV_FOLDERS_ITEM_MAP['search'] = search_item
        self._populate_dirs_tree_view('search', results)

    def eventFilter(self, widget, event):
        if widget.objectName() == 'btn_slideshow':
            if event.type() == QtCore.QEvent.Enter:
                self.label_thumbs_toolbar_tooltip.setText(
                    "Play Fullscreen Slideshow")
            elif event.type() == QtCore.QEvent.Leave:
                self.label_thumbs_toolbar_tooltip.setText("")
        return QtWidgets.QWidget.eventFilter(self, widget, event)

    def handle_search(self, search_term):
        self._clear_search()
        if search_term != '':
            searches = self._meta_files_mgr.search_scan_dirs(search_term)
            self._populate_search_tree_view(searches)
        else:
            self._remove_search_tree_view()

    def handle_action_file_locate_triggered(self):
        curr_sel_ids = self.get_current_selection_ids()
        if 'sd_id' in curr_sel_ids and 'si_id' in curr_sel_ids:
            dr_img = self._meta_files_mgr.get_image_from_id(
                curr_sel_ids['si_id'], curr_sel_ids['sd_id'])
            explorer_process = QtCore.QProcess()
            explorer_process.setProgram('explorer.exe')
            explorer_process.setArguments([
                '/select,%s' %
                QtCore.QDir.toNativeSeparators(dr_img['abspath'])
            ])
            explorer_process.startDetached()

    def handle_action_folder_locate_triggered(self):
        curr_sel_ids = self.get_current_selection_ids()
        if 'sd_id' in curr_sel_ids:
            dr_sd = self._meta_files_mgr.get_scan_dir(curr_sel_ids['sd_id'])
            explorer_process = QtCore.QProcess()
            explorer_process.setProgram('explorer.exe')
            explorer_process.setArguments([
                '/select,%s' % QtCore.QDir.toNativeSeparators(dr_sd['abspath'])
            ])
            explorer_process.startDetached()

    def handle_action_small_thumbs_triggered(self):
        if self.action_small_thumbs.isChecked():
            self.hslider_thumb_size.triggerAction(
                self.hslider_thumb_size.SliderToMinimum)

    def handle_action_normal_thumbs_triggered(self):
        if self.action_normal_thumbs.isChecked():
            slider_value = self.hslider_thumb_size.value()
            if slider_value < 128:
                self.hslider_thumb_size.triggerAction(
                    self.hslider_thumb_size.SliderPageStepAdd)
            elif slider_value > 128 and slider_value <= 192:
                self.hslider_thumb_size.triggerAction(
                    self.hslider_thumb_size.SliderPageStepSub)
            elif slider_value > 192 and slider_value <= 256:
                self.hslider_thumb_size.triggerAction(
                    self.hslider_thumb_size.SliderPageStepSub)
                self.hslider_thumb_size.triggerAction(
                    self.hslider_thumb_size.SliderPageStepSub)

    def handle_action_thumbnail_caption_none_triggered(self):
        if self.action_caption_none.isChecked():
            settings.save(SettingType.UI_THUMBS_CAPTION_DISPLAY_MODE,
                          Thumb_Caption_Type.NoCaption.name)
            curr_sel_ids = self.get_current_selection_ids()
            if 'sd_id' in curr_sel_ids:
                self._load_dir_images(curr_sel_ids['sd_id'])

    def handle_action_thumbnail_caption_filename_triggered(self):
        if self.action_caption_filename.isChecked():
            settings.save(SettingType.UI_THUMBS_CAPTION_DISPLAY_MODE,
                          Thumb_Caption_Type.FileName.name)
            curr_sel_ids = self.get_current_selection_ids()
            if 'sd_id' in curr_sel_ids:
                self._load_dir_images(curr_sel_ids['sd_id'])

    def action_folder_manager_clicked(self):
        self.folder_mgr_window = FolderManagerWindow(self)
        self.folder_mgr_window.accepted.connect(
            self._on_folder_manager_window_accepted)
        self.folder_mgr_window.setModal(True)
        self.folder_mgr_window.show()

    def _on_folder_manager_window_accepted(self):
        self._repopulate_scan_dir_list_model()
        self._run_watcher()

    def action_properties_clicked(self):
        if self.action_properties.isChecked():
            curr_sel_ids = self.get_current_selection_ids()
            if 'sd_id' in curr_sel_ids and 'si_id' in curr_sel_ids:
                img_props = self._meta_files_mgr.get_img_properties(
                    curr_sel_ids['si_id'], curr_sel_ids['sd_id'])
                self.properties_widget.setup_properties(img_props)

            self.toolBox_metadata.setCurrentIndex(0)
            self.frame_metadata.show()
            self.toolbutton_properties.setChecked(True)
            self.toolbutton_tags.setChecked(False)
            self.action_tags.setChecked(False)
            settings.save(SettingType.UI_METADATA_SHOW_PROPS, True)
            settings.save(SettingType.UI_METADATA_SHOW_TAGS, False)
        else:
            self.frame_metadata.hide()
            self.toolbutton_properties.setChecked(False)
            settings.save(SettingType.UI_METADATA_SHOW_PROPS, False)
            settings.save(SettingType.UI_METADATA_SHOW_TAGS, False)
            # Forces the list view to re-render after the metadata window is hidden
            self._thumbs_view_model.layoutChanged.emit()

    def action_tags_clicked(self):
        if self.action_tags.isChecked():
            self.toolBox_metadata.setCurrentIndex(1)
            self.frame_metadata.show()
            self.toolbutton_tags.setChecked(True)
            self.toolbutton_properties.setChecked(False)
            self.action_properties.setChecked(False)
            settings.save(SettingType.UI_METADATA_SHOW_PROPS, False)
            settings.save(SettingType.UI_METADATA_SHOW_TAGS, True)
        else:
            self.frame_metadata.hide()
            self.toolbutton_tags.setChecked(False)
            settings.save(SettingType.UI_METADATA_SHOW_PROPS, False)
            settings.save(SettingType.UI_METADATA_SHOW_TAGS, False)
            # Forces the list view to re-render after the metadata window is hidden
            self._thumbs_view_model.layoutChanged.emit()

    def show_image_properties(self):
        curr_sel_ids = self.get_current_selection_ids()
        if 'sd_id' in curr_sel_ids and 'si_id' in curr_sel_ids:
            img_props = self._meta_files_mgr.get_img_properties(
                curr_sel_ids['si_id'], curr_sel_ids['sd_id'])
            self.properties_widget.setup_properties(img_props)

        self.toolBox_metadata.setCurrentIndex(0)
        self.frame_metadata.show()
        self.toolbutton_properties.setChecked(True)
        self.toolbutton_tags.setChecked(False)
        self.action_properties.setChecked(True)
        self.action_tags.setChecked(False)

    def handle_action_settings_triggered(self):
        self.settings_window = SettingsWindow(self)
        self.settings_window.setModal(True)
        self.settings_window.show()

    def on_buttongroup_metadata_clicked(self, button):
        if button.objectName() == 'toolbutton_tags':
            self.action_tags.trigger()
        elif button.objectName() == 'toolbutton_properties':
            self.action_properties.trigger()

    def on_hslider_thumb_size_value_changed(self, value):
        if self.hslider_thumb_size.value() == 64:
            self.action_small_thumbs.setChecked(True)
        elif self.hslider_thumb_size.value() == 128:
            self.action_normal_thumbs.setChecked(True)
        else:
            self.action_small_thumbs.setChecked(False)
            self.action_normal_thumbs.setChecked(False)

        self.listView_thumbs.setIconSize(QSize(value, value))
        self.listView_thumbs.setGridSize(QSize(value + 20, value + 20))

        settings.save(SettingType.UI_THUMBS_SIZE, value)

    def _handle_check_for_updates_clicked(self):
        if not self._update_mgr:
            self._update_mgr = UpdateManager()
        self._update_mgr.get_updates()

    def _load_dir_images(self, sd_id):
        self._clear_thumbs()
        load_count = self.listView_thumbs.get_visible_thumb_count(
            self.hslider_thumb_size.value() + 20)
        print("load count %d" % load_count)
        self._loader_load_scandir.emit(sd_id, 0, load_count,
                                       ScrollDirection.Down)
        QScroller.grabGesture(self.listView_thumbs.viewport(),
                              QScroller.LeftMouseButtonGesture)

    def _load_dir_images_for_scroll_up(self, sd_id, serial, count):
        # self._clear_thumbs()
        load_count = self.listView_thumbs.get_visible_thumb_count(
            self.hslider_thumb_size.value() + 20)
        self._loader_load_scandir.emit(sd_id, serial, count,
                                       ScrollDirection.Up)
        QScroller.grabGesture(self.listView_thumbs.viewport(),
                              QScroller.LeftMouseButtonGesture)

    def _load_dir_images_for_scroll_down(self, sd_id, serial, count):
        # self._clear_thumbs()
        load_count = self.listView_thumbs.get_visible_thumb_count(
            self.hslider_thumb_size.value() + 20)
        self._loader_load_scandir.emit(sd_id, serial, count,
                                       ScrollDirection.Down)
        QScroller.grabGesture(self.listView_thumbs.viewport(),
                              QScroller.LeftMouseButtonGesture)

    @Slot(int, int)
    def on_load_dir_images_for_scroll_up(self, serial, count):
        cur_sel_ids = self.get_current_selection_ids()
        if 'sd_id' in cur_sel_ids:
            self._load_dir_images_for_scroll_up(cur_sel_ids['sd_id'], serial,
                                                count)

    @Slot(int, int)
    def on_load_dir_images_for_scroll_down(self, serial, count):
        cur_sel_ids = self.get_current_selection_ids()
        if 'sd_id' in cur_sel_ids:
            self._load_dir_images_for_scroll_down(cur_sel_ids['sd_id'], serial,
                                                  count)

    def _handle_load_scan_dir_info_success(self, dir_info):
        self.lbl_dir_name.setText(dir_info['name'])

    @Slot(object, int)
    def _handle_load_images_sucess(self, images, scrollDirection):
        img_count = len(images)
        print('Recevied %s images' % img_count)
        LOGGER.debug('Recevied %s images' % img_count)
        for img in images:
            img['thumb'] = QImage.fromData(img['thumb'])
            item = QStandardItem()

            thumb_caption_type = settings.get(
                SettingType.UI_THUMBS_CAPTION_DISPLAY_MODE,
                Thumb_Caption_Type.NoCaption.name)
            if thumb_caption_type == Thumb_Caption_Type.FileName.name:
                item.setText(img['name'])

            item.setData(img['id'], QtCore.Qt.UserRole + 1)
            item.setData(img['serial'], QtCore.Qt.UserRole + 2)
            item.setIcon(QIcon(QPixmap.fromImage(img['thumb'])))
            item.setText(str(img['serial']))

            if scrollDirection == ScrollDirection.Up:
                self._thumbs_view_model.insertRow(0, item)
            if scrollDirection == ScrollDirection.Down:
                self._thumbs_view_model.appendRow(item)

    def _clear_thumbs(self):
        self._thumbs_view_model.clear()
        self.lbl_dir_name.setText('')

    def _tv_add_scan_dir(self, dir_info, highlight=False):
        item_title = "%s(%s)" % (dir_info['name'], dir_info['img_count'])
        item = QStandardItem(item_title)
        item.setData(dir_info['id'], QtCore.Qt.UserRole + 1)
        item.setSizeHint(QSize(item.sizeHint().width(), 24))
        item.setIcon(QIcon(':/images/icon_folder'))
        if highlight:
            bold_font = QFont()
            bold_font.setBold(True)
            item.setFont(bold_font)
        folder_item = self._TV_FOLDERS_ITEM_MAP[0]
        folder_item.appendRow(item)
        self._TV_FOLDERS_ITEM_MAP[dir_info['id']] = item

    @Slot()
    def start_slideshow(self):
        selected = self.treeView_scandirs.selectedIndexes()
        sd_id = selected[0].data(QtCore.Qt.UserRole + 1)

        img_serial = 1
        thumb_selected = self.listView_thumbs.selectedIndexes()
        if len(thumb_selected) > 0:
            img_serial = thumb_selected[0].data(QtCore.Qt.UserRole + 2)

        if sd_id > 0:
            self._slideshow = SlideshowWindow(sd_id, img_serial)
            self._slideshow.setWindowFlags(QtCore.Qt.CustomizeWindowHint
                                           | QtCore.Qt.FramelessWindowHint)
            self._slideshow.showFullScreen()

    @Slot(QModelIndex)
    def on_thumb_clicked(self, mindex):
        selected = self.treeView_scandirs.selectedIndexes()
        sd_id = selected[0].data(QtCore.Qt.UserRole + 1)
        si_id = mindex.data(QtCore.Qt.UserRole + 1)
        img_props = self._meta_files_mgr.get_img_properties(si_id, sd_id)
        self.lbl_selection_summary.setText(
            self.get_thumb_selection_summary(img_props))

        if self.action_properties.isChecked():
            self.properties_widget.setup_properties(img_props)

    @Slot()
    def on_thumb_listview_empty_area_clicked(self):
        selected_thumb = self.listView_thumbs.selectedIndexes()
        if len(selected_thumb) == 0:
            selected = self.treeView_scandirs.selectedIndexes()
            sd_id = selected[0].data(QtCore.Qt.UserRole + 1)
            if sd_id:
                props = self._meta_files_mgr.get_dir_properties(sd_id)
                self.lbl_selection_summary.setText(
                    self.get_dir_selection_summary(props))

    def get_dir_selection_summary(self, props):
        img_count = props['img_count']
        modified = props['modified'].toString('dd MMMM yyyy')
        size = props['size']
        return "%s pictures        %s        %s on disk" % (img_count,
                                                            modified, size)

    def get_thumb_selection_summary(self, props):
        filename = props['filename']
        modified = props['DateTime'] if 'DateTime' in props else ''
        dimensions = props['dimensions']
        filesize = props['filesize']
        return "%s        %s        %s        %s" % (filename, modified,
                                                     dimensions, filesize)

    @Slot(QModelIndex)
    def on_scan_dir_treeView_clicked(self, index):
        sd_id = index.data(QtCore.Qt.UserRole + 1)
        # Categories tree nodes will not contain 'data'
        if sd_id:
            item = self._TV_FOLDERS_ITEM_MAP[sd_id]
            bold_font = QFont()
            bold_font.setBold(False)
            item.setFont(bold_font)
            props = self._meta_files_mgr.get_dir_properties(sd_id)
            self.lbl_selection_summary.setText(
                self.get_dir_selection_summary(props))
            self._load_dir_images(sd_id)

    @Slot(object)
    def on_new_img_found(self, img_info):
        self.statusBar().showMessage("Found new image: %s - %s" %
                                     (img_info['dir'], img_info['filename']))

    @Slot(object)
    def on_dir_added_or_updated(self, dir_info):
        if dir_info['id'] in self._TV_FOLDERS_ITEM_MAP:
            item = self._TV_FOLDERS_ITEM_MAP[dir_info['id']]
            item_title = "%s(%s)" % (dir_info['name'], dir_info['img_count'])
            item.setText(item_title)
            bold_font = QFont()
            bold_font.setBold(True)
            item.setFont(bold_font)
        else:
            self._tv_add_scan_dir(dir_info, True)

    @Slot(object)
    def on_dir_empty_deleted(self, dir_info):
        if dir_info['id'] in self._TV_FOLDERS_ITEM_MAP:
            item = self._TV_FOLDERS_ITEM_MAP[dir_info['id']]
            item_index = self._dirs_list_model.indexFromItem(item)
            parent_index = self._dirs_list_model.indexFromItem(
                self._TV_FOLDERS_ITEM_MAP[0])
            self._dirs_list_model.removeRow(item_index.row(), parent_index)
            self._TV_FOLDERS_ITEM_MAP.pop(dir_info['id'])

    @Slot()
    def on_watch_dir_empty_deleted_done(self):
        # Here we make sure a folder is always selected if one more folders
        # ever get deleted by the watcher thread. If no folders exits, just
        # display an empty thumbs list
        cur_sel_ids = self.get_current_selection_ids()
        print(cur_sel_ids)
        if 'sd_id' not in cur_sel_ids:
            self._make_default_dir_list_selection()
            cur_sel_ids = self.get_current_selection_ids()
            if 'sd_id' in cur_sel_ids:
                self._load_dir_images(cur_sel_ids['sd_id'])
            else:
                self._clear_thumbs()

    @Slot(object, object)
    def on_watch_all_done(self, elapsed, suffix):
        self.statusBar().clearMessage()
        self._is_watcher_running = False
        self.action_rescan.setEnabled(True)
        self.statusBar().showMessage("Folder scan completed in %.2f %s" %
                                     (elapsed, suffix))

    def get_current_selection_ids(self):
        selected_ids = {}
        selected = self.treeView_scandirs.selectedIndexes()
        if len(selected) <= 0:
            return selected_ids
        selected_ids['sd_id'] = selected[0].data(QtCore.Qt.UserRole + 1)
        selected_thumb = self.listView_thumbs.selectedIndexes()
        if len(selected_thumb) <= 0:
            return selected_ids
        selected_ids['si_id'] = selected_thumb[0].data(QtCore.Qt.UserRole + 1)
        return selected_ids

    def action_exit_clicked(self):
        self.close()

    def _clear_search(self):
        if 'search' in self._TV_FOLDERS_ITEM_MAP:
            search_item = self._TV_FOLDERS_ITEM_MAP['search']
            search_item.removeRows(0, search_item.rowCount())

    def _clear_folders_tree_view(self):
        folder_item = self._TV_FOLDERS_ITEM_MAP[0]
        self._TV_FOLDERS_ITEM_MAP.clear()
        self._TV_FOLDERS_ITEM_MAP[0] = folder_item
        folder_item.removeRows(0, folder_item.rowCount())

    def _remove_search_tree_view(self):
        if 'search' in self._TV_FOLDERS_ITEM_MAP:
            root_tree_item = self._dirs_list_model.invisibleRootItem()
            root_tree_item.removeRow(0)
            self._TV_FOLDERS_ITEM_MAP.pop('search')

    def is_watcher_running(self):
        return self._is_watcher_running
class MainWindow:
    def __init__(self):
        self.__dataModel = None
        self.__operation_result = None

    def buildGui(self):
        ui_file_loader = QUiLoader()
        ui_file_loader.registerCustomWidget(ResizeStackWidget)
        ui_file_loader.registerCustomWidget(ConditionalWizard)

        main_ui_file = QFile("ui/main_wizard.ui")
        main_ui_file.open(QFile.ReadOnly)
        self._window = ui_file_loader.load(main_ui_file)
        main_ui_file.close()

        #
        def fc(type, name):
            return self._window.findChild(type, name)

        def fc_l(name):
            return fc(QLineEdit, name)

        def fc_b(name):
            return fc(QPushButton, name)

        def fc_cb(name):
            return fc(QComboBox, name)

        def fc_p(name):
            l = fc_l(name + "_lineEdit")
            b = fc_b(name + "_pushButton")
            if l is None:
                raise Exception("lineEdit {}_lineEdit not found".format(name))
            if b is None:
                raise Exception(
                    "pushButton {}_pushButton not found".format(name))
            return [l, b]

        # page 0

        self.__operation_cb = fc_cb("operation_comboBox")
        self.__operation_cb.currentIndexChanged.connect(self.onOperationSelect)

        self.__source_rsw = fc(ResizeStackWidget, "source_stackedWidget")
        self.__sink_rsw = fc(ResizeStackWidget, "sink_stackedWidget")

        self.__datasource_stm = STM32CubeMX_DataSource(self._window,
                                                       fc_p("source_stm"))
        self.__datasource_eagle = AutodeskEagle_DataSource(
            self._window, fc_p("source_eagle_sch"), fc_p("source_eagle_ic"))

        self.__datasink_stm = STM32CubeMX_DataSink(self._window,
                                                   fc_p("sink_stm"))
        self.__datasink_eagle = AutodeskEagle_DataSink(self._window,
                                                       fc_p("sink_eagle_sch"),
                                                       fc_p("sink_eagle_brd"),
                                                       fc_p("sink_eagle_ic"))

        self._window.registerCallback(self.onPageChanged)

        # page 1

        self.__changelog_model = QStandardItemModel()
        self.__error_model = QStandardItemModel()

        self.__changelog_tableView = fc(QTableView, "changelog_tableView")
        self.__changelog_tableView.setModel(self.__changelog_model)
        self.__error_tableView = fc(QTableView, "error_tableView")
        self.__error_tableView.setModel(self.__error_model)

        # page 2

        self.__target_rsw = fc(ResizeStackWidget, "target_stackedWidget")

        self.__datatarget_stm = STM32CubeMX_DataTarget(self._window,
                                                       fc_p("target_stm"))
        self.__datatarget_eagle = AutodeskEagle_DataTarget(
            self._window, fc_p("target_eagle_sch"), fc_p("target_eagle_brd"))

        self.config_by_idx = {
            # operation idx
            # stm => eagle
            0:
            ConfigEntry(source_rsw_idx=0,
                        sink_rsw_idx=1,
                        target_rsw_idx=1,
                        source_input=self.__datasource_stm,
                        sink_input=self.__datasink_eagle,
                        target_input=self.__datatarget_eagle),
            # eagle => stm
            1:
            ConfigEntry(source_rsw_idx=1,
                        sink_rsw_idx=0,
                        target_rsw_idx=0,
                        source_input=self.__datasource_eagle,
                        sink_input=self.__datasink_stm,
                        target_input=self.__datatarget_stm)
        }

        self.__operation_cb.setCurrentIndex(0)
        self.onOperationSelect(0)

    def show(self):
        self._window.show()

    @Slot()
    def onOperationSelect(self, idx):
        # clean settings and entries
        for dm in [
                self.__datasource_stm, self.__datasource_eagle,
                self.__datasink_stm, self.__datasink_eagle,
                self.__datatarget_stm, self.__datatarget_eagle
        ]:
            dm.clear()

        config = self.config_by_idx[idx]

        self.__source_rsw.setCurrentIndex(config.source_rsw_idx)
        self.__sink_rsw.setCurrentIndex(config.sink_rsw_idx)
        self.__target_rsw.setCurrentIndex(config.target_rsw_idx)

    def onPageChanged(self, page_idx):
        if page_idx == -1:
            return self.onWriteFiles()
        if page_idx == 0:
            return self.onDataSelected()
        if page_idx == 1:
            return True
        return False

    def onDataSelected(self):
        config = self.config_by_idx[self.__operation_cb.currentIndex()]

        source = config.source_input
        sink = config.sink_input
        target = config.target_input

        source.load()
        sink.load()

        target.copyInputFrom(sink)

        if source.isLoaded() == False or sink.isLoaded() == False:
            return False

        source_model = source.getModel()
        sink_model = sink.getModel()

        tnn = TargetNetContainer.fromNNC(sink_model, source_model)
        self.__operation_result = sink.apply(tnn)

        # display log
        self.__changelog_model.clear()
        self.__changelog_model.setHorizontalHeaderLabels(["Pin", "From", "To"])
        for entry in self.__operation_result.log:
            self.__changelog_model.appendRow([
                QStandardItem(str(entry.name)),
                QStandardItem(str(entry.net)),
                QStandardItem(str(entry.new_net))
            ])

        # display errors
        self.__error_model.clear()
        self.__error_model.setHorizontalHeaderLabels(["Type", "Message"])
        for line in self.__operation_result.errors:
            self.__error_model.appendRow(
                [QStandardItem("Error"),
                 QStandardItem(line)])
        for line in self.__operation_result.warnings:
            self.__error_model.appendRow(
                [QStandardItem("Warning"),
                 QStandardItem(line)])

        self.__changelog_tableView.resizeColumnsToContents()
        self.__error_tableView.resizeColumnsToContents()

        return True

    def onWriteFiles(self):
        if self.__operation_result is None:
            return False

        config = self.config_by_idx[self.__operation_cb.currentIndex()]

        config.target_input.write(self.__operation_result)
예제 #21
0
class DataConnection(ProjectItem):
    """Data Connection class.

    Attributes:
        toolbox (ToolboxUI): QMainWindow instance
        name (str): Object name
        description (str): Object description
        references (list): List of file references
        x (int): Initial X coordinate of item icon
        y (int): Initial Y coordinate of item icon
    """
    def __init__(self, toolbox, name, description, references, x, y):
        """Class constructor."""
        super().__init__(name, description)
        self._toolbox = toolbox
        self._project = self._toolbox.project()
        self.item_type = "Data Connection"
        # self._widget = DataConnectionWidget(self, self.item_type)
        self.reference_model = QStandardItemModel()  # References to files
        self.data_model = QStandardItemModel(
        )  # Paths of project internal files. These are found in DC data directory
        self.datapackage_icon = QIcon(QPixmap(":/icons/datapkg.png"))
        self.data_dir_watcher = QFileSystemWatcher(self)
        # Make project directory for this Data Connection
        self.data_dir = os.path.join(self._project.project_dir,
                                     self.short_name)
        try:
            create_dir(self.data_dir)
            self.data_dir_watcher.addPath(self.data_dir)
        except OSError:
            self._toolbox.msg_error.emit(
                "[OSError] Creating directory {0} failed."
                " Check permissions.".format(self.data_dir))
        # Populate references model
        self.references = references
        self.populate_reference_list(self.references)
        # Populate data (files) model
        data_files = self.data_files()
        self.populate_data_list(data_files)
        self._graphics_item = DataConnectionImage(self._toolbox, x - 35,
                                                  y - 35, 70, 70, self.name)
        self.spine_datapackage_form = None
        # self.ui.toolButton_datapackage.setMenu(self.datapackage_popup_menu)  # TODO: OBSOLETE?
        self._sigs = self.make_signal_handler_dict()

    def make_signal_handler_dict(self):
        """Returns a dictionary of all shared signals and their handlers.
        This is to enable simpler connecting and disconnecting."""
        s = dict()
        s[self._toolbox.ui.toolButton_dc_open_dir.
          clicked] = self.open_directory
        s[self._toolbox.ui.toolButton_plus.clicked] = self.add_references
        s[self._toolbox.ui.toolButton_minus.clicked] = self.remove_references
        s[self._toolbox.ui.toolButton_add.clicked] = self.copy_to_project
        s[self._toolbox.ui.pushButton_datapackage.
          clicked] = self.show_spine_datapackage_form
        s[self._toolbox.ui.treeView_dc_references.
          doubleClicked] = self.open_reference
        s[self._toolbox.ui.treeView_dc_data.
          doubleClicked] = self.open_data_file
        s[self.data_dir_watcher.directoryChanged] = self.refresh
        s[self._toolbox.ui.treeView_dc_references.
          files_dropped] = self.add_files_to_references
        s[self._toolbox.ui.treeView_dc_data.
          files_dropped] = self.add_files_to_data_dir
        s[self._graphics_item.master().scene().
          files_dropped_on_dc] = self.receive_files_dropped_on_dc
        return s

    def activate(self):
        """Restore selections and connect signals."""
        self.restore_selections(
        )  # Do this before connecting signals or funny things happen
        super().connect_signals()

    def deactivate(self):
        """Save selections and disconnect signals."""
        self.save_selections()
        if not super().disconnect_signals():
            logging.error("Item {0} deactivation failed".format(self.name))
            return False
        return True

    def restore_selections(self):
        """Restore selections into shared widgets when this project item is selected."""
        self._toolbox.ui.label_dc_name.setText(self.name)
        self._toolbox.ui.treeView_dc_references.setModel(self.reference_model)
        self._toolbox.ui.treeView_dc_data.setModel(self.data_model)
        self.refresh()

    def save_selections(self):
        """Save selections in shared widgets for this project item into instance variables."""
        pass

    def set_icon(self, icon):
        self._graphics_item = icon

    def get_icon(self):
        """Returns the item representing this data connection in the scene."""
        return self._graphics_item

    @Slot("QVariant", name="add_files_to_references")
    def add_files_to_references(self, paths):
        """Add multiple file paths to reference list.

        Args:
            paths (list): A list of paths to files
        """
        for path in paths:
            if path in self.references:
                self._toolbox.msg_warning.emit(
                    "Reference to file <b>{0}</b> already available".format(
                        path))
                return
            self.references.append(os.path.abspath(path))
        self.populate_reference_list(self.references)

    @Slot("QGraphicsItem", "QVariant", name="receive_files_dropped_on_dc")
    def receive_files_dropped_on_dc(self, item, file_paths):
        """Called when files are dropped onto a data connection graphics item.
        If the item is this Data Connection's graphics item, add the files to data."""
        if item == self._graphics_item:
            self.add_files_to_data_dir(file_paths)

    @Slot("QVariant", name="add_files_to_data_dir")
    def add_files_to_data_dir(self, file_paths):
        """Add files to data directory"""
        for file_path in file_paths:
            src_dir, filename = os.path.split(file_path)
            self._toolbox.msg.emit(
                "Copying file <b>{0}</b> to <b>{1}</b>".format(
                    filename, self.name))
            try:
                shutil.copy(file_path, self.data_dir)
            except OSError:
                self._toolbox.msg_error.emit("[OSError] Copying failed")
                return
        data_files = self.data_files()
        self.populate_data_list(data_files)

    @Slot(bool, name="open_directory")
    def open_directory(self, checked=False):
        """Open file explorer in Data Connection data directory."""
        url = "file:///" + self.data_dir
        # noinspection PyTypeChecker, PyCallByClass, PyArgumentList
        res = QDesktopServices.openUrl(QUrl(url, QUrl.TolerantMode))
        if not res:
            self._toolbox.msg_error.emit(
                "Failed to open directory: {0}".format(self.data_dir))

    @Slot(bool, name="add_references")
    def add_references(self, checked=False):
        """Let user select references to files for this data connection."""
        # noinspection PyCallByClass, PyTypeChecker, PyArgumentList
        answer = QFileDialog.getOpenFileNames(self._toolbox,
                                              "Add file references",
                                              APPLICATION_PATH, "*.*")
        file_paths = answer[0]
        if not file_paths:  # Cancel button clicked
            return
        for path in file_paths:
            if path in self.references:
                self._toolbox.msg_warning.emit(
                    "Reference to file <b>{0}</b> already available".format(
                        path))
                continue
            self.references.append(os.path.abspath(path))
        self.populate_reference_list(self.references)

    @Slot(bool, name="remove_references")
    def remove_references(self, checked=False):
        """Remove selected references from reference list.
        Do not remove anything if there are no references selected.
        """
        indexes = self._toolbox.ui.treeView_dc_references.selectedIndexes()
        if not indexes:  # Nothing selected
            self._toolbox.msg.emit("Please select references to remove")
            return
        else:
            rows = [ind.row() for ind in indexes]
            rows.sort(reverse=True)
            for row in rows:
                self.references.pop(row)
            self._toolbox.msg.emit("Selected references removed")
        self.populate_reference_list(self.references)

    @Slot(bool, name="copy_to_project")
    def copy_to_project(self, checked=False):
        """Copy selected file references to this Data Connection's data directory."""
        selected_indexes = self._toolbox.ui.treeView_dc_references.selectedIndexes(
        )
        if len(selected_indexes) == 0:
            self._toolbox.msg_warning.emit("No files to copy")
            return
        for index in selected_indexes:
            file_path = self.reference_model.itemFromIndex(index).data(
                Qt.DisplayRole)
            if not os.path.exists(file_path):
                self._toolbox.msg_error.emit(
                    "File <b>{0}</b> does not exist".format(file_path))
                continue
            src_dir, filename = os.path.split(file_path)
            self._toolbox.msg.emit(
                "Copying file <b>{0}</b> to Data Connection <b>{1}</b>".format(
                    filename, self.name))
            try:
                shutil.copy(file_path, self.data_dir)
            except OSError:
                self._toolbox.msg_error.emit("[OSError] Copying failed")
                continue

    @Slot("QModelIndex", name="open_reference")
    def open_reference(self, index):
        """Open reference in default program."""
        if not index:
            return
        if not index.isValid():
            logging.error("Index not valid")
            return
        else:
            reference = self.file_references()[index.row()]
            url = "file:///" + reference
            # noinspection PyTypeChecker, PyCallByClass, PyArgumentList
            res = QDesktopServices.openUrl(QUrl(url, QUrl.TolerantMode))
            if not res:
                self._toolbox.msg_error.emit(
                    "Failed to open reference:<b>{0}</b>".format(reference))

    @Slot("QModelIndex", name="open_data_file")
    def open_data_file(self, index):
        """Open data file in default program."""
        if not index:
            return
        if not index.isValid():
            logging.error("Index not valid")
            return
        else:
            data_file = self.data_files()[index.row()]
            url = "file:///" + os.path.join(self.data_dir, data_file)
            # noinspection PyTypeChecker, PyCallByClass, PyArgumentList
            res = QDesktopServices.openUrl(QUrl(url, QUrl.TolerantMode))
            if not res:
                self._toolbox.msg_error.emit(
                    "Opening file <b>{0}</b> failed".format(data_file))

    @busy_effect
    def show_spine_datapackage_form(self):
        """Show spine_datapackage_form widget."""
        if self.spine_datapackage_form:
            if self.spine_datapackage_form.windowState() & Qt.WindowMinimized:
                # Remove minimized status and restore window with the previous state (maximized/normal state)
                self.spine_datapackage_form.setWindowState(
                    self.spine_datapackage_form.windowState()
                    & ~Qt.WindowMinimized | Qt.WindowActive)
                self.spine_datapackage_form.activateWindow()
            else:
                self.spine_datapackage_form.raise_()
            return
        self.spine_datapackage_form = SpineDatapackageWidget(self)
        self.spine_datapackage_form.destroyed.connect(
            self.datapackage_form_destroyed)
        self.spine_datapackage_form.show()

    @Slot(name="datapackage_form_destroyed")
    def datapackage_form_destroyed(self):
        self.spine_datapackage_form = None

    def make_new_file(self):
        """Create a new blank file to this Data Connections data directory."""
        msg = "File name"
        # noinspection PyCallByClass, PyTypeChecker, PyArgumentList
        answer = QInputDialog.getText(self._toolbox,
                                      "Create new file",
                                      msg,
                                      flags=Qt.WindowTitleHint
                                      | Qt.WindowCloseButtonHint)
        file_name = answer[0]
        if not file_name:  # Cancel button clicked
            return
        if file_name.strip() == "":
            return
        # Check that file name has no invalid chars
        if any(True for x in file_name if x in INVALID_FILENAME_CHARS):
            msg = "File name <b>{0}</b> contains invalid characters.".format(
                file_name)
            # noinspection PyTypeChecker, PyArgumentList, PyCallByClass
            QMessageBox.information(self._toolbox, "Creating file failed", msg)
            return
        file_path = os.path.join(self.data_dir, file_name)
        if os.path.exists(file_path):
            msg = "File <b>{0}</b> already exists.".format(file_name)
            # noinspection PyTypeChecker, PyArgumentList, PyCallByClass
            QMessageBox.information(self._toolbox, "Creating file failed", msg)
            return
        try:
            with open(file_path, "w") as fp:
                self._toolbox.msg.emit(
                    "File <b>{0}</b> created to Data Connection <b>{1}</b>".
                    format(file_name, self.name))
        except OSError:
            msg = "Please check directory permissions."
            # noinspection PyTypeChecker, PyArgumentList, PyCallByClass
            QMessageBox.information(self._toolbox, "Creating file failed", msg)
        return

    def remove_files(self):
        """Remove selected files from data directory."""
        indexes = self._toolbox.ui.treeView_dc_data.selectedIndexes()
        if not indexes:  # Nothing selected
            self._toolbox.msg.emit("Please select files to remove")
            return
        else:
            file_list = list()
            for index in indexes:
                file_at_index = self.data_model.itemFromIndex(index).data(
                    Qt.DisplayRole)
                file_list.append(file_at_index)
            files = "\n".join(file_list)
            msg = "The following files will be removed permanently from the project\n\n" \
                  "{0}\n\n" \
                  "Are you sure?".format(files)
            # noinspection PyCallByClass, PyTypeChecker
            answer = QMessageBox.question(
                self._toolbox, "Remove {0} file(s)?".format(len(file_list)),
                msg, QMessageBox.Yes, QMessageBox.No)
            if not answer == QMessageBox.Yes:
                return
            for filename in file_list:
                path_to_remove = os.path.join(self.data_dir, filename)
                try:
                    os.remove(path_to_remove)
                    self._toolbox.msg.emit(
                        "File <b>{0}</b> removed".format(path_to_remove))
                except OSError:
                    self._toolbox.msg_error.emit(
                        "Removing file {0} failed.\nCheck permissions.".format(
                            path_to_remove))
        return

    def file_references(self):
        """Return a list of paths to files that are in this item as references."""
        return self.references

    def data_files(self):
        """Return a list of files that are in the data directory."""
        if not os.path.isdir(self.data_dir):
            return None
        return os.listdir(self.data_dir)

    @Slot(name="refresh")
    def refresh(self):
        """Refresh data files QTreeView.
        NOTE: Might lead to performance issues."""
        d = self.data_files()
        self.populate_data_list(d)

    def find_file(self, fname, visited_items):
        """Search for filename in references and data and return the path if found.
        Args:
            fname (str): File name (no path)
            visited_items (list): List of project item names that have been visited

        Returns:
            Full path to file that matches the given file name or None if not found.
        """
        # logging.debug("Looking for file {0} in DC {1}.".format(fname, self.name))
        if self in visited_items:
            self._toolbox.msg_warning.emit(
                "There seems to be an infinite loop in your project. Please fix the "
                "connections and try again. Detected at {0}.".format(
                    self.name))
            return None
        if fname in self.data_files():
            # logging.debug("{0} found in DC {1}".format(fname, self.name))
            self._toolbox.msg.emit(
                "\t<b>{0}</b> found in Data Connection <b>{1}</b>".format(
                    fname, self.name))
            path = os.path.join(self.data_dir, fname)
            return path
        for path in self.file_references(
        ):  # List of paths including file name
            p, fn = os.path.split(path)
            if fn == fname:
                # logging.debug("{0} found in DC {1}".format(fname, self.name))
                self._toolbox.msg.emit(
                    "\tReference for <b>{0}</b> found in Data Connection <b>{1}</b>"
                    .format(fname, self.name))
                return path
        visited_items.append(self)
        for input_item in self._toolbox.connection_model.input_items(
                self.name):
            # Find item from project model
            found_index = self._toolbox.project_item_model.find_item(
                input_item)
            if not found_index:
                self._toolbox.msg_error.emit(
                    "Item {0} not found. Something is seriously wrong.".format(
                        input_item))
                continue
            item = self._toolbox.project_item_model.project_item(found_index)
            if item.item_type in ["Data Store", "Data Connection"]:
                path = item.find_file(fname, visited_items)
                if path is not None:
                    return path
        return None

    def find_files(self, pattern, visited_items):
        """Search for files matching the given pattern (with wildcards) in references
        and data and return a list of matching paths.

        Args:
            pattern (str): File name (no path). May contain wildcards.
            visited_items (list): List of project item names that have been visited

        Returns:
            List of matching paths. List is empty if no matches found.
        """
        paths = list()
        if self in visited_items:
            self._toolbox.msg_warning.emit(
                "There seems to be an infinite loop in your project. Please fix the "
                "connections and try again. Detected at {0}.".format(
                    self.name))
            return paths
        # Search files that match the pattern from this Data Connection's data directory
        for data_file in self.data_files(
        ):  # data_file is a filename (no path)
            if fnmatch.fnmatch(data_file, pattern):
                # self._toolbox.msg.emit("\t<b>{0}</b> matches pattern <b>{1}</b> in Data Connection <b>{2}</b>"
                #                        .format(data_file, pattern, self.name))
                path = os.path.join(self.data_dir, data_file)
                paths.append(path)
        # Search files that match the pattern from this Data Connection's references
        for ref_file in self.file_references(
        ):  # List of paths including file name
            p, fn = os.path.split(ref_file)
            if fnmatch.fnmatch(fn, pattern):
                # self._toolbox.msg.emit("\tReference <b>{0}</b> matches pattern <b>{1}</b> "
                #                        "in Data Connection <b>{2}</b>".format(fn, pattern, self.name))
                paths.append(ref_file)
        visited_items.append(self)
        # Find items that are connected to this Data Connection
        for input_item in self._toolbox.connection_model.input_items(
                self.name):
            found_index = self._toolbox.project_item_model.find_item(
                input_item)
            if not found_index:
                self._toolbox.msg_error.emit(
                    "Item {0} not found. Something is seriously wrong.".format(
                        input_item))
                continue
            item = self._toolbox.project_item_model.project_item(found_index)
            if item.item_type in ["Data Store", "Data Connection"]:
                matching_paths = item.find_files(pattern, visited_items)
                if matching_paths is not None:
                    paths = paths + matching_paths
                    return paths
        return paths

    def populate_reference_list(self, items):
        """List file references in QTreeView.
        If items is None or empty list, model is cleared.
        """
        self.reference_model.clear()
        self.reference_model.setHorizontalHeaderItem(
            0, QStandardItem("References"))  # Add header
        if items is not None:
            for item in items:
                qitem = QStandardItem(item)
                qitem.setFlags(~Qt.ItemIsEditable)
                qitem.setData(item, Qt.ToolTipRole)
                qitem.setData(
                    self._toolbox.style().standardIcon(QStyle.SP_FileLinkIcon),
                    Qt.DecorationRole)
                self.reference_model.appendRow(qitem)

    def populate_data_list(self, items):
        """List project internal data (files) in QTreeView.
        If items is None or empty list, model is cleared.
        """
        self.data_model.clear()
        self.data_model.setHorizontalHeaderItem(
            0, QStandardItem("Data"))  # Add header
        if items is not None:
            for item in items:
                qitem = QStandardItem(item)
                qitem.setFlags(~Qt.ItemIsEditable)
                if item == 'datapackage.json':
                    qitem.setData(self.datapackage_icon, Qt.DecorationRole)
                else:
                    qitem.setData(QFileIconProvider().icon(QFileInfo(item)),
                                  Qt.DecorationRole)
                full_path = os.path.join(self.data_dir,
                                         item)  # For drag and drop
                qitem.setData(full_path, Qt.UserRole)
                self.data_model.appendRow(qitem)

    def update_name_label(self):
        """Update Data Connection tab name label. Used only when renaming project items."""
        self._toolbox.ui.label_dc_name.setText(self.name)
예제 #22
0
class SpchtChecker(QDialog):

    def __init__(self):
        super(SpchtChecker, self).__init__()
        FIXEDFONT = QFontDatabase.systemFont(QFontDatabase.FixedFont)
        FIXEDFONT.setPointSize(10)
        self.taube = Spcht()
        # * Window Setup
        self.setBaseSize(1280, 720)
        self.setMinimumSize(720, 480)
        self.setWindowTitle(i18n['window_title'])
        self.setWindowFlags(QtCore.Qt.WindowMinimizeButtonHint & QtCore.Qt.WindowMaximizeButtonHint)

        main_layout = QGridLayout(self)

        # left side
        top_file_bar = QHBoxLayout()
        self.linetext_spcht_filepath = QLineEdit()
        self.linetext_spcht_filepath.setPlaceholderText(i18n['str_sdf_file_placeholder'])
        self.linetext_spcht_filepath.setReadOnly(True)
        #self.btn_create_spcht = QPushButton(i18n['btn_create_spcht'])
        self.btn_load_spcht_file = QPushButton(i18n['btn_sdf_txt'])
        self.btn_load_spcht_retry = QPushButton(i18n['generic_retry'])
        self.btn_load_spcht_retry.setDisabled(True)
        top_file_bar.addWidget(self.linetext_spcht_filepath)
        #top_file_bar.addWidget(self.btn_create_spcht)
        top_file_bar.addWidget(self.btn_load_spcht_file)
        top_file_bar.addWidget(self.btn_load_spcht_retry)

        bottom_file_bar = QHBoxLayout()
        self.str_testdata_filepath = QLineEdit()
        self.str_testdata_filepath.setPlaceholderText(i18n['str_jsonfile_placeholder'])
        self.str_testdata_filepath.setReadOnly(True)
        self.linetext_subject_prefix = QLineEdit()
        self.linetext_subject_prefix.setPlaceholderText(i18n['str_subject_placeholder'])
        self.linetext_subject_prefix.setReadOnly(True)
        self.linetext_subject_prefix.setMaximumWidth(250)
        self.btn_load_testdata_file = QPushButton(i18n['btn_testdata_txt'])
        self.btn_load_testdata_file.setToolTip(i18n['btn_testdata_tooltip'])
        self.btn_load_testdata_file.setDisabled(True)
        self.btn_load_testdata_retry = QPushButton(i18n['generic_retry'])
        self.btn_load_testdata_retry.setToolTip(i18n['btn_retry_tooltip'])
        self.btn_load_testdata_retry.setDisabled(True)
        bottom_file_bar.addWidget(self.str_testdata_filepath)
        bottom_file_bar.addWidget(self.linetext_subject_prefix)
        bottom_file_bar.addWidget(self.btn_load_testdata_file)
        bottom_file_bar.addWidget(self.btn_load_testdata_retry)

        # middle part - View 1
        center_layout = QHBoxLayout()

        control_bar_above_treeview = QGridLayout()
        control_bar_above_treeview.setMargin(0)
        self.btn_tree_expand = QPushButton(i18n['generic_expandall'])
        self.btn_tree_expand.setFlat(True)
        self.btn_tree_expand.setFixedHeight(15)
        self.btn_tree_collapse = QPushButton(i18n['generic_collapseall'])
        self.btn_tree_collapse.setFlat(True)
        self.btn_tree_collapse.setFixedHeight(15)
        self.treeview_main_spcht_data = QTreeView()
        self.spchttree_view_model = QStandardItemModel()
        self.spchttree_view_model.setHorizontalHeaderLabels(
            [i18n['generic_name'], i18n['generic_predicate'], i18n['generic_source'], i18n['generic_objects'], i18n['generic_info'], i18n['generic_comments']])
        self.treeview_main_spcht_data.setModel(self.spchttree_view_model)
        self.treeview_main_spcht_data.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.treeview_main_spcht_data.setUniformRowHeights(True)
        control_bar_above_treeview.addWidget(self.btn_tree_expand, 0, 0)
        control_bar_above_treeview.addWidget(self.btn_tree_collapse, 0, 1)
        control_bar_above_treeview.setColumnStretch(2, 1)
        control_bar_above_treeview.addWidget(self.treeview_main_spcht_data, 1, 0, 1, 3)

        label_fields = QLabel("Fields")
        self.lst_fields = QListView()
        self.lst_fields.setMaximumWidth(200)
        self.lst_fields_model = QStandardItemModel()
        self.lst_fields.setModel(self.lst_fields_model)
        fields = QVBoxLayout()
        fields.addWidget(label_fields)
        fields.addWidget(self.lst_fields)

        label_graphs = QLabel("Graphs")
        self.lst_graphs = QListView()
        self.lst_graphs.setMaximumWidth(300)
        self.lst_graphs_model = QStandardItemModel()
        self.lst_graphs.setModel(self.lst_graphs_model)
        graphs = QVBoxLayout()
        graphs.addWidget(label_graphs)
        graphs.addWidget(self.lst_graphs)

        center_layout.addLayout(control_bar_above_treeview)
        center_layout.addLayout(fields)
        center_layout.addLayout(graphs)

        # middle part - View 2
        self.console = QTextEdit()
        self.console.setReadOnly(True)
        self.console.setFont(FIXEDFONT)

        # middle part - View 3
        self.txt_tabview = QTextEdit()
        self.txt_tabview.setReadOnly(True)
        self.txt_tabview.setFont(FIXEDFONT)
        self.tbl_tabview = QTableView()
        self.tbl_tabview.horizontalHeader().setStretchLastSection(True)
        self.tbl_tabview.horizontalHeader().setSectionsClickable(False)
        self.mdl_tbl_sparql = QStandardItemModel()
        self.mdl_tbl_sparql.setHorizontalHeaderLabels(["resource identifier", "property name", "property value"])
        self.tbl_tabview.setModel(self.mdl_tbl_sparql)
        self.tbl_tabview.setColumnWidth(0, 300)
        self.tbl_tabview.setColumnWidth(1, 300)

        tabView = QTabWidget()
        tabView.setTabShape(QTabWidget.Triangular)
        tabView.addTab(self.txt_tabview, "Text")
        tabView.addTab(self.tbl_tabview, "Table")


        # bottom
        self.bottomStack = QStackedWidget()
        self.bottomStack.setContentsMargins(0, 0, 0, 0)
        self.bottomStack.setMaximumHeight(20)
        self.btn_tristate = QPushButton()
        self.btn_tristate.setMaximumWidth(60)
        self.btn_tristate.setFlat(True)
        self.tristate = 0
        self.notifybar = QStatusBar()
        self.notifybar.setSizeGripEnabled(False)
        self.processBar = QProgressBar()
        bottombar = QHBoxLayout()
        bottombar.setContentsMargins(0, 0, 0, 0)
        bottombar.addWidget(self.btn_tristate)
        bottombar.addWidget(self.notifybar)
        randombarasWidget = QWidget()
        randombarasWidget.setLayout(bottombar)
        self.bottomStack.addWidget(randombarasWidget)
        self.bottomStack.addWidget(self.processBar)

        # * explorer layout
        self.explorer = QWidget()
        self.explore_main_vertical = QVBoxLayout(self.explorer)

        # ? top row explorer
        self.explorer_top_layout = QHBoxLayout()

        self.linetext_field_filter = QLineEdit(self.explorer)
        self.linetext_field_filter.setPlaceholderText(i18n['linetext_field_filter_placeholder'])
        # additional widgets here

        self.explorer_top_layout.addWidget(self.linetext_field_filter)
        self.explore_main_vertical.addLayout(self.explorer_top_layout)

        # ? central row
        self.explorer_center_layout = QHBoxLayout()

        self.explorer_toolbox = QToolBox(self.explorer)
        self.explorer_toolbox_page1 = QWidget()
        self.explorer_toolbox_page2 = QWidget()
        self.explorer_toolbox.addItem(self.explorer_toolbox_page1, i18n['toolbox_page1'])
        self.explorer_toolbox.addItem(self.explorer_toolbox_page2, i18n['toolbox_page2'])
        self.explorer_dictionary_treeview = QTreeView(self.explorer_toolbox_page1)
        self.explorer_marc_treeview = QTreeView(self.explorer_toolbox_page2)

        self.explorer_center_layout.addWidget(self.explorer_toolbox)
        self.explore_main_vertical.addLayout(self.explorer_center_layout)

        # ? bottom row
        self.explorer_bottom_layout = QHBoxLayout()

        self.explorer_left_horizontal_spacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
        self.explorer_right_horizontal_spacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
        self.explorer_leftleft_button = QPushButton("<<")
        self.explorer_left_button = QPushButton("<")
        self.explorer_rightright_button = QPushButton(">>")
        self.explorer_right_button = QPushButton(">")
        self.explorer_bottom_center_layout = QVBoxLayout()
        self.explorer_linetext_search = QLineEdit(self.explorer)
        self.explorer_center_search_button = QPushButton(i18n['find'])
        self.explorer_bottom_center_layout.addWidget(self.explorer_linetext_search)
        self.explorer_bottom_center_layout.addWidget(self.explorer_center_search_button)

        self.explorer_bottom_layout.addItem(self.explorer_left_horizontal_spacer)
        self.explorer_bottom_layout.addWidget(self.explorer_leftleft_button)
        self.explorer_bottom_layout.addWidget(self.explorer_left_button)
        self.explorer_bottom_layout.addLayout(self.explorer_bottom_center_layout)
        self.explorer_bottom_layout.addWidget(self.explorer_right_button)
        self.explorer_bottom_layout.addWidget(self.explorer_rightright_button)
        self.explorer_bottom_layout.addItem(self.explorer_right_horizontal_spacer)

        self.explore_main_vertical.addLayout(self.explorer_bottom_layout)

        # general layouting
        self.MainPageLayout = QStackedWidget()
        randomStackasWidget = QWidget()
        randomStackasWidget.setLayout(center_layout)
        self.MainPageLayout.addWidget(self.console)
        self.MainPageLayout.addWidget(randomStackasWidget)
        self.MainPageLayout.addWidget(tabView)
        self.MainPageLayout.addWidget(self.explorer)

        main_layout.addLayout(top_file_bar, 0, 0)
        main_layout.addWidget(self.MainPageLayout, 1, 0)
        main_layout.addLayout(bottom_file_bar, 2, 0)
        #main_layout.addLayout(bottombar, 3, 0)
        main_layout.addWidget(self.bottomStack, 3, 0)

        # * Event Binds
        self.btn_load_spcht_file.clicked.connect(self.btn_spcht_load_dialogue)
        self.btn_load_spcht_retry.clicked.connect(self.btn_spcht_load_retry)
        self.btn_tristate.clicked.connect(self.toogleTriState)
        self.btn_load_testdata_file.clicked.connect(self.btn_clk_loadtestdata)
        self.btn_load_testdata_retry.clicked.connect(self.btn_clk_loadtestdata_retry)
        self.btn_tree_expand.clicked.connect(self.treeview_main_spcht_data.expandAll)
        self.btn_tree_collapse.clicked.connect(self.treeview_main_spcht_data.collapseAll)
        self.toogleTriState(0)

        # various
        self.console.insertPlainText(time_log(f"Init done, program started"))
        self.console.insertPlainText(f"Working Directory: {os.getcwd()}")

    def btn_spcht_load_retry(self):
        self.load_spcht(self.linetext_spcht_filepath.displayText())

    def load_spcht(self, path_To_File):
        try:
            with open(path_To_File, "r") as file:
                testdict = json.load(file)
                status, output = SpchtUtility.schema_validation(testdict, schema="./SpchtSchema.json")
        except json.decoder.JSONDecodeError as e:
            self.console.insertPlainText(time_log(f"JSON Error: {str(e)}"))
            self.write_status("Json error while loading Spcht")
            self.toogleTriState(0)
            return None
        except FileNotFoundError as e:
            self.console.insertPlainText(time_log(f"File not Found: {str(e)}"))
            self.write_status("Spcht file could not be found")
            self.toogleTriState(0)
            return None

        if status:
            if not self.taube.load_descriptor_file(path_To_File):
                self.console.insertPlainText(time_log(
                    f"Unknown error while loading SPCHT, this is most likely something the checker engine doesnt account for, it might be 'new'"))
                self.write_status("Unexpected kind of error while loading Spcht")
                return False
            self.toogleTriState(1)
            self.btn_load_testdata_file.setDisabled(False)
            self.populate_treeview_with_spcht()
            self.populate_text_views()
            self.write_status("Loaded spcht discriptor file")
        else:
            self.console.insertPlainText(time_log(f"SPCHT Schema Error: {output}"))
            self.write_status("Loading of spcht failed")
            self.toogleTriState(0)
            return None

    def populate_treeview_with_spcht(self):
        i = 0
        # populate views
        if self.spchttree_view_model.hasChildren():
            self.spchttree_view_model.removeRows(0, self.spchttree_view_model.rowCount())
        for each in self.taube:
            i += 1
            tree_row = QStandardItem(each.get('name', f"Element #{i}"))
            SpchtChecker.populate_treeview_recursion(tree_row, each)
            tree_row.setEditable(False)
            self.spchttree_view_model.appendRow(tree_row)
            self.treeview_main_spcht_data.setFirstColumnSpanned(i - 1, self.treeview_main_spcht_data.rootIndex(), True)

    @staticmethod
    def populate_treeview_recursion(parent, node):
        info = ""
        if node.get('type') == "mandatory":
            col0 = QStandardItem("!!!")
            col0.setToolTip("This field is mandatory")
        else:
            col0 = QStandardItem("")
        col1 = QStandardItem(node.get('predicate', ""))
        col1.setToolTip(node.get('predicate', ""))
        col2 = QStandardItem(node.get('source'))
        fields = node.get('field', "") + " |"
        if 'alternatives' in node:
            fields += " Alts: "
            for each in node['alternatives']:
                fields += f"{each}, "
        col3 = QStandardItem(fields[:-2])
        col3.setToolTip(fields[:-2])
        # other fields
        additionals = ["append", "prepend", "cut", "replace", "match", "joined_field"]
        for each in additionals:
            if each in node:
                info += f"{node[each]}; "
        col5 = QStandardItem(info[:-2])
        col5.setToolTip(info[:2])
        # comments
        commentlist = []
        for each in node.keys():
            finding = re.match(r"(?i)^(comment).*$", each)
            if finding is not None:
                commentlist.append(finding.string)
        commentText = ""
        commentBubble = ""
        for each in commentlist:
            commentText += node[each] + ", "
            commentBubble += node[each] + "\n"
        col6 = QStandardItem(commentText[:-2])
        col6.setToolTip(commentBubble[:-1])
        disableEdits(col0, col1, col2, col3, col5, col6)
        parent.appendRow([col0, col1, col2, col3, col5, col6])
        if 'fallback' in node:
            SpchtChecker.populate_treeview_recursion(parent, node['fallback'])

    def populate_text_views(self):
        # retrieve used fields & graphs
        fields = self.taube.get_node_fields()
        predicates = self.taube.get_node_predicates()
        self.lst_fields_model.clear()
        self.lst_graphs_model.clear()
        for each in fields:
            tempItem = QStandardItem(each)
            tempItem.setEditable(False)
            self.lst_fields_model.appendRow(tempItem)
        for each in predicates:
            tempItem = QStandardItem(each)
            tempItem.setEditable(False)
            self.lst_graphs_model.appendRow(tempItem)

    def toogleTriState(self, status=0):
        toggleTexts = ["Console", "View", "Tests", "Explorer"]
        if isinstance(status, bool):  # connect calls as false
            if self.tristate == 2:
                self.tristate = 0
            else:
                self.tristate += 1
            self.MainPageLayout.setCurrentIndex(self.tristate)
        else:
            self.MainPageLayout.setCurrentIndex(status)
            self.tristate = self.MainPageLayout.currentIndex()
        self.btn_tristate.setText(toggleTexts[self.tristate])

    def btn_spcht_load_dialogue(self):
        path_To_File, file_type = QtWidgets.QFileDialog.getOpenFileName(self, "Open spcht descriptor file", "../", "Spcht Json File (*.spcht.json);;Json File (*.json);;Every file (*.*)")

        if not path_To_File :
            return None

        self.btn_load_spcht_retry.setDisabled(False)
        self.linetext_spcht_filepath.setText(path_To_File)
        self.load_spcht(path_To_File)

    def btn_clk_loadtestdata(self):
        path_To_File, type = QtWidgets.QFileDialog.getOpenFileName(self, "Open sample data", "../",
                                                                   "Json File (*.json);;Every file (*.*)")

        if path_To_File == "":
            return None

        graphtext = self.linetext_subject_prefix.displayText()
        graph, status = QtWidgets.QInputDialog.getText(self, "Insert Subject name",
                                                    "Insert non-identifier part of the subject that is supposed to be mapped onto",
                                                    text=graphtext)
        if status is False or graph.strip() == "":
            return None
        if self.btn_act_loadtestdata(path_To_File, graph):
            self.btn_load_testdata_retry.setDisabled(False)
            self.str_testdata_filepath.setText(path_To_File)
            self.linetext_subject_prefix.setText(graph)

    def btn_clk_loadtestdata_retry(self):
        self.load_spcht(self.linetext_spcht_filepath.displayText())
        self.btn_act_loadtestdata(self.str_testdata_filepath.displayText(), self.linetext_subject_prefix.displayText())
        # its probably bad style to directly use interface element text

    def btn_act_loadtestdata(self, filename, graph):
        debug_dict = {}  # TODO: loading of definitions
        basePath = Path(filename)
        descriPath = os.path.join(f"{basePath.parent}", f"{basePath.stem}.descri{basePath.suffix}")
        print(descriPath)
        # the ministry for bad python hacks presents you this path thingy, pathlib has probably something better i didnt find in 10 seconds of googling
        try:
            with open(descriPath) as file:  # complex file operation here
                temp_dict = json.load(file)
                if isinstance(temp_dict, dict):
                    code_green = 1
                    for key, value in temp_dict.items():
                        if not isinstance(key, str) or not isinstance(value, str):
                            self.write_status("Auxilliary data isnt in expected format")
                            code_green = 0
                            break
                    if code_green == 1:
                        debug_dict = temp_dict
        except FileNotFoundError:
            self.write_status("No auxilliary data has been found")
            pass  # nothing happens
        except json.JSONDecodeError:
            self.write_status("Loading of auxilliary testdata failed due a json error")
            pass  # also okay
        # loading debug data from debug dict if possible
        time_process_start = datetime.now()
        try:
            with open(filename, "r") as file:
                thetestset = json.load(file)
        except FileNotFoundError:
            self.write_status("Loading of example Data file failed.")
            return False
        except json.JSONDecodeError as e:
            self.write_status(f"Example data contains json errors: {e}")
            self.console.insertPlainText(time_log(f"JSON Error in Example File: {str(e)}"))
            return False
        tbl_list = []
        text_list = []
        thetestset = handle_variants(thetestset)
        self.progressMode(True)
        self.processBar.setMaximum(len(thetestset))
        i = 0
        for entry in thetestset:
            i += 1
            self.processBar.setValue(i)
            try:
                temp = self.taube.process_data(entry, graph)
            except Exception as e:  # probably an AttributeError but i actually cant know, so we cast the WIDE net
                self.progressMode(False)
                self.write_status(f"SPCHT interpreting encountered an exception {e}")
                return False
            if isinstance(temp, list):
                text_list.append(
                "\n=== {} - {} ===\n".format(entry.get('id', "Unknown ID"), debug_dict.get(entry.get('id'), "Ohne Name")))
                for each in temp:
                    if each[3] == 0:
                        tbl_list.append((each[0], each[1], each[2]))
                        tmp_sparql = f"<{each[0]}> <{each[1]}> \"{each[2]}\" . \n"
                    else:  # "<{}> <{}> <{}> .\n".format(graph + ressource, node['graph'], facet))
                        tmp_sparql = f"<{each[0]}> <{each[1]}> <{each[2]}> . \n"
                        tbl_list.append((each[0], each[1], f"<{each[2]}>"))
                    text_list.append(tmp_sparql)
        # txt view
        self.txt_tabview.clear()
        for each in text_list:
            self.txt_tabview.insertPlainText(each)
        # table view
        if self.mdl_tbl_sparql.hasChildren():
            self.mdl_tbl_sparql.removeRows(0, self.mdl_tbl_sparql.rowCount())
        for each in tbl_list:
            col0 = QStandardItem(each[0])
            col1 = QStandardItem(each[1])
            col2 = QStandardItem(each[2])
            disableEdits(col0, col1, col2)
            self.mdl_tbl_sparql.appendRow([col0, col1, col2])
        self.toogleTriState(2)
        time3 = datetime.now()-time_process_start
        self.write_status(f"Testdata processing finished, took {delta_time_human(microseconds=time3.microseconds)}")
        self.progressMode(False)
        return True

    def write_status(self, text):
        self.notifybar.showMessage(time_log(text, time_string="%H:%M:%S", spacer=" ", end=""))

    def progressMode(self, mode):
        # ! might go hay wire if used elsewhere cause it resets the buttons in a sense, unproblematic when
        # ! only used in processData cause all buttons are active there
        if mode:
            self.btn_load_testdata_retry.setDisabled(True)
            self.btn_load_testdata_file.setDisabled(True)
            self.btn_load_spcht_retry.setDisabled(True)
            self.btn_load_spcht_file.setDisabled(True)
            self.bottomStack.setCurrentIndex(1)
        else:
            self.btn_load_testdata_retry.setDisabled(False)
            self.btn_load_testdata_file.setDisabled(False)
            self.btn_load_spcht_retry.setDisabled(False)
            self.btn_load_spcht_file.setDisabled(False)
            self.bottomStack.setCurrentIndex(0)
    def testClear(self):

        model = QStandardItemModel()
        root = model.invisibleRootItem()
        model.clear()
        self.assertFalse(shiboken.isValid(root))
예제 #24
0
class View(ProjectItem):
    def __init__(self, name, description, x, y, toolbox, project, logger):
        """
        View class.

        Args:
            name (str): Object name
            description (str): Object description
            x (float): Initial X coordinate of item icon
            y (float): Initial Y coordinate of item icon
            toolbox (ToolboxUI): a toolbox instance
            project (SpineToolboxProject): the project this item belongs to
            logger (LoggerInterface): a logger instance
        """
        super().__init__(name, description, x, y, project, logger)
        self._ds_views = {}
        self._references = dict()
        self.reference_model = QStandardItemModel()  # References to databases
        self._spine_ref_icon = QIcon(QPixmap(":/icons/Spine_db_ref_icon.png"))

    @staticmethod
    def item_type():
        """See base class."""
        return "View"

    @staticmethod
    def category():
        """See base class."""
        return "Views"

    def make_signal_handler_dict(self):
        """Returns a dictionary of all shared signals and their handlers.
        This is to enable simpler connecting and disconnecting."""
        s = super().make_signal_handler_dict()
        s[self._properties_ui.toolButton_view_open_dir.
          clicked] = lambda checked=False: self.open_directory()
        s[self._properties_ui.pushButton_view_open_ds_view.
          clicked] = self.open_view
        return s

    def activate(self):
        """Restore selections and connect signals."""
        self.restore_selections()
        super().connect_signals()

    def deactivate(self):
        """Save selections and disconnect signals."""
        self.save_selections()
        if not super().disconnect_signals():
            logging.error("Item %s deactivation failed", self.name)
            return False
        return True

    def restore_selections(self):
        """Restore selections into shared widgets when this project item is selected."""
        self._properties_ui.label_view_name.setText(self.name)
        self._properties_ui.treeView_view.setModel(self.reference_model)

    def save_selections(self):
        """Save selections in shared widgets for this project item into instance variables."""
        self._properties_ui.treeView_view.setModel(None)

    @Slot(bool)
    def open_view(self, checked=False):
        """Opens references in a view window.
        """
        indexes = self._selected_indexes()
        database_urls = self._database_urls(indexes)
        if not database_urls:
            return
        db_urls = [str(x[0]) for x in database_urls]
        # Mangle database paths to get a hashable string identifying the view window.
        view_id = ";".join(sorted(db_urls))
        if self._restore_existing_view_window(view_id):
            return
        view_window = self._make_view_window(database_urls)
        if not view_window:
            return
        view_window.show()
        view_window.destroyed.connect(lambda: self._ds_views.pop(view_id))
        self._ds_views[view_id] = view_window

    def populate_reference_list(self):
        """Populates reference list."""
        self.reference_model.clear()
        self.reference_model.setHorizontalHeaderItem(
            0, QStandardItem("References"))  # Add header
        for db in sorted(self._references, reverse=True):
            qitem = QStandardItem(db)
            qitem.setFlags(~Qt.ItemIsEditable)
            qitem.setData(self._spine_ref_icon, Qt.DecorationRole)
            self.reference_model.appendRow(qitem)

    def update_name_label(self):
        """Update View tab name label. Used only when renaming project items."""
        self._properties_ui.label_view_name.setText(self.name)

    def execute_forward(self, resources):
        """see base class"""
        self._update_references_list(resources)
        return True

    def _do_handle_dag_changed(self, resources):
        """Update the list of references that this item is viewing."""
        self._update_references_list(resources)

    def _update_references_list(self, resources_upstream):
        """Updates the references list with resources upstream.

        Args:
            resources_upstream (list): ProjectItemResource instances
        """
        self._references.clear()
        for resource in resources_upstream:
            if resource.type_ == "database" and resource.scheme == "sqlite":
                url = make_url(resource.url)
                self._references[url.database] = (url, resource.provider.name)
            elif resource.type_ == "file":
                filepath = resource.path
                if os.path.splitext(filepath)[1] == '.sqlite':
                    url = URL("sqlite", database=filepath)
                    self._references[url.database] = (url,
                                                      resource.provider.name)
        self.populate_reference_list()

    def _selected_indexes(self):
        """Returns selected indexes."""
        selection_model = self._properties_ui.treeView_view.selectionModel()
        if not selection_model.hasSelection():
            self._properties_ui.treeView_view.selectAll()
        return self._properties_ui.treeView_view.selectionModel().selectedRows(
        )

    def _database_urls(self, indexes):
        """Returns list of tuples (url, provider) for given indexes."""
        return [
            self._references[index.data(Qt.DisplayRole)] for index in indexes
        ]

    def _restore_existing_view_window(self, view_id):
        """Restores an existing view window and returns True if the operation was successful."""
        if view_id not in self._ds_views:
            return False
        view_window = self._ds_views[view_id]
        if view_window.windowState() & Qt.WindowMinimized:
            view_window.setWindowState(view_window.windowState()
                                       & ~Qt.WindowMinimized | Qt.WindowActive)
        view_window.activateWindow()
        return True

    def _make_view_window(self, db_maps):
        try:
            return DataStoreForm(self._project.db_mngr, *db_maps)
        except SpineDBAPIError as e:
            self._logger.msg_error.emit(e.msg)

    def tear_down(self):
        """Tears down this item. Called by toolbox just before closing. Closes all view windows."""
        for view in self._ds_views.values():
            view.close()

    def notify_destination(self, source_item):
        """See base class."""
        if source_item.item_type() == "Tool":
            self._logger.msg.emit(
                "Link established. You can visualize the ouput from Tool "
                f"<b>{source_item.name}</b> in View <b>{self.name}</b>.")
        elif source_item.item_type() == "Data Store":
            self._logger.msg.emit(
                "Link established. You can visualize Data Store "
                f"<b>{source_item.name}</b> in View <b>{self.name}</b>.")
        else:
            super().notify_destination(source_item)

    @staticmethod
    def default_name_prefix():
        """see base class"""
        return "View"
예제 #25
0
class DataConnection(ProjectItem):
    def __init__(self,
                 toolbox,
                 project,
                 logger,
                 name,
                 description,
                 x,
                 y,
                 references=None):
        """Data Connection class.

        Args:
            toolbox (ToolboxUI): QMainWindow instance
            project (SpineToolboxProject): the project this item belongs to
            logger (LoggerInterface): a logger instance
            name (str): Object name
            description (str): Object description
            x (float): Initial X coordinate of item icon
            y (float): Initial Y coordinate of item icon
            references (list): a list of file paths
        """
        super().__init__(name, description, x, y, project, logger)
        self._toolbox = toolbox
        self.reference_model = QStandardItemModel()  # References to files
        self.data_model = QStandardItemModel(
        )  # Paths of project internal files. These are found in DC data directory
        self.datapackage_icon = QIcon(QPixmap(":/icons/datapkg.png"))
        self.data_dir_watcher = None
        # Populate references model
        if references is None:
            references = list()
        # Convert relative paths to absolute
        self.references = [
            deserialize_path(r, self._project.project_dir) for r in references
        ]
        self.populate_reference_list(self.references)
        # Populate data (files) model
        data_files = self.data_files()
        self.populate_data_list(data_files)
        self.spine_datapackage_form = None

    def set_up(self):
        self.data_dir_watcher = QFileSystemWatcher(self)
        if os.path.isdir(self.data_dir):
            self.data_dir_watcher.addPath(self.data_dir)
        self.data_dir_watcher.directoryChanged.connect(self.refresh)

    @staticmethod
    def item_type():
        """See base class."""
        return ItemInfo.item_type()

    @staticmethod
    def item_category():
        """See base class."""
        return ItemInfo.item_category()

    def execution_item(self):
        """Creates DataConnection's execution counterpart."""
        data_files = [
            os.path.join(self.data_dir, f) for f in self.data_files()
        ]
        return ExecutableItem(self.name, self.file_references(), data_files,
                              self._logger)

    def make_signal_handler_dict(self):
        """Returns a dictionary of all shared signals and their handlers.
        This is to enable simpler connecting and disconnecting."""
        s = super().make_signal_handler_dict()
        # pylint: disable=unnecessary-lambda
        s[self._properties_ui.toolButton_dc_open_dir.
          clicked] = lambda checked=False: self.open_directory()
        s[self._properties_ui.toolButton_plus.clicked] = self.add_references
        s[self._properties_ui.toolButton_minus.
          clicked] = self.remove_references
        s[self._properties_ui.toolButton_add.clicked] = self.copy_to_project
        s[self._properties_ui.pushButton_datapackage.
          clicked] = self.show_spine_datapackage_form
        s[self._properties_ui.treeView_dc_references.
          doubleClicked] = self.open_reference
        s[self._properties_ui.treeView_dc_data.
          doubleClicked] = self.open_data_file
        s[self._properties_ui.treeView_dc_references.
          files_dropped] = self.add_files_to_references
        s[self._properties_ui.treeView_dc_data.
          files_dropped] = self.add_files_to_data_dir
        s[self.get_icon().
          files_dropped_on_icon] = self.receive_files_dropped_on_icon
        s[self._properties_ui.treeView_dc_references.
          del_key_pressed] = lambda: self.remove_references()
        s[self._properties_ui.treeView_dc_data.
          del_key_pressed] = lambda: self.remove_files()
        return s

    def restore_selections(self):
        """Restore selections into shared widgets when this project item is selected."""
        self._properties_ui.label_dc_name.setText(self.name)
        self._properties_ui.treeView_dc_references.setModel(
            self.reference_model)
        self._properties_ui.treeView_dc_data.setModel(self.data_model)

    @Slot("QVariant")
    def add_files_to_references(self, paths):
        """Add multiple file paths to reference list.

        Args:
            paths (list): A list of paths to files
        """
        repeated_paths = []
        new_paths = []
        for path in paths:
            if any(os.path.samefile(path, ref) for ref in self.references):
                repeated_paths.append(path)
            else:
                new_paths.append(path)
        repeated_paths = ", ".join(repeated_paths)
        if repeated_paths:
            self._logger.msg_warning.emit(
                f"Reference to file(s) <b>{repeated_paths}</b> already available"
            )
        if new_paths:
            self._toolbox.undo_stack.push(
                AddDCReferencesCommand(self, new_paths))

    def do_add_files_to_references(self, paths):
        abspaths = [os.path.abspath(path) for path in paths]
        self.references.extend(abspaths)
        self.populate_reference_list(self.references)

    @Slot("QGraphicsItem", list)
    def receive_files_dropped_on_icon(self, icon, file_paths):
        """Called when files are dropped onto a data connection graphics item.
        If the item is this Data Connection's graphics item, add the files to data."""
        if icon == self.get_icon():
            self.add_files_to_data_dir(file_paths)

    @Slot("QVariant")
    def add_files_to_data_dir(self, file_paths):
        """Add files to data directory"""
        for file_path in file_paths:
            filename = os.path.split(file_path)[1]
            self._logger.msg.emit(
                f"Copying file <b>{filename}</b> to <b>{self.name}</b>")
            try:
                shutil.copy(file_path, self.data_dir)
            except OSError:
                self._logger.msg_error.emit("[OSError] Copying failed")
                return

    @Slot(bool)
    def add_references(self, checked=False):
        """Let user select references to files for this data connection."""
        # noinspection PyCallByClass, PyTypeChecker, PyArgumentList
        answer = QFileDialog.getOpenFileNames(self._toolbox,
                                              "Add file references",
                                              self._project.project_dir, "*.*")
        file_paths = answer[0]
        if not file_paths:  # Cancel button clicked
            return
        self.add_files_to_references(file_paths)

    @Slot(bool)
    def remove_references(self, checked=False):
        """Remove selected references from reference list.
        Do not remove anything if there are no references selected.
        """
        indexes = self._properties_ui.treeView_dc_references.selectedIndexes()
        if not indexes:  # Nothing selected
            self._logger.msg.emit("Please select references to remove")
            return
        references = [ind.data(Qt.DisplayRole) for ind in indexes]
        self._toolbox.undo_stack.push(
            RemoveDCReferencesCommand(self, references))
        self._logger.msg.emit("Selected references removed")

    def do_remove_references(self, references):
        self.references = [
            r for r in self.references
            if not any(os.path.samefile(r, ref) for ref in references)
        ]
        self.populate_reference_list(self.references)

    @Slot(bool)
    def copy_to_project(self, checked=False):
        """Copy selected file references to this Data Connection's data directory."""
        selected_indexes = self._properties_ui.treeView_dc_references.selectedIndexes(
        )
        if not selected_indexes:
            self._logger.msg_warning.emit("No files to copy")
            return
        for index in selected_indexes:
            file_path = self.reference_model.itemFromIndex(index).data(
                Qt.DisplayRole)
            if not os.path.exists(file_path):
                self._logger.msg_error.emit(
                    f"File <b>{file_path}</b> does not exist")
                continue
            filename = os.path.split(file_path)[1]
            self._logger.msg.emit(
                f"Copying file <b>{filename}</b> to Data Connection <b>{self.name}</b>"
            )
            try:
                shutil.copy(file_path, self.data_dir)
            except OSError:
                self._logger.msg_error.emit("[OSError] Copying failed")
                continue

    @Slot("QModelIndex")
    def open_reference(self, index):
        """Open reference in default program."""
        if not index:
            return
        if not index.isValid():
            logging.error("Index not valid")
            return
        reference = self.file_references()[index.row()]
        url = "file:///" + reference
        # noinspection PyTypeChecker, PyCallByClass, PyArgumentList
        res = open_url(url)
        if not res:
            self._logger.msg_error.emit(
                f"Failed to open reference:<b>{reference}</b>")

    @Slot("QModelIndex")
    def open_data_file(self, index):
        """Open data file in default program."""
        if not index:
            return
        if not index.isValid():
            logging.error("Index not valid")
            return
        data_file = self.data_files()[index.row()]
        url = "file:///" + os.path.join(self.data_dir, data_file)
        # noinspection PyTypeChecker, PyCallByClass, PyArgumentList
        res = open_url(url)
        if not res:
            self._logger.msg_error.emit(
                f"Opening file <b>{data_file}</b> failed")

    @busy_effect
    def show_spine_datapackage_form(self):
        """Show spine_datapackage_form widget."""
        if self.spine_datapackage_form:
            if self.spine_datapackage_form.windowState() & Qt.WindowMinimized:
                # Remove minimized status and restore window with the previous state (maximized/normal state)
                self.spine_datapackage_form.setWindowState(
                    self.spine_datapackage_form.windowState()
                    & ~Qt.WindowMinimized | Qt.WindowActive)
                self.spine_datapackage_form.activateWindow()
            else:
                self.spine_datapackage_form.raise_()
            return
        self.spine_datapackage_form = SpineDatapackageWidget(self)
        self.spine_datapackage_form.destroyed.connect(
            self.datapackage_form_destroyed)
        self.spine_datapackage_form.show()

    @Slot()
    def datapackage_form_destroyed(self):
        """Notify a connection that datapackage form has been destroyed."""
        self.spine_datapackage_form = None

    def make_new_file(self):
        """Create a new blank file to this Data Connections data directory."""
        msg = "File name"
        # noinspection PyCallByClass, PyTypeChecker, PyArgumentList
        answer = QInputDialog.getText(self._toolbox,
                                      "Create new file",
                                      msg,
                                      flags=Qt.WindowTitleHint
                                      | Qt.WindowCloseButtonHint)
        file_name = answer[0]
        if not file_name.strip():
            return
        # Check that file name has no invalid chars
        if any(True for x in file_name if x in INVALID_FILENAME_CHARS):
            msg = f"File name <b>{file_name}</b> contains invalid characters."
            self._logger.information_box.emit("Creating file failed", msg)
            return
        file_path = os.path.join(self.data_dir, file_name)
        if os.path.exists(file_path):
            msg = f"File <b>{file_name}</b> already exists."
            self._logger.information_box.emit("Creating file failed", msg)
            return
        try:
            with open(file_path, "w"):
                self._logger.msg.emit(
                    f"File <b>{file_name}</b> created to Data Connection <b>{self.name}</b>"
                )
        except OSError:
            msg = "Please check directory permissions."
            self._logger.information_box.emit("Creating file failed", msg)
        return

    def remove_files(self):
        """Remove selected files from data directory."""
        indexes = self._properties_ui.treeView_dc_data.selectedIndexes()
        if not indexes:  # Nothing selected
            self._logger.msg.emit("Please select files to remove")
            return
        file_list = list()
        for index in indexes:
            file_at_index = self.data_model.itemFromIndex(index).data(
                Qt.DisplayRole)
            file_list.append(file_at_index)
        files = "\n".join(file_list)
        msg = (
            "The following files will be removed permanently from the project\n\n"
            "{0}\n\n"
            "Are you sure?".format(files))
        title = "Remove {0} File(s)".format(len(file_list))
        message_box = QMessageBox(QMessageBox.Question,
                                  title,
                                  msg,
                                  QMessageBox.Ok | QMessageBox.Cancel,
                                  parent=self._toolbox)
        message_box.button(QMessageBox.Ok).setText("Remove Files")
        answer = message_box.exec_()
        if answer == QMessageBox.Cancel:
            return
        for filename in file_list:
            path_to_remove = os.path.join(self.data_dir, filename)
            try:
                os.remove(path_to_remove)
                self._logger.msg.emit(f"File <b>{path_to_remove}</b> removed")
            except OSError:
                self._logger.msg_error.emit(
                    f"Removing file {path_to_remove} failed.\nCheck permissions."
                )
        return

    def file_references(self):
        """Returns a list of paths to files that are in this item as references."""
        return self.references

    def data_files(self):
        """Returns a list of files that are in the data directory."""
        if not os.path.isdir(self.data_dir):
            return []
        files = list()
        with os.scandir(self.data_dir) as scan_iterator:
            for entry in scan_iterator:
                if entry.is_file():
                    files.append(entry.path)
        return files

    @Slot("QString")
    def refresh(self, _=None):
        """Refresh data files in Data Connection Properties.
        NOTE: Might lead to performance issues."""
        d = self.data_files()
        self.populate_data_list(d)

    def populate_reference_list(self, items, emit_item_changed=True):
        """List file references in QTreeView.
        If items is None or empty list, model is cleared.
        """
        self.reference_model.clear()
        self.reference_model.setHorizontalHeaderItem(
            0, QStandardItem("References"))  # Add header
        if items is not None:
            for item in items:
                qitem = QStandardItem(item)
                qitem.setFlags(~Qt.ItemIsEditable)
                qitem.setData(item, Qt.ToolTipRole)
                qitem.setData(
                    self._toolbox.style().standardIcon(QStyle.SP_FileLinkIcon),
                    Qt.DecorationRole)
                self.reference_model.appendRow(qitem)
        if emit_item_changed:
            self.item_changed.emit()

    def populate_data_list(self, items):
        """List project internal data (files) in QTreeView.
        If items is None or empty list, model is cleared.
        """
        self.data_model.clear()
        self.data_model.setHorizontalHeaderItem(
            0, QStandardItem("Data"))  # Add header
        if items is not None:
            for item in items:
                qitem = QStandardItem(item)
                qitem.setFlags(~Qt.ItemIsEditable)
                if item == 'datapackage.json':
                    qitem.setData(self.datapackage_icon, Qt.DecorationRole)
                else:
                    qitem.setData(QFileIconProvider().icon(QFileInfo(item)),
                                  Qt.DecorationRole)
                full_path = os.path.join(self.data_dir,
                                         item)  # For drag and drop
                qitem.setData(full_path, Qt.UserRole)
                self.data_model.appendRow(qitem)
        self.item_changed.emit()

    def update_name_label(self):
        """Update Data Connection tab name label. Used only when renaming project items."""
        self._properties_ui.label_dc_name.setText(self.name)

    def resources_for_direct_successors(self):
        """see base class"""
        refs = self.file_references()
        f_list = [os.path.join(self.data_dir, f) for f in self.data_files()]
        resources = [
            ProjectItemResource(self, "file", url=pathlib.Path(ref).as_uri())
            for ref in refs + f_list
        ]
        return resources

    def _do_handle_dag_changed(self, resources):
        """See base class."""
        if not self.file_references() and not self.data_files():
            self.add_notification(
                "This Data Connection does not have any references or data. "
                "Add some in the Data Connection Properties panel.")

    def item_dict(self):
        """Returns a dictionary corresponding to this item."""
        d = super().item_dict()
        # Convert paths to relative before saving
        d["references"] = [
            serialize_path(f, self._project.project_dir)
            for f in self.file_references()
        ]
        return d

    def rename(self, new_name):
        """Rename this item.

        Args:
            new_name (str): New name
        Returns:
            bool: True if renaming succeeded, False otherwise
        """
        dirs = self.data_dir_watcher.directories()
        if dirs:
            self.data_dir_watcher.removePaths(dirs)
        if not super().rename(new_name):
            self.data_dir_watcher.addPaths(dirs)
            return False
        self.data_dir_watcher.addPath(self.data_dir)
        self.refresh()
        return True

    def tear_down(self):
        """Tears down this item. Called by toolbox just before closing.
        Closes the SpineDatapackageWidget instances opened."""
        if self.spine_datapackage_form:
            self.spine_datapackage_form.close()
        watched_paths = self.data_dir_watcher.directories()
        if watched_paths:
            self.data_dir_watcher.removePaths(watched_paths)
        self.data_dir_watcher.deleteLater()

    def notify_destination(self, source_item):
        """See base class."""
        if source_item.item_type() == "Tool":
            self._logger.msg.emit(
                f"Link established. Tool <b>{source_item.name}</b> output files will be "
                f"passed as references to item <b>{self.name}</b> after execution."
            )
        elif source_item.item_type() in ["Data Store", "Importer"]:
            # Does this type of link do anything?
            self._logger.msg.emit("Link established.")
        else:
            super().notify_destination(source_item)

    @staticmethod
    def default_name_prefix():
        """See base class."""
        return "Data Connection"
class MainWindow(QMainWindow):
    """
    A class used to represent an main window

    ...

    Attributes
    ----------
    ui: Ui_MainWindow
        UI wrapper instance
    _taskManager : TaskManager
        manage the tasks
    _logManager: LogManager:
        manage the log history for each task
    SelectedTask: MirrorTask
        points the current task selected on the TableView
    TaskList : List
        list of tasks
    Methods
    -------
    EnableControls(bEnable=True)
        Enables/disables UI controls with bEnable flag
    On_Change(selected, deselected)
        Tasks table view selection changed slot function
    On_Edit(index)
        TableView DoubleClicked slot function
    LoadTable()
        Makes the list of tasks
    EditTask()
        Opens the Task Edit Dialog box
    UpdateListItem(task)
        Updates the current selected item on TableView
    AddListItem(task):
        Adds the new task to the list
    TrySaveTask(task):
        Saves the task to XML file
    StartOperation(reverse):
        Perfomrs Backup/Restore operation
    _enter_toggle
        callback function before Robocopy thread begins
    _exit_toggle
        callback function before Robocopy thread finished

    Signals:
    --------
    log : (str)
        emited to print the log
    updateList: (str)
        emitted when the copy task has completed

    Slots:
    --------
    Log
        Logs the message
    Btn_Clicked(btn):
        Slot function to handle button clicks
    CopyCompleted(str_msg='')
        Slot for CopyCompleted signal comes from 'Backup/Restore' operation
    """
    # Signals
    log = Signal(str)
    updateList = Signal(str)

    def __init__(self):
        super(MainWindow, self).__init__()
        # make UI from design UI file
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        # connects slots to siganls
        self.log.connect(self.Log)
        self.updateList.connect(self.CopyCompleted)
        self.ui.btnNew.clicked.connect(
            lambda: self.Btn_Clicked(self.ui.btnNew))  # noqa
        self.ui.btnEdit.clicked.connect(
            lambda: self.Btn_Clicked(self.ui.btnEdit))  # noqa
        self.ui.btnRemove.clicked.connect(
            lambda: self.Btn_Clicked(self.ui.btnRemove))  # noqa
        self.ui.btnHistory.clicked.connect(
            lambda: self.Btn_Clicked(self.ui.btnHistory))  # noqa
        self.ui.btnSchedule.clicked.connect(
            lambda: self.Btn_Clicked(self.ui.btnSchedule))  # noqa
        self.ui.btnRestore.clicked.connect(
            lambda: self.Btn_Clicked(self.ui.btnRestore))  # noqa
        self.ui.btnBackup.clicked.connect(
            lambda: self.Btn_Clicked(self.ui.btnBackup))  # noqa

        # create the status bar
        self.ui.statusBar.addWidget(QLabel(f'{Settings.Product_Label}   '))
        self.ui.statusBar.addWidget(
            QLabel(f'ver:{Settings.Product_Version}   '))  # noqa
        self.linkLabel = QLabel(f'{Settings.Product_AboutLink}   ')
        self.linkLabel.setOpenExternalLinks(True)
        self.ui.statusBar.addWidget(self.linkLabel)

        # initiates the taskmanager and logmanager
        self._taskManager = TaskManager()
        self._logManager = LogManager()

        # create selectionmodel for tableview
        self.taskViewModel = QStandardItemModel(self)
        # set the model to tableview
        self.ui.tableView.setModel(self.taskViewModel)
        # connects double-clicked slot to the table
        self.ui.tableView.doubleClicked.connect(self.On_Edit)
        # connects table selct event
        self.ui.tableView.selectionModel().selectionChanged.connect(
            self.On_Change)  # noqa
        # overrides table cell delegate
        self.tableLabelDelegate = LabelItemDelegate(self.taskViewModel,
                                                    self.ui.tableView)  # noqa
        # apply the delegate to every column
        for i in range(4):
            self.ui.tableView.setItemDelegateForColumn(
                i, self.tableLabelDelegate)  # noqa
        # set the Selected Task to None
        self.SelectedTask = None
        # create Task List
        self.TaskList = []
        # Loads the table
        self.LoadTable()

    def EnableControls(self, bEnable=True):
        """Enables/disables UI controls with bEnable flag.

        Parameters
        ----------
        bEnable : Bool, optional
            The flag to disable controls (default is True)
        """
        self.ui.btnNew.setEnabled(bEnable)
        self.ui.btnEdit.setEnabled(bEnable)
        self.ui.btnRemove.setEnabled(bEnable)
        self.ui.btnHistory.setEnabled(bEnable)
        self.ui.btnBackup.setEnabled(bEnable)
        self.ui.btnRestore.setEnabled(bEnable)
        self.ui.btnSchedule.setEnabled(bEnable)

    def On_Change(self, selected, deselected):
        '''
        Tasks table view selection changed slot function
        This function switches the current task whenever user clicks
        an item on the TableView control
        Parameters:
        ----------
        selected: QModelIndex
            newly selected tableview cell
        deselected: QModelIndex
            previously selected tableview cell
        '''
        try:
            # check if there is at least one selected task
            flag = len(self.ui.tableView.selectionModel().selectedRows()) > 0
            # set the UI buttons enable/disable
            self.EnableControls(flag)
            # get the current selected index
            current_index = self.ui.tableView.selectionModel().currentIndex(
            ).row()  # noqa
            # if selected index is valid
            if current_index >= 0:
                # update the current task
                self.SelectedTask = self.TaskList[current_index]
            else:
                # enables the 'New Task' button
                self.ui.btnNew.setEnabled(True)
        except Exception as e:
            print(f"On_Change Error:{e}")

    def On_Edit(self, index):
        '''
        TableView DoubleClicked slot function
        Parameters:
        -----------
        index: QModelIndex
            double-clicked Table view cell
        '''
        try:
            # updates the current task from the selected index
            self.SelectedTask = self.TaskList[index.row()]
            # Invokes the 'Edit Task' dialog
            self.EditTask()
        except Exception as e:
            print(f"On_Edit Error: {e}")

    def LoadTable(self):
        '''
            Makes the list of tasks
            This function loads the tasks in _taskManager and lists them
        '''
        try:
            # clear the table model
            self.taskViewModel.clear()
            # get the list of tasks
            self.TaskList = self._taskManager.LoadTasks()
            # for each task
            for task in self.TaskList:
                # get the scheduled state of the task
                _str = Settings.Schedule_String if task.Scheduled else Settings.UnScheduled_String  # noqa
                # inserts new Table row
                self.taskViewModel.appendRow([
                    QStandardItem(task.Source),
                    QStandardItem(task.Target),
                    QStandardItem(_str),
                    QStandardItem(task.LastOperation)
                ])
            # names the each column label
            self.taskViewModel.setHorizontalHeaderLabels(
                Settings.Table_ColumnNames)  # noqa
            # if there is at least on task, selects the first task and
            # updates the selected task
            if len(self.TaskList) > 0:
                # set the current cell
                self.ui.tableView.setCurrentIndex(
                    self.ui.tableView.model().index(0, 1))
                # updates the UI components
                self.EnableControls(True)
                # updates the selected task
                self.SelectedTask = self.TaskList[0]

            # resize column width
            wth = [200, 200, 100, 200]
            for i in range(4):
                self.ui.tableView.setColumnWidth(i, wth[i])
            # make the last column hit the end
            self.ui.tableView.horizontalHeader().setSectionResizeMode(
                3, QHeaderView.Stretch)
            # set the text align to left
            self.ui.tableView.horizontalHeader().setDefaultAlignment(
                Qt.AlignLeft)
        except Exception as e:
            print(f"Load Table Error:{e}")

    def EditTask(self):
        '''
        Opens the Task Edit Dialog box
        '''
        try:
            # if selected task is none return
            if self.SelectedTask is None:
                return
            # make new dialog instance
            dlg = DlgEditTask(self, self.SelectedTask)
            # returns if user hit the Cancel button
            if dlg.exec_() != QDialog.Accepted:
                return
            # updates the Selected Task
            self.SelectedTask = copy.deepcopy(dlg._task)
            # Saves the edited task
            if self.TrySaveTask(self.SelectedTask):
                # Updates task list
                self.UpdateListItem(self.SelectedTask)
        except Exception as e:
            print(f"Edit Task Error:{e}")

    def CopyCompleted(self, str_msg=''):
        '''
        Slot for CopyCompleted signal comes from 'Backup/Restore' operation
        '''
        try:
            # get the current timestamp
            timestamp = datetime.datetime.now().strftime(
                Settings.Log_DateFormat)
            # updates the LastOperation time
            self.SelectedTask.LastOperation = timestamp
            # updates the selected task on the TableView
            self.UpdateListItem(self.SelectedTask)
            # Save the task
            self.TrySaveTask(self.SelectedTask)
            # Puts the log
            self._logManager.AddLog(self.SelectedTask, str_msg, timestamp)
        except Exception as e:
            print(f"Copy Completed Error{e}")

    def UpdateListItem(self, task):
        '''
        Updates the current selected item on TableView
        Parameters:
        -----------
        task: MirrorTask
            currently selected task
        '''
        try:
            # if selected task is None, return
            if task is None:
                return
            # Checks if the selected task exist on task list
            index = -1
            for i in range(len(self.TaskList)):
                # compare GUIDs
                if self.TaskList[i].Guid == task.Guid:
                    index = i
                    break
            # if not EXIST, return
            if index < 0:
                return
            # updates the Task Manager
            self.TaskList[i] = self.SelectedTask
            # Rewrites the TableView item
            self.taskViewModel.setItem(index, 0, QStandardItem(task.Source))
            self.taskViewModel.setItem(index, 1, QStandardItem(task.Target))
            _str = Settings.Schedule_String if task.Scheduled else Settings.UnScheduled_String  # noqa
            self.taskViewModel.setItem(index, 2, QStandardItem(_str))
            self.taskViewModel.setItem(index, 3,
                                       QStandardItem(task.LastOperation))
        except Exception as e:
            print(f"UpdateListItem Error:{e}")

    def Btn_Clicked(self, btn):
        '''
        Slot function to handle button clicks
        Parameters:
        -----------
        btn : QPushButton
            sender that user hit the button
        '''
        try:
            # 'New Task' button
            if btn == self.ui.btnNew:
                # creates new task instance
                new_task = MirrorTask()
                # Opens the 'Edit Task' dialog with created task
                dlg = DlgEditTask(self, new_task)
                # if 'Cancel' was hit, return
                if dlg.exec_() != QDialog.Accepted:
                    return
                # updates the created task from dialog
                new_task = copy.deepcopy(dlg._task)
                # saves the new task
                if self.TrySaveTask(new_task):
                    # and adds to the list
                    self.AddListItem(new_task)
            # 'Edit' button
            elif btn == self.ui.btnEdit:
                # invokes EditTask()
                self.EditTask()
            # Remove button
            elif btn == self.ui.btnRemove:
                # check if there is anyone selected
                indices = self.ui.tableView.selectionModel().selectedRows()
                # if None of them is selected return
                if len(indices) == 0:
                    return
                # Confirms if user really wants to remove
                res = QMessageBox.question(
                    self, 'Confirmation', Settings.Task_Remove_Msg,
                    QMessageBox.StandardButtons(QMessageBox.Yes
                                                | QMessageBox.No))  # noqa
                if res == QMessageBox.No:
                    return
                try:
                    # delete task from logManager
                    self._logManager.DeleteTask(self.SelectedTask)
                    # delete task from taskManager
                    self._taskManager.DeleteTask(self.SelectedTask)
                    # set the Selected task as None
                    self.SelectedTask = None
                except Exception:
                    QMessageBox.information(self, "I/O Error",
                                            f"{Settings.Task_Remove_ErrMsg}")
                    return
                # if successfully removed, reload the list
                self.LoadTable()
            # History button
            elif btn == self.ui.btnHistory:
                # Opens the History Dialog with selected task
                dlg = DlgHistory(self, self.SelectedTask)
                dlg.exec_()
            # Schedule button
            elif btn == self.ui.btnSchedule:
                # Opens the Schedule Dialog with slected task
                dlg = DlgScedule(self, self.SelectedTask)
                if dlg.exec_() != QDialog.Accepted:
                    return
                # Save the selected task
                if self.TrySaveTask(self.SelectedTask):
                    # Update the Table list
                    self.UpdateListItem(self.SelectedTask)
            # Restore button
            elif btn == self.ui.btnRestore:
                self.StartOperation(False)
            # Backup button
            elif btn == self.ui.btnBackup:
                self.StartOperation(True)
        except Exception as e:
            print(f'Btn_Clicked Error:{e}')

    def AddListItem(self, task):
        '''
        Adds the new task to the list
        '''
        try:
            # inserts new row
            self.taskViewModel.appendRow([
                QStandardItem(task.Source),
                QStandardItem(task.Target),
                QStandardItem(task.LastOperation)
            ])
            # updates the Selected Task
            self.SelectedTask = task
            # inserts new task to TaskList
            self.TaskList.append(task)
            # selects the newly added row
            self.ui.tableView.setCurrentIndex(self.ui.tableView.model().index(
                len(self.TaskList) - 1, 1))
        except Exception as e:
            print(f"AddListItem Err:{e}")

    def TrySaveTask(self, task):
        '''
        Saves the task to XML file
        Return Value:
        ------------
            True:   Successfully Saved
            False:  An error was raised
        '''
        result = False
        try:
            result = True
            self._taskManager.SaveTask(task)
        except Exception as ex:
            QMessageBox.information(self, "Error",
                                    f"{Settings.Task_Save_ErrMsg}")
            print(f"TrySaveTask Error: {ex}")
            result = False
        return result

    def StartOperation(self, reverse):
        '''
        Perfomrs Backup/Restore operation
        Parameters:
        -----------
        reverse: Boolean
            True: Backup, False: Restore
        '''
        try:
            # backups the selected task
            selectedTask = copy.deepcopy(self.SelectedTask)
            # set the direction
            selectedTask._direction = reverse
            if selectedTask is None:
                return
            # check is robocopy file path is valid
            exe_path = os.getcwd() + Settings.RoboPath
            # if not shows error msg and return
            if not os.path.exists(exe_path):
                QMessageBox.warning(self, "Error",
                                    f"{exe_path} does not exist!")
                return
            # Pops up Pending Dialog
            if DlgPending(self, selectedTask).exec_() != QDialog.Accepted:
                return
            # set the parent of selected task of this
            selectedTask.setParent(self)
            try:
                # performs robo copy
                robocopy_thread = Thread(target=decorate_callback(
                    selectedTask.run, self._enter_toggle, self._exit_toggle))
                robocopy_thread.start()
            except Exception as e:
                print(f"RoboCopy Thread Error:{e}")
        except Exception as e:
            print(f"StartOperation Error:{e}")

    def Log(self, msg=''):
        '''
        Logs the message
        Parameters:
        -----------
        msg: string
            String to be added to log view
        '''
        try:
            pre_txt = self.ui.txtLog.toPlainText()
            self.ui.txtLog.setPlainText(pre_txt + msg)
        except Exception as e:
            print(f"Log Error:{e}")

    def _enter_toggle(self):
        '''
        callback function before Robocopy thread begins
        '''
        self.EnableControls(False)

    def _exit_toggle(self):
        '''
        callback function before Robocopy thread finished
        '''
        self.EnableControls(True)
예제 #27
0
class QFlightWaypointList(QTableView):
    def __init__(self, package: Package, flight: Flight):
        super().__init__()
        self.package = package
        self.flight = flight

        self.model = QStandardItemModel(self)
        self.setModel(self.model)
        self.model.setHorizontalHeaderLabels(["Name", "Alt", "TOT/DEPART"])

        header = self.horizontalHeader()
        header.setSectionResizeMode(0, QHeaderView.ResizeToContents)
        self.update_list()

        self.selectionModel().setCurrentIndex(
            self.indexAt(QPoint(1, 1)), QItemSelectionModel.Select
        )

    def update_list(self):
        # We need to keep just the row and rebuild the index later because the
        # QModelIndex will not be valid after the model is cleared.
        current_index = self.currentIndex().row()
        self.model.clear()

        self.model.setHorizontalHeaderLabels(["Name", "Alt", "TOT/DEPART"])

        waypoints = self.flight.flight_plan.waypoints
        for row, waypoint in enumerate(waypoints):
            self.add_waypoint_row(row, self.flight, waypoint)
        self.selectionModel().setCurrentIndex(
            self.model.index(current_index, 0), QItemSelectionModel.Select
        )
        self.resizeColumnsToContents()
        total_column_width = self.verticalHeader().width() + self.lineWidth()
        for i in range(0, self.model.columnCount()):
            total_column_width += self.columnWidth(i) + self.lineWidth()
        self.setFixedWidth(total_column_width)

    def add_waypoint_row(
        self, row: int, flight: Flight, waypoint: FlightWaypoint
    ) -> None:
        self.model.insertRow(self.model.rowCount())

        self.model.setItem(row, 0, QWaypointItem(waypoint, row))

        altitude = int(waypoint.alt.feet)
        altitude_type = "AGL" if waypoint.alt_type == "RADIO" else "MSL"
        altitude_item = QStandardItem(f"{altitude} ft {altitude_type}")
        altitude_item.setEditable(False)
        self.model.setItem(row, 1, altitude_item)

        tot = self.tot_text(flight, waypoint)
        tot_item = QStandardItem(tot)
        tot_item.setEditable(False)
        self.model.setItem(row, 2, tot_item)

    def tot_text(self, flight: Flight, waypoint: FlightWaypoint) -> str:
        if waypoint.waypoint_type == FlightWaypointType.TAKEOFF:
            return self.takeoff_text(flight)
        prefix = ""
        time = flight.flight_plan.tot_for_waypoint(waypoint)
        if time is None:
            prefix = "Depart "
            time = flight.flight_plan.depart_time_for_waypoint(waypoint)
        if time is None:
            return ""
        time = timedelta(seconds=int(time.total_seconds()))
        return f"{prefix}T+{time}"

    @staticmethod
    def takeoff_text(flight: Flight) -> str:
        takeoff_time = flight.flight_plan.takeoff_time()
        # Handle custom flight plans where we can't estimate the takeoff time.
        if takeoff_time is None:
            takeoff_time = timedelta()
        start_time = timedelta(seconds=int(takeoff_time.total_seconds()))
        return f"T+{start_time}"
예제 #28
0
class Main(QWidget):
    def __init__(self):
        super().__init__()

        self.user_entry = QLineEdit("user5301")
        # self.user_entry.setStyleSheet("font: corbel; font-size: 12px;")
        self.pass_entry = QLineEdit("e", self)
        self.odbc = ""
        self.bars = ""
        self.table_query = ""
        self.pass_entry.setEchoMode(QLineEdit.PasswordEchoOnEdit)
        self.ru_entry = QLineEdit("328845", self)
        # self.ru_entry.setStyleSheet("font: corbel; font-size: 12px;")
        self.cur_entry = QLineEdit("840")
        # self.cur_entry.setStyleSheet("font: corbel; font-size: 12px;")
        self.balance_entry = QLineEdit("213000001")
        # self.balance_entry.setStyleSheet("font: corbel; font-size: 12px;")
        self.ok_button = QPushButton(
            "Запрос (Пользователь и пароль должны быть правильными.)")
        self.ok_button.setStyleSheet(
            "font: corbel; font-size: 12px; color: rgb(0, 0, 255)")
        # self.ok_button.setStyleSheet("color: rgb(160, 70, 70)")
        self.error_label = QLabel("Количество строк или ошибки будут тут")
        self.error_label.setStyleSheet("color: rgb(255, 0, 0)")

        self.start_button1 = QPushButton("Старт")
        self.stop_button1 = QPushButton("Стоп")
        self.start_button1.setEnabled(True)
        self.stop_button1.setEnabled(False)
        self.start_button1.setStyleSheet("background-color: rgb(81,142,144)")
        self.interval_l = QLabel("Интервал")
        # self.interval_l.setStyleSheet("font: corbel; font-size: 12px; color: rgb(0, 0, 255)")
        self.interval_e = QLineEdit()
        self.timer_id = 0
        self.table = QTableView()
        # Create model
        # self.sqm = QSqlQueryModel(parent=self.table)
        self.standart_item_model = QStandardItemModel()

        self.init_ui()

    def on_start(self):
        password = self.pass_entry.text()
        if password == "e":
            self.print_and_label("Вы не ввели пароль!")
        else:
            self.timer_id = self.startTimer(int(self.interval_e.text()))
            self.start_button1.setEnabled(False)
            self.stop_button1.setEnabled(True)

    def on_stop(self):
        print("Таймер остановлен.", self.timer_id)
        if self.timer_id:
            self.killTimer(self.timer_id)
            self.timer_id = 0
            self.start_button1.setEnabled(True)
            self.stop_button1.setEnabled(False)

    def print_and_label(self, text):
        print(text)
        self.error_label.setText(text)

    def run(self):

        rows = connect_to_base_and_execute(
            self.table_query.format(self.ru_entry.text().strip(),
                                    self.cur_entry.text().strip(),
                                    int(self.balance_entry.text().strip())),
            self.error_label, self.user_entry.text(),
            keyring.get_password(getpass.getuser(),
                                 self.user_entry.text()), self.ok_button, "''")
        self.standart_item_model.clear()
        self.table.clearSpans()
        item_list = []
        if rows:
            #print("rows", rows)
            for values in rows:
                item_list = []
                for value in values:
                    if type(value) == int:
                        item = QStandardItem(str(value))
                    else:
                        item = QStandardItem(value)
                    item_list.append(item)
                self.standart_item_model.appendRow(item_list)
            self.standart_item_model.setHorizontalHeaderLabels(
                ["Счет", "РУ", "Валюта", "Остаток"])
            self.table.setModel(self.standart_item_model)
            self.table.setColumnWidth(0, 150)
            self.table.setColumnWidth(1, 60)
            self.table.setColumnWidth(2, 80)
            self.table.setColumnWidth(3, 150)
            if self.standart_item_model.rowCount() > 0:
                frequency = 2500
                duration = 2000
                winsound.Beep(frequency, duration)

    def timerEvent(self, event):
        # self.error_label.setText("Сработал таймер" + str(event.timerId()))

        print("Сработал таймер", str(event.timerId()))
        self.run()

    def init_ui(self):
        file_name = 'ini_balance'
        with open(file_name) as f:
            lines = f.readlines()
            try:
                self.interval_e.setText(lines[0])
            except:
                self.error_label.setText(
                    ' Возможно в первой строке файла ini_balance нет времени таймера!'
                )
            try:
                self.bars = lines[1]
            except:
                self.error_label.setText(
                    ' Возможно во второй строке файла ini_balance нет запроса!'
                )
            try:
                self.table_query = lines[2]
            except:
                self.error_label.setText(
                    ' Возможно в третьей строке файла ini_balance нет запроса!'
                )

        label = QLabel(self)
        label.setAlignment(Qt.AlignRight)
        label.resize(30, 30)
        image = QPixmap("b.jfif", format="JPG").scaled(label.width(),
                                                       label.height())
        #image = QPixmap("mon.png", format="PNG")

        label.setPixmap(image)
        self.group = QGroupBox("Таймер остатка")
        self.group.setStyleSheet("font: corbel; font-size: 14px;")
        v_group = QVBoxLayout()  # Контейнер для группы
        v_group.addWidget(self.start_button1)
        v_group.addWidget(self.stop_button1)
        v_group.addWidget(self.interval_l)
        v_group.addWidget(self.interval_e)
        self.group.setLayout(v_group)

        form = QFormLayout()
        form.addRow("", label)
        form.addRow("По&льзователь", self.user_entry)
        form.addRow("&Пароль", self.pass_entry)
        form.addRow("&МФО области", self.ru_entry)
        form.addRow("&Валюта", self.cur_entry)
        form.addRow("&Необходимый остаток", self.balance_entry)
        form.addRow("", self.ok_button)
        form.addRow("", self.group)
        form.addRow("&Результат", self.table)
        form.addRow("&Ошибки", self.error_label)

        self.setLayout(form)
        # k10 - work with password
        # is_right in - hash, return - T/F; set_p in - pass return - hash
        hash_pass = hashlib.md5(self.pass_entry.text().encode('utf-8'))
        # print("hash", hash_pass)
        # keyring.set_password(getpass.getuser(), self.user_entry.text(), self.pass_entry.text())
        self.pass_entry.editingFinished.connect(
            lambda: k10.keyring_pass(getpass.getuser(), self.user_entry.text(
            ), self.pass_entry, hash_pass, self.ru_entry))
        self.ok_button.clicked.connect(lambda: self.run())
        self.start_button1.clicked.connect(self.on_start)
        self.stop_button1.clicked.connect(self.on_stop)
        self.setGeometry(300, 100, 650, 550)
        self.setWindowTitle('Ждем деньги')

        self.show()
class ToolSpecificationWidget(QWidget):
    def __init__(self, toolbox, tool_specification=None):
        """A widget to query user's preferences for a new tool specification.

        Args:
            toolbox (ToolboxUI): QMainWindow instance
            tool_specification (ToolSpecification): If given, the form is pre-filled with this specification
        """
        from ..ui.tool_specification_form import Ui_Form

        super().__init__(parent=toolbox, f=Qt.Window)  # Inherit stylesheet from ToolboxUI
        # Setup UI from Qt Designer file
        self.ui = Ui_Form()
        self.ui.setupUi(self)
        # Class attributes
        self._toolbox = toolbox
        self._project = self._toolbox.project()
        # init models
        self.sourcefiles_model = QStandardItemModel()
        self.inputfiles_model = QStandardItemModel()
        self.inputfiles_opt_model = QStandardItemModel()
        self.outputfiles_model = QStandardItemModel()
        # Add status bar to form
        self.statusbar = QStatusBar(self)
        self.statusbar.setFixedHeight(20)
        self.statusbar.setSizeGripEnabled(False)
        self.statusbar.setStyleSheet(STATUSBAR_SS)
        self.ui.horizontalLayout_statusbar_placeholder.addWidget(self.statusbar)
        # init ui
        self.ui.treeView_sourcefiles.setModel(self.sourcefiles_model)
        self.ui.treeView_inputfiles.setModel(self.inputfiles_model)
        self.ui.treeView_inputfiles_opt.setModel(self.inputfiles_opt_model)
        self.ui.treeView_outputfiles.setModel(self.outputfiles_model)
        self.ui.treeView_sourcefiles.setStyleSheet(TREEVIEW_HEADER_SS)
        self.ui.treeView_inputfiles.setStyleSheet(TREEVIEW_HEADER_SS)
        self.ui.treeView_inputfiles_opt.setStyleSheet(TREEVIEW_HEADER_SS)
        self.ui.treeView_outputfiles.setStyleSheet(TREEVIEW_HEADER_SS)
        self.ui.comboBox_tooltype.addItem("Select type...")
        self.ui.comboBox_tooltype.addItems(TOOL_TYPES)
        # if a specification is given, fill the form with data from it
        if tool_specification:
            self.ui.lineEdit_name.setText(tool_specification.name)
            check_state = Qt.Checked if tool_specification.execute_in_work else Qt.Unchecked
            self.ui.checkBox_execute_in_work.setCheckState(check_state)
            self.ui.textEdit_description.setPlainText(tool_specification.description)
            self.ui.lineEdit_args.setText(" ".join(tool_specification.cmdline_args))
            tool_types = [x.lower() for x in TOOL_TYPES]
            index = tool_types.index(tool_specification.tooltype) + 1
            self.ui.comboBox_tooltype.setCurrentIndex(index)
        # Init lists
        self.main_program_file = ""
        self.sourcefiles = list(tool_specification.includes) if tool_specification else list()
        self.inputfiles = list(tool_specification.inputfiles) if tool_specification else list()
        self.inputfiles_opt = list(tool_specification.inputfiles_opt) if tool_specification else list()
        self.outputfiles = list(tool_specification.outputfiles) if tool_specification else list()
        self.def_file_path = tool_specification.def_file_path if tool_specification else None
        self.program_path = tool_specification.path if tool_specification else None
        self.definition = dict()
        # Get first item from sourcefiles list as the main program file
        try:
            self.main_program_file = self.sourcefiles.pop(0)
            self.ui.lineEdit_main_program.setText(os.path.join(self.program_path, self.main_program_file))
        except IndexError:
            pass  # sourcefiles list is empty
        # Populate lists (this will also create headers)
        self.populate_sourcefile_list(self.sourcefiles)
        self.populate_inputfiles_list(self.inputfiles)
        self.populate_inputfiles_opt_list(self.inputfiles_opt)
        self.populate_outputfiles_list(self.outputfiles)
        self.ui.lineEdit_name.setFocus()
        self.ui.label_mainpath.setText(self.program_path)
        # Add includes popup menu
        self.add_source_files_popup_menu = AddIncludesPopupMenu(self)
        self.ui.toolButton_add_source_files.setMenu(self.add_source_files_popup_menu)
        self.ui.toolButton_add_source_files.setStyleSheet('QToolButton::menu-indicator { image: none; }')
        # Add create new or add existing main program popup menu
        self.add_main_prgm_popup_menu = CreateMainProgramPopupMenu(self)
        self.ui.toolButton_add_main_program.setMenu(self.add_main_prgm_popup_menu)
        self.ui.toolButton_add_source_files.setStyleSheet('QToolButton::menu-indicator { image: none; }')
        self.ui.toolButton_add_cmdline_tag.setMenu(self._make_add_cmdline_tag_menu())
        self.connect_signals()

    def connect_signals(self):
        """Connect signals to slots."""
        self.ui.toolButton_add_source_files.clicked.connect(self.show_add_source_files_dialog)
        self.ui.toolButton_add_source_dirs.clicked.connect(self.show_add_source_dirs_dialog)
        self.ui.lineEdit_main_program.file_dropped.connect(self.set_main_program_path)
        self.ui.treeView_sourcefiles.files_dropped.connect(self.add_dropped_includes)
        self.ui.treeView_sourcefiles.doubleClicked.connect(self.open_includes_file)
        self.ui.toolButton_minus_source_files.clicked.connect(self.remove_source_files)
        self.ui.toolButton_plus_inputfiles.clicked.connect(self.add_inputfiles)
        self.ui.toolButton_minus_inputfiles.clicked.connect(self.remove_inputfiles)
        self.ui.toolButton_plus_inputfiles_opt.clicked.connect(self.add_inputfiles_opt)
        self.ui.toolButton_minus_inputfiles_opt.clicked.connect(self.remove_inputfiles_opt)
        self.ui.toolButton_plus_outputfiles.clicked.connect(self.add_outputfiles)
        self.ui.toolButton_minus_outputfiles.clicked.connect(self.remove_outputfiles)
        self.ui.pushButton_ok.clicked.connect(self.handle_ok_clicked)
        self.ui.pushButton_cancel.clicked.connect(self.close)
        # Enable removing items from QTreeViews by pressing the Delete key
        self.ui.treeView_sourcefiles.del_key_pressed.connect(self.remove_source_files_with_del)
        self.ui.treeView_inputfiles.del_key_pressed.connect(self.remove_inputfiles_with_del)
        self.ui.treeView_inputfiles_opt.del_key_pressed.connect(self.remove_inputfiles_opt_with_del)
        self.ui.treeView_outputfiles.del_key_pressed.connect(self.remove_outputfiles_with_del)

    def populate_sourcefile_list(self, items):
        """List source files in QTreeView.
        If items is None or empty list, model is cleared.
        """
        self.sourcefiles_model.clear()
        self.sourcefiles_model.setHorizontalHeaderItem(0, QStandardItem("Additional source files"))  # Add header
        if items is not None:
            for item in items:
                qitem = QStandardItem(item)
                qitem.setFlags(~Qt.ItemIsEditable)
                qitem.setData(QFileIconProvider().icon(QFileInfo(item)), Qt.DecorationRole)
                self.sourcefiles_model.appendRow(qitem)

    def populate_inputfiles_list(self, items):
        """List input files in QTreeView.
        If items is None or empty list, model is cleared.
        """
        self.inputfiles_model.clear()
        self.inputfiles_model.setHorizontalHeaderItem(0, QStandardItem("Input files"))  # Add header
        if items is not None:
            for item in items:
                qitem = QStandardItem(item)
                qitem.setData(QFileIconProvider().icon(QFileInfo(item)), Qt.DecorationRole)
                self.inputfiles_model.appendRow(qitem)

    def populate_inputfiles_opt_list(self, items):
        """List optional input files in QTreeView.
        If items is None or empty list, model is cleared.
        """
        self.inputfiles_opt_model.clear()
        self.inputfiles_opt_model.setHorizontalHeaderItem(0, QStandardItem("Optional input files"))  # Add header
        if items is not None:
            for item in items:
                qitem = QStandardItem(item)
                qitem.setData(QFileIconProvider().icon(QFileInfo(item)), Qt.DecorationRole)
                self.inputfiles_opt_model.appendRow(qitem)

    def populate_outputfiles_list(self, items):
        """List output files in QTreeView.
        If items is None or empty list, model is cleared.
        """
        self.outputfiles_model.clear()
        self.outputfiles_model.setHorizontalHeaderItem(0, QStandardItem("Output files"))  # Add header
        if items is not None:
            for item in items:
                qitem = QStandardItem(item)
                qitem.setData(QFileIconProvider().icon(QFileInfo(item)), Qt.DecorationRole)
                self.outputfiles_model.appendRow(qitem)

    @Slot(bool, name="browse_main_program")
    def browse_main_program(self, checked=False):
        """Open file browser where user can select the path of the main program file."""
        # noinspection PyCallByClass, PyTypeChecker, PyArgumentList
        answer = QFileDialog.getOpenFileName(self, "Add existing main program file", APPLICATION_PATH, "*.*")
        file_path = answer[0]
        if not file_path:  # Cancel button clicked
            return
        self.set_main_program_path(file_path)

    @Slot("QString", name="set_main_program_path")
    def set_main_program_path(self, file_path):
        """Set main program file and folder path."""
        folder_path = os.path.split(file_path)[0]
        self.program_path = os.path.abspath(folder_path)
        # Update UI
        self.ui.lineEdit_main_program.setText(file_path)
        self.ui.label_mainpath.setText(self.program_path)

    @Slot()
    def new_main_program_file(self):
        """Creates a new blank main program file. Let's user decide the file name and path.
         Alternative version using only one getSaveFileName dialog.
         """
        # noinspection PyCallByClass
        answer = QFileDialog.getSaveFileName(self, "Create new main program", APPLICATION_PATH)
        file_path = answer[0]
        if not file_path:  # Cancel button clicked
            return
        # Remove file if it exists. getSaveFileName has asked confirmation for us.
        try:
            os.remove(file_path)
        except OSError:
            pass
        try:
            with open(file_path, "w"):
                pass
        except OSError:
            msg = "Please check directory permissions."
            # noinspection PyTypeChecker, PyArgumentList, PyCallByClass
            QMessageBox.information(self, "Creating file failed", msg)
            return
        main_dir = os.path.dirname(file_path)
        self.program_path = os.path.abspath(main_dir)
        # Update UI
        self.ui.lineEdit_main_program.setText(file_path)
        self.ui.label_mainpath.setText(self.program_path)

    @Slot(name="new_source_file")
    def new_source_file(self):
        """Let user create a new source file for this tool specification."""
        path = self.program_path if self.program_path else APPLICATION_PATH
        # noinspection PyCallByClass, PyTypeChecker, PyArgumentList
        dir_path = QFileDialog.getSaveFileName(self, "Create source file", path, "*.*")
        file_path = dir_path[0]
        if file_path == '':  # Cancel button clicked
            return
        # create file. NOTE: getSaveFileName does the 'check for existence' for us
        open(file_path, 'w').close()
        self.add_single_include(file_path)

    @Slot(bool, name="show_add_source_files_dialog")
    def show_add_source_files_dialog(self, checked=False):
        """Let user select source files for this tool specification."""
        path = self.program_path if self.program_path else APPLICATION_PATH
        # noinspection PyCallByClass, PyTypeChecker, PyArgumentList
        answer = QFileDialog.getOpenFileNames(self, "Add source file", path, "*.*")
        file_paths = answer[0]
        if not file_paths:  # Cancel button clicked
            return
        for path in file_paths:
            if not self.add_single_include(path):
                continue

    @Slot(bool, name="show_add_source_dirs_dialog")
    def show_add_source_dirs_dialog(self, checked=False):
        """Let user select a source directory for this tool specification.
        All files and sub-directories will be added to the source files.
        """
        path = self.program_path if self.program_path else APPLICATION_PATH
        # noinspection PyCallByClass, PyTypeChecker, PyArgumentList
        answer = QFileDialog.getExistingDirectory(self, "Select a directory to add to source files", path)
        file_paths = list()
        for root, _, files in os.walk(answer):
            for file in files:
                file_paths.append(os.path.abspath(os.path.join(root, file)))
        for path in file_paths:
            if not self.add_single_include(path):
                continue

    @Slot("QVariant", name="add_dropped_includes")
    def add_dropped_includes(self, file_paths):
        """Adds dropped file paths to Source files list."""
        for path in file_paths:
            if not self.add_single_include(path):
                continue

    def add_single_include(self, path):
        """Add file path to Source files list."""
        dirname, file_pattern = os.path.split(path)
        # logging.debug("program path:{0}".format(self.program_path))
        # logging.debug("{0}, {1}".format(dirname, file_pattern))
        if not self.program_path:
            self.program_path = dirname
            self.ui.label_mainpath.setText(self.program_path)
            path_to_add = file_pattern
        else:
            # check if path is a descendant of main dir.
            common_prefix = os.path.commonprefix([os.path.abspath(self.program_path), os.path.abspath(path)])
            # logging.debug("common_prefix:{0}".format(common_prefix))
            if common_prefix != self.program_path:
                self.statusbar.showMessage(
                    "Source file {0}'s location is invalid " "(should be in main directory)".format(file_pattern), 5000
                )
                return False
            path_to_add = os.path.relpath(path, self.program_path)
        if self.sourcefiles_model.findItems(path_to_add):
            self.statusbar.showMessage("Source file {0} already included".format(path_to_add), 5000)
            return False
        qitem = QStandardItem(path_to_add)
        qitem.setFlags(~Qt.ItemIsEditable)
        qitem.setData(QFileIconProvider().icon(QFileInfo(path_to_add)), Qt.DecorationRole)
        self.sourcefiles_model.appendRow(qitem)
        return True

    @busy_effect
    @Slot("QModelIndex", name="open_includes_file")
    def open_includes_file(self, index):
        """Open source file in default program."""
        if not index:
            return
        if not index.isValid():
            self._toolbox.msg_error.emit("Selected index not valid")
            return
        includes_file = self.sourcefiles_model.itemFromIndex(index).text()
        _, ext = os.path.splitext(includes_file)
        if ext in [".bat", ".exe"]:
            self._toolbox.msg_warning.emit(
                "Sorry, opening files with extension <b>{0}</b> not implemented. "
                "Please open the file manually.".format(ext)
            )
            return
        url = "file:///" + os.path.join(self.program_path, includes_file)
        # noinspection PyCallByClass, PyTypeChecker, PyArgumentList
        res = QDesktopServices.openUrl(QUrl(url, QUrl.TolerantMode))
        if not res:
            self._toolbox.msg_error.emit("Failed to open file: <b>{0}</b>".format(includes_file))

    @Slot(name="remove_source_files_with_del")
    def remove_source_files_with_del(self):
        """Support for deleting items with the Delete key."""
        self.remove_source_files()

    @Slot(bool, name="remove_source_files")
    def remove_source_files(self, checked=False):
        """Remove selected source files from include list.
        Do not remove anything if there are no items selected.
        """
        indexes = self.ui.treeView_sourcefiles.selectedIndexes()
        if not indexes:  # Nothing selected
            self.statusbar.showMessage("Please select the source files to remove", 3000)
        else:
            rows = [ind.row() for ind in indexes]
            rows.sort(reverse=True)
            for row in rows:
                self.sourcefiles_model.removeRow(row)
            if self.sourcefiles_model.rowCount() == 0:
                if self.ui.lineEdit_main_program.text().strip() == "":
                    self.program_path = None
                    self.ui.label_mainpath.clear()
            self.statusbar.showMessage("Selected source files removed", 3000)

    @Slot(bool, name="add_inputfiles")
    def add_inputfiles(self, checked=False):
        """Let user select input files for this tool specification."""
        msg = (
            "Add an input file or a directory required by your program. Wildcards "
            "<b>are not</b> supported.<br/><br/>"
            "Examples:<br/>"
            "<b>data.csv</b> -> File is copied to the same work directory as the main program.<br/>"
            "<b>input/data.csv</b> -> Creates subdirectory /input to work directory and "
            "copies file data.csv there.<br/>"
            "<b>output/</b> -> Creates an empty directory into the work directory.<br/><br/>"
        )
        # noinspection PyCallByClass, PyTypeChecker, PyArgumentList
        answer = QInputDialog.getText(self, "Add input item", msg, flags=Qt.WindowTitleHint | Qt.WindowCloseButtonHint)
        file_name = answer[0]
        if not file_name:  # Cancel button clicked
            return
        qitem = QStandardItem(file_name)
        qitem.setData(QFileIconProvider().icon(QFileInfo(file_name)), Qt.DecorationRole)
        self.inputfiles_model.appendRow(qitem)

    @Slot(name="remove_inputfiles_with_del")
    def remove_inputfiles_with_del(self):
        """Support for deleting items with the Delete key."""
        self.remove_inputfiles()

    @Slot(bool, name="remove_inputfiles")
    def remove_inputfiles(self, checked=False):
        """Remove selected input files from list.
        Do not remove anything if there are no items selected.
        """
        indexes = self.ui.treeView_inputfiles.selectedIndexes()
        if not indexes:  # Nothing selected
            self.statusbar.showMessage("Please select the input files to remove", 3000)
        else:
            rows = [ind.row() for ind in indexes]
            rows.sort(reverse=True)
            for row in rows:
                self.inputfiles_model.removeRow(row)
            self.statusbar.showMessage("Selected input files removed", 3000)

    @Slot(bool, name="add_inputfiles_opt")
    def add_inputfiles_opt(self, checked=False):
        """Let user select optional input files for this tool specification."""
        msg = (
            "Add optional input files that may be utilized by your program. <br/>"
            "Wildcards are supported.<br/><br/>"
            "Examples:<br/>"
            "<b>data.csv</b> -> If found, file is copied to the same work directory as the main program.<br/>"
            "<b>*.csv</b> -> All found CSV files are copied to the same work directory as the main program.<br/>"
            "<b>input/data_?.dat</b> -> All found files matching the pattern 'data_?.dat' will be copied to <br/>"
            "input/ subdirectory under the same work directory as the main program.<br/><br/>"
        )
        # noinspection PyCallByClass, PyTypeChecker, PyArgumentList
        answer = QInputDialog.getText(
            self, "Add optional input item", msg, flags=Qt.WindowTitleHint | Qt.WindowCloseButtonHint
        )
        file_name = answer[0]
        if not file_name:  # Cancel button clicked
            return
        qitem = QStandardItem(file_name)
        qitem.setData(QFileIconProvider().icon(QFileInfo(file_name)), Qt.DecorationRole)
        self.inputfiles_opt_model.appendRow(qitem)

    @Slot(name="remove_inputfiles_opt_with_del")
    def remove_inputfiles_opt_with_del(self):
        """Support for deleting items with the Delete key."""
        self.remove_inputfiles_opt()

    @Slot(bool, name="remove_inputfiles_opt")
    def remove_inputfiles_opt(self, checked=False):
        """Remove selected optional input files from list.
        Do not remove anything if there are no items selected.
        """
        indexes = self.ui.treeView_inputfiles_opt.selectedIndexes()
        if not indexes:  # Nothing selected
            self.statusbar.showMessage("Please select the optional input files to remove", 3000)
        else:
            rows = [ind.row() for ind in indexes]
            rows.sort(reverse=True)
            for row in rows:
                self.inputfiles_opt_model.removeRow(row)
            self.statusbar.showMessage("Selected optional input files removed", 3000)

    @Slot(bool, name="add_outputfiles")
    def add_outputfiles(self, checked=False):
        """Let user select output files for this tool specification."""
        msg = (
            "Add output files that will be archived into the Tool results directory after the <br/>"
            "Tool specification has finished execution. Wildcards are supported.<br/><br/>"
            "Examples:<br/>"
            "<b>results.csv</b> -> File is copied from work directory into results.<br/> "
            "<b>*.csv</b> -> All CSV files will copied into results.<br/> "
            "<b>output/*.gdx</b> -> All GDX files from the work subdirectory /output will be copied into <br/>"
            "results /output subdirectory.<br/><br/>"
        )
        # noinspection PyCallByClass, PyTypeChecker, PyArgumentList
        answer = QInputDialog.getText(self, "Add output item", msg, flags=Qt.WindowTitleHint | Qt.WindowCloseButtonHint)
        file_name = answer[0]
        if not file_name:  # Cancel button clicked
            return
        qitem = QStandardItem(file_name)
        qitem.setData(QFileIconProvider().icon(QFileInfo(file_name)), Qt.DecorationRole)
        self.outputfiles_model.appendRow(qitem)

    @Slot(name="remove_outputfiles_with_del")
    def remove_outputfiles_with_del(self):
        """Support for deleting items with the Delete key."""
        self.remove_outputfiles()

    @Slot(bool, name="remove_outputfiles")
    def remove_outputfiles(self, checked=False):
        """Remove selected output files from list.
        Do not remove anything if there are no items selected.
        """
        indexes = self.ui.treeView_outputfiles.selectedIndexes()
        if not indexes:  # Nothing selected
            self.statusbar.showMessage("Please select the output files to remove", 3000)
        else:
            rows = [ind.row() for ind in indexes]
            rows.sort(reverse=True)
            for row in rows:
                self.outputfiles_model.removeRow(row)
            self.statusbar.showMessage("Selected output files removed", 3000)

    @Slot()
    def handle_ok_clicked(self):
        """Checks that everything is valid, creates Tool spec definition dictionary and adds Tool spec to project."""
        # Check that tool type is selected
        if self.ui.comboBox_tooltype.currentIndex() == 0:
            self.statusbar.showMessage("Tool type not selected", 3000)
            return
        self.definition["name"] = self.ui.lineEdit_name.text()
        self.definition["description"] = self.ui.textEdit_description.toPlainText()
        self.definition["tooltype"] = self.ui.comboBox_tooltype.currentText().lower()
        flags = Qt.MatchContains
        # Check that path of main program file is valid before saving it
        main_program = self.ui.lineEdit_main_program.text().strip()
        if not os.path.isfile(main_program):
            self.statusbar.showMessage("Main program file is not valid", 6000)
            return
        # Fix for issue #241
        folder_path, file_path = os.path.split(main_program)
        self.program_path = os.path.abspath(folder_path)
        self.ui.label_mainpath.setText(self.program_path)
        self.definition["execute_in_work"] = self.ui.checkBox_execute_in_work.isChecked()
        self.definition["includes"] = [file_path]
        self.definition["includes"] += [i.text() for i in self.sourcefiles_model.findItems("", flags)]
        self.definition["inputfiles"] = [i.text() for i in self.inputfiles_model.findItems("", flags)]
        self.definition["inputfiles_opt"] = [i.text() for i in self.inputfiles_opt_model.findItems("", flags)]
        self.definition["outputfiles"] = [i.text() for i in self.outputfiles_model.findItems("", flags)]
        # Strip whitespace from args before saving it to JSON
        self.definition["cmdline_args"] = ToolSpecification.split_cmdline_args(self.ui.lineEdit_args.text())
        for k in REQUIRED_KEYS:
            if not self.definition[k]:
                self.statusbar.showMessage("{} missing".format(k), 3000)
                return
        # Create new Tool specification
        short_name = self.definition["name"].lower().replace(" ", "_")
        self.def_file_path = os.path.join(self.program_path, short_name + ".json")
        if self.call_add_tool_specification():
            self.close()

    def call_add_tool_specification(self):
        """Adds or updates Tool specification according to user's selections.
        If the name is the same as an existing tool specification, it is updated and
        auto-saved to the definition file. (User is editing an existing
        tool specification.) If the name is not in the tool specification model, creates
        a new tool specification and offer to save the definition file. (User is
        creating a new tool specification from scratch or spawning from an existing one).
        """
        # Load tool specification
        path = self.program_path
        tool = self._project.load_tool_specification_from_dict(self.definition, path)
        if not tool:
            self.statusbar.showMessage("Adding Tool specification failed", 3000)
            return False
        # Check if a tool specification with this name already exists
        row = self._toolbox.tool_specification_model.tool_specification_row(tool.name)
        if row >= 0:  # NOTE: Row 0 at this moment has 'No tool', but in the future it may change. Better be ready.
            old_tool = self._toolbox.tool_specification_model.tool_specification(row)
            def_file = old_tool.get_def_path()
            tool.set_def_path(def_file)
            if tool.__dict__ == old_tool.__dict__:  # Nothing changed. We're done here.
                return True
            # logging.debug("Updating definition for tool specification '{}'".format(tool.name))
            self._toolbox.update_tool_specification(row, tool)
        else:
            # noinspection PyCallByClass, PyTypeChecker, PyArgumentList
            answer = QFileDialog.getSaveFileName(
                self, "Save Tool specification file", self.def_file_path, "JSON (*.json)"
            )
            if answer[0] == "":  # Cancel button clicked
                return False
            def_file = os.path.abspath(answer[0])
            tool.set_def_path(def_file)
            self._toolbox.add_tool_specification(tool)
        # Save path of main program file relative to definition file in case they differ
        def_path = os.path.dirname(def_file)
        if def_path != self.program_path:
            self.definition["includes_main_path"] = os.path.relpath(self.program_path, def_path)
        # Save file descriptor
        with open(def_file, "w") as fp:
            try:
                json.dump(self.definition, fp, indent=4)
            except ValueError:
                self.statusbar.showMessage("Error saving file", 3000)
                self._toolbox.msg_error.emit("Saving Tool specification file failed. Path:{0}".format(def_file))
                return False
        return True

    def keyPressEvent(self, e):
        """Close Setup form when escape key is pressed.

        Args:
            e (QKeyEvent): Received key press event.
        """
        if e.key() == Qt.Key_Escape:
            self.close()

    def closeEvent(self, event=None):
        """Handle close window.

        Args:
            event (QEvent): Closing event if 'X' is clicked.
        """
        if event:
            event.accept()

    def _make_add_cmdline_tag_menu(self):
        """Constructs a popup menu for the '@@' button."""
        menu = QMenu(self.ui.toolButton_add_cmdline_tag)
        action = menu.addAction(str(CmdlineTag.URL_INPUTS))
        action.triggered.connect(self._add_cmdline_tag_url_inputs)
        action.setToolTip("Insert a tag that is replaced by all input database URLs.")
        action = menu.addAction(str(CmdlineTag.URL_OUTPUTS))
        action.triggered.connect(self._add_cmdline_tag_url_outputs)
        action.setToolTip("Insert a tag that is replaced be all output database URLs.")
        action = menu.addAction(str(CmdlineTag.URL))
        action.triggered.connect(self._add_cmdline_tag_data_store_url)
        action.setToolTip("Insert a tag that is replaced by the URL provided by Data Store '<data-store-name>'.")
        action = menu.addAction(str(CmdlineTag.OPTIONAL_INPUTS))
        action.triggered.connect(self._add_cmdline_tag_optional_inputs)
        action.setToolTip("Insert a tag that is replaced by a list of optional input files.")
        return menu

    def _insert_spaces_around_tag_in_args_edit(self, tag_length, restore_cursor_to_tag_end=False):
        """
        Inserts spaces before/after @@ around cursor position/selection

        Expects cursor to be at the end of the tag.
        """
        args_edit = self.ui.lineEdit_args
        text = args_edit.text()
        cursor_position = args_edit.cursorPosition()
        if cursor_position == len(text) or (cursor_position < len(text) - 1 and not text[cursor_position].isspace()):
            args_edit.insert(" ")
            appended_spaces = 1
            text = args_edit.text()
        else:
            appended_spaces = 0
        tag_start = cursor_position - tag_length
        if tag_start > 1 and text[tag_start - 2 : tag_start] == CMDLINE_TAG_EDGE:
            args_edit.setCursorPosition(tag_start)
            args_edit.insert(" ")
            prepended_spaces = 1
        else:
            prepended_spaces = 0
        if restore_cursor_to_tag_end:
            args_edit.setCursorPosition(cursor_position + prepended_spaces)
        else:
            args_edit.setCursorPosition(cursor_position + appended_spaces + prepended_spaces)

    @Slot("QAction")
    def _add_cmdline_tag_url_inputs(self, _):
        """Inserts @@url_inputs@@ tag to command line arguments."""
        tag = CmdlineTag.URL_INPUTS
        self.ui.lineEdit_args.insert(tag)
        self._insert_spaces_around_tag_in_args_edit(len(tag))

    @Slot("QAction")
    def _add_cmdline_tag_url_outputs(self, _):
        """Inserts @@url_outputs@@ tag to command line arguments."""
        tag = CmdlineTag.URL_OUTPUTS
        self.ui.lineEdit_args.insert(tag)
        self._insert_spaces_around_tag_in_args_edit(len(tag))

    @Slot("QAction")
    def _add_cmdline_tag_data_store_url(self, _):
        """Inserts @@url:<data-store-name>@@ tag to command line arguments and selects '<data-store-name>'."""
        args_edit = self.ui.lineEdit_args
        tag = CmdlineTag.URL
        args_edit.insert(tag)
        self._insert_spaces_around_tag_in_args_edit(len(tag), restore_cursor_to_tag_end=True)
        cursor_position = args_edit.cursorPosition()
        args_edit.setSelection(cursor_position - len(CMDLINE_TAG_EDGE + "<data-store_name>"), len("<data-store_name>"))

    @Slot("QAction")
    def _add_cmdline_tag_optional_inputs(self, _):
        """Inserts @@optional_inputs@@ tag to command line arguments."""
        tag = CmdlineTag.OPTIONAL_INPUTS
        self.ui.lineEdit_args.insert(tag)
        self._insert_spaces_around_tag_in_args_edit(len(tag))
    def __init__(self, parent=None, graph=None, name=None, id=None):
        super().__init__(parent)
        self.setWindowFlag(Qt.Window, True)
        self.setWindowOpacity(0.9)
        self.parent = parent
        self.ui = Ui_ShowMatrix()
        self.ui.setupUi(self)
        self.__graph = graph
        self.id = id
        data = None
        HorizontalHeaderList = []
        VerticalHeaderList = []
        dataDetailView = self.ui.dataView
        dataDetailView.setEditTriggers(QAbstractItemView.NoEditTriggers)

        if name == "邻接矩阵":
            if id == 0:
                self.setWindowTitle(name + self._tr("BezierMatrixWidget",',元素为边数'))
                data = self.__graph.adjacentMatrixWithEdges()
            else:
                self.setWindowTitle(name + self._tr("BezierMatrixWidget",',元素为权重'))
                data = self.__graph.adjacentMatrixWithWeight()
                if not (str(type(data)).find('matrix') >= 0):
                    return
            for node in self.__graph:
                HorizontalHeaderList.append(f'V{node.id()}')
            VerticalHeaderList = HorizontalHeaderList
        elif name == "可达矩阵":
            temp = self.__graph.reachableMatrix()
            data = temp[0]
            step = temp[1]
            for node in self.__graph:
                HorizontalHeaderList.append(f'V{node.id()}')
            VerticalHeaderList = HorizontalHeaderList
            self.setWindowTitle(f'{name},步数:{step}')
            label = QLabel(f"步数{step}")
            self.ui.layout.addWidget(label)

        elif name == "关联矩阵":
            self.setWindowTitle(name)
            VerticalHeaderList.clear()
            HorizontalHeaderList.clear()
            for node in self.__graph:
                VerticalHeaderList.append(f'V{node.id()}')

            for edge in self.__graph.edges():
                HorizontalHeaderList.append(f'e{edge.id()}')

            data = self.__graph.incidenceMatrix()
            HorizontalHeaderList.reverse()
            VerticalHeaderList.reverse()

        HorizontalHeaderList.reverse()
        colCount = data.shape[1]
        rowCount = data.shape[0]
        data = data.tolist()

        dataModel = QStandardItemModel(rowCount, colCount, self)
        dataSelectionModel = QItemSelectionModel(dataModel)
        dataModel.clear()
        dataModel.setHorizontalHeaderLabels(HorizontalHeaderList)
        dataModel.setVerticalHeaderLabels(VerticalHeaderList)
        dataDetailView.setModel(dataModel)
        dataDetailView.setSelectionModel(dataSelectionModel)
        dataDetailView.verticalHeader().setSectionResizeMode(QHeaderView.Fixed)
        dataDetailView.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
        dataDetailView.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
        dataDetailView.setAlternatingRowColors(True)

        for i in range(rowCount):
            strList = []
            for x in data[i]:
                strList.append(f"{x}")
            for j in range(colCount):
                item = QStandardItem(strList[j])
                dataModel.setItem(i, j, item)

        dataModel.setRowCount(rowCount)

        parent.disconnectGraph()
예제 #31
0
class MainWindow(QMainWindow):
    _tr = QCoreApplication.translate

    def __init__(self, parent=None):
        super().__init__(parent)  # 调用父类构造函数,创建窗体
        self.__scene = None  # 创建QGraphicsScene
        self.__view = None  # 创建图形视图组件
        self.ui = Ui_MainWindow()  # 创建UI对象
        self.ui.setupUi(self)  # 构造UI界面
        self.operatorFile = OperatorFile(self)

        self.__translator = None
        title = self.tr("基于Python的图的绘制及相关概念的可视化展示")
        self.setWindowTitle(title)

        self.ui.nodeDetails.setEnabled(False)
        self.ui.edgeDetails.setEnabled(False)
        self.ui.actionSave.setEnabled(False)

        self.edgeModel = QStandardItemModel(5, 5, self)
        self.edgeSelectionModel = QItemSelectionModel(self.edgeModel)
        self.edgeModel.dataChanged.connect(self.do_updateEdgeWeight)

        self.nodeModel = QStandardItemModel(5, 4, self)
        self.nodeSelectionModel = QItemSelectionModel(self.nodeModel)
        self.nodeModel.dataChanged.connect(self.do_updateNodeWeight)

        self.spinWeight = WeightSpinDelegate(0, 200, 1, self)

        self.ui.tabWidget.setVisible(False)
        self.ui.tabWidget.clear()
        self.ui.tabWidget.setTabsClosable(True)
        self.ui.tabWidget.setDocumentMode(True)
        self.setCentralWidget(self.ui.tabWidget)
        self.setAutoFillBackground(True)

        self.__buildStatusBar()  # 构造状态栏
        self.__buildUndoCommand()  # 初始化撤销重做系统
        self.__initModeMenu()
        self.__lastColumnFlag = Qt.NoItemFlags

        self.iniGraphicsSystem()

        self.__ItemId = 0  # 绘图项自定义数据的key
        self.__ItemDesc = 1  # 绘图项自定义数据的key

        self.__nodeNum = 0  # 结点的序号
        self.__edgeNum = 0  # 边的序号
        self.__textNum = 0

        self.lastColumnFlags = (Qt.ItemIsSelectable | Qt.ItemIsUserCheckable
                                | Qt.ItemIsEnabled)

        self.__graph = Graph()

    ##  ==============自定义功能函数============

    def nodeNum(self):
        return self.__nodeNum

    def edgeNum(self):
        return self.__edgeNum

    def scene(self):
        self.viewAndScene()
        return self.__scene

    def view(self):
        self.viewAndScene()
        return self.__view

    def graph(self):
        return self.__graph

    def __buildStatusBar(self):  ##构造状态栏
        self.__labViewCord = QLabel(self._tr("MainWindow", "视图坐标:"))
        self.__labViewCord.setMinimumWidth(150)
        self.ui.statusbar.addWidget(self.__labViewCord)

        self.__labSceneCord = QLabel(self._tr("MainWindow", "场景坐标:"))
        self.__labSceneCord.setMinimumWidth(150)
        self.ui.statusbar.addWidget(self.__labSceneCord)

        self.__labItemCord = QLabel(self._tr("MainWindow", "图元坐标:"))
        self.__labItemCord.setMinimumWidth(150)
        self.ui.statusbar.addWidget(self.__labItemCord)

        self.__labItemInfo = QLabel(self._tr("MainWindow", "图元信息: "))
        self.ui.statusbar.addPermanentWidget(self.__labItemInfo)
        self.__labModeInfo = QLabel(self._tr("MainWindow", "有向图模式"))
        self.ui.statusbar.addPermanentWidget(self.__labModeInfo)

    def __buildUndoCommand(self):
        self.undoStack = QUndoStack()
        self.addAction(self.ui.actionUndo)
        self.addAction(self.ui.actionRedo)
        self.ui.undoView.setStack(self.undoStack)

    def __setItemProperties(self, item, desc):  ##item是具体类型的QGraphicsItem
        self.__nodeNum = len(self.singleItems(BezierNode))
        self.__edgeNum = len(self.singleItems(BezierEdge))
        self.__textNum = len(self.singleItems(BezierText))
        item.setFlag(QGraphicsItem.ItemIsFocusable)
        item.setFlag(QGraphicsItem.ItemIsMovable)
        item.setFlag(QGraphicsItem.ItemIsSelectable)
        item.setPos(-150 + randint(1, 200), -200 + randint(1, 200))

        if type(item) is BezierNode:
            newNum = self.checkSort(self.__scene.uniqueIdList(BezierNode))
            if newNum is not None:
                item.setData(self.__ItemId, newNum)
                item.textCp.setPlainText("V" + str(newNum))
            else:
                item.setData(self.__ItemId, self.__nodeNum)
                item.textCp.setPlainText("V" + str(self.__nodeNum))
            self.__nodeNum = 1 + self.__nodeNum
        elif type(item) is BezierEdge:
            newNum = self.checkSort(self.__scene.uniqueIdList(BezierEdge))
            if newNum is not None:
                item.setData(self.__ItemId, newNum)
                item.textCp.setPlainText("e" + str(newNum))
            else:
                item.setData(self.__ItemId, self.__edgeNum)
                item.textCp.setPlainText("e" + str(self.__edgeNum))
            self.__edgeNum = 1 + self.__edgeNum
        elif type(item) is BezierText:
            newNum = self.checkSort(self.__scene.uniqueIdList(BezierText))
            if newNum is not None:
                item.setData(self.__ItemId, newNum)
            else:
                item.setData(self.__ItemId, self.__textNum)
            self.__textNum = 1 + self.__textNum

        item.setData(self.__ItemDesc, desc)  # 图件描述

        self.__scene.addItem(item)
        self.__scene.clearSelection()

        item.setSelected(True)

    def __setBrushColor(self, item):  ##设置填充颜色
        color = item.brush().__color()
        color = QColorDialog.getColor(color, self,
                                      self._tr("MainWindow", "选择填充颜色"))
        if color.isValid():
            item.setBrush(QBrush(color))

    def __initFileMenu(self):
        self.ui.actionOpen.triggered.connect(self.do_open_file)
        self.ui.actionSave.triggered.connect(self.do_save_file)
        self.ui.actionQuit.triggered.connect(self.close)

    def __initModeMenu(self):
        modeMenuGroup = QActionGroup(self)
        modeMenuGroup.addAction(self.ui.actionDigraph_Mode)
        modeMenuGroup.addAction(self.ui.actionRedigraph_Mode)

    def __updateEdgeView(self):
        edges = self.singleItems(BezierEdge)
        if len(edges):
            self.ui.edgeDetails.setEnabled(True)
        else:
            return
        edgeColCount = 5

        self.edgeModel.clear()
        edgeHeaderList = [
            self._tr("MainWindow", 'ID'),
            self._tr("MainWindow", '始点'),
            self._tr("MainWindow", '终点'),
            self._tr("MainWindow", '坐标'),
            self._tr("MainWindow", '权重')
        ]
        self.edgeModel.setHorizontalHeaderLabels(edgeHeaderList)
        self.edgeSelectionModel.currentChanged.connect(self.do_curEdgeChanged)

        self.ui.edgeDetails.setModel(self.edgeModel)
        self.ui.edgeDetails.setSelectionModel(self.edgeSelectionModel)
        self.ui.edgeDetails.verticalHeader().setSectionResizeMode(
            QHeaderView.Fixed)
        self.ui.edgeDetails.horizontalHeader().setSectionResizeMode(
            QHeaderView.ResizeToContents)
        self.ui.edgeDetails.horizontalHeader().setSectionResizeMode(
            QHeaderView.ResizeToContents)
        self.ui.edgeDetails.setAlternatingRowColors(True)
        self.edgeModel.setRowCount(len(edges))
        self.ui.edgeDetails.setItemDelegateForColumn(4, self.spinWeight)
        edges.reverse()
        for i in range(len(edges)):
            edge: BezierEdge = edges[i]
            sourceNode = f'V{edge.sourceNode.data(self.__ItemId)}' if edge.sourceNode else None
            destNode = f'V{edge.destNode.data(self.__ItemId)}' if edge.destNode else None

            strList = [
                f"e{edge.data(self.__ItemId)}", sourceNode, destNode,
                f"x:{edge.pos().x()},y:{edge.pos().y()}", f"{edge.weight()}"
            ]

            for j in range(edgeColCount):
                item = QStandardItem(strList[j])
                if j != edgeColCount - 1:
                    item.setFlags(self.__lastColumnFlag)
                self.edgeModel.setItem(i, j, item)

    def __updateNodeView(self):
        nodes = self.singleItems(BezierNode)
        if len(nodes):
            self.ui.nodeDetails.setEnabled(True)
        else:
            return
        nodeColCount = 4
        self.nodeModel.clear()
        nodeHeaderList = [
            self._tr("MainWindow", 'ID'),
            self._tr("MainWindow", '边数'),
            self._tr("MainWindow", '坐标'),
            self._tr("MainWindow", '权重')
        ]
        if self.ui.actionDigraph_Mode.isChecked():
            nodeHeaderList.append(self._tr("MainWindow", '出度'))
            nodeHeaderList.append(self._tr("MainWindow", '入度'))
            nodeColCount += 2
        else:
            nodeHeaderList.append(self._tr("MainWindow", "度"))
            nodeColCount += 1
        self.nodeModel.setHorizontalHeaderLabels(nodeHeaderList)
        self.nodeModel.setRowCount(len(nodes))
        self.nodeSelectionModel.currentChanged.connect(self.do_curNodeChanged)
        self.ui.nodeDetails.setModel(self.nodeModel)
        self.ui.nodeDetails.setSelectionModel(self.nodeSelectionModel)
        self.ui.nodeDetails.verticalHeader().setSectionResizeMode(
            QHeaderView.Fixed)
        self.ui.nodeDetails.horizontalHeader().setSectionResizeMode(
            QHeaderView.ResizeToContents)
        self.ui.nodeDetails.setAlternatingRowColors(True)
        self.ui.nodeDetails.setItemDelegateForColumn(3, self.spinWeight)
        nodes.reverse()
        for i in range(len(nodes)):
            node: BezierNode = nodes[i]
            strList = [
                f"V{node.data(self.__ItemId)}",
                str(len(node.bezierEdges)),
                f"x:{node.pos().x()},y:{node.pos().y()}",
                str(node.weight())
            ]
            if self.ui.actionDigraph_Mode.isChecked():
                strList.append(
                    f'{node.degrees(self.ui.actionDigraph_Mode.isChecked())[1]}'
                )
                strList.append(
                    f'{node.degrees(self.ui.actionDigraph_Mode.isChecked())[0]}'
                )
            else:
                strList.append(
                    f'{node.degrees(self.ui.actionDigraph_Mode.isChecked())}')
            for j in range(nodeColCount):
                item = QStandardItem(strList[j])
                if j != 3:
                    item.setFlags(self.__lastColumnFlag)
                self.nodeModel.setItem(i, j, item)

    def iniGraphicsSystem(self, name=None):  ##初始化 Graphics View系统
        scene = GraphicsScene()  # 创建QGraphicsScene
        view = GraphicsView(self, scene)  # 创建图形视图组件
        view.mouseMove.connect(self.do_mouseMove)  # 鼠标移动
        view.mouseClicked.connect(self.do_mouseClicked)  # 左键按下
        scene.itemMoveSignal.connect(self.do_shapeMoved)
        scene.itemLock.connect(self.do_nodeLock)
        scene.isHasItem.connect(self.do_checkIsHasItems)
        if name:
            title = name
        else:
            text = self.tr('未命名')
            title = f'{text}{self.ui.tabWidget.count()}'
        curIndex = self.ui.tabWidget.addTab(view, title)
        self.ui.tabWidget.setCurrentIndex(curIndex)
        self.ui.tabWidget.setVisible(True)

        ##  4个信号与槽函数的关联

        # self.view.mouseDoubleClick.connect(self.do_mouseDoubleClick)  # 鼠标双击
        # self.view.keyPress.connect(self.do_keyPress)  # 左键按下

    def singleItems(self, className) -> list:
        self.viewAndScene()
        return self.__scene.singleItems(className)

    def connectGraph(self):
        self.__graph.setMode(self.ui.actionDigraph_Mode.isChecked())
        items = self.__scene.uniqueItems()
        nodeList = []
        edgeList = []
        if not len(items):
            return
        for item in items:
            if type(item) is BezierNode:
                nodeList.append(item)
            elif type(item) is BezierEdge:
                edgeList.append(item)
        for node in nodeList:
            self.__graph.addVertex(node.data(self.__ItemId))

        badEdgeList = []
        for i in range(len(edgeList)):
            for edge in edgeList:
                edge: BezierEdge
                if edge.data(self.__ItemId) == i:
                    if edge.sourceNode and edge.destNode:
                        self.__graph.addEdge(
                            edge.sourceNode.data(self.__ItemId),
                            edge.destNode.data(self.__ItemId), edge.weight())
                    else:
                        badEdgeList.append(edge)

        if len(badEdgeList) != 0:
            self.disconnectGraph()
            string = ""
            for x in range(len(badEdgeList)):
                demo = "、"
                if x == len(badEdgeList) - 1:
                    demo = ""
                string = f'{string}e{badEdgeList[x].data(self.__ItemId)}{demo}'
            QMessageBox.warning(
                self, self._tr("MainWindow", "连接故障!"),
                self._tr("MainWindow", "警告,") + string +
                self._tr("MainWindow", "的连接不完整"))
            return False

        return True

    def disconnectGraph(self):
        self.__graph.clearAllData()

    def viewAndScene(self):
        if self.ui.tabWidget.count():
            self.__view: GraphicsView = self.ui.tabWidget.currentWidget()
            self.__scene = self.__view.scene()

    def standardGraphData(self):
        mode = int(self.ui.actionDigraph_Mode.isChecked())
        nodes = self.__scene.singleItems(BezierNode)
        edges = self.__scene.singleItems(BezierEdge)
        texts = self.__scene.singleItems(BezierText)
        nodeDataList = []
        edgeDataList = []
        textDataList = []
        for node in nodes:
            node: BezierNode
            data = [
                node.data(self.__ItemId),
                node.weight(),
                node.pos().x(),
                node.pos().y()
            ]
            nodeDataList.append(data)

        for edge in edges:
            edge: BezierEdge
            data = [edge.data(self.__ItemId)]
            if edge.sourceNode:
                data.append(edge.sourceNode.data(self.__ItemId))
            else:
                data.append(-1)
            if edge.destNode:
                data.append(edge.destNode.data(self.__ItemId))
            else:
                data.append(-1)

            data = data + [
                edge.weight(),
                edge.beginCp.point().x(),
                edge.beginCp.point().y(),
                edge.edge1Cp.point().x(),
                edge.edge1Cp.point().y(),
                edge.edge2Cp.point().x(),
                edge.edge2Cp.point().y(),
                edge.endCp.point().x(),
                edge.endCp.point().y(),
                edge.scenePos().x(),
                edge.scenePos().y()
            ]
            edgeDataList.append(data)

        for text in texts:
            text: BezierText
            data = [
                text.data(self.__ItemId),
                text.toPlainText(),
                text.scenePos().x(),
                text.scenePos().y()
            ]
            textDataList.append(data)

        nodeDataList.reverse()
        edgeDataList.reverse()
        textDataList.reverse()

        return [mode, nodeDataList, edgeDataList, textDataList]

    def reverseStandardData(self, excelData):
        graphName = excelData[0]
        mode = excelData[1]
        nodes = []
        edges = []
        texts = []
        self.ui.actionDigraph_Mode.setChecked(bool(mode))

        for nodeDetail in excelData[2]:
            node = BezierNode()
            node.textCp.setPlainText(f"V{nodeDetail[0]}")
            node.setData(self.__ItemId, nodeDetail[0])
            nodeText = self._tr("MainWindow", "顶点")
            node.setData(self.__ItemDesc, nodeText)
            if len(nodeDetail) < 3:
                for i in range(2):
                    intRandom = randint(-400, 400)
                    nodeDetail.append(intRandom)

            node.setPos(QPointF(nodeDetail[2], nodeDetail[3]))
            node.weightCp.setPlainText(str(nodeDetail[1]))

            nodes.append(node)

        for edgeDetail in excelData[3]:
            edge = BezierEdge()
            edge.setData(self.__ItemId, edgeDetail[0])
            edge.setData(self.__ItemDesc, "边")
            edge.textCp.setPlainText(f"e{edgeDetail[0]}")
            edge.weightCp.setPlainText(str(edgeDetail[3]))

            if len(edgeDetail) <= 4:
                for i in range(10):
                    intRandom = randint(-400, 400)
                    edgeDetail.append(intRandom)

            edge.setPos(QPointF(edgeDetail[12], edgeDetail[13]))

            if edgeDetail[1] >= 0:
                for node in nodes:
                    node: BezierNode
                    if node.data(self.__ItemId) == edgeDetail[1]:
                        edge.setSourceNode(node)
                        node.addBezierEdge(edge, ItemType.SourceType)
                        line = QLineF(edge.mapFromScene(node.pos()),
                                      edge.edge1Cp.point())
                        length = line.length()
                        edgeOffset = QPointF(line.dx() * 10 / length,
                                             line.dy() * 10 / length)
                        source = edge.mapFromScene(node.pos()) + edgeOffset
                        edge.setSpecialControlPoint(source,
                                                    ItemType.SourceType)
                        edge.beginCp.setVisible(False)
            else:
                edge.setSpecialControlPoint(
                    QPointF(edgeDetail[4], edgeDetail[5]), ItemType.SourceType)

            if edgeDetail[2] >= 0:
                for node in nodes:
                    node: BezierNode
                    if node.data(self.__ItemId) == edgeDetail[2]:
                        edge.setDestNode(node)
                        node.addBezierEdge(edge, ItemType.DestType)
                        line = QLineF(edge.mapFromScene(node.pos()),
                                      edge.edge2Cp.point())
                        length = line.length()
                        edgeOffset = QPointF(line.dx() * 10 / length,
                                             line.dy() * 10 / length)
                        if mode:
                            dest = edge.mapFromScene(
                                node.pos()) + edgeOffset * 2.3
                        else:
                            dest = edge.mapFromScene(node.pos()) + edgeOffset
                        edge.setSpecialControlPoint(dest, ItemType.DestType)
                        edge.endCp.setVisible(False)
            else:
                edge.setSpecialControlPoint(
                    QPointF(edgeDetail[10], edgeDetail[11]), ItemType.DestType)

            edge.setEdgeControlPoint(QPointF(edgeDetail[6], edgeDetail[7]),
                                     ItemType.SourceType)
            edge.setEdgeControlPoint(QPointF(edgeDetail[8], edgeDetail[9]),
                                     ItemType.DestType)
            edge.centerCp.setPoint(edge.updateCenterPos())

            edges.append(edge)

        if len(excelData) <= 4:
            return [graphName, nodes + edges]

        for textDetail in excelData[4]:
            text = BezierText(str(textDetail[1]))
            text.setData(self.__ItemId, textDetail[0])
            text.setData(self.__ItemDesc, "文本")
            text.setPos(textDetail[2], textDetail[3])
            texts.append(text)

        return [graphName, nodes + edges + texts]

    def setTranslator(self, translator, language):
        self.__translator = translator
        if language == 'EN':
            self.ui.actionSetEnglish.setChecked(True)
        else:
            self.ui.actionSetChinese.setChecked(True)

    @classmethod
    def checkSort(cls, index: list):
        index.sort()
        for i in range(len(index)):
            if i != index[i]:
                return i

    # ==============event处理函数==========================

    # def closeEvent(self, event):  # 退出函数
    #
    #     msgBox = QMessageBox()
    #     msgBox.setWindowTitle('关闭')
    #     msgBox.setText("是否保存")
    #     msgBox.setIcon(QMessageBox.Question)
    #     btn_Do_notSave = msgBox.addButton('不保存', QMessageBox.AcceptRole)
    #     btn_cancel = msgBox.addButton('取消', QMessageBox.RejectRole)
    #     btn_save = msgBox.addButton('保存', QMessageBox.AcceptRole)
    #     msgBox.setDefaultButton(btn_save)
    #     msgBox.exec_()
    #
    #     if msgBox.clickedButton() == btn_Do_notSave:
    #         event.accept()
    #     elif msgBox.clickedButton() == btn_cancel:
    #         event.ignore()
    #     elif msgBox.clickedButton() == btn_save:
    #         self.do_save_file()
    #         event.accept()

    # def contextMenuEvent(self, event):  # 右键菜单功能
    #     rightMouseMenu = QMenu(self)
    #
    #     rightMouseMenu.addAction(self.ui.actionNew)
    #     rightMouseMenu.addAction(self.ui.actionOpen)
    #
    #     self.action = rightMouseMenu.exec_(self.mapToGlobal(event.pos()))

    #  ==========由connectSlotsByName()自动连接的槽函数============
    @Slot()  # 新建画板
    def on_actionNew_triggered(self):
        self.iniGraphicsSystem()

    @Slot()  # 添加边
    def on_actionArc_triggered(self):  # 添加曲线
        item = BezierEdge()
        item.setGraphMode(self.ui.actionDigraph_Mode.isChecked())
        self.__setItemProperties(item, self._tr("MainWindow", "边"))
        self.do_addItem(item)
        self.__updateEdgeView()
        self.__updateNodeView()

    @Slot()  # 添加顶点
    def on_actionCircle_triggered(self):  # 添加原点
        self.viewAndScene()
        item = BezierNode()
        self.__setItemProperties(item, self._tr("MainWindow", "顶点"))
        self.do_addItem(item)
        self.__updateNodeView()
        self.__updateEdgeView()

    @Slot()  # 添加注释
    def on_actionAdd_Annotation_triggered(self):
        self.viewAndScene()
        strText, OK = QInputDialog.getText(self, self._tr("MainWindow", "输入"),
                                           self._tr("MainWindow", "请输入文字"))
        if not OK:
            return
        item = BezierText(strText)
        self.__setItemProperties(item, self._tr("MainWindow", "注释"))
        self.do_addItem(item)

    @Slot(bool)  # 显示和隐藏结点权重
    def on_actionShowNodesWeight_toggled(self, check: bool):
        nodes = self.__scene.singleItems(BezierNode)
        for node in nodes:
            node: BezierNode
            node.weightCp.setVisible(check)

        # if check:
        #     self.ui.actionShowNodesWeight.setText("隐藏顶点权重")
        # else:
        #     self.ui.actionShowNodesWeight.setText("显示顶点权重")

    @Slot(bool)  # 显示和隐藏边权重
    def on_actionShowEdgesWeight_toggled(self, check: bool):
        edges = self.__scene.singleItems(BezierEdge)
        for edge in edges:
            edge: BezierEdge
            edge.weightCp.setVisible(check)
        # if check:
        #     self.ui.actionShowEdgesWeight.setText("隐藏边权重")
        # else:
        #     self.ui.actionShowEdgesWeight.setText("显示边权重")

    @Slot(bool)  # 显示和隐藏边的控制点
    def on_actionHideControlPoint_toggled(self, check: bool):
        edges = self.__scene.singleItems(BezierEdge)
        for edge in edges:
            edge: BezierEdge
            for point in edge.pointList:
                point.setVisible(check)
            if edge.sourceNode:
                edge.beginCp.setVisible(False)
            if edge.destNode:
                edge.endCp.setVisible(False)

    @Slot()  # 简单通路
    def on_actionEasy_Pathway_triggered(self):
        self.viewAndScene()
        items = self.__scene.nodeList
        if len(items) == 0:
            QMessageBox.warning(self, self._tr("MainWindow", "警告"),
                                self._tr("MainWindow", "对不起,你没有选择起始节点"))
            return
        elif len(items) != 2:
            QMessageBox.warning(self, self._tr("MainWindow", "警告"),
                                self._tr("MainWindow", "选择的起始点数目不符合要求"))
            return

        if self.connectGraph():
            PathWay = ShowDataWidget(self,
                                     items,
                                     self.__graph,
                                     name=self._tr("MainWindow", "简单通路"))
            PathWay.pathSignal.connect(self.do_ShowSelectPath)
            if PathWay.easyPath():
                PathWay.updateToolWidget()
                PathWay.show()

        self.disconnectGraph()

    @Slot()  # 简单回路
    def on_actionEasy_Loop_triggered(self):
        self.viewAndScene()
        items = self.__scene.nodeList
        if len(items) == 0:
            QMessageBox.warning(self, self._tr("MainWindow", "警告"),
                                self._tr("MainWindow", "对不起,你没有选择起点"))
            return

        if self.connectGraph():
            LoopWay = ShowDataWidget(self,
                                     items,
                                     self.__graph,
                                     name=self._tr("MainWindow", "简单回路"))
            LoopWay.pathSignal.connect(self.do_ShowSelectPath)
            if LoopWay.easyLoop():
                LoopWay.updateToolWidget(mode=1)
                LoopWay.show()
        self.disconnectGraph()

    @Slot()  # 初级通路
    def on_actionPrimary_Pathway_triggered(self):
        items = self.__scene.nodeList
        if len(items) == 0:
            QMessageBox.warning(self, self._tr("MainWindow", "警告"),
                                self._tr("MainWindow", "对不起,你没有选择起始节点"))
            return
        elif len(items) != 2:
            QMessageBox.warning(self, self._tr("MainWindow", "警告"),
                                self._tr("MainWindow", "选择的起始点数目不符合要求"))
            return

        if self.connectGraph():
            PathWay = ShowDataWidget(self, items, self.__graph, name="初级通路")
            PathWay.pathSignal.connect(self.do_ShowSelectPath)
            if PathWay.primaryPath():
                PathWay.updateToolWidget(path=1)
                PathWay.show()

        self.disconnectGraph()

    @Slot()  # 初级回路
    def on_actionPrimary_Loop_triggered(self):
        items = self.__scene.nodeList
        if len(items) == 0:
            QMessageBox.warning(self, self._tr("MainWindow", "警告"),
                                self._tr("MainWindow", "对不起,你没有选择起点"))
            return

        if self.connectGraph():
            LoopWay = ShowDataWidget(self,
                                     items,
                                     self.__graph,
                                     name=self._tr("MainWindow", "初级回路"))
            LoopWay.pathSignal.connect(self.do_ShowSelectPath)
            if LoopWay.primaryLoop():
                LoopWay.updateToolWidget(mode=1, path=1)
                LoopWay.show()

        self.disconnectGraph()

    @Slot()  # 邻接矩阵 边数
    def on_action_EdgeNum_triggered(self):
        self.viewAndScene()
        items = self.__scene.singleItems(BezierNode)
        if len(items) == 0:
            QMessageBox.warning(self, self._tr("MainWindow", "警告"),
                                self._tr("MainWindow", "图中没有结点"))
            return
        if self.connectGraph():
            MatrixTable = ShowMatrixWidget(self, self.__graph,
                                           self._tr("MainWindow", "邻接矩阵"), 0)
            MatrixTable.show()

    @Slot()  # 邻接矩阵 权重
    def on_actionWeight_triggered(self):
        self.viewAndScene()
        items = self.__scene.singleItems(BezierNode)
        if len(items) == 0:
            QMessageBox.warning(self, self._tr("MainWindow", "警告"),
                                self._tr("MainWindow", "图中没有结点"))
            return
        if self.connectGraph():
            if not self.__graph.multipleOrSimple():
                MatrixTable = ShowMatrixWidget(self, self.__graph,
                                               self._tr("MainWindow", "邻接矩阵"),
                                               1)
                MatrixTable.show()
            else:
                QMessageBox.information(self, "Sorry", "这个图不是简单图")
                self.disconnectGraph()

    @Slot()  # 可达矩阵
    def on_actionReachable_Matrix_triggered(self):
        self.viewAndScene()
        items = self.__scene.singleItems(BezierNode)
        if len(items) == 0:
            QMessageBox.warning(self, self._tr("MainWindow", "警告"),
                                self._tr("MainWindow", "图中没有结点"))
            return
        if self.connectGraph():
            MatrixTable = ShowMatrixWidget(self, self.__graph,
                                           self._tr("MainWindow", "可达矩阵"))
            MatrixTable.show()

    @Slot()  # 关联矩阵
    def on_actionIncidence_Matrix_Undigraph_triggered(self):
        self.viewAndScene()
        items = self.__scene.singleItems(BezierNode)
        if len(items) == 0:
            QMessageBox.warning(self, self._tr("MainWindow", "警告"),
                                self._tr("MainWindow", "图中没有结点"))
            return
        if self.ui.actionDigraph_Mode.isChecked():
            items = self.singleItems(BezierEdge)
            badNodeList = []
            badNodes = ''
            for x in range(len(items)):
                if items[x].sourceNode is not None and items[
                        x].destNode is not None:
                    if items[x].sourceNode == items[x].destNode:
                        badNodeList.append(items[x])
                        demo = "、"
                        if x == len(items) - 1:
                            demo = ""
                        badNodes = f"{badNodes}V{items[x].sourceNode.data(self.__ItemId)}{demo}"
            if len(badNodeList):
                text = self._tr("MainWindow", '有向图的关联矩阵需要有向图无环,而')
                text1 = self._tr("MainWindow", '存在环!')
                QMessageBox.warning(self, self._tr("MainWindow", "致命错误"),
                                    f"{text}{badNodes}{text1}")
                return
        if self.connectGraph():
            MatrixTable = ShowMatrixWidget(self, self.__graph,
                                           self._tr("MainWindow", "关联矩阵"))
            MatrixTable.show()

    @Slot()  # 图的连通性
    def on_actionConnectivity_triggered(self):
        name = ''
        if self.connectGraph():
            num = self.__graph.connectivity()
            if num is False:
                name = self._tr("MainWindow", '此图为非连通图')
            elif num == 2:
                name = self._tr("MainWindow", "此图为单向连通图")
            elif num == 3:
                name = self._tr("MainWindow", "此图为强连通图")
            elif num == 1:
                name = self._tr("MainWindow", '此图为连通图')

            QMessageBox.information(self, self._tr("MainWindow", "图的连通性"),
                                    name)

            self.disconnectGraph()

    @Slot()  # 完全图判定
    def on_actionCompleteGraph_triggered(self):
        if self.connectGraph():
            edge = self.__graph.completeGraph()
            if edge:
                name = self._tr("MainWindow", "此图为完全图")
            else:
                name = self._tr("MainWindow", '此图不是完全图')

            QMessageBox.information(self, self._tr("MainWindow", "完全图判定"),
                                    name)

            self.disconnectGraph()

    @Slot()  # 简单图多重图判定
    def on_actionMultipleOrSimple_triggered(self):
        if self.connectGraph():
            edges = self.__graph.multipleOrSimple()
            if not edges:
                QMessageBox.information(self,
                                        self._tr("MainWindow", "简单图与多重图的判定"),
                                        self._tr("MainWindow", "此图为简单图"))

            else:
                parallelSides = ShowDataWidget(
                    self, edges, self.__graph,
                    self._tr("MainWindow", "简单图与多重图的判定"))
                parallelSides.multipleOrSimple()
                parallelSides.show()

    @Slot()  # 最短路径
    def on_actionShortestPath_triggered(self):

        items = self.__scene.nodeList
        if len(items) == 0:
            QMessageBox.warning(self, self._tr("MainWindow", "警告"),
                                self._tr("MainWindow", "对不起,你没有选择结点"))
            return
        if self.connectGraph():
            ShortestPath = ShowDataWidget(self,
                                          items,
                                          self.__graph,
                                          name=self._tr("MainWindow", "最短路径"))
            ShortestPath.pathSignal.connect(self.do_ShowSelectPath)
            if ShortestPath.shortestPath():
                ShortestPath.updateToolWidget(mode=1, path=2)
                ShortestPath.show()

    @Slot()  # 撤销
    def on_actionUndo_triggered(self):  # 撤销
        self.undoStack.undo()
        self.__updateEdgeView()
        self.__updateNodeView()

    @Slot()  # 重做
    def on_actionRedo_triggered(self):  # 重做
        self.viewAndScene()
        self.undoStack.redo()
        self.__updateEdgeView()
        self.__updateNodeView()

    @Slot()  # 帮助
    def on_actionHelp_Document_triggered(self):
        open("https://github.com/BBlance/Discrete_math.graph_theory")

    # @Slot()
    # def on_actionPen_Color_triggered(self):  # 画笔颜色
    #     iniColor = self.view.getPenColor()
    #     color = QColorDialog.getColor(iniColor, self, "选择颜色")
    #     if color.isValid():
    #         self.view.setPenColor(color)

    # @Slot()
    # def on_actionPen_Thickness_triggered(self):  # 画笔粗细
    #     self.viewAndScene()
    #     iniThickness = self.__view.getPenThickness()
    #     intPenStyle = self.__view.getPenStyle()
    #     thicknessDialog = ThicknessDialog(None, self._tr("MainWindow", "画笔粗细与样式"), iniThickness, intPenStyle)
    #     ret = thicknessDialog.exec_()
    #     thickness = thicknessDialog.getThickness()
    #     penStyle = thicknessDialog.getPenStyle()
    #     self.__view.setPenStyle(penStyle)
    #     self.__view.setPenThickness(thickness)

    @Slot()
    def on_actionBackground_Color_triggered(self):
        self.viewAndScene()
        # iniColor = self.__view.getBackgroundColor()
        # color = QColorDialog.getColor(iniColor, self, "选择颜色")
        # if color.isValid():
        #     self.__view.setBackgroundBrush(color)
        for item in self.standardGraphData():
            print(item)

    # @Slot(bool)
    # def on_actionProperty_And_History_triggered(self, checked):
    #     self.ui.dockWidget.setVisible(checked)

    @Slot()  # 保存文件
    def on_actionSave_triggered(self):
        self.viewAndScene()
        tableName = self.ui.tabWidget.tabText(self.ui.tabWidget.currentIndex())
        filename = self.operatorFile.saveGraphData(self.standardGraphData(),
                                                   tableName)
        if filename:
            index = self.ui.tabWidget.currentIndex()
            self.ui.tabWidget.setTabText(index, filename.baseName())

    @Slot()  # 读取文件
    def on_actionOpen_triggered(self):
        graph = self.operatorFile.openGraphData()
        if graph:
            graph = self.reverseStandardData(graph)
            self.iniGraphicsSystem(graph[0])
            for item in graph[1]:
                self.__scene.addItem(item)
            self.__updateNodeView()
            self.__updateEdgeView()
            self.__scene.update()

    @Slot()  # 另存为
    def on_actionSave_As_triggered(self):
        filename = self.operatorFile.saveExcelAs(self.standardGraphData())
        if filename:
            index = self.ui.tabWidget.currentIndex()
            self.ui.tabWidget.setTabText(index, filename.baseName())

    @Slot()  # 导出数据
    def on_actionOutputData_triggered(self):
        data = self.standardGraphData()
        data = data[:3]
        dataCpoy = [data[0], [], []]
        for node in data[1]:
            node = node[:2]
            dataCpoy[1].append(node)

        for edge in data[2]:
            edge = edge[:4]
            dataCpoy[2].append(edge)

        if self.operatorFile.outputData(dataCpoy):
            title = self.tr("恭喜")
            strInfo = self.tr("数据导出成功")
            QMessageBox.information(self, title, strInfo)

    @Slot()  # 导入数据
    def on_actionImportData_triggered(self):
        data = self.operatorFile.inputData()
        if data:
            graph = self.reverseStandardData(data)
            self.iniGraphicsSystem(graph[0])
            for item in graph[1]:
                self.__scene.addItem(item)
            self.__updateNodeView()
            self.__updateEdgeView()
            self.__scene.update()

    @Slot()
    def on_actionSave_Image_triggered(self):
        self.viewAndScene()
        savePath, fileType = QFileDialog.getSaveFileName(
            self, self._tr("MainWindow", '保存图片'), '.\\', '*bmp;;*.png')
        filename = os.path.basename(savePath)
        if filename != "":
            self.__view.saveImage(savePath, fileType)

    @Slot()
    def on_actionDelete_triggered(self):
        self.viewAndScene()
        self.do_deleteItem()

    @Slot(bool)
    def on_actionDigraph_Mode_toggled(self, checked: bool):
        self.__labModeInfo.setText(self._tr("MainWindow", "有向图模式"))
        self.__graph.setMode(checked)
        items = self.__scene.singleItems(BezierEdge)
        for item in items:
            item: BezierEdge
            item.setGraphMode(True)
            item.update()

    @Slot(bool)
    def on_actionRedigraph_Mode_toggled(self, checked: bool):
        self.__labModeInfo.setText(self._tr("MainWindow", "无向图模式"))
        self.__graph.setMode(checked)
        items = self.__scene.singleItems(BezierEdge)
        for item in items:
            item: BezierEdge
            item.setGraphMode(False)
            item.update()

    @Slot(int)
    def on_tabWidget_currentChanged(self, index):  # ui.tabWidget当前页面变化
        self.viewAndScene()
        if self.__view and self.__scene:
            self.__updateEdgeView()
            self.__updateNodeView()

        hasTabs = self.ui.tabWidget.count() > 0  # 再无页面时

        self.ui.tabWidget.setVisible(hasTabs)
        self.ui.dockWidget.setVisible(hasTabs)
        self.ui.actionProperty_And_History.setChecked(hasTabs)

    @Slot(int)
    def on_tabWidget_tabCloseRequested(self, index):  # 分页关闭时关闭窗体
        if index < 0:
            return
        view = self.ui.tabWidget.widget(index)
        view.close()
        # self.__view = None
        # self.__scene = None

    #  =============自定义槽函数===============================
    def do_nodeLock(self, item):
        self.__updateNodeView()
        self.__updateEdgeView()

    def do_mouseMove(self, point):  ##鼠标移动
        ##鼠标移动事件,point是 GraphicsView的坐标,物理坐标
        view = self._tr("MainWindow", '视图坐标:')
        scene = self._tr("MainWindow", '场景坐标:')
        self.__labViewCord.setText("%s%d,%d" % (view, point.x(), point.y()))
        pt = self.ui.tabWidget.currentWidget().mapToScene(point)  # 转换到Scene坐标
        self.__labSceneCord.setText("%s%.0f,%.0f" % (scene, pt.x(), pt.y()))

    def do_mouseClicked(self, point):  ##鼠标单击
        pt = self.__view.mapToScene(point)  # 转换到Scene坐标
        item = self.__scene.itemAt(pt, self.__view.transform())  # 获取光标下的图形项
        if item is None:
            return
        pm = item.mapFromScene(pt)  # 转换为绘图项的局部坐标
        itemInfo = self._tr("MainWindow", "Item 坐标:")
        self.__labItemCord.setText("%s%.0f,%.0f" % (itemInfo, pm.x(), pm.y()))
        data = f"{item.data(self.__ItemDesc)}, ItemId={item.data(self.__ItemId)}"
        if type(item) is BezierEdge:
            data = f"{data},EdgeId=e{item.data(self.__ItemId)}"
        elif type(item) is BezierNode:
            data = f"{data}, NodeId=V{item.data(self.__ItemId)}"
        self.__labItemInfo.setText(data)

    def do_mouseDoubleClick(self, point):  ##鼠标双击
        pt = self.__view.mapToScene(point)  # 转换到Scene坐标,QPointF
        item = self.__scene.itemAt(pt, self.__view.transform())  # 获取光标下的绘图项
        if item is None:
            return

        className = str(type(item))  # 将类名称转换为字符串

        if className.find("QGraphicsRectItem") >= 0:  # 矩形框
            self.__setBrushColor(item)
        elif className.find(
                "QGraphicsEllipseItem") >= 0:  # 椭圆和圆都是 QGraphicsEllipseItem
            self.__setBrushColor(item)
        elif className.find("QGraphicsPolygonItem") >= 0:  # 梯形和三角形
            self.__setBrushColor(item)
        elif className.find("QGraphicsLineItem") >= 0:  # 直线,设置线条颜色
            pen = item.pen()
            color = item.pen().__color()
            color = QColorDialog.getColor(color, self, "选择线条颜色")
            if color.isValid():
                pen.setColor(color)
                item.setPen(pen)
        elif className.find("QGraphicsTextItem") >= 0:  # 文字,设置字体
            font = item.font()
            font, OK = QFontDialog.getFont(font)
            if OK:
                item.setFont(font)

    def do_addItem(self, item):
        add = AddCommand(self, self.__scene, item)
        self.undoStack.push(add)

    def do_shapeMoved(self, item, pos):
        move = MoveCommand(item, pos)
        self.undoStack.push(move)

    def do_deleteItem(self):
        items = self.__scene.selectedItems()
        cnt = len(items)
        for i in range(cnt):
            item = items[i]
            if str(type(item)).find("BezierNode") >= 0:
                item: BezierNode
                for edge in item.bezierEdges:
                    for node, itemType in edge.items():
                        if itemType == ItemType.SourceType:
                            node.setSourceNode(None)
                        elif itemType == ItemType.DestType:
                            node.setDestNode(None)
                self.__nodeNum -= 1
            elif str(type(item)).find("BezierEdge") >= 0:
                item: BezierEdge
                sourceNode: BezierNode = item.sourceNode
                destNode: BezierNode = item.destNode
                if sourceNode:
                    sourceNodeList = sourceNode.bezierEdges
                    for sourceEdge in sourceNodeList:
                        for edge in sourceEdge.keys():
                            if item is edge:
                                sourceNodeList.remove(sourceEdge)
                if destNode:
                    destNodeList = destNode.bezierEdges
                    for destEdge in destNodeList:
                        for edge in destEdge.keys():
                            if item is edge:
                                destNodeList.remove(destEdge)
                self.__edgeNum -= 1

            self.__scene.removeItem(item)  # 删除绘图项

    def do_curEdgeChanged(self, current, previous):
        if current is not None:
            text = f"当前单元格{current.row()},{current.column()}"
            item = self.edgeModel.itemFromIndex(current)

    def do_curNodeChanged(self, current, previous):
        if current is not None:
            text = f"当前单元格{current.row()},{current.column()}"
            item = self.nodeModel.itemFromIndex(current)

    def do_updateEdgeWeight(self, topLeft, bottomRight):
        if topLeft.column() == 4:
            edges = self.__scene.singleItems(BezierEdge)
            for edge in edges:
                edge: BezierEdge
                if edge.textCp.toPlainText() == self.edgeModel.index(
                        topLeft.row(), 0, QModelIndex()).data():
                    edge.weightCp.setPlainText(str(topLeft.data()))
                    self.__scene.update()

    def do_updateNodeWeight(self, topLeft, bottomRight):
        if topLeft.column() == 3:
            nodes = self.__scene.singleItems(BezierNode)
            for node in nodes:
                node: BezierNode
                if node.textCp.toPlainText() == self.nodeModel.index(
                        topLeft.row(), 0, QModelIndex()).data():
                    node.weightCp.setPlainText(str(topLeft.data()))
                    self.__scene.update()

    def do_ShowSelectPath(self, pathList: list):
        self.__scene.clearSelection()
        items = self.__scene.uniqueItems()
        for item in items:
            if item.textCp.toPlainText() in pathList:
                item.setSelected(True)

    def do_checkIsHasItems(self, num):
        if num:
            self.ui.actionSave.setEnabled(True)
        else:
            self.ui.actionSave.setEnabled(False)
    def testClear(self):

        model = QStandardItemModel()
        root = model.invisibleRootItem()
        model.clear()
        self.assertFalse(shiboken.isValid(root))