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)
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)
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)
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()
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)
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)
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_()
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()
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()
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")
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()
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
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')
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}"
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)
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
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"
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)
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)
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))
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"
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)
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}"
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()
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)