def initScores(container: QWidget): container.removeWidget(state["scoreWidget"]) state["scoreWidget"].deleteLater() newScoreWidget = QWidget() newScoreWidget.hide() scoreLayout = QGridLayout() scoreLayout.setAlignment(Qt.AlignTop) diff1 = QLabel("Easy") scoreLayout.addWidget(diff1, 0, 1) for i, el in enumerate(state["highScores"]["easy"]): text = "{} {}".format(el[0], el[1]) topPlayer = QLabel(text) scoreLayout.addWidget(topPlayer, i + 1, 1) diff2 = QLabel("Normal") scoreLayout.addWidget(diff2, 0, 2) for i, el in enumerate(state["highScores"]["normal"]): text = "{} {}".format(el[0], el[1]) topPlayer = QLabel(text) scoreLayout.addWidget(topPlayer, i + 1, 2) diff3 = QLabel("Hard") scoreLayout.addWidget(diff3, 0, 3) for i, el in enumerate(state["highScores"]["hard"]): text = "{} {}".format(el[0], el[1]) topPlayer = QLabel(text) scoreLayout.addWidget(topPlayer, i + 1, 3) newScoreWidget.setLayout(scoreLayout) state["scoreWidget"] = newScoreWidget container.addWidget(newScoreWidget)
class Folder(QWidget): def __init__(self, parent, open_file_call, folder_path): super().__init__(parent) self.folder_path = folder_path self.folder_open = False self.setStyleSheet(styles.file_path_button) self.main_v_layout = QVBoxLayout() self.main_v_layout.setSpacing(2) self.main_v_layout.setMargin(0) self.files = QWidget(self) self.files.hide() self.files.setStyleSheet(styles.files_list) self.files.setContentsMargins(15, 0, 0, 0) self.files_v_layout = QVBoxLayout() self.folder_btn = QPushButton(QIcon(icon.FOLDER_I()), self.folder_path.split(os.path.sep)[-1], self) self.folder_btn.clicked.connect(self.open_folder) self.main_v_layout.addWidget(self.folder_btn) _list = sorted(os.listdir(self.folder_path)) for file in _list: file_absolute_path = self.folder_path + os.path.sep + file if os.path.isdir(file_absolute_path): self.files_v_layout.addWidget( Folder(self.files, open_file_call, self.folder_path + os.path.sep + file)) continue if os.path.splitext(file_absolute_path)[1] != '.csv': continue file_btn = FilePathButton(self.files, file, file_absolute_path, open_file_call) self.files_v_layout.addWidget(file_btn) self.files.setLayout(self.files_v_layout) self.main_v_layout.addWidget(self.files) self.setLayout(self.main_v_layout) def open_folder(self): self.close() if self.folder_open else self.open() self.folder_open = not self.folder_open def open(self): self.files.show() self.folder_btn.setIcon(QIcon(icon.OPENED_FOLDER_I())) def close(self): self.files.hide() self.folder_btn.setIcon(QIcon(icon.FOLDER_I()))
def _showRow(self, label: str, widget: QWidget, show: bool): if show and not widget.isVisible(): widget.show() self._form_layout.addRow(label, widget) elif not show and widget.isVisible(): label = self._form_layout.labelForField(widget) widget.hide() label.hide() self._form_layout.removeWidget(widget) self._form_layout.removeWidget(label) del label
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 build_export(self, has_table: bool = True, has_plot: bool = True) -> QWidget: """Construct the export layout but set it into a widget because we want to hide it.""" export_layout = super().build_export(has_table, has_plot) export_widget = QWidget() export_widget.setLayout(export_layout) # Hide widget until MC is calculated export_widget.hide() return export_widget
def initStats(container: QWidget, stateInfo: dict): container.removeWidget(stateInfo["statsWidget"]) stateInfo["statsWidget"].deleteLater() newstatsWidget = QWidget() newstatsWidget.hide() statsLayout = QVBoxLayout() for el in stateInfo["statistics"]: infoLabel = QLabel(el) statsLayout.addWidget(infoLabel) newstatsWidget.setLayout(statsLayout) container.addWidget(newstatsWidget) stateInfo["statsWidget"] = newstatsWidget
class ActionWidget(QWidget): _icon_picker: Optional[icon_picker.IconPicker] = None def __init__(self, parent: QWidget, manager: logic.DeviceManager): QWidget.__init__(self) self._manager = manager self._key_task = None self._action_widget = QWidget(self) self._action_widget.hide() layout = QGridLayout() self._icon_button = QPushButton(self._action_widget, clicked=self._icon_clicked) self._icon_button.setFixedSize(72, 72) layout.addWidget(self._icon_button, 0, 0) self._title = QLabel(self._action_widget) layout.addWidget(self._title, 0, 1) layout.addWidget(QLabel('Name'), 1, 0) self._name = QLineEdit(self._action_widget) layout.addWidget(self._name, 1, 1) self._action_widget.setLayout(layout) layout_container = QHBoxLayout() layout_container.addWidget(self._action_widget) self.setLayout(layout_container) def select(self, key_index): if key_index is not None: self._title.setText('Key ' + str(key_index)) self._key_task = self._manager.config_screen.get_key_task( key_index) if self._key_task is None: self._name.setText(self._manager.make_unique_task_name('Task')) else: self._name.setText(self._key_task.name) self._action_widget.show() else: self._action_widget.hide() def _icon_clicked(self): if ActionWidget._icon_picker is None: ActionWidget._icon_picker = icon_picker.IconPicker( None, self._manager.get_icon_folder()) ActionWidget._icon_picker.show()
class VarietyOptionWidget(QWidget): def __init__(self, *args, **kwargs): super(VarietyOptionWidget, self).__init__(*args, **kwargs) layout = QVBoxLayout() layout.setContentsMargins(QMargins(1, 1, 1, 1)) opts_layout = QHBoxLayout() opts_layout.setContentsMargins(QMargins(0, 0, 0, 0)) self.add_button = QPushButton("新建品种", self) opts_layout.addWidget(self.add_button) opts_layout.addStretch() layout.addLayout(opts_layout) # 新建品种的widget self.new_variety_widget = QWidget(self) add_layout = QGridLayout() add_layout.setContentsMargins(QMargins(100, 50, 100, 50)) add_layout.addWidget(QLabel("所属类别:", self), 0, 0) self.belong_group = QComboBox(self) add_layout.addWidget(self.belong_group, 0, 1) add_layout.addWidget(QLabel("属交易所", self), 1, 0) self.belong_exchange = QComboBox(self) add_layout.addWidget(self.belong_exchange, 1, 1) add_layout.addWidget(QLabel("中文名称", self), 2, 0) self.zh_name = QLineEdit(self) add_layout.addWidget(self.zh_name, 2, 1) add_layout.addWidget(QLabel("交易代码", self), 3, 0) self.en_name = QLineEdit(self) add_layout.addWidget(self.en_name, 3, 1) self.commit_new_button = QPushButton("确定提交", self) add_layout.addWidget(self.commit_new_button, 4, 0, 1, 2) self.new_variety_widget.setLayout(add_layout) layout.addWidget(self.new_variety_widget) self.new_variety_widget.hide() # 隐藏新建填写信息的界面 self.variety_table = QTableWidget(self) layout.addWidget(self.variety_table) self.setLayout(layout)
def _create_controls_section(self) -> QWidget: btn_allow = QPushButton('Allow') btn_allow.clicked.connect(self._on_allow_click) btn_block = QPushButton('Block') btn_block.clicked.connect(self._on_block_click) btn_reject = QPushButton('Reject') btn_reject.clicked.connect(self._on_reject_click) layout = QHBoxLayout() layout.addWidget(btn_allow) layout.addWidget(btn_block) layout.addWidget(btn_reject) layout.setContentsMargins(0, 0, 0, 0) controls_section = QWidget() controls_section.setLayout(layout) controls_section.hide() # will show only on selection return controls_section
class WidgetPySignal(UsesQApplication): """Tests the connection of python signals to QWidget qt slots.""" def setUp(self): super(WidgetPySignal, self).setUp() self.obj = Dummy() self.widget = QWidget() def tearDown(self): super(WidgetPySignal, self).tearDown() del self.obj del self.widget def testShow(self): """Emission of a python signal to QWidget slot show()""" self.widget.hide() QObject.connect(self.obj, SIGNAL('dummy()'), self.widget, SLOT('show()')) self.assert_(not self.widget.isVisible()) self.obj.emit(SIGNAL('dummy()')) self.assert_(self.widget.isVisible())
class WidgetPySignal(UsesQApplication): """Tests the connection of python signals to QWidget qt slots.""" def setUp(self): super(WidgetPySignal, self).setUp() self.obj = Dummy() self.widget = QWidget() def tearDown(self): super(WidgetPySignal, self).tearDown() del self.obj del self.widget def testShow(self): """Emission of a python signal to QWidget slot show()""" self.widget.hide() QObject.connect(self.obj, SIGNAL("dummy()"), self.widget, SLOT("show()")) self.assert_(not self.widget.isVisible()) self.obj.emit(SIGNAL("dummy()")) self.assert_(self.widget.isVisible())
class ItemWidget(QWidget): ItemUpdated = Signal() _KEYS = ( "type", "title", "authors", "authors_bais", "editors", "editors_bais", "abstract", "date", "publication", "volume", "issue", "pages", "year", "edition", "series", "publisher", "isbns", "institution", "degree", "texkey", "inspire_id", "arxiv_id", "dois", "urls", "files" ) _FORMAT_TYPE = { "A": "Article", "B": "Book", "C": "Book Chapter", "N": "Note", "P": "Conference Proceedings", "R": "Report", "T": "Thesis" } _FORMAT_FUNCTIONS = { "type": lambda x: ItemWidget._FORMAT_TYPE.get(x, ""), "authors": "\n".join, "editors": "\n".join, "isbns": "\n".join, # Alternatively one could use # lambda x: "\n".join([a.split(",", 1)[0] for a in x]) "inspire_id": lambda x: str(x) if x is not None else None, "year": lambda x: str(x) if x is not None else None, "dois": "\n".join, "urls": "\n".join } def __init__(self, parent=None): super().__init__(parent) self._source = None self._table = None self._id = -1 self._updating = False self._scroll = QScrollArea() # self._scroll.setBackgroundRole(QPalette.Base) self._scroll.setWidgetResizable(True) self._date = LineEdit() min_height = self._date.sizeHint().height() self._type = ComboBox() self._title = MultiLineTextEdit(min_height) self._authors = LineSplitTextEdit(min_height) self._editors = LineSplitTextEdit(min_height) self._abstract = MultiLineTextEdit(min_height) self._publication = LineEdit() self._volume = LineEdit() self._issue = LineEdit() self._pages = LineEdit() self._year = LineEdit() self._edition = LineEdit() self._series = LineEdit() self._publisher = LineEdit() self._isbns = LineSplitTextEdit(min_height) self._institution = LineEdit() self._degree = LineEdit() self._texkey = LineEdit() self._inspire_id = LineEdit() self._arxiv_id = LineEdit() self._dois = LineSplitTextEdit(min_height) self._urls = LineSplitTextEdit(min_height) self._files = FileList() for (k, v) in ItemWidget._FORMAT_TYPE.items(): self._type.addItem(v, k) # self._type.currentIndexChanged[int].connect(self._RefreshTypeFields) bold_font = self._title.document().defaultFont() bold_font.setBold(True) self._title.document().setDefaultFont(bold_font) self._files.FileDropRequested.connect(self._DropFile) self._fields = { "type": self._type, "title": self._title, "authors": self._authors, "editors": self._editors, "abstract": self._abstract, "date": self._date, "publication": self._publication, "volume": self._volume, "issue": self._issue, "pages": self._pages, "year": self._year, "edition": self._edition, "series": self._series, "publisher": self._publisher, "isbns": self._isbns, "institution": self._institution, "degree": self._degree, "texkey": self._texkey, "inspire_id": self._inspire_id, "arxiv_id": self._arxiv_id, "dois": self._dois, "urls": self._urls } self._details_frame = QGroupBox() self._details_layout = QFormLayout() self._scroll_widget = QWidget() self._SetupScrollUI() self._scroll.setWidget(self._scroll_widget) self._scroll_widget.hide() self._save = QToolButton() self._save.setIcon(QIcon(icons.SAVE)) self._save.clicked.connect(self._Save) self._reload = QToolButton() self._reload.setIcon(QIcon(icons.RELOAD)) self._reload.clicked.connect(self.DisplayItem) self._file_add = QToolButton() self._file_add.setIcon(QIcon(icons.FILE_ADD)) self._file_add.clicked.connect(self._AddFile) self._tool_widget = QWidget() self._SetupToolUI() layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self._scroll) layout.addWidget(self._tool_widget) self.setLayout(layout) self.Clear() def _SetupScrollUI(self): self._details_layout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow) self._details_layout.addRow("Journal", self._publication) self._details_layout.addRow("Editors", self._editors) self._details_layout.addRow("Volume", self._volume) self._details_layout.addRow("Issue", self._issue) self._details_layout.addRow("Pages", self._pages) self._details_layout.addRow("Edition", self._edition) self._details_layout.addRow("Series", self._series) self._details_layout.addRow("Publisher", self._publisher) self._details_layout.addRow("ISBNs", self._isbns) self._details_layout.addRow("Institution", self._institution) self._details_layout.addRow("Degree", self._degree) self._details_layout.addRow("Year", self._year) self._details_layout.setVerticalSpacing(0) self._details_frame.setLayout(self._details_layout) form_layout = QFormLayout() form_layout.setContentsMargins(0, 0, 0, 0) form_layout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow) form_layout.addRow("Type", self._type) form_layout.addRow("Title", self._title) form_layout.addRow("Authors", self._authors) form_layout.addRow("Abstract", self._abstract) form_layout.addRow("Date", self._date) form_layout.addRow("", self._details_frame) form_layout.addRow("BibTeX", self._texkey) form_layout.addRow("INSPIRE", self._inspire_id) form_layout.addRow("arXiv", self._arxiv_id) form_layout.addRow("DOIs", self._dois) form_layout.addRow("URLs", self._urls) form_layout.addRow("Files", self._files) form_layout.setVerticalSpacing(0) form = QWidget() form.setLayout(form_layout) scroll_layout = QVBoxLayout() scroll_layout.addWidget(form) scroll_layout.addStretch(1) self._scroll_widget.setLayout(scroll_layout) def _SetupToolUI(self): tool_layout = QHBoxLayout() tool_layout.setContentsMargins(0, 0, 0, 0) tool_layout.addWidget(self._file_add) tool_layout.addStretch(1) tool_layout.addWidget(self._reload) tool_layout.addWidget(self._save) self._tool_widget.setLayout(tool_layout) self._reload.setEnabled(False) self._save.setEnabled(False) self._file_add.setEnabled(False) for w in self._fields.values(): w.Edited.connect(self._EnableRefreshSave) # def _RefreshTypeFields(self, index): # pass def _EnableRefreshSave(self): self._reload.setEnabled(True) self._save.setEnabled(True) def _Save(self): data = {} for (k, w) in self._fields.items(): data[k] = w.Read() authors_raw = [" ".join(a.split()) for a in data["authors"]] authors = [] authors_bais = [] for a in authors_raw: (n, b) = ItemWidget._ParseAuthor(a) authors.append(n) authors_bais.append(b) data["authors"] = authors data["authors_bais"] = authors_bais editors_raw = [" ".join(e.split()) for e in data["editors"]] editors = [] editors_bais = [] for e in editors_raw: (n, b) = ItemWidget._ParseAuthor(e) editors.append(n) editors_bais.append(b) data["editors"] = editors data["editors_bais"] = editors_bais if data["date"] is not None: try: data["date"] = ItemWidget._ParseDate(data["date"]) except: del data["date"] if data["year"] is not None: try: data["year"] = datetime.strptime(data["year"], "%Y").strftime("%Y") except: del data["year"] if data["inspire_id"] is not None: try: data["inspire_id"] = int(data["inspire_id"]) except: del data["inspire_id"] self._table.EditRow(self._id, data) self.ItemUpdated.emit() def _AddFile(self): files_dir = self._source.FilesDir() if files_dir is None: QMessageBox.critical(None, "Error", "Cannot access storage folder.") return (file_path, _) = QFileDialog.getOpenFileName( None, "Open Document", files_dir, "Documents (*.pdf *.djvu)" ) if file_path == "": return file_name = os.path.basename(file_path) try: renaming = self._source.SaveFiles((file_path,)) except: QMessageBox.critical(None, "Error", "Error while copying the file.") else: self._AppendFile(renaming.get(file_name, file_name)) def _AppendFile(self, file_name): data = self._table.GetRow(self._id, ("files",)) if file_name in data: return data["files"].append(file_name) # Here self._updating is used to prevent calls to DisplayItem() # triggered by an Updated() signal emitted by the database. self._updating = True self._table.EditRow(self._id, data) self._updating = False self._files.Write(data["files"]) def _DropFile(self, file_name): data = self._table.GetRow(self._id, ("files",)) data["files"] = [f for f in data["files"] if f != file_name] # Here self._updating is used to prevent calls to DisplayItem() # triggered by an Updated() signal emitted by the database. self._updating = True self._table.EditRow(self._id, data) self._updating = False self._files.Write(data["files"]) def SetLocalSource(self, source): if self._source == source: return self._source = source self._SetTable(source.table) def SetTable(self, database_table): if self._table == database_table: return self._source = None self._SetTable(database_table) def _SetTable(self, database_table): if self._table is not None: self._table.Cleared.disconnect(self.Clear) self._table = database_table self._table.Cleared.connect(self.Clear) if self._source is None: self._files.setEnabled(False) self._files.SetFolder(None) else: self._files.setEnabled(True) try: self._files.SetFolder(self._source.FilesDir()) except: self._files.SetFolder(None) def Clear(self): self._scroll_widget.hide() self._scroll.verticalScrollBar().hide() def DisplayItem(self, id_=False): if self._updating: return if id_: self._id = id_ if self._id == -1: self.Clear() self._reload.setEnabled(False) self._save.setEnabled(False) self._file_add.setEnabled(False) return record = self._table.GetRow(self._id, ItemWidget._KEYS) authors = [] for (a, b) in zip(record["authors"], record["authors_bais"]): if b is None: authors.append(a) else: authors.append(a + " (" + b + ")") record["authors"] = authors editors = [] for (e, b) in zip(record["editors"], record["editors_bais"]): if b is None: editors.append(e) else: editors.append(e + " (" + b + ")") record["editors"] = editors for (k, v) in ItemWidget._FORMAT_FUNCTIONS.items(): record[k] = v(record[k]) for (k, w) in self._fields.items(): w.Write(record[k]) self._files.Write(record["files"]) self._scroll.verticalScrollBar().show() self._scroll_widget.show() self._reload.setEnabled(False) self._save.setEnabled(False) if self._source is not None: self._file_add.setEnabled(True) @staticmethod def _ParseAuthor(author): split = author.split("(", 1) author = split[0].strip() if len(split) == 1: return(author, None) bai = split[1].split(")", 1) if len(bai) == 1: return(author, None) bai = bai[0].replace(" ", "") # The sanity check on the BAI could be further refined return(author, bai) @staticmethod def _ParseDate(date_string): n_dashes = date_string.count("-") if n_dashes == 2: return datetime.strptime(date_string, "%Y-%m-%d").strftime("%Y-%m-%d") if n_dashes == 1: return datetime.strptime(date_string, "%Y-%m").strftime("%Y-%m") return datetime.strptime(date_string, "%Y").strftime("%Y")
class GoalPreviewerLayout(QVBoxLayout): def __init__(self): super().__init__() # ─────────────────── Check boxes ────────────────── # self.code_mode_checkbox = QCheckBox('L∃∀N mode') checkbox_lyt = QHBoxLayout() checkbox_lyt.addStretch() checkbox_lyt.addWidget(self.code_mode_checkbox) self.code_mode_checkbox.clicked.connect(self.toggle_code_mode) # ───────────────── Friendly widget ──────────────── # self.friendly_wgt = QWidget() friendly_wgt_lyt = QVBoxLayout() friendly_wgt_lyt.setContentsMargins(0, 0, 0, 0) propobj_lyt = QHBoxLayout() objects, properties = QListWidget(), QListWidget() objects_lyt, properties_lyt = QVBoxLayout(), QVBoxLayout() objects_lyt.addWidget(QLabel('Objects:')) properties_lyt.addWidget(QLabel('Properties:')) objects.setFont(QFont('Fira Code')) properties.setFont(QFont('Fira Code')) objects.addItems(['X : a set', 'x : X']) properties.addItems(['X is compact']) objects_lyt.addWidget(objects) properties_lyt.addWidget(properties) propobj_lyt.addLayout(objects_lyt) propobj_lyt.addLayout(properties_lyt) target_wgt = QLineEdit('Shit f**k X is continuous over Riemann') target_wgt.setFont(QFont('Fira Code')) friendly_wgt_lyt.addLayout(propobj_lyt) friendly_wgt_lyt.addWidget(QLabel('Target:')) friendly_wgt_lyt.addWidget(target_wgt) self.friendly_wgt.setLayout(friendly_wgt_lyt) # ─────────────────── Code widget ────────────────── # self.code_wgt = QTextEdit() self.code_wgt.setReadOnly(True) self.code_wgt.setFont(QFont('Menlo')) self.code_wgt.setText('Lean code goes here') # ──────────────────── Organize ──────────────────── # self.code_mode_checkbox.setChecked(False) self.friendly_wgt.show() self.code_wgt.hide() self.addWidget(self.friendly_wgt) self.addWidget(self.code_wgt) self.addLayout(checkbox_lyt) @Slot() def toggle_code_mode(self): if self.code_mode_checkbox.isChecked(): self.friendly_wgt.hide() self.code_wgt.show() else: self.friendly_wgt.show() self.code_wgt.hide()
class Vfo(): mqtt: MqttHandler def __init__(self, name: str, mqtt: MqttHandler, networkHandler: NetworkHandler, warnings: WarningHandler, rxButton: QPushButton, txButton: QPushButton, fcLabel: QLabel, bwLabel: QLabel, modeButton: QPushButton, freqButton: QPushButton): self.name = name self.mqtt = mqtt self.networkHandler = networkHandler self.warnings = warnings self.rxButton = rxButton self.txButton = txButton self.bwLabel = bwLabel self.modeButton = modeButton self.modeButton.setStyleSheet( self.modeButton.styleSheet() + "font: Waree; font-size: 64px; font-weight: bold;") self.modeButton.setText("") # Replace freq QPushButton with one that supports rich text layout = freqButton.parent().layout() self.freqButton = RichTextPushButton() self.freqButton.setStyleSheet( "background-color: transparent;\nborder: 1px solid transparent;" "font: Waree; font-size: 80px; font-weight: bold;") layout.replaceWidget(freqButton, self.freqButton) freqButton.setParent(None) # Delete old button self.freqButton.setText("Click to select freq") self.transverter = None self.transverterMutex = Lock() self.transverterResponded = Event() self.sdrChannel = None self.mqttTopic = None self.transverterMutex.acquire() self._stepSize = 10000 # Popup for mode selection self.modeWindow = QWidget() ui = modeSelector.Ui_main() ui.setupUi(self.modeWindow) ui.button_cw.clicked.connect(lambda: self.publish_mode("CW")) ui.button_usb.clicked.connect(lambda: self.publish_mode("USB")) ui.button_lsb.clicked.connect(lambda: self.publish_mode("LSB")) ui.button_tone.clicked.connect(lambda: self.publish_mode("Tone")) self.modeWindow.setWindowFlags(Qt.FramelessWindowHint | Qt.Popup) # Popup for frequency entry self.freqWindow = QWidget() ui = freqWindow.Ui_main() ui.setupUi(self.freqWindow) ui.button_0.clicked.connect(lambda: self.handle_keypress(0)) ui.button_1.clicked.connect(lambda: self.handle_keypress(1)) ui.button_2.clicked.connect(lambda: self.handle_keypress(2)) ui.button_3.clicked.connect(lambda: self.handle_keypress(3)) ui.button_4.clicked.connect(lambda: self.handle_keypress(4)) ui.button_5.clicked.connect(lambda: self.handle_keypress(5)) ui.button_6.clicked.connect(lambda: self.handle_keypress(6)) ui.button_7.clicked.connect(lambda: self.handle_keypress(7)) ui.button_8.clicked.connect(lambda: self.handle_keypress(8)) ui.button_9.clicked.connect(lambda: self.handle_keypress(9)) ui.button_G.clicked.connect(lambda: self.handle_keypress("G")) ui.button_M.clicked.connect(lambda: self.handle_keypress("M")) ui.button_k.clicked.connect(lambda: self.handle_keypress("k")) ui.button_x.clicked.connect(lambda: self.handle_keypress("x")) ui.button_backspace.clicked.connect(lambda: self.handle_keypress(-1)) ui.button_dp.clicked.connect(lambda: self.handle_keypress(".")) self.enteredFreqLabel = ui.label_enteredFreq self.freqWindow.setWindowFlags(Qt.FramelessWindowHint | Qt.Popup) self.freqButton.clicked.connect(self.open_freq_window) self.freq = None self.enteredFreqString = "" self.publishedFreq = None self.mode = None self.channel = None self.rxEnabled = False self.txEnabled = False self.enable_rx(False) self.enable_tx(False) # Can connect toggle RX to button # TX has to be handled at higher level as there must be <= 1 TX VFOs self.rxButton.clicked.connect(self.toggle_rx) def enable_rx(self, enabled: bool) -> None: if not self.transverter and enabled: logging.warning( f"Cannot enable RX on VFO {self.name}without transverter") return self.rxEnabled = enabled if self.rxEnabled: self.rxButton.setIcon(QIcon('resources/img/icon_rx_enabled.png')) else: self.rxButton.setIcon(QIcon('resources/img/icon_rx_disabled.png')) def toggle_rx(self): """ Toggles whether RX is enabled """ if self.transverter: self.enable_rx(not self.rxEnabled) def enable_tx(self, enabled: bool) -> None: if not self.transverter and enabled: logging.warning( f"Cannot enable TX on VFO {self.name}without transverter") return self.txEnabled = enabled if self.txEnabled: self.txButton.setIcon(QIcon('resources/img/icon_tx_enabled.png')) else: self.txButton.setIcon(QIcon('resources/img/icon_tx_disabled.png')) def toggle_tx(self): """ Toggles whether TX is enabled """ if self.transverter: self.enable_tx(not self.txEnabled) def set_transverter(self, transverter: Transverter) -> None: self.transverterMutex.acquire() self.transverter = transverter logging.info(f"VFO {self.name} now controlling transverter" " \"{transverter.name}\"") self.transverterMutex.release() def handle_keypress(self, key): constants = {"G": 1e9, "M": 1e6, "k": 1e3, "x": 1} if key in ["G", "M", "k", "x"]: freq = float(self.enteredFreq) * constants[key] if self.set_freq(freq): # Successfully set frequency self.freqWindow.hide() elif key == -1: self.enteredFreq = self.enteredFreq[:-1] self.enteredFreqLabel.setText(self.enteredFreq) else: self.enteredFreq += str(key) self.enteredFreqLabel.setText(self.enteredFreq) def open_freq_window(self): self.enteredFreq = "" self.enteredFreqLabel.setText(self.enteredFreq) self.freqWindow.show() def set_freq(self, freq) -> bool: """ Attempts to set VFO to frequency. Returns True if it succeeds """ if not self.transverter or \ freq < self.transverter.minFreq or \ freq > self.transverter.maxFreq: # Current transverter is unsuitable or we don't have one if not self.get_suitable_transverter(freq): # Can't find a suitable one return False # We now have a suitable transverter self.publish_freq(freq) logging.info(f"VFO {self.name} set to {readable_freq(freq)}") return True def get_suitable_transverter(self, freq) -> bool: """ Attempts to find a transverter that can operate at a frequency and requests control of that transverter. Returns True if succeeds """ # Have to find a suitable transverter candidates = self.networkHandler.get_supported_transverters(freq) if not candidates: self.warnings.add_status("No transverter found for {}".format( readable_freq(freq))) return False elif len(candidates) == 1: transverter = candidates[0] if transverter.channel: # @TODO How to behave if transverter is already controlled raise NotImplementedError else: if self.request_transverter_control(transverter): return True else: # Request failed return False # @TODO else: # TODO decide between multiple possibilities raise NotImplementedError def request_transverter_control(self, transverter: Transverter) -> bool: """ Requests control of a specific transverter """ if self.transverter: self.surrender_transverter_control() assert self.transverter is None assert self.sdrChannel is None logging.info('Attempting to take control of transverter "{}"'.format( transverter.name)) self.transverterMutex.release() self.mqtt.register_callback( "/{}/requestResponse".format(transverter.sdrMac), self.process_transverter_control_request_response, requiresMainThread=False) self.transverterResponded.clear() try: self.mqtt.publish( "/{}/requests".format(transverter.sdrMac), json.dumps({ "address": transverter.address, "controllerName": NAME, "controllerMac": get_mac(), "vfo": self.name })) if not self.transverterResponded.wait(timeout=5): # Requested timed out self.warnings.add_warning( NAME, "MQTT", f"Timed out waiting for response from {transverter.sdrMac}" ) else: # Got here so SDR responded, self.sdrChannel now either # contains the channel name if everything was successfuly # or None if it failed if self.sdrChannel: self.transverter = transverter self.mqttTopic = "/{}/channel{}".format( transverter.sdrMac, self.sdrChannel) self.mqtt.register_callback(self.mqttTopic, self.rx_status) else: # SDR responded but refused connection self.warnings.add_warning( NAME, "MQTT", f"Refused transverter control by {transverter.sdrMac}") finally: self.mqtt.remove_callback("/{}/requestResponse".format( transverter.sdrMac)) self.transverterMutex.acquire() return bool(self.transverter) def process_transverter_control_request_response(self, msg): self.transverterMutex.acquire() self.transverterResponse = msg if (msg["status"] == "success" and msg["controllerMac"] == get_mac() and msg["vfo"] == self.name): self.sdrChannel = msg["channel"] self.transverterMutex.release() self.transverterResponded.set() def process_transverter_control_surrender_response(self, msg): self.transverterMutex.acquire() self.transverterResponse = msg if (msg["status"] == "success" and msg["controllerMac"] is None and msg["vfo"] is None): self.sdrChannel = None self.transverterMutex.release() self.transverterResponded.set() def surrender_transverter_control(self) -> None: """ Stops this VFO being the controller of a transverter """ logging.info("Surrendering control of transverter {}".format( self.transverter.name)) self.transverterMutex.release() self.mqtt.register_callback( "/{}/requestResponse".format(self.transverter.sdrMac), self.process_transverter_control_surrender_response, requiresMainThread=False) self.transverterResponded.clear() sdrMac = self.transverter.sdrMac try: self.mqtt.publish( "/{}/requests".format(sdrMac), json.dumps({ "address": self.transverter.address, "controllerName": None, "controllerMac": None, "vfo": None })) if not self.transverterResponded.wait(timeout=5): # Requested timed out self.warnings.add_warning( NAME, "MQTT", f"Timed out waiting for response from {sdrMac}") else: # Got here so SDR responded, self.sdrChannel now either # contains None if everything was successfuly # or the old channel name if it failed if self.sdrChannel is None: self.mqtt.remove_callback(self.mqttTopic) self.sdrChannel = None self.mqttTopic = None self.transverter = None else: # SDR responded but refused connection self.warnings.add_warning( NAME, "MQTT", f"Refused release of transverter control by {sdrMac}") finally: self.mqtt.remove_callback("/{}/requestResponse".format(sdrMac)) self.transverterMutex.acquire() def publish_freq(self, freq): x = {"freq": freq} self.mqtt.publish("{}/set".format(self.mqttTopic), json.dumps(x)) self.publishedFreq = int(freq) def publish_mode(self, mode): x = {"mode": mode} self.mqtt.publish("{}/set".format(self.mqttTopic), json.dumps(x)) self.modeWindow.hide() def increment(self): if self.transverter: self.publish_freq(self.publishedFreq + self._stepSize) def decrement(self): if self.transverter: self.publish_freq(self.publishedFreq - self._stepSize) def rx_status(self, msg): freq = msg['freq'] mode = msg['mode'] if (freq != self.freq): self.freq = freq # Easier to work with strings now freq = str(freq) prettyFreq = "" stepIndex = log10(self._stepSize) for i in range(len(freq)): if (i == stepIndex): prettyFreq = "<u>" + freq[len(freq) - 1 - i] + "</u>" + prettyFreq else: prettyFreq = freq[len(freq) - 1 - i] + prettyFreq if ((i + 1) % 3 == 0): prettyFreq = "." + prettyFreq self.freqButton.setText(prettyFreq.lstrip(".")) logging.debug(f"VFO {self.name} set to {readable_freq(self.freq)}") if mode != self.mode: if self.mode is None: self.modeButton.clicked.connect(self.modeWindow.show) self.mode = mode self.modeButton.setText(mode) if self.modeWindow.isVisible(): self.modeWindow.hide() logging.debug(f"VFO {self.name} set to {self.mode}")
class Application(QApplication): def __init__(self): super().__init__() self.node_set = NodeSet() self.parent = QWidget() self.parent.hide() self.parent.setWindowFlags(self.parent.windowFlags() & ~QtCore.Qt.Tool) self.system_tray = SystemTray(self.parent, self.node_set) self.setQuitOnLastWindowClosed(False) self.aboutToQuit.connect(self.quit_app) self.system_tray.show() self.system_tray.showMessage('Nodes starting...', 'Bitcoin and Lightning are syncing') self.node_set.bitcoin.file.file_watcher.fileChanged.connect( self.check_restart_required) self.node_set.lnd.file.file_watcher.fileChanged.connect( self.check_restart_required) self.timer = QTimer(self) self.timer.start(1000) self.timer.timeout.connect(self.check_restart_required) self.timer.singleShot(1000, self.check_version) def check_restart_required(self): if self.node_set.bitcoin.restart_required or self.node_set.lnd.restart_required: pass else: pass def check_version(self): latest_version = LauncherSoftware().get_latest_release_version() if latest_version is None: return latest_major, latest_minor, latest_bugfix = latest_version.split('.') major, minor, bugfix = NODE_LAUNCHER_RELEASE.split('.') major_upgrade = latest_major > major minor_upgrade = (latest_major == major and latest_minor > minor) bugfix_upgrade = (latest_major == major and latest_minor == minor and latest_bugfix > bugfix) if major_upgrade or minor_upgrade or bugfix_upgrade: message_box = QMessageBox(self) message_box.setTextFormat(Qt.RichText) message_box.setText(UPGRADE) message_box.setInformativeText( f'Your version: {NODE_LAUNCHER_RELEASE}\n' f'New version: {latest_version}') message_box.exec_() @Slot() def quit_app(self): self.node_set.lnd.process.terminate() self.node_set.lnd.process.waitForFinished(2000) self.node_set.bitcoin.process.terminate() self.node_set.bitcoin.process.waitForFinished(20000) self.node_set.bitcoin.process.kill() QCoreApplication.exit(0)
class ScaleWidget(QWidget): stretches_list = ['powerdist', 'asinh', 'contrastbias', 'histogram', 'linear', 'log', 'power', 'sinh', 'sqrt', 'square'] intervals_list = ['zscale', 'minmax', 'manual', 'percentile', 'asymetric'] def __init__(self, parent, scales_model, cmap_model): QWidget.__init__(self, parent) self.parent = parent self.scalesModel = scales_model self.cmapModel = cmap_model self.ignore_signals = False layout = QVBoxLayout() layout.setSpacing(0) # pernament linear stretch self.perm_linear_widget = self.createLinearSliders() layout.addWidget(QLabel('Linear')) layout.addWidget(self.perm_linear_widget) # comboboxes self.combobox_widget = QWidget() self.combobox_layout = self.createComboboxes() self.combobox_widget.setLayout(self.combobox_layout) layout.addWidget(self.combobox_widget) # Stretch self.stretch_sliders_widget = QWidget() self.stretch_sliders_layout = self.createStretchStackedLayout() self.stretch_sliders_widget.setLayout(self.stretch_sliders_layout) layout.addWidget(self.stretch_sliders_widget) # Interval self.interval_sliders_widget = QWidget() self.interval_sliders_layout = self.createIntervalStackedLayout() self.interval_sliders_widget.setLayout(self.interval_sliders_layout) layout.addWidget(self.interval_sliders_widget) self.setLayout(layout) self.adjustCombos() self.adjustCmapCombo() self.adjustSliders() self.scalesModel.observe(lambda change: self.onScaleModelChange(change)) self.cmapModel.observe(lambda change: self.onCmapModelChange(change)) def createStretchStackedLayout(self): self.stretchStackedLayout = QStackedLayout() asinh = self.createAsinhParamsSliders() contrastbias = self.createContrastbiasParamsSliders() histogram = QLabel('') linear = QLabel('') log = self.createLogSliders() powerdist = self.createPowerdistSliders() power = self.createPowerSliders() sinh = self.createSinhSliders() sqrt = QLabel('') square = QLabel('') self.stretchStackedLayout.addWidget(powerdist) self.stretchStackedLayout.addWidget(asinh) self.stretchStackedLayout.addWidget(contrastbias) self.stretchStackedLayout.addWidget(histogram) self.stretchStackedLayout.addWidget(linear) self.stretchStackedLayout.addWidget(log) self.stretchStackedLayout.addWidget(power) self.stretchStackedLayout.addWidget(sinh) self.stretchStackedLayout.addWidget(sqrt) self.stretchStackedLayout.addWidget(square) return self.stretchStackedLayout def createIntervalStackedLayout(self): self.intervalStackedLayout = QStackedLayout() manual = self.createManualParamsSliders() percentile = self.createPercentileParamsSliders() asymetric = self.createAsymetricParamsSliders() zscale = self.createZscaleParamsSliders() self.intervalStackedLayout.addWidget(zscale) self.intervalStackedLayout.addWidget(QLabel("")) self.intervalStackedLayout.addWidget(manual) self.intervalStackedLayout.addWidget(percentile) self.intervalStackedLayout.addWidget(asymetric) return self.intervalStackedLayout def createManualParamsSliders(self): widget = QWidget() layout = QFormLayout() layout.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow) self.manual_vmin = FloatSlider(min=0.0, max=30000.0) self.manual_vmax = IntSlider(min=0, max=30000) self.manual_vmin.valueChanged.connect(lambda val=vars: self.onSliderChange('interval_manual_vmin', val)) self.manual_vmax.valueChanged.connect(lambda val=vars: self.onSliderChange('interval_manual_vmax', val)) layout.addRow('vmin', self.manual_vmin) layout.addRow('vmax', self.manual_vmax) widget.setLayout(layout) return widget def createPercentileParamsSliders(self): widget = QWidget() layout = QFormLayout() layout.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow) self.percentile_percentile = FloatSlider(min=0.1, max=2.0) self.percentile_nsamples = IntSlider(min=1, max=2000) self.percentile_percentile.valueChanged.connect( lambda val=vars: self.onSliderChange('interval_percentile_percentile', val)) self.percentile_nsamples.valueChanged.connect( lambda val=vars: self.onSliderChange('interval_percentile_nsamples', val)) layout.addRow('percentile', self.percentile_percentile) layout.addRow('samples', self.percentile_nsamples) widget.setLayout(layout) return widget def createAsymetricParamsSliders(self): widget = QWidget() layout = QFormLayout() layout.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow) self.asymetric_lpercentile = FloatSlider(min=0.0, max=2.0) self.asymetric_upercentile = FloatSlider(min=0.0, max=2.0) self.asymetric_nsamples = IntSlider(min=0, max=2000) self.asymetric_lpercentile.valueChanged.connect( lambda val=vars: self.onSliderChange('interval_asymetric_lower_percentile', val)) self.asymetric_upercentile.valueChanged.connect( lambda val=vars: self.onSliderChange('interval_asymetric_upper_percentile', val)) self.asymetric_nsamples.valueChanged.connect( lambda val=vars: self.onSliderChange('interval_asymetric_nsamples', val)) layout.addRow("l_percentile", self.asymetric_lpercentile) layout.addRow("u_percentile", self.asymetric_upercentile) layout.addRow("samples", self.asymetric_nsamples) widget.setLayout(layout) widget.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) return widget def createZscaleParamsSliders(self): widget = QWidget() layout = QFormLayout() layout.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow) self.zscale_nsamples = IntSlider(min=0, max=2000) self.zscale_contrast = FloatSlider(min=0.0, max=2.0) self.zscale_mreject = FloatSlider(min=0.0, max=2.0) self.zscale_minpixels = IntSlider(min=0.0, max=20) self.zscale_krej = FloatSlider(min=0.0, max=5.0) self.zscale_miterations = IntSlider(min=0, max=20) self.zscale_nsamples.valueChanged.connect(lambda val=vars: self.onSliderChange('interval_zscale_nsamples', val)) self.zscale_contrast.valueChanged.connect(lambda val=vars: self.onSliderChange('interval_zscale_contrast', val)) self.zscale_mreject.valueChanged.connect(lambda val=vars: self.onSliderChange('interval_zscale_maxreject', val)) self.zscale_minpixels.valueChanged.connect( lambda val=vars: self.onSliderChange('interval_zscale_minpixels', val)) self.zscale_krej.valueChanged.connect(lambda val=vars: self.onSliderChange('interval_zscale_krej', val)) self.zscale_miterations.valueChanged.connect( lambda val=vars: self.onSliderChange('interval_zscale_maxiterations', val)) layout.addRow("samples", self.zscale_nsamples) layout.addRow("contrast", self.zscale_contrast) layout.addRow("max reject", self.zscale_mreject) layout.addRow("pixels", self.zscale_minpixels) layout.addRow("krej", self.zscale_krej) layout.addRow("m_iterations", self.zscale_miterations) widget.setLayout(layout) return widget def createComboboxes(self): layout = QHBoxLayout() self.stretch_combobox = QComboBox() self.stretch_combobox.setFocusPolicy(Qt.NoFocus) self.stretch_combobox.addItems(self.stretches_list) self.interval_combobox = QComboBox() self.interval_combobox.setFocusPolicy(Qt.NoFocus) self.interval_combobox.addItems(self.intervals_list) self.color_combobox = QComboBox() self.color_combobox.setFocusPolicy(Qt.NoFocus) self.color_combobox.addItems(self.cmapModel.colormaps.keys()) self.color_combobox.setCurrentText(self.cmapModel.cmap_idx) layout.addWidget(self.stretch_combobox) layout.addWidget(self.interval_combobox) layout.addWidget(self.color_combobox) self.stretch_combobox.activated.connect(lambda activated: self.on_select_stretch()) self.interval_combobox.activated.connect(lambda activated: self.on_select_interval()) self.color_combobox.activated.connect(lambda activated: self.on_select_cmap()) return layout # For functionalty below, see FitsPlotterControlled.scale_from_model() # def fitsNormalization(self, stretch, interval): # self.parent.fits_image.set_normalization(stretch=stretch, # interval=interval, # stretchkwargs=self.scalesModel.dictionary[stretch], # intervalkwargs=self.scalesModel.dictionary[interval]) # # self.parent.fits_image.invalidate() # self.parent.updateFitsInWidgets() def createAsinhParamsSliders(self): widget = QWidget() layout = QFormLayout() layout.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow) self.asinh_a = FloatSlider(min=0.1, max=2.0) self.asinh_a.valueChanged.connect( lambda val=vars: self.onSliderChange('stretch_asinh_a', val)) layout.addRow('a', self.asinh_a) widget.setLayout(layout) return widget def createContrastbiasParamsSliders(self): widget = QWidget() layout = QFormLayout() layout.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow) self.contrast_contrast = FloatSlider(min=0.0, max=4.0) self.contrast_bias = FloatSlider(min=0.0, max=2.0) self.contrast_contrast.valueChanged.connect( lambda val=vars: self.onSliderChange('stretch_contrastbias_contrast', val)) self.contrast_bias.valueChanged.connect(lambda val=vars: self.onSliderChange('stretch_contrastbias_bias', val)) layout.addRow('contrast', self.contrast_contrast) layout.addRow('bias', self.contrast_bias) widget.setLayout(layout) return widget def createLinearSliders(self): widget = QWidget() layout = QFormLayout() layout.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow) self.linear_slope = FloatSlider(min=0.1, max=3.0) self.linear_intercept = FloatSlider(min=-1.0, max=1.0) self.linear_slope.valueChanged.connect(lambda val=vars: self.onSliderChange('stretch_linear_slope', val)) self.linear_intercept.valueChanged.connect( lambda val=vars: self.onSliderChange('stretch_linear_intercept', val)) layout.addRow('slope', self.linear_slope) layout.addRow('intercept', self.linear_intercept) widget.setLayout(layout) return widget def createLogSliders(self): widget = QWidget() layout = QFormLayout() layout.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow) self.log_a = FloatSlider(min=0.1, max=2000.0) self.log_a.valueChanged.connect(lambda val=vars: self.onSliderChange('stretch_log_a', val)) layout.addRow('a', self.log_a) widget.setLayout(layout) return widget def createPowerdistSliders(self): widget = QWidget() layout = QFormLayout() layout.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow) self.powerdist_a = FloatSlider(min=10.0, max=2000.0) self.powerdist_a.valueChanged.connect(lambda val=vars: self.onSliderChange('stretch_powerdist_a', val)) layout.addRow('a', self.powerdist_a) widget.setLayout(layout) return widget def createPowerSliders(self): widget = QWidget() layout = QFormLayout() layout.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow) self.power_a = FloatSlider(min=0.1, max=2.0) self.power_a.valueChanged.connect(lambda val=vars: self.onSliderChange('stretch_power_a', val)) layout.addRow('a', self.power_a) widget.setLayout(layout) return widget def createSinhSliders(self): widget = QWidget() layout = QFormLayout() layout.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow) self.sinh_a = FloatSlider(min=0.1, max=1.0) self.sinh_a.valueChanged.connect(lambda val=vars: self.onSliderChange('stretch_sinh_a', val)) layout.addRow('a', self.sinh_a) widget.setLayout(layout) return widget def onSliderChange(self, param, value): if self.ignore_signals: return self.ignore_signals = True try: setattr(self.scalesModel, param, value) finally: self.ignore_signals = False def onScaleModelChange(self, change): if self.ignore_signals: # avoid selfupdate on moving sliders return self.adjustCombos() self.adjustSliders() def onCmapModelChange(self, change): self.adjustCmapCombo() def on_select_stretch(self): self.scalesModel.selected_stretch = self.stretch_combobox.currentText() def on_select_interval(self): self.scalesModel.selected_interval = self.interval_combobox.currentText() def on_select_cmap(self): self.cmapModel.cmap_idx = self.color_combobox.currentText() def adjustCombos(self): """Select layout page from model and set scale combos value""" self.stretch_combobox.setCurrentText(self.scalesModel.selected_stretch) self.interval_combobox.setCurrentText(self.scalesModel.selected_interval) try: self.stretchStackedLayout.setCurrentIndex( self.stretches_list.index(self.scalesModel.selected_stretch)) except ValueError: pass try: self.intervalStackedLayout.setCurrentIndex( self.intervals_list.index(self.scalesModel.selected_interval)) except ValueError: pass try: self.adjustWidgetHeight(self.scalesModel.selected_stretch, self.scalesModel.selected_interval) except ValueError: pass # self.fitsNormalization(self.stretch_combobox.currentText(), self.interval_combobox.currentText()) def adjustWidgetHeight(self, stretch, interval): self.perm_linear_widget.setMaximumHeight(100) self.combobox_widget.setMaximumHeight(40) widget_height = 160 if stretch == 'linear' or stretch == 'histogram' or stretch == 'sqrt' or stretch == 'square': self.stretch_sliders_widget.hide() else: self.stretch_sliders_widget.show() if stretch == 'contrastbias' or stretch == 'linear': widget_height = widget_height + 100 self.stretch_sliders_widget.setMaximumHeight(100) else: widget_height = widget_height + 50 self.stretch_sliders_widget.setMaximumHeight(50) if interval == 'minmax': self.interval_sliders_widget.hide() else: self.interval_sliders_widget.show() if interval == 'asymetric': widget_height = widget_height + 120 self.interval_sliders_widget.setMaximumHeight(120) elif interval == 'zscale': widget_height = widget_height + 210 self.interval_sliders_widget.setMaximumHeight(210) else: widget_height = widget_height + 80 self.interval_sliders_widget.setMaximumHeight(80) self.setMaximumHeight(widget_height) def adjustCmapCombo(self): """Set combo value""" self.color_combobox.setCurrentText(self.cmapModel.cmap_idx) def adjustSliders(self): ignore_signals = self.ignore_signals self.ignore_signals = True try: self.manual_vmin.set_value_from_settings(self.scalesModel.interval_manual_vmin) self.manual_vmax.set_value_from_settings(self.scalesModel.interval_manual_vmax) self.percentile_percentile.set_value_from_settings(self.scalesModel.interval_percentile_percentile) self.percentile_nsamples.set_value_from_settings(self.scalesModel.interval_percentile_nsamples) self.asymetric_lpercentile.set_value_from_settings(self.scalesModel.interval_asymetric_lower_percentile) self.asymetric_upercentile.set_value_from_settings(self.scalesModel.interval_asymetric_upper_percentile) self.asymetric_nsamples.set_value_from_settings(self.scalesModel.interval_asymetric_nsamples) self.zscale_nsamples.set_value_from_settings(self.scalesModel.interval_zscale_nsamples) self.zscale_contrast.set_value_from_settings(self.scalesModel.interval_zscale_contrast) self.zscale_mreject.set_value_from_settings(self.scalesModel.interval_zscale_maxreject) self.zscale_minpixels.set_value_from_settings(self.scalesModel.interval_zscale_minpixels) self.zscale_krej.set_value_from_settings(self.scalesModel.interval_zscale_krej) self.zscale_miterations.set_value_from_settings(self.scalesModel.interval_zscale_maxiterations) self.asinh_a.set_value_from_settings(self.scalesModel.stretch_asinh_a) self.contrast_contrast.set_value_from_settings(self.scalesModel.stretch_contrastbias_contrast) self.contrast_bias.set_value_from_settings(self.scalesModel.stretch_contrastbias_bias) self.linear_slope.set_value_from_settings(self.scalesModel.stretch_linear_slope) self.linear_intercept.set_value_from_settings(self.scalesModel.stretch_linear_intercept) self.log_a.set_value_from_settings(self.scalesModel.stretch_log_a) self.powerdist_a.set_value_from_settings(self.scalesModel.stretch_powerdist_a) self.power_a.set_value_from_settings(self.scalesModel.stretch_power_a) self.sinh_a.set_value_from_settings(self.scalesModel.stretch_sinh_a) finally: self.ignore_signals = ignore_signals def writeSlidersValues(self): # TODO: Move Save/Load settings to model object settings = QSettings() settings.beginGroup("Sliders") settings.setValue("asinh/a", self.scalesModel.stretch_asinh_a) settings.setValue("contrast/contrast", self.scalesModel.stretch_contrastbias_contrast) settings.setValue("contrast/bias", self.scalesModel.stretch_contrastbias_bias) settings.setValue("linear/slope", self.scalesModel.stretch_linear_slope) settings.setValue("linear/intercept", self.scalesModel.stretch_linear_intercept) settings.setValue("log/a", self.scalesModel.stretch_log_a) settings.setValue("powerdist/a", self.scalesModel.stretch_powerdist_a) settings.setValue("power/a", self.scalesModel.stretch_power_a) settings.setValue("sinh/a", self.scalesModel.stretch_sinh_a) settings.setValue("manual/vmin", self.scalesModel.interval_manual_vmin) settings.setValue("manual/vmax", self.scalesModel.interval_manual_vmax) settings.setValue("percentile/percentile", self.scalesModel.interval_percentile_percentile) settings.setValue("percentile/nsamples", self.scalesModel.interval_percentile_nsamples) settings.setValue("asymetric/lpercentile", self.scalesModel.interval_asymetric_lower_percentile) settings.setValue("asymetric/upercentile", self.scalesModel.interval_asymetric_upper_percentile) settings.setValue("asymetric/nsamples", self.scalesModel.interval_asymetric_nsamples) settings.setValue("zscale/contrast", self.scalesModel.interval_zscale_contrast) settings.setValue("zscale/nsamples", self.scalesModel.interval_zscale_nsamples) settings.setValue("zscale/maxreject", self.scalesModel.interval_zscale_maxreject) settings.setValue("zscale/minpixels", self.scalesModel.interval_zscale_minpixels) settings.setValue("zscale/krej", self.scalesModel.interval_zscale_krej) settings.setValue("zscale/maxiterations", self.scalesModel.interval_zscale_maxiterations) settings.setValue("cmap", self.cmapModel.cmap_idx) settings.setValue("stretch", self.scalesModel.selected_stretch) settings.setValue("interval", self.scalesModel.selected_interval) # settings.setValue("cmap", self.color_combobox.currentIndex()) settings.endGroup() def readSlidersValues(self): settings = QSettings() settings.beginGroup("Sliders") self.setModelValue('cmap_idx', settings.value("cmap"), model=self.cmapModel) self.setModelValue('selected_stretch', settings.value("stretch")) self.setModelValue('selected_interval', settings.value("interval")) # self.color_combobox.setCurrentIndex(settings.value("cmap", 3)) self.setModelValue('stretch_asinh_a', float(settings.value('asinh/a'))) self.setModelValue('stretch_contrastbias_contrast', float(settings.value('contrast/contrast'))) self.setModelValue('stretch_contrastbias_bias', float(settings.value('contrast/bias'))) self.setModelValue('stretch_linear_slope', float(settings.value('linear/slope'))) self.setModelValue('stretch_linear_intercept', float(settings.value('linear/intercept'))) self.setModelValue('stretch_log_a', float(settings.value('log/a'))) self.setModelValue('stretch_powerdist_a', float(settings.value('powerdist/a'))) self.setModelValue('stretch_power_a', float(settings.value('power/a'))) self.setModelValue('stretch_sinh_a', float(settings.value('sinh/a'))) self.setModelValue('interval_manual_vmin', float(settings.value('manual/vmin'))) self.setModelValue('interval_manual_vmax', int(settings.value('manual/vmax'))) self.setModelValue('interval_percentile_percentile', float(settings.value('percentile/percentile'))) self.setModelValue('interval_percentile_nsamples', int(settings.value('percentile/nsamples'))) self.setModelValue('interval_asymetric_lower_percentile', float(settings.value('asymetric/lpercentile'))) self.setModelValue('interval_asymetric_upper_percentile', float(settings.value('asymetric/upercentile'))) self.setModelValue('interval_asymetric_nsamples', int(settings.value('asymetric/nsamples'))) self.setModelValue('interval_zscale_nsamples', int(settings.value('zscale/nsamples'))) self.setModelValue('interval_zscale_contrast', float(settings.value('zscale/contrast'))) self.setModelValue('interval_zscale_maxreject', float(settings.value('zscale/maxreject'))) self.setModelValue('interval_zscale_minpixels', int(settings.value('zscale/minpixels'))) self.setModelValue('interval_zscale_krej', float(settings.value('zscale/krej'))) self.setModelValue('interval_zscale_maxiterations', int(settings.value('zscale/maxiterations'))) settings.endGroup() def setModelValue(self, model_key, value, model=None): if model is None: model = self.scalesModel try: setattr(model, model_key, value) pass except TraitError: if value is not None: print(f'Attempt to set {model_key} := {value} caused exception. Ignored')
class mainWindow(QObject): signalRun = Signal(SpiderThread) signalRunApi = Signal(apiTester) def __init__(self): QObject.__init__(self) # must init parent QObject,if you want to use signal self.widget = QWidget() self.ipLabel = QLabel(self.widget) self.thread = QThread() self.worker = Worker() self.worker.moveToThread(self.thread) self.man = SpiderThread() self.api = apiTester() # 1 2:loop 3: time self.testStatus = [False,1,0,"ip","mac"] # advance config self.findCoreStop = True self.cleanCacheSet = False self.useApiTest = False self.showTestProgress = True self.saveTestLog = False self.saveLogPath = "" self.chromePath = "" self.showChrome = False self.coreDumpPath = "" # ui form self.passwordLabel = QLabel(self.widget) self.ipLineEdit = QLineEdit(self.widget) self.passwordLineEdit = QLineEdit(self.widget) self.startBtn = QPushButton(self.widget) self.stopBtn = QPushButton(self.widget) self.messageBox = QTextEdit(self.widget) self.messageBox.setReadOnly(True) self.userLabel = QLabel(self.widget) self.userLineEdit = QLineEdit(self.widget) self.intervalLabel = QLabel(self.widget) self.intervalSpinBox = QSpinBox(self.widget) self.loopLabel = QLabel(self.widget) self.loopSpinBox = QSpinBox(self.widget) self.intervalSpinBox.setRange(0,9999) self.loopSpinBox.setRange(1,9999) self.radioReboot = QRadioButton(self.widget) self.radioProvision = QRadioButton(self.widget) self.radioFactory = QRadioButton(self.widget) # self.apiCheckBox = QCheckBox(self.widget) self.menu = QMenu() self.gxpAction = QAction("Classic UI") self.grp2602Action = QAction("Ant Design UI") self.gxpAction.setCheckable(True) self.grp2602Action.setCheckable(True) self.menu.addAction(self.gxpAction) self.menu.addAction(self.grp2602Action) self.webLabel = QLabel(self.widget) self.webBtn = QPushButton(self.widget) self.webBtn.setMenu(self.menu) self.clearBtn = QPushButton(self.widget) self.messageList = deque() self.timer = QTimer() self.advanceBtn = QPushButton(self.widget) self.infoLabel = QLabel(self.widget) # provision widget self.provWidget = QWidget(self.widget) self.ver1Label = QLabel(self.provWidget) self.ver2Label = QLabel(self.provWidget) self.ver3Label = QLabel(self.provWidget) self.ver1LineEdit = QLineEdit(self.provWidget) self.ver2LineEdit = QLineEdit(self.provWidget) self.ver3LineEdit = QLineEdit(self.provWidget) self.dir1Label = QLabel(self.provWidget) self.dir2Label = QLabel(self.provWidget) self.dir3Label = QLabel(self.provWidget) self.dir1LineEdit = QLineEdit(self.provWidget) self.dir2LineEdit = QLineEdit(self.provWidget) self.dir3LineEdit = QLineEdit(self.provWidget) self.radioHttp = QRadioButton(self.provWidget) self.radioHttps = QRadioButton(self.provWidget) self.radioTftp = QRadioButton(self.provWidget) self.radioFtp = QRadioButton(self.provWidget) self.radioFtps = QRadioButton(self.provWidget) self.radioWindow = QRadioButton(self.provWidget) # advance widget self.advanceWidget = QWidget() self.checkCoreBox = QCheckBox(self.advanceWidget) self.cleanCache = QCheckBox(self.advanceWidget) self.checkSaveLogBox = QCheckBox(self.advanceWidget) self.selectDirBtn = QPushButton(self.advanceWidget) self.saveDirLabel = QLabel(self.advanceWidget) self.saveDirLabel.setStyleSheet("background:white") self.checkProgressBox = QCheckBox(self.advanceWidget) self.advanceOkBtn = QPushButton(self.advanceWidget) self.selectChromeBtn = QPushButton(self.advanceWidget) self.chromePathLabel = QLabel(self.advanceWidget) self.chromePathLabel.setStyleSheet("background:white") self.checkShowChromeBox = QCheckBox(self.advanceWidget) self.chromeLabel = QLabel(self.advanceWidget) self.pcapLabel = QLabel(self.advanceWidget) self.apiCheckBox = QCheckBox(self.advanceWidget) self.corePathLabel = QLabel(self.advanceWidget) self.corePathLabel.setStyleSheet("background:white") self.corePathBtn = QPushButton(self.advanceWidget) self.interfaceMenu = QComboBox(self.advanceWidget) self.interfaceMenu.addItem('Default') self.aiOptionBox= QCheckBox(self.advanceWidget) a = IFACES print(a) for i in a.keys(): print(a[i].description) self.interfaceMenu.addItem(a[i].description) # connect singal and slot self.startBtn.clicked.connect(self.clickedStarBtn) self.radioProvision.clicked.connect(self.clickedProvision) self.radioReboot.clicked.connect(self.clickedOthers) self.radioFactory.clicked.connect(self.clickedOthers) self.stopBtn.clicked.connect(self.clickedStopBtn) self.grp2602Action.triggered.connect(self.clickedGrp2602) self.gxpAction.triggered.connect(self.clickedGxpType) self.timer.timeout.connect(self.updateMessage) self.apiCheckBox.stateChanged.connect(self.apiTestBoxCheck) self.clearBtn.clicked.connect(self.clickedClearBtn) self.advanceBtn.clicked.connect(self.clickedAdvanceBtn) self.advanceOkBtn.clicked.connect(self.clickedAdvanceOkBtn) self.checkSaveLogBox.stateChanged.connect(self.saveLogBoxCheck) self.selectChromeBtn.clicked.connect(self.clickedSelectChromeBtn) self.selectDirBtn.clicked.connect(self.clickedSelectDirBtn) self.corePathBtn.clicked.connect(self.clickedCorePathBtn) self.worker.signalJobEnd.connect(self.slotTestStoped) self.worker.apiTestFinished.connect(self.slotTestStoped) self.signalRun.connect(self.worker.dowork) self.signalRunApi.connect(self.worker.doworkApi) # self.man.signalUpdateMessage.connect(self.pushMessage) def setupUI(self): # set text content /value self.widget.setWindowTitle("自动重启升降级测试工具") self.ipLabel.setText("初始IP:") self.passwordLabel.setText("密码:") self.startBtn.setText("开始测试") self.stopBtn.setText("停止测试") self.userLabel.setText("用户名:") self.intervalLabel.setText("间隔时间:") self.loopLabel.setText("测试次数:") self.intervalSpinBox.setValue(130) self.radioFactory.setText("恢复出厂") self.radioProvision.setText("升降级") self.radioReboot.setText("重启") self.ver1Label.setText("版本 1:") self.ver2Label.setText("版本 2:") self.ver3Label.setText("版本 3:") self.dir1Label.setText("路径 1:") self.dir2Label.setText("路径 2:") self.dir3Label.setText("路径 3:") self.radioHttp.setText("Http") self.radioHttps.setText("Https") self.radioTftp.setText("Tftp") self.radioFtp.setText("Ftp") self.radioFtps.setText("Ftps") self.apiCheckBox.setText("使用API测试,配置CoreDump下载路径") self.webLabel.setText("网页类型:") self.webBtn.setText("请选择UI类型") self.clearBtn.setText("清空输入") self.advanceWidget.setWindowTitle("高级设置") self.advanceBtn.setText("高级设置") self.checkCoreBox.setText("发现Core Dump时停止测试") self.cleanCache.setText("清除页面cache") self.checkSaveLogBox.setText("保存测试日志") self.selectDirBtn.setText("浏览") self.checkProgressBox.setText("显示底部状态条") self.advanceOkBtn.setText("OK") self.checkShowChromeBox.setText("测试时显示Chrome浏览器") self.selectChromeBtn.setText("浏览") self.chromeLabel.setText("Chrome浏览器路径") self.infoLabel.setText("未开始测试") self.pcapLabel.setText("Net Interface") self.corePathBtn.setText("浏览") self.radioWindow.setText("网页拖拽文件") # self.aiOptionBox.setText("AI") #init value self.saveDirLabel.hide() self.selectDirBtn.hide() self.gxpAction.setChecked(True) self.radioReboot.click() self.radioHttp.click() self.stopBtn.setEnabled(False) self.passwordLineEdit.setEchoMode(QLineEdit.PasswordEchoOnEdit) # set position------------------------------- xPos = 20 yPos = 30 colum2 = xPos +200 # line 1 self.ipLabel.move(xPos,yPos) self.intervalLabel.move(colum2,yPos) self.intervalSpinBox.move(colum2+60,yPos-2) self.ipLineEdit.move(xPos+50,yPos-2) # line 2 line2 = yPos +40 self.passwordLabel.move(xPos,line2) self.passwordLineEdit.move(xPos+50,line2-2) self.loopLabel.move(colum2,line2) self.loopSpinBox.move(colum2+60,line2-2) # line3 line3 = yPos +80 self.userLabel.move(xPos,line3) self.userLineEdit.move(xPos+50,line3-2) self.radioReboot.move(colum2,line3) self.radioFactory.move(colum2+60,line3) self.radioProvision.move(colum2,line3+30) self.webLabel.move(xPos,line3+40) self.webBtn.move(xPos+60,line3+35) # provWidget self.provWidget.resize(400,130) self.provWidget.move(xPos,line3+70) spaceY = 30 x = 0 y = 0 cl = 200 self.ver1Label.move(x,y) self.ver1LineEdit.move(x+50,y) self.ver2Label.move(x,y+spaceY) self.ver2LineEdit.move(x+50,y+spaceY) self.ver3Label.move(x,y+spaceY*2) self.ver3LineEdit.move(x+50,y+spaceY*2) self.dir1Label.move(cl,y) self.dir1LineEdit.move(cl+50,y) self.dir2Label.move(cl,y+spaceY) self.dir2LineEdit.move(cl+50,y+spaceY) self.dir3Label.move(cl,y+spaceY*2) self.dir3LineEdit.move(cl+50,y+spaceY*2) self.radioHttp.move(x,y+spaceY*3) self.radioHttps.move(x+50,y+spaceY*3) self.radioTftp.move(x+110,y+spaceY*3) self.radioFtp.move(x+160,y+spaceY*3) self.radioFtps.move(x+210,y+spaceY*3) self.radioWindow.move(x+265,y+spaceY*3) # advance widget self.advanceWidget.resize(300,400) x = 20 y = 20 space = 30 self.checkCoreBox.move(x,y) self.cleanCache.move(x,y+space) self.checkProgressBox.move(x,y+space*2) self.checkShowChromeBox.move(x,y+space*3) self.apiCheckBox.move(x,y+space*4) self.corePathBtn.move(x-2,y+space*5-8) self.corePathLabel.move(x+35,y+space*5-8) y += 40 self.chromeLabel.move(x,y+space*4+10) self.selectChromeBtn.move(x-2,y+space*5) self.chromePathLabel.move(x+35,y+space*5+2) self.checkSaveLogBox.move(x,y+space*6+10) self.selectDirBtn.move(x-2,y+space*7) self.saveDirLabel.move(x+35,y+space*7+2) self.advanceOkBtn.move(x+170,y+space*10+10) self.interfaceMenu.move(x-5,y+space*8+10) self.pcapLabel.move(x,y+space*8-2) # self.aiOptionBox.move(x, y+space*9+8) # set size self.messageBox.resize(373,155) # self.widget.resize(410,400) self.loopSpinBox.resize(60,25) self.intervalSpinBox.resize(60,25) self.webBtn.resize(100,25) self.saveDirLabel.resize(185,22) self.selectDirBtn.resize(32,24) self.selectChromeBtn.resize(32,24) self.chromePathLabel.resize(185,22) self.infoLabel.resize(400, 25) self.corePathBtn.resize(32,24) self.corePathLabel.resize(185,22) # self.provWidget.hide() self.changePosition(True) self.widget.show() self.loadCache() # ----------------end of setupUI --------------------- def changePosition(self,hide): xPos = 20 if hide: buttonLine = 200 self.widget.resize(420,415) else: buttonLine = 310 self.widget.resize(420,524) self.startBtn.move(xPos,buttonLine) self.stopBtn.move(xPos+90,buttonLine) self.clearBtn.move(xPos+180,buttonLine) self.advanceBtn.move(xPos+270,buttonLine) self.messageBox.move(xPos,buttonLine+30) boxH = self.messageBox.height() self.infoLabel.move(xPos,buttonLine+boxH+35) def setItemEnable(self,enable): self.provWidget.setEnabled(enable) self.ipLineEdit.setEnabled(enable) self.passwordLineEdit.setEnabled(enable) self.userLineEdit.setEnabled(enable) self.intervalSpinBox.setEnabled(enable) self.loopSpinBox.setEnabled(enable) self.radioFactory.setEnabled(enable) self.radioReboot.setEnabled(enable) self.radioProvision.setEnabled(enable) self.advanceBtn.setEnabled(enable) self.startBtn.setEnabled(enable) self.clearBtn.setEnabled(enable) if self.useApiTest: self.webBtn.setEnabled(False) else: self.webBtn.setEnabled(enable) self.stopBtn.setEnabled(not enable) def outputError(self,str): appstr = "<span style=\"color:red\">" appstr += str + "</span>" self.messageBox.append(appstr) def outputWarning(self,str): appstr = "<span style=\"color:orange\">" appstr += str + "</span>" self.messageBox.append(appstr) def loadCache(self): file = QFile("cache") if not file.open(QIODevice.ReadOnly | QIODevice.Text): return inStream = QTextStream(file) # ip self.ipLineEdit.setText(inStream.readLine()) # passwordLabel self.passwordLineEdit.setText(inStream.readLine()) # user self.userLineEdit.setText(inStream.readLine()) # ver1 self.ver1LineEdit.setText(inStream.readLine()) self.dir1LineEdit.setText(inStream.readLine()) # ver2 self.ver2LineEdit.setText(inStream.readLine()) self.dir2LineEdit.setText(inStream.readLine()) # ver3 self.ver3LineEdit.setText(inStream.readLine()) self.dir3LineEdit.setText(inStream.readLine()) self.intervalSpinBox.setValue(int(inStream.readLine())) self.loopSpinBox.setValue(int(inStream.readLine())) # web type button webType = inStream.readLine() if webType == "gxpAction": self.grp2602Action.setChecked(False) self.gxpAction.setChecked(True) self.webBtn.setText(self.gxpAction.text()) else: self.grp2602Action.setChecked(True) self.gxpAction.setChecked(False) self.webBtn.setText(self.grp2602Action.text()) testType = inStream.readLine() if testType == "reboot": self.radioReboot.setChecked(True) elif testType == "provision": self.radioProvision.setChecked(True) self.changePosition(False) self.provWidget.show() else: self.radioFactory.setChecked(True) serverType = inStream.readLine() if serverType == "Http": self.radioHttp.setChecked(True) elif serverType == "Https": self.radioHttps.setChecked(True) elif serverType == "Tftp": self.radioTftp.setChecked(True) elif serverType == "Ftp": self.radioFtp.setChecked(True) elif serverType == "Ftps": self.radioFtps.setChecked(True) else: self.radioWindow.setChecked(True) if inStream.readLine() == "True": self.findCoreStop = True else: self.findCoreStop = False if inStream.readLine() == "True": self.cleanCacheSet = True else: self.cleanCacheSet = False if inStream.readLine() == "True": self.useApiTest = True self.webBtn.setEnabled(False) else: self.useApiTest = False self.corePathBtn.hide() self.corePathLabel.hide() if inStream.readLine() == "True": self.showTestProgress = True else: self.showTestProgress = False self.infoLabel.hide() if inStream.readLine() == "True": self.showChrome = True else: self.showChrome = False self.chromePath = inStream.readLine() if inStream.readLine() == "True": self.saveTestLog = True else: self.saveTestLog = False self.saveLogPath = inStream.readLine() self.coreDumpPath = inStream.readLine() file.close() def saveCache(self): file = QFile("cache") if not file.open(QIODevice.WriteOnly): return content = self.ipLineEdit.text() + "\n" content += self.passwordLineEdit.text() + "\n" content += self.userLineEdit.text() + "\n" content += self.ver1LineEdit.text() + "\n" content += self.dir1LineEdit.text() + "\n" content += self.ver2LineEdit.text() + "\n" content += self.dir2LineEdit.text() + "\n" content += self.ver3LineEdit.text() + "\n" content += self.dir3LineEdit.text() + "\n" content += str(self.intervalSpinBox.value()) + "\n" content += str(self.loopSpinBox.value()) + "\n" if self.gxpAction.isChecked(): content += "gxpAction\n" else: content += "grp2602Action\n" if self.radioReboot.isChecked(): content += "reboot\n" elif self.radioProvision.isChecked(): content += "provision\n" else: content += "factory\n" if self.radioHttp.isChecked(): content += "Http\n" elif self.radioHttps.isChecked(): content += "Https\n" elif self.radioTftp.isChecked(): content += "Tftp\n" elif self.radioFtp.isChecked(): content += "Ftp\n" elif self.radioFtps.isChecked(): content += "Ftps\n" else : content += "Window\n" content += str(self.findCoreStop) + "\n" content += str(self.cleanCacheSet) + "\n" content += str(self.useApiTest) + "\n" content += str(self.showTestProgress) + "\n" content += str(self.showChrome) + "\n" content += self.chromePath +"\n" content += str(self.saveTestLog) +"\n" content += self.saveLogPath +"\n" content += self.coreDumpPath +"\n" byteArr = bytes(content,"utf-8") file.write(QByteArray(byteArr)) file.close() def checkBeforeRun(self): containError = False #---------check Ip address-------------- if self.ipLineEdit.text() == "": containError = True self.outputError("IP地址不能为空!") else: pattern = re.compile("^((1[0-9][0-9]\.)|(2[0-4][0-9]\.)|(25[0-5]\.)|([1-9][0-9]\.)|([0-9]\.)){3}((1[0-9][0-9])|(2[0-4][0-9])|(25[0-5])|([1-9][0-9])|([0-9]))$") if not pattern.search(self.ipLineEdit.text()): containError = True self.outputError("IP地址格式错误,检查是否含有多余空格!(仅支持IPV4)") #------------------------ if self.passwordLineEdit.text() == "": containError = True self.outputError("密码不能为空!") if self.userLineEdit.text() == "": containError = True self.outputError("用户名不能为空!") if self.intervalSpinBox.value() <= 40: self.outputWarning("间隔时间过短,可能对测试造成影响") if not self.radioProvision.isChecked() and not self.radioReboot.isChecked() and \ not self.radioFactory.isChecked(): containError = True self.outputError("必须选择测试方式(重启,升降级,恢复出厂)") # check provision ---------- if self.radioProvision.isChecked(): if self.ver1LineEdit.text() == "" or self.ver2LineEdit.text() == "" or \ self.dir1LineEdit.text() == "" or self.dir2LineEdit.text() == "": containError = True self.outputError("升降级测试至少填上前两个版本及其路径") bin_name = "" if os.path.exists(os.path.abspath("config.ini") ): f = open(os.path.abspath("config.ini") , "r") line = f.readline() while line: option = line.split("=") if option[0] == "firmbinname": if option[1].strip('"') != "": filenamebin = option[1].strip() bin_name = filenamebin.strip('"') pass line = f.readline() f.close() filename = os.path.join(self.dir1LineEdit.text(), bin_name) if not os.path.exists(filename) and self.radioWindow.isChecked(): containError = True self.outputError("firmware1 文件不存在!" + filename) filename = os.path.join(self.dir2LineEdit.text(), bin_name) if not os.path.exists(filename) and self.radioWindow.isChecked(): containError = True self.outputError("firmware2 文件不存在!" + filename) filename = os.path.join(self.dir3LineEdit.text(), bin_name) if self.ver3LineEdit.text() != "" and self.dir3LineEdit.text() == "": containError = True self.outputError("填写了版本3,但对应路径为空!") if self.dir3LineEdit.text() != "" and self.ver3LineEdit.text() == "": containError = True self.outputError("填写了路径3,但对应版本为空!") elif self.dir3LineEdit.text() != "" and self.radioWindow.isChecked() and not os.path.exists(filename): containError = True self.outputError("firmware3 文件不存在!" + filename) if not self.radioFtp.isChecked() and not self.radioFtps.isChecked() and \ not self.radioHttp.isChecked() and not self.radioHttps.isChecked() and \ not self.radioTftp.isChecked() and not self.radioWindow.isChecked(): containError = True self.outputError("升降级测试必须选择服务器类型(Tftp,Ftp,Ftps,Http,Https)") return containError def startTest(self): ip = self.ipLineEdit.text() passwd = self.passwordLineEdit.text() username = self.userLineEdit.text() ptime = self.intervalSpinBox.value() loop = self.loopSpinBox.value() modelType = self.webBtn.text() if self.gxpAction.isChecked(): device_type = "GXP21XX" elif self.grp2602Action.isChecked(): device_type = "GRP260X" if self.radioReboot.isChecked(): task_type = "reboot" elif self.radioProvision.isChecked(): task_type = "provision" text_ver1 = self.ver1LineEdit.text() text_ver2 = self.ver2LineEdit.text() text_ver3 = self.ver3LineEdit.text() text_dir1 = self.dir1LineEdit.text() text_dir2 = self.dir2LineEdit.text() text_dir3 = self.dir3LineEdit.text() prov_dict = {"ver1": text_ver1.strip(), "dir1": text_dir1.strip(), "ver2": text_ver2.strip(), "dir2": text_dir2.strip(), "ver3": text_ver3.strip(), "dir3": text_dir3.strip()} if self.radioHttp.isChecked(): self.man.update_prov_setting("HTTP", prov_dict) elif self.radioHttps.isChecked(): self.man.update_prov_setting("HTTPS", prov_dict) elif self.radioTftp.isChecked(): self.man.update_prov_setting("TFTP", prov_dict) elif self.radioFtp.isChecked(): self.man.update_prov_setting("FTP", prov_dict) elif self.radioFtps.isChecked(): self.man.update_prov_setting("FTPS", prov_dict) elif self.radioWindow.isChecked(): self.man.update_prov_setting("browser", prov_dict) else: task_type = "reset" coredump_stop = True headless_flag = False clean_cache = False if self.checkCoreBox.isChecked() == False: self.messageBox.append("Find core dump will not stop") coredump_stop = False if self.cleanCache.isChecked() == True: clean_cache = True if self.checkShowChromeBox.isChecked() == True or self.radioWindow.isChecked() == True: headless_flag = False else: headless_flag = True # ai_mode = False # if self.aiOptionBox.isChecked() == True: # ai_mode = True browser_path = "" if self.chromePathLabel.text() != "": browser_path = self.chromePathLabel.text() self.testStatus = [True,1,0,"ip",""] print(self.interfaceMenu.currentText()) self.man.setStatus(self.testStatus) self.man.setMessageList(self.messageList) self.man.update_setting(ip.strip(), username.strip(), passwd.strip(), device_type, \ task_type, loop, ptime, coredump_stop, headless_flag, browser_path, \ clean_cache, self.interfaceMenu.currentText(), False) def startApiTest(self): ip = self.ipLineEdit.text() passwd = self.passwordLineEdit.text() username = self.userLineEdit.text() ptime = self.intervalSpinBox.value() loop = self.loopSpinBox.value() testType = "Reboot" if self.radioProvision.isChecked(): testType = "Provision" self.api.setValue(ip,username,passwd,ptime,loop,testType) v1 = self.ver1LineEdit.text() v2 = self.ver2LineEdit.text() v3 = self.ver3LineEdit.text() d1 = self.dir1LineEdit.text() d2 = self.dir2LineEdit.text() d3 = self.dir3LineEdit.text() self.api.setVersion(v1,v2,v3,d1,d2,d3) self.api.setTestStatus(self.testStatus,self.messageList,self.coreDumpPath) if self.radioHttp.isChecked(): self.api.setServerType("http") elif self.radioHttps.isChecked(): self.api.setServerType("https") elif self.radioTftp.isChecked(): self.api.setServerType("tftp") elif self.radioFtp.isChecked(): self.api.setServerType("ftp") else: #self.radioFtps.isChecked() self.api.setServerType("ftps") self.api.setFoundCoreStop(self.findCoreStop) # slot --------------------------------- def apiTestBoxCheck(self,state): if state == 2: self.corePathBtn.show() self.corePathLabel.show() else: self.corePathBtn.hide() self.corePathLabel.hide() def clickedCorePathBtn(self): dir = QFileDialog.getExistingDirectory(self.advanceWidget,"选择Core Dump存放路径","/home") if dir != "": self.corePathLabel.setText(dir) self.coreDumpPath = dir def clickedSelectDirBtn(self): dir = QFileDialog.getExistingDirectory(self.advanceWidget,"选择日志存放路径","/home") if dir != "": self.saveDirLabel.setText(dir) self.saveLogPath = dir def clickedSelectChromeBtn(self): fileName = QFileDialog.getOpenFileName(self.advanceWidget,"选择谷歌浏览器","/home","Chrome (*.exe)") if fileName != "": self.chromePathLabel.setText(fileName[0]) self.chromePath = fileName[0] def saveLogBoxCheck(self,state): if state == 2: # checked self.selectDirBtn.show() self.saveDirLabel.show() else: self.selectDirBtn.hide() self.saveDirLabel.hide() def clickedAdvanceOkBtn(self): self.findCoreStop = self.checkCoreBox.isChecked() self.cleanCacheSet = self.cleanCache.isChecked() self.useApiTest = self.apiCheckBox.isChecked() self.showTestProgress = self.checkProgressBox.isChecked() self.saveTestLog = self.checkSaveLogBox.isChecked() self.saveLogPath = self.saveDirLabel.text() self.showChrome = self.checkShowChromeBox.isChecked() self.chromePath = self.chromePathLabel.text() self.coreDumpPath = self.corePathLabel.text() if self.useApiTest: self.webBtn.setEnabled(False) self.corePathBtn.show() self.corePathLabel.show() else: self.webBtn.setEnabled(True) self.corePathBtn.hide() self.corePathLabel.hide() if self.showTestProgress: self.infoLabel.show() else: self.infoLabel.hide() self.saveCache() self.advanceWidget.hide() def clickedAdvanceBtn(self): self.advanceWidget.hide() self.checkCoreBox.setChecked(self.findCoreStop) self.cleanCache.setChecked(self.cleanCacheSet) self.apiCheckBox.setChecked(self.useApiTest) self.checkProgressBox.setChecked(self.showTestProgress) self.checkSaveLogBox.setChecked(self.saveTestLog) self.saveDirLabel.setText(self.saveLogPath) self.checkShowChromeBox.setChecked(self.showChrome) self.chromePathLabel.setText(self.chromePath) self.corePathLabel.setText(self.coreDumpPath) self.advanceWidget.show() def slotTestStoped(self): self.testStatus[0] = False self.setItemEnable(True) self.timer.stop() self.updateMessage() self.thread.quit() # save Test log if self.saveTestLog: fileName = time.strftime("%Y_%m_%d.%H_%M_%S.",time.localtime()) if self.radioReboot.isChecked(): fileName += "reboot" elif self.radioProvision: fileName += "provision" else: fileName += "factoryReset" fileName += ".htm" if self.saveLogPath == "": self.outputWarning("日志地址没有设置,无法保存") else: fileName = self.saveLogPath + "\\" + fileName print(fileName) file = QFile(fileName) if not file.open(QIODevice.WriteOnly): self.outputError("打开文件错误,保存日志失败") return byteArr = bytes(self.messageBox.toHtml(),"utf-8") file.write(QByteArray(byteArr)) file.close() def clickedClearBtn(self): self.ipLineEdit.setText("") self.passwordLineEdit.setText("") self.userLineEdit.setText("") self.ver1LineEdit.setText("") self.dir1LineEdit.setText("") self.ver2LineEdit.setText("") self.dir2LineEdit.setText("") self.ver3LineEdit.setText("") self.dir3LineEdit.setText("") def clickedStarBtn(self): if self.checkBeforeRun(): return self.messageBox.clear() self.saveCache() self.messageBox.append("Init Setting...") self.setItemEnable(False) self.timer.start(500) self.thread.start() # deside use what to test if self.useApiTest: if self.radioFactory.isChecked(): self.outputWarning("Api not support Factory Reset, will test as Gxp type web driver") self.clickedGxpType() self.startTest() self.signalRun.emit(self.man) else: self.startApiTest() self.signalRunApi.emit(self.api) else: self.startTest() self.signalRun.emit(self.man) def clickedStopBtn(self): self.stopBtn.setEnabled(False) self.testStatus[0] = False self.man.quit() self.outputWarning("正在停止...") def clickedProvision(self): self.provWidget.show() self.changePosition(False) def clickedOthers(self): self.provWidget.hide() self.changePosition(True) def clickedGxpType(self): self.gxpAction.setChecked(True) self.grp2602Action.setChecked(False) self.webBtn.setText(self.gxpAction.text()) def clickedGrp2602(self): self.gxpAction.setChecked(False) self.grp2602Action.setChecked(True) self.webBtn.setText(self.grp2602Action.text()) def updateMessage(self): while len(self.messageList) >0: info = self.messageList.popleft() self.messageBox.append(info) if self.testStatus[0] == False: self.infoLabel.setText("未开始测试") else: info = "第" + str(self.testStatus[1]) + "次测试 " if self.testStatus[2] > 0: info += "等待:{} 秒".format( self.testStatus[2] ) else: info += "运行中" info += " " + self.testStatus[3] info += " " + self.testStatus[4] self.infoLabel.setText(info)
class FlowStylusModesWidget(QWidget): def __init__(self, flow): super(FlowStylusModesWidget, self).__init__() # GENERAL ATTRIBUTES self.flow = flow self.pen_color = QColor(255, 255, 0) # stylus button self.stylus_button = QPushButton('stylus') # show/hide self.stylus_button.clicked.connect(self.on_stylus_button_clicked) # mode self.set_stylus_mode_comment_button = QPushButton('comment') self.set_stylus_mode_comment_button.clicked.connect( self.on_comment_button_clicked) self.set_stylus_mode_edit_button = QPushButton('edit') self.set_stylus_mode_edit_button.clicked.connect( self.on_edit_button_clicked) # pen style self.pen_color_button = QPushButton('color') self.pen_color_button.clicked.connect(self.on_choose_color_clicked) self.pen_width_slider = QSlider(Qt.Horizontal) self.pen_width_slider.setRange(1, 100) # -> /10 self.pen_width_slider.setValue(20) self.pen_style_widget = QWidget() pen_style_layout = QHBoxLayout() pen_style_layout.addWidget(self.pen_color_button) pen_style_layout.addWidget(self.pen_width_slider) self.pen_style_widget.setLayout(pen_style_layout) # MERGE SETTINGS self.settings_widget = QWidget() settings_layout = QHBoxLayout() settings_layout.addWidget(self.pen_style_widget) settings_layout.addWidget(self.set_stylus_mode_comment_button) settings_layout.addWidget(self.set_stylus_mode_edit_button) self.settings_widget.setLayout(settings_layout) # MAIN LAYOUT main_horizontal_layout = QHBoxLayout() main_horizontal_layout.addWidget(self.settings_widget) main_horizontal_layout.addWidget(self.stylus_button) self.setLayout(main_horizontal_layout) self.setStyleSheet(''' background: transparent; border: none; ''') self.push_button_style_sheet_start = ''' QPushButton:hover:pressed { background-color: rgba(100, 100, 100, 150); } QPushButton { border: 1px solid #aaaaaa; border-radius: 8px; height: 25px; width: 50px;''' # color may be dynamic self.std_push_button_style_sheet = self.push_button_style_sheet_start + ''' background-color: rgba(10, 10, 10, 150); color: #aaaaaa; } ''' self.set_stylus_mode_comment_button.setStyleSheet( self.std_push_button_style_sheet) self.set_stylus_mode_edit_button.setStyleSheet( self.std_push_button_style_sheet) self.stylus_button.setStyleSheet(self.std_push_button_style_sheet) self.update_color_button_SS() self.stylus_button.setFixedWidth(50) self.set_stylus_mode_edit_button.setFixedWidth(60) self.set_stylus_mode_comment_button.setFixedWidth(60) self.pen_style_widget.hide() self.settings_widget.hide() def on_stylus_button_clicked(self): if self.settings_widget.isHidden(): self.settings_widget.show() elif not self.settings_widget.isHidden(): self.settings_widget.hide() self.adjustSize() self.flow.set_stylus_proxy_pos() def on_edit_button_clicked(self): self.flow.stylus_mode = 'edit' self.pen_style_widget.hide() # if I don't hide and show the settings_widget manually here, the stylus mode buttons take up the additional # space when clicking on comment and then edit. self.adjustSize() does not seem to work properly here... self.settings_widget.hide() self.settings_widget.show() self.adjustSize() self.flow.set_stylus_proxy_pos() # self.flow.setDragMode(QGraphicsView.RubberBandDrag) def on_comment_button_clicked(self): self.flow.stylus_mode = 'comment' self.pen_style_widget.show() self.adjustSize() self.flow.set_stylus_proxy_pos() # self.flow.setDragMode(QGraphicsView.NoDrag) def on_choose_color_clicked(self): self.pen_color = QColorDialog.getColor( self.pen_color, options=QColorDialog.ShowAlphaChannel, title='Choose pen color') self.update_color_button_SS() def update_color_button_SS(self): self.pen_color_button.setStyleSheet( self.push_button_style_sheet_start + ''' color: black; background-color: ''' + self.pen_color.name() + '''; }''') def get_pen_settings(self): return { 'color': self.pen_color.name(), 'base stroke weight': self.pen_width_slider.value() / 10 }
class ImportDialog(QDialog): """ A widget for importing data into a Spine db. Currently used by DataStoreForm. It embeds three widgets that alternate depending on user's actions: - `select_widget` is a `QWidget` for selecting the source data type (CSV, Excel, etc.) - `_import_preview` is an `ImportPreviewWidget` for defining Mappings to associate with the source data - `_error_widget` is an `ImportErrorWidget` to show errors from import operations """ data_ready = Signal(dict) _SETTINGS_GROUP_NAME = "importDialog" def __init__(self, settings, parent): """ Args: settings (QSettings): settings for storing/restoring window state parent (DataStoreForm): parent widget """ from ...ui.import_source_selector import Ui_ImportSourceSelector # pylint: disable=import-outside-toplevel super().__init__(parent) self.setWindowFlag(Qt.Window, True) self.setWindowFlag(Qt.WindowMinMaxButtonsHint, True) self.setWindowTitle("Import data") # state self._mapped_data = None self._mapping_errors = [] connector_list = [ CSVConnector, ExcelConnector, SqlAlchemyConnector, GdxConnector, JSONConnector ] self.connectors = {c.DISPLAY_NAME: c for c in connector_list} self._selected_connector = None self.active_connector = None self._current_view = "connector" self._settings = settings self._preview_window_state = {} # create widgets self._import_preview = None self._error_widget = ImportErrorsWidget() self._error_widget.hide() self._dialog_buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Abort | QDialogButtonBox.Cancel) self._dialog_buttons.button(QDialogButtonBox.Abort).setText("Back") self._layout = QVBoxLayout() # layout self.select_widget = QWidget(self) self._select_widget_ui = Ui_ImportSourceSelector() self._select_widget_ui.setupUi(self.select_widget) self.setLayout(QVBoxLayout()) self.layout().addLayout(self._layout) self.layout().addWidget(self._dialog_buttons) self._layout.addWidget(self._error_widget) self._layout.addWidget(self.select_widget) # set list items self._select_widget_ui.source_list.blockSignals(True) self._select_widget_ui.source_list.addItems( list(self.connectors.keys())) self._select_widget_ui.source_list.clearSelection() self._select_widget_ui.source_list.blockSignals(False) # connect signals self._select_widget_ui.source_list.currentItemChanged.connect( self.connector_selected) self._select_widget_ui.source_list.activated.connect( self.launch_import_preview) self._dialog_buttons.button(QDialogButtonBox.Ok).clicked.connect( self._handle_ok_clicked) self._dialog_buttons.button(QDialogButtonBox.Cancel).clicked.connect( self._handle_cancel_clicked) self._dialog_buttons.button(QDialogButtonBox.Abort).clicked.connect( self._handle_back_clicked) self.data_ready.connect(self.parent().import_data) # init ok button self.set_ok_button_availability() self._dialog_buttons.button(QDialogButtonBox.Abort).hide() @property def mapped_data(self): return self._mapped_data @property def mapping_errors(self): return self._mapping_errors @Slot("QListWidgetItem", "QListWidgetItem") def connector_selected(self, current, _previous): connector = None if current: connector = self.connectors.get(current.text(), None) self._selected_connector = connector self.set_ok_button_availability() def set_ok_button_availability(self): if self._current_view == "connector": if self._selected_connector: self._dialog_buttons.button( QDialogButtonBox.Ok).setEnabled(True) else: self._dialog_buttons.button( QDialogButtonBox.Ok).setEnabled(False) elif self._current_view == "preview": if self._import_preview.checked_tables: self._dialog_buttons.button( QDialogButtonBox.Ok).setEnabled(True) else: self._dialog_buttons.button( QDialogButtonBox.Ok).setEnabled(False) else: self._dialog_buttons.button(QDialogButtonBox.Ok).setEnabled(True) @Slot(dict, list) def _handle_data_ready(self, data, errors): if errors: errors = [ f"{table_name}: {error_message}" for table_name, error_message in errors ] self._error_widget.set_import_state(errors) self.set_error_widget_as_main_widget() return self.data_ready.emit(data) self.accept() @Slot() def _handle_ok_clicked(self): if self._current_view == "connector": self.launch_import_preview() elif self._current_view == "preview": self._import_preview.request_mapped_data() elif self._current_view == "error": self.accept() @Slot() def _handle_cancel_clicked(self): self.reject() @Slot() def _handle_back_clicked(self): self.set_preview_as_main_widget() @Slot() def launch_import_preview(self): if self._selected_connector: # create instance of connector connector_settings = { "gams_directory": self._gams_system_directory() } self.active_connector = ConnectionManager(self._selected_connector, connector_settings) valid_source = self.active_connector.connection_ui() if valid_source: # Create instance of ImportPreviewWidget and configure self._import_preview = ImportEditor(self.active_connector, self) self._import_preview.set_loading_status(True) self._import_preview.tableChecked.connect( self.set_ok_button_availability) # Connect handle_data_ready method to the widget self._import_preview.mappedDataReady.connect( self._handle_data_ready) self._layout.addWidget(self._import_preview) self.active_connector.connectionFailed.connect( self._handle_failed_connection) self.active_connector.connectionReady.connect( self.set_preview_as_main_widget) self.active_connector.init_connection() else: # remove connector object. self.active_connector.deleteLater() self.active_connector = None @Slot(str) def _handle_failed_connection(self, msg): """Handle failed connection, show error message and select widget Arguments: msg {str} -- str with message of reason for failed connection. """ if self.active_connector: self.active_connector.close_connection() self.active_connector.deleteLater() self.active_connector = None if self._import_preview: self._import_preview.deleteLater() self._import_preview = None notification = Notification(self, msg) notification.show() @Slot() def set_preview_as_main_widget(self): self._current_view = "preview" self.select_widget.hide() self._error_widget.hide() self._import_preview.show() self._restore_preview_ui() self._dialog_buttons.button(QDialogButtonBox.Abort).hide() self.set_ok_button_availability() def set_error_widget_as_main_widget(self): self._current_view = "error" if self._import_preview is not None and not self._import_preview.isHidden( ): self._preview_window_state["maximized"] = self.windowState( ) == Qt.WindowMaximized self._preview_window_state["size"] = self.size() self._preview_window_state["position"] = self.pos() splitters = dict() self._preview_window_state["splitters"] = splitters for splitter in self._import_preview.findChildren(QSplitter): splitters[splitter.objectName()] = splitter.saveState() self.select_widget.hide() self._error_widget.show() self._import_preview.hide() self._dialog_buttons.button(QDialogButtonBox.Abort).show() self.set_ok_button_availability() def _restore_preview_ui(self): """Restore UI state from previous session.""" if not self._preview_window_state: self._settings.beginGroup(self._SETTINGS_GROUP_NAME) window_size = self._settings.value("windowSize") window_pos = self._settings.value("windowPosition") n_screens = self._settings.value("n_screens", defaultValue=1) window_maximized = self._settings.value("windowMaximized", defaultValue='false') splitter_state = {} for splitter in self._import_preview.findChildren(QSplitter): splitter_state[splitter] = self._settings.value( splitter.objectName() + "_splitterState") self._settings.endGroup() original_size = self.size() if window_size: self.resize(window_size) else: self.setGeometry( QStyle.alignedRect( Qt.LeftToRight, Qt.AlignCenter, QSize(1000, 700), QApplication.desktop().availableGeometry(self))) if window_pos: self.move(window_pos) if len(QGuiApplication.screens()) < int(n_screens): # There are less screens available now than on previous application startup self.move( 0, 0) # Move this widget to primary screen position (0,0) ensure_window_is_on_screen(self, original_size) if window_maximized == 'true': self.setWindowState(Qt.WindowMaximized) for splitter, state in splitter_state.items(): if state: splitter.restoreState(state) else: self.resize(self._preview_window_state["size"]) self.move(self._preview_window_state["position"]) self.setWindowState(self._preview_window_state["maximized"]) for splitter in self._import_preview.findChildren(QSplitter): name = splitter.objectName() splitter.restoreState( self._preview_window_state["splitters"][name]) def closeEvent(self, event): """Stores window's settings and accepts the event.""" if self._import_preview is not None: self._settings.beginGroup(self._SETTINGS_GROUP_NAME) self._settings.setValue("n_screens", len(QGuiApplication.screens())) if not self._import_preview.isHidden(): self._settings.setValue("windowSize", self.size()) self._settings.setValue("windowPosition", self.pos()) self._settings.setValue( "windowMaximized", self.windowState() == Qt.WindowMaximized) for splitter in self._import_preview.findChildren(QSplitter): self._settings.setValue( splitter.objectName() + "_splitterState", splitter.saveState()) elif self._preview_window_state: self._settings.setValue("windowSize", self._preview_window_state["size"]) self._settings.setValue("windowPosition", self._preview_window_state["position"]) self._settings.setValue("windowMaximized", self._preview_window_state.maximized) for name, state in self._preview_window_state["splitters"]: self._settings.setValue(name + "_splitterState", state) self._settings.endGroup() event.accept() def _gams_system_directory(self): """Returns GAMS system path from Toolbox settings or None if GAMS default is to be used.""" path = self._settings.value("appSettings/gamsPath", defaultValue=None) if not path: path = find_gams_directory() if path is not None and os.path.isfile(path): path = os.path.dirname(path) return path
class Application(QApplication): def __init__(self): super().__init__() self.parent = QWidget() self.parent.hide() self.parent.setWindowFlags(self.parent.windowFlags() & ~QtCore.Qt.Tool) self.setQuitOnLastWindowClosed(False) self.aboutToQuit.connect(self.quit_app) self.node_set = NodeSet() self.system_tray = SystemTray(self.parent, self.node_set) def start(self): threadpool = QThreadPool() worker = Worker(fn=self.check_version) threadpool.start(worker) self.system_tray.show() self.node_set.start() status = self.exec_() return status @staticmethod def check_version(progress_callback): latest_version = LauncherSoftware().get_latest_release_version() if latest_version is None: return latest_major, latest_minor, latest_bugfix = latest_version.split('.') major, minor, bugfix = NODE_LAUNCHER_RELEASE.split('.') major_upgrade = latest_major > major minor_upgrade = (latest_major == major and latest_minor > minor) bugfix_upgrade = (latest_major == major and latest_minor == minor and latest_bugfix > bugfix) if major_upgrade or minor_upgrade or bugfix_upgrade: message_box = QMessageBox() message_box.setTextFormat(Qt.RichText) message_box.setText(UPGRADE) message_box.setInformativeText( f'Your version: {NODE_LAUNCHER_RELEASE}\n' f'New version: {latest_version}') message_box.exec_() @Slot() def quit_app(self): log.debug('quit_app') self.system_tray.show_message(title='Stopping LND...') self.node_set.lnd_node.stop() self.node_set.lnd_node.process.waitForFinished(-1) self.node_set.bitcoind_node.stop() self.system_tray.show_message(title='Stopping bitcoind...') self.node_set.bitcoind_node.process.waitForFinished(-1) self.node_set.tor_node.process.kill() self.node_set.tor_node.process.waitForFinished(-1) self.system_tray.show_message(title='Exiting Node Launcher', timeout=1) QCoreApplication.exit(0)
class ImportDialog(QDialog): """ A widget for importing data into a Spine db. Currently used by DataStoreForm. It embeds three widgets that alternate depending on user's actions: - `select_widget` is a `QWidget` for selecting the source data type (CSV, Excel, etc.) - `_import_preview` is an `ImportPreviewWidget` for defining Mappings to associate with the source data - `_error_widget` is an `ImportErrorWidget` to show errors from import operations """ _SETTINGS_GROUP_NAME = "importDialog" def __init__(self, settings, parent): """ Args: settings (QSettings): settings for storing/restoring window state parent (QWidget): parent widget """ from ..ui.import_source_selector import Ui_ImportSourceSelector super().__init__(parent) self.setWindowFlag(Qt.Window, True) self.setWindowFlag(Qt.WindowMinMaxButtonsHint, True) self.setWindowTitle("Import data") # DB mapping if parent is not None: self._db_map = parent.db_maps[0] # state self._mapped_data = None self._mapping_errors = [] self.connector_list = [CSVConnector, ExcelConnector, SqlAlchemyConnector, GdxConnector] self.connector_list = {c.DISPLAY_NAME: c for c in self.connector_list} self._selected_connector = None self.active_connector = None self._current_view = "connector" self._settings = settings self._preview_window_state = {} # create widgets self._import_preview = None self._error_widget = ImportErrorWidget() self._error_widget.hide() self._dialog_buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Abort | QDialogButtonBox.Cancel) self._dialog_buttons.button(QDialogButtonBox.Abort).setText("Back") self._layout = QVBoxLayout() # layout self.select_widget = QWidget(self) self._select_widget_ui = Ui_ImportSourceSelector() self._select_widget_ui.setupUi(self.select_widget) self.setLayout(QVBoxLayout()) self.layout().addLayout(self._layout) self.layout().addWidget(self._dialog_buttons) self._layout.addWidget(self._error_widget) self._layout.addWidget(self.select_widget) # set list items self._select_widget_ui.source_list.blockSignals(True) self._select_widget_ui.source_list.addItems(list(self.connector_list.keys())) self._select_widget_ui.source_list.clearSelection() self._select_widget_ui.source_list.blockSignals(False) # connect signals self._select_widget_ui.source_list.currentItemChanged.connect(self.connector_selected) self._select_widget_ui.source_list.activated.connect(self.launch_import_preview) self._dialog_buttons.button(QDialogButtonBox.Ok).clicked.connect(self.ok_clicked) self._dialog_buttons.button(QDialogButtonBox.Cancel).clicked.connect(self.cancel_clicked) self._dialog_buttons.button(QDialogButtonBox.Abort).clicked.connect(self.back_clicked) # init ok button self.set_ok_button_availability() self._dialog_buttons.button(QDialogButtonBox.Abort).hide() @property def mapped_data(self): return self._mapped_data @property def mapping_errors(self): return self._mapping_errors @Slot() def connector_selected(self, selection): connector = None if selection: connector = self.connector_list.get(selection.text(), None) self._selected_connector = connector self.set_ok_button_availability() def set_ok_button_availability(self): if self._current_view == "connector": if self._selected_connector: self._dialog_buttons.button(QDialogButtonBox.Ok).setEnabled(True) else: self._dialog_buttons.button(QDialogButtonBox.Ok).setEnabled(False) elif self._current_view == "preview": if self._import_preview.checked_tables: self._dialog_buttons.button(QDialogButtonBox.Ok).setEnabled(True) else: self._dialog_buttons.button(QDialogButtonBox.Ok).setEnabled(False) else: self._dialog_buttons.button(QDialogButtonBox.Ok).setEnabled(True) @busy_effect def import_data(self, data, errors): errors = [f"{table_name}: {error_message}" for table_name, error_message in errors] try: import_num, import_errors = spinedb_api.import_data(self._db_map, **data) import_errors = [f"{e.db_type}: {e.msg}" for e in import_errors] errors.extend(import_errors) except spinedb_api.SpineIntegrityError as err: self._db_map.rollback_session() self._error_widget.set_import_state(0, [err.msg]) self.set_error_widget_as_main_widget() except spinedb_api.SpineDBAPIError as err: self._db_map.rollback_session() self._error_widget.set_import_state(0, ["Unable to import Data: %s", err.msg]) self.set_error_widget_as_main_widget() if errors: self._error_widget.set_import_state(import_num, errors) self.set_error_widget_as_main_widget() return False return True @Slot() def data_ready(self, data, errors): if self.import_data(data, errors): self.accept() @Slot() def ok_clicked(self): if self._current_view == "connector": self.launch_import_preview() elif self._current_view == "preview": self._import_preview.request_mapped_data() elif self._current_view == "error": self.accept() @Slot() def cancel_clicked(self): if self._db_map.has_pending_changes(): self._db_map.rollback_session() self.reject() @Slot() def back_clicked(self): if self._db_map.has_pending_changes(): self._db_map.rollback_session() self.set_preview_as_main_widget() @Slot() def launch_import_preview(self): if self._selected_connector: # create instance of connector self.active_connector = ConnectionManager(self._selected_connector) valid_source = self.active_connector.connection_ui() if valid_source: # Create instance of ImportPreviewWidget and configure self._import_preview = ImportPreviewWidget(self.active_connector, self) self._import_preview.set_loading_status(True) self._import_preview.tableChecked.connect(self.set_ok_button_availability) # Connect data_ready method to the widget self._import_preview.mappedDataReady.connect(self.data_ready) self._layout.addWidget(self._import_preview) self.active_connector.connectionFailed.connect(self._handle_failed_connection) self.active_connector.init_connection() # show preview widget self.set_preview_as_main_widget() else: # remove connector object. self.active_connector.deleteLater() self.active_connector = None @Slot(str) def _handle_failed_connection(self, msg): """Handle failed connection, show error message and select widget Arguments: msg {str} -- str with message of reason for failed connection. """ self.select_widget.hide() self._error_widget.hide() self._import_preview.hide() if self.active_connector: self.active_connector.close_connection() self.active_connector.deleteLater() self.active_connector = None if self._import_preview: self._import_preview.deleteLater() self._import_preview = None ok_button = QPushButton() ok_button.setText("Ok") temp_widget = QWidget() temp_widget.setLayout(QVBoxLayout()) temp_widget.layout().addWidget(QLabel(msg)) temp_widget.layout().addWidget(ok_button) ok_button.clicked.connect(self.select_widget.show) ok_button.clicked.connect(temp_widget.deleteLater) self.layout().addWidget(temp_widget) def set_preview_as_main_widget(self): self._current_view = "preview" self.select_widget.hide() self._error_widget.hide() self._import_preview.show() self._restore_preview_ui() self._dialog_buttons.button(QDialogButtonBox.Abort).hide() self.set_ok_button_availability() def set_error_widget_as_main_widget(self): self._current_view = "error" if self._import_preview is not None and not self._import_preview.isHidden(): self._preview_window_state["maximized"] = self.windowState() == Qt.WindowMaximized self._preview_window_state["size"] = self.size() self._preview_window_state["position"] = self.pos() splitters = dict() self._preview_window_state["splitters"] = splitters for splitter in self._import_preview.findChildren(QSplitter): splitters[splitter.objectName()] = splitter.saveState() self.select_widget.hide() self._error_widget.show() self._import_preview.hide() self._dialog_buttons.button(QDialogButtonBox.Abort).show() self.set_ok_button_availability() def _restore_preview_ui(self): """Restore UI state from previous session.""" if not self._preview_window_state: self._settings.beginGroup(self._SETTINGS_GROUP_NAME) window_size = self._settings.value("windowSize") window_pos = self._settings.value("windowPosition") n_screens = self._settings.value("n_screens", defaultValue=1) window_maximized = self._settings.value("windowMaximized", defaultValue='false') splitter_state = {} for splitter in self._import_preview.findChildren(QSplitter): splitter_state[splitter] = self._settings.value(splitter.objectName() + "_splitterState") self._settings.endGroup() if window_size: self.resize(window_size) else: self.setGeometry( QStyle.alignedRect( Qt.LeftToRight, Qt.AlignCenter, QSize(1000, 700), QApplication.desktop().availableGeometry(self) ) ) if window_pos: self.move(window_pos) if window_maximized == 'true': self.setWindowState(Qt.WindowMaximized) for splitter, state in splitter_state.items(): if state: splitter.restoreState(state) if len(QGuiApplication.screens()) < int(n_screens): # There are less screens available now than on previous application startup self.move(0, 0) # Move this widget to primary screen position (0,0) else: self.resize(self._preview_window_state["size"]) self.move(self._preview_window_state["position"]) self.setWindowState(self._preview_window_state["maximized"]) for splitter in self._import_preview.findChildren(QSplitter): name = splitter.objectName() splitter.restoreState(self._preview_window_state["splitters"][name]) def closeEvent(self, event): """Stores window's settings and accepts the event.""" if self._import_preview is not None: self._settings.beginGroup(self._SETTINGS_GROUP_NAME) self._settings.setValue("n_screens", len(QGuiApplication.screens())) if not self._import_preview.isHidden(): self._settings.setValue("windowSize", self.size()) self._settings.setValue("windowPosition", self.pos()) self._settings.setValue("windowMaximized", self.windowState() == Qt.WindowMaximized) for splitter in self._import_preview.findChildren(QSplitter): self._settings.setValue(splitter.objectName() + "_splitterState", splitter.saveState()) elif self._preview_window_state: self._settings.setValue("windowSize", self._preview_window_state["size"]) self._settings.setValue("windowPosition", self._preview_window_state["position"]) self._settings.setValue("windowMaximized", self._preview_window_state.maximized) for name, state in self._preview_window_state["splitters"]: self._settings.setValue(name + "_splitterState", state) self._settings.endGroup() event.accept()
class QWizardProcessPage(QWizardPage): """A QWizards page with a log. Useful for pages that need to capture the output of a process.""" class _ExecutionManager: """A descriptor that stores a QProcessExecutionManager. When ``execution_finished`` is emitted, it shows the button to copy the process log. """ public_name = None private_name = None def __set_name__(self, owner, name): self.public_name = name self.private_name = '_' + name def __get__(self, obj, objtype=None): return getattr(obj, self.private_name) def __set__(self, obj, value): setattr(obj, self.private_name, value) try: value.execution_finished.connect(obj.widget_copy.show) except AttributeError: pass msg = Signal(str) msg_warning = Signal(str) msg_error = Signal(str) msg_success = Signal(str) msg_proc = Signal(str) msg_proc_error = Signal(str) _exec_mngr = _ExecutionManager() def __init__(self, parent): super().__init__(parent) self._log = MonoSpaceFontTextBrowser(self) self._exec_mngr = None self._successful = False layout = QVBoxLayout(self) layout.addWidget(self._log) self.widget_copy = QWidget() self.widget_copy.hide() self._button_copy = QPushButton("Copy log") self._label_copy = QLabel("Log copied to clipboard.") self._label_copy.hide() layout_copy = QHBoxLayout(self.widget_copy) layout_copy.addWidget(self._button_copy) layout_copy.addWidget(self._label_copy) layout_copy.addStretch() layout.addWidget(self.widget_copy) self._connect_signals() def _connect_signals(self): self.msg.connect(self._add_msg) self.msg_warning.connect(self._add_msg_warning) self.msg_error.connect(self._add_msg_error) self.msg_success.connect(self._add_msg_succes) self.msg_proc.connect(self._add_msg) self.msg_proc_error.connect(self._add_msg_error) self._button_copy.clicked.connect(self._handle_copy_clicked) @Slot(bool) def _handle_copy_clicked(self, _=False): self._label_copy.show() qApp.clipboard().setText(self._log.toPlainText()) # pylint: disable=undefined-variable def _add_msg(self, msg): self._log.append(format_log_message("msg", msg, show_datetime=False)) def _add_msg_warning(self, msg): self._log.append( format_log_message("msg_warning", msg, show_datetime=False)) def _add_msg_error(self, msg): self._log.append( format_log_message("msg_error", msg, show_datetime=False)) def _add_msg_succes(self, msg): self._log.append( format_log_message("msg_success", msg, show_datetime=False)) def isComplete(self): return self._exec_mngr is None def cleanupPage(self): super().cleanupPage() if self._exec_mngr is not None: self._exec_mngr.stop_execution() self.msg_error.emit("Aborted by the user")
class MonteCarloTab(NewAnalysisTab): def __init__(self, parent=None): super(MonteCarloTab, self).__init__(parent) self.parent: LCAResultsSubTab = parent self.layout.addLayout(get_header_layout('Monte Carlo Simulation')) self.scenario_label = QLabel("Scenario:") self.include_box = QGroupBox("Include uncertainty for:", self) grid = QGridLayout() self.include_tech = QCheckBox("Technosphere", self) self.include_tech.setChecked(True) self.include_bio = QCheckBox("Biosphere", self) self.include_bio.setChecked(True) self.include_cf = QCheckBox("Characterization Factors", self) self.include_cf.setChecked(True) self.include_parameters = QCheckBox("Parameters", self) self.include_parameters.setChecked(True) grid.addWidget(self.include_tech, 0, 0) grid.addWidget(self.include_bio, 0, 1) grid.addWidget(self.include_cf, 1, 0) grid.addWidget(self.include_parameters, 1, 1) self.include_box.setLayout(grid) self.add_MC_ui_elements() self.table = LCAResultsTable() self.table.table_name = 'MonteCarlo_' + self.parent.cs_name self.plot = MonteCarloPlot(self.parent) self.plot.hide() self.plot.plot_name = 'MonteCarlo_' + self.parent.cs_name self.layout.addWidget(self.plot) self.export_widget = self.build_export(has_plot=True, has_table=True) self.layout.addWidget(self.export_widget) self.layout.setAlignment(QtCore.Qt.AlignTop) self.connect_signals() def connect_signals(self): self.button_run.clicked.connect(self.calculate_mc_lca) # signals.monte_carlo_ready.connect(self.update_mc) # self.combobox_fu.currentIndexChanged.connect(self.update_plot) self.combobox_methods.currentIndexChanged.connect( # ignore the index and send the cs_name instead lambda x: self.update_mc(cs_name=self.parent.cs_name)) # signals # self.radio_button_biosphere.clicked.connect(self.button_clicked) # self.radio_button_technosphere.clicked.connect(self.button_clicked) if self.using_presamples: self.scenario_box.currentIndexChanged.connect( self.parent.update_scenario_data) self.parent.update_scenario_box_index.connect( lambda index: self.set_combobox_index(self.scenario_box, index )) def add_MC_ui_elements(self): layout_mc = QVBoxLayout() # H-LAYOUT start simulation self.button_run = QPushButton('Run Simulation') self.label_iterations = QLabel('Iterations:') self.iterations = QLineEdit('10') self.iterations.setFixedWidth(40) self.iterations.setValidator(QtGui.QIntValidator(1, 1000)) self.label_seed = QLabel('Random seed:') self.label_seed.setToolTip( 'Seed value (integer) for the random number generator. ' 'Use this for reproducible samples.') self.seed = QLineEdit('') self.seed.setFixedWidth(30) self.hlayout_run = QHBoxLayout() self.hlayout_run.addWidget(self.scenario_label) self.hlayout_run.addWidget(self.scenario_box) self.hlayout_run.addWidget(self.button_run) self.hlayout_run.addWidget(self.label_iterations) self.hlayout_run.addWidget(self.iterations) self.hlayout_run.addWidget(self.label_seed) self.hlayout_run.addWidget(self.seed) self.hlayout_run.addWidget(self.include_box) self.hlayout_run.addStretch(1) layout_mc.addLayout(self.hlayout_run) # self.label_running = QLabel('Running a Monte Carlo simulation. Please allow some time for this. ' # 'Please do not run another simulation at the same time.') # self.layout_mc.addWidget(self.label_running) # self.label_running.hide() # # buttons for all FUs or for all methods # self.radio_button_all_fu = QRadioButton("For all functional units") # self.radio_button_all_methods = QRadioButton("Technosphere flows") # # self.radio_button_biosphere.setChecked(True) # self.radio_button_technosphere.setChecked(False) # # self.label_for_all_fu = QLabel('For all functional units') # self.combobox_fu = QRadioButton() # self.hlayout_fu = QHBoxLayout() # FU selection # self.label_fu = QLabel('Choose functional unit') # self.combobox_fu = QComboBox() # self.hlayout_fu = QHBoxLayout() # # self.hlayout_fu.addWidget(self.label_fu) # self.hlayout_fu.addWidget(self.combobox_fu) # self.hlayout_fu.addStretch() # self.layout_mc.addLayout(self.hlayout_fu) # method selection self.method_selection_widget = QWidget() self.label_methods = QLabel('Choose LCIA method') self.combobox_methods = QComboBox() self.hlayout_methods = QHBoxLayout() self.hlayout_methods.addWidget(self.label_methods) self.hlayout_methods.addWidget(self.combobox_methods) self.hlayout_methods.addStretch() self.method_selection_widget.setLayout(self.hlayout_methods) layout_mc.addWidget(self.method_selection_widget) self.method_selection_widget.hide() self.layout.addLayout(layout_mc) def build_export(self, has_table: bool = True, has_plot: bool = True) -> QWidget: """Construct the export layout but set it into a widget because we want to hide it.""" export_layout = super().build_export(has_table, has_plot) export_widget = QWidget() export_widget.setLayout(export_layout) # Hide widget until MC is calculated export_widget.hide() return export_widget @QtCore.Slot(name="calculateMcLca") def calculate_mc_lca(self): self.method_selection_widget.hide() self.plot.hide() self.export_widget.hide() iterations = int(self.iterations.text()) seed = None if self.seed.text(): print('SEED: ', self.seed.text()) try: seed = int(self.seed.text()) except ValueError: traceback.print_exc() QMessageBox.warning( self, 'Warning', 'Seed value must be an integer number or left empty.') self.seed.setText('') return includes = { "technosphere": self.include_tech.isChecked(), "biosphere": self.include_bio.isChecked(), "cf": self.include_cf.isChecked(), "parameters": self.include_parameters.isChecked(), } try: self.parent.mc.calculate(iterations=iterations, seed=seed, **includes) self.update_mc() except InvalidParamsError as e: # This can occur if uncertainty data is missing or otherwise broken # print(e) traceback.print_exc() QMessageBox.warning(self, 'Could not perform Monte Carlo simulation', str(e)) # a threaded way for this - unfortunatley this crashes as: # pypardsio_solver is used for the 'spsolve' and 'factorized' functions. Python crashes on windows if multiple # instances of PyPardisoSolver make calls to the Pardiso library # worker_thread = WorkerThread() # print('Created local worker_thread') # worker_thread.set_mc(self.parent.mc, iterations=iterations) # print('Passed object to thread.') # worker_thread.start() # self.label_running.show() # # thread = NewCSMCThread() #self.parent.mc # thread.calculation_finished.connect( # lambda x: print('Calculation finished.')) # thread.start() # # give us a thread and start it # thread = QtCore.QThread() # thread.start() # # # create a worker and move it to our extra thread # worker = Worker() # worker.moveToThread(thread) # self.parent.mct.start() # self.parent.mct.run(iterations=iterations) # self.parent.mct.finished() # objThread = QtCore.QThread() # obj = QObjectMC() # self.parent.cs_name # obj.moveToThread(objThread) # obj.finished.connect(objThread.quit) # objThread.started.connect(obj.long_running) # # objThread.finished.connect(app.exit) # objThread.finished.connect( # lambda x: print('Finished Thread!') # ) # objThread.start() # objThread = QtCore.QThread() # obj = SomeObject() # obj.moveToThread(objThread) # obj.finished.connect(objThread.quit) # objThread.started.connect(obj.long_running) # objThread.finished.connect( # lambda x: print('Finished Thread!') # ) # objThread.start() # self.method_selection_widget.show() # self.plot.show() # self.export_widget.show() def configure_scenario(self): super().configure_scenario() self.scenario_label.setVisible(self.using_presamples) def update_tab(self): self.update_combobox(self.combobox_methods, [str(m) for m in self.parent.mc.methods]) # self.update_combobox(self.combobox_methods, [str(m) for m in self.parent.mct.mc.methods]) def update_mc(self, cs_name=None): # act = self.combobox_fu.currentText() # activity_index = self.combobox_fu.currentIndex() # act_key = self.parent.mc.activity_keys[activity_index] # if cs_name != self.parent.cs_name: # relevant if several CS are open at the same time # return # self.label_running.hide() self.method_selection_widget.show() self.export_widget.show() method_index = self.combobox_methods.currentIndex() method = self.parent.mc.methods[method_index] # data = self.parent.mc.get_results_by(act_key=act_key, method=method) self.df = self.parent.mc.get_results_dataframe(method=method) self.update_table() self.update_plot(method=method) filename = '_'.join([ str(x) for x in [self.parent.cs_name, 'Monte Carlo results', str(method)] ]) self.plot.plot_name, self.table.table_name = filename, filename def update_plot(self, method): idx = self.layout.indexOf(self.plot) self.plot.figure.clf() self.plot.deleteLater() self.plot = MonteCarloPlot(self.parent) self.layout.insertWidget(idx, self.plot) self.plot.plot(self.df, method=method) self.plot.show() if self.layout.parentWidget(): self.layout.parentWidget().updateGeometry() def update_table(self): self.table.sync(self.df)
class QVTKRenderWindowInteractor(QVTKRWIBaseClass): """ A QVTKRenderWindowInteractor for Python and Qt. Uses a vtkGenericRenderWindowInteractor to handle the interactions. Use GetRenderWindow() to get the vtkRenderWindow. Create with the keyword stereo=1 in order to generate a stereo-capable window. The user interface is summarized in vtkInteractorStyle.h: - Keypress j / Keypress t: toggle between joystick (position sensitive) and trackball (motion sensitive) styles. In joystick style, motion occurs continuously as long as a mouse button is pressed. In trackball style, motion occurs when the mouse button is pressed and the mouse pointer moves. - Keypress c / Keypress o: toggle between camera and object (actor) modes. In camera mode, mouse events affect the camera position and focal point. In object mode, mouse events affect the actor that is under the mouse pointer. - Button 1: rotate the camera around its focal point (if camera mode) or rotate the actor around its origin (if actor mode). The rotation is in the direction defined from the center of the renderer's viewport towards the mouse position. In joystick mode, the magnitude of the rotation is determined by the distance the mouse is from the center of the render window. - Button 2: pan the camera (if camera mode) or translate the actor (if object mode). In joystick mode, the direction of pan or translation is from the center of the viewport towards the mouse position. In trackball mode, the direction of motion is the direction the mouse moves. (Note: with 2-button mice, pan is defined as <Shift>-Button 1.) - Button 3: zoom the camera (if camera mode) or scale the actor (if object mode). Zoom in/increase scale if the mouse position is in the top half of the viewport; zoom out/decrease scale if the mouse position is in the bottom half. In joystick mode, the amount of zoom is controlled by the distance of the mouse pointer from the horizontal centerline of the window. - Keypress 3: toggle the render window into and out of stereo mode. By default, red-blue stereo pairs are created. Some systems support Crystal Eyes LCD stereo glasses; you have to invoke SetStereoTypeToCrystalEyes() on the rendering window. Note: to use stereo you also need to pass a stereo=1 keyword argument to the constructor. - Keypress e: exit the application. - Keypress f: fly to the picked point - Keypress p: perform a pick operation. The render window interactor has an internal instance of vtkCellPicker that it uses to pick. - Keypress r: reset the camera view along the current view direction. Centers the actors and moves the camera so that all actors are visible. - Keypress s: modify the representation of all actors so that they are surfaces. - Keypress u: invoke the user-defined function. Typically, this keypress will bring up an interactor that you can type commands in. - Keypress w: modify the representation of all actors so that they are wireframe. """ # Map between VTK and Qt cursors. _CURSOR_MAP = { 0: Qt.ArrowCursor, # VTK_CURSOR_DEFAULT 1: Qt.ArrowCursor, # VTK_CURSOR_ARROW 2: Qt.SizeBDiagCursor, # VTK_CURSOR_SIZENE 3: Qt.SizeFDiagCursor, # VTK_CURSOR_SIZENWSE 4: Qt.SizeBDiagCursor, # VTK_CURSOR_SIZESW 5: Qt.SizeFDiagCursor, # VTK_CURSOR_SIZESE 6: Qt.SizeVerCursor, # VTK_CURSOR_SIZENS 7: Qt.SizeHorCursor, # VTK_CURSOR_SIZEWE 8: Qt.SizeAllCursor, # VTK_CURSOR_SIZEALL 9: Qt.PointingHandCursor, # VTK_CURSOR_HAND 10: Qt.CrossCursor, # VTK_CURSOR_CROSSHAIR } def __init__(self, parent=None, **kw): # the current button self._ActiveButton = Qt.NoButton # private attributes self.__saveX = 0 self.__saveY = 0 self.__saveModifiers = Qt.NoModifier self.__saveButtons = Qt.NoButton self.__wheelDelta = 0 # do special handling of some keywords: # stereo, rw try: stereo = bool(kw['stereo']) except KeyError: stereo = False try: rw = kw['rw'] except KeyError: rw = None # create base qt-level widget if QVTKRWIBase == "QWidget": if "wflags" in kw: wflags = kw['wflags'] else: wflags = Qt.WindowFlags() QWidget.__init__(self, parent, wflags | Qt.MSWindowsOwnDC) elif QVTKRWIBase == "QGLWidget": QGLWidget.__init__(self, parent) if rw: # user-supplied render window self._RenderWindow = rw else: self._RenderWindow = vtk.vtkRenderWindow() WId = self.winId() # Python2 if type(WId).__name__ == 'PyCObject': from ctypes import pythonapi, c_void_p, py_object pythonapi.PyCObject_AsVoidPtr.restype = c_void_p pythonapi.PyCObject_AsVoidPtr.argtypes = [py_object] WId = pythonapi.PyCObject_AsVoidPtr(WId) # Python3 elif type(WId).__name__ == 'PyCapsule': from ctypes import pythonapi, c_void_p, py_object, c_char_p pythonapi.PyCapsule_GetName.restype = c_char_p pythonapi.PyCapsule_GetName.argtypes = [py_object] name = pythonapi.PyCapsule_GetName(WId) pythonapi.PyCapsule_GetPointer.restype = c_void_p pythonapi.PyCapsule_GetPointer.argtypes = [py_object, c_char_p] WId = pythonapi.PyCapsule_GetPointer(WId, name) self._RenderWindow.SetWindowInfo(str(int(WId))) if stereo: # stereo mode self._RenderWindow.StereoCapableWindowOn() self._RenderWindow.SetStereoTypeToCrystalEyes() try: self._Iren = kw['iren'] except KeyError: self._Iren = vtk.vtkGenericRenderWindowInteractor() self._Iren.SetRenderWindow(self._RenderWindow) # do all the necessary qt setup self.setAttribute(Qt.WA_OpaquePaintEvent) self.setAttribute(Qt.WA_PaintOnScreen) self.setMouseTracking(True) # get all mouse events self.setFocusPolicy(Qt.WheelFocus) self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)) self._Timer = QTimer(self) self._Timer.timeout.connect(self.TimerEvent) self._Iren.AddObserver('CreateTimerEvent', self.CreateTimer) self._Iren.AddObserver('DestroyTimerEvent', self.DestroyTimer) self._Iren.GetRenderWindow().AddObserver('CursorChangedEvent', self.CursorChangedEvent) #Create a hidden child widget and connect its destroyed signal to its #parent ``Finalize`` slot. The hidden children will be destroyed before #its parent thus allowing cleanup of VTK elements. self._hidden = QWidget(self) self._hidden.hide() self._hidden.destroyed.connect(self.Finalize) def __getattr__(self, attr): """Makes the object behave like a vtkGenericRenderWindowInteractor""" if attr == '__vtk__': return lambda t=self._Iren: t elif hasattr(self._Iren, attr): return getattr(self._Iren, attr) else: raise AttributeError(self.__class__.__name__ + " has no attribute named " + attr) def Finalize(self): ''' Call internal cleanup method on VTK objects ''' self._RenderWindow.Finalize() def CreateTimer(self, obj, evt): self._Timer.start(10) def DestroyTimer(self, obj, evt): self._Timer.stop() return 1 def TimerEvent(self): self._Iren.TimerEvent() def CursorChangedEvent(self, obj, evt): """Called when the CursorChangedEvent fires on the render window.""" # This indirection is needed since when the event fires, the current # cursor is not yet set so we defer this by which time the current # cursor should have been set. QTimer.singleShot(0, self.ShowCursor) def HideCursor(self): """Hides the cursor.""" self.setCursor(Qt.BlankCursor) def ShowCursor(self): """Shows the cursor.""" vtk_cursor = self._Iren.GetRenderWindow().GetCurrentCursor() qt_cursor = self._CURSOR_MAP.get(vtk_cursor, Qt.ArrowCursor) self.setCursor(qt_cursor) def closeEvent(self, evt): self.Finalize() def sizeHint(self): return QSize(400, 400) def paintEngine(self): return None def paintEvent(self, ev): self._Iren.Render() def resizeEvent(self, ev): w = self.width() h = self.height() vtk.vtkRenderWindow.SetSize(self._RenderWindow, w, h) self._Iren.SetSize(w, h) self._Iren.ConfigureEvent() self.update() def _GetCtrlShift(self, ev): ctrl = shift = False if hasattr(ev, 'modifiers'): if ev.modifiers() & Qt.ShiftModifier: shift = True if ev.modifiers() & Qt.ControlModifier: ctrl = True else: if self.__saveModifiers & Qt.ShiftModifier: shift = True if self.__saveModifiers & Qt.ControlModifier: ctrl = True return ctrl, shift def enterEvent(self, ev): ctrl, shift = self._GetCtrlShift(ev) self._Iren.SetEventInformationFlipY(self.__saveX, self.__saveY, ctrl, shift, chr(0), 0, None) self._Iren.EnterEvent() def leaveEvent(self, ev): ctrl, shift = self._GetCtrlShift(ev) self._Iren.SetEventInformationFlipY(self.__saveX, self.__saveY, ctrl, shift, chr(0), 0, None) self._Iren.LeaveEvent() def mousePressEvent(self, ev): ctrl, shift = self._GetCtrlShift(ev) repeat = 0 if ev.type() == QEvent.MouseButtonDblClick: repeat = 1 self._Iren.SetEventInformationFlipY(ev.x(), ev.y(), ctrl, shift, chr(0), repeat, None) self._ActiveButton = ev.button() if self._ActiveButton == Qt.LeftButton: self._Iren.LeftButtonPressEvent() elif self._ActiveButton == Qt.RightButton: self._Iren.RightButtonPressEvent() elif self._ActiveButton == Qt.MidButton: self._Iren.MiddleButtonPressEvent() def mouseReleaseEvent(self, ev): ctrl, shift = self._GetCtrlShift(ev) self._Iren.SetEventInformationFlipY(ev.x(), ev.y(), ctrl, shift, chr(0), 0, None) if self._ActiveButton == Qt.LeftButton: self._Iren.LeftButtonReleaseEvent() elif self._ActiveButton == Qt.RightButton: self._Iren.RightButtonReleaseEvent() elif self._ActiveButton == Qt.MidButton: self._Iren.MiddleButtonReleaseEvent() def mouseMoveEvent(self, ev): self.__saveModifiers = ev.modifiers() self.__saveButtons = ev.buttons() self.__saveX = ev.x() self.__saveY = ev.y() ctrl, shift = self._GetCtrlShift(ev) self._Iren.SetEventInformationFlipY(ev.x(), ev.y(), ctrl, shift, chr(0), 0, None) self._Iren.MouseMoveEvent() def keyPressEvent(self, ev): ctrl, shift = self._GetCtrlShift(ev) if ev.key() < 256: key = str(ev.text()) else: key = chr(0) keySym = _qt_key_to_key_sym(ev.key()) if shift and len(keySym) == 1 and keySym.isalpha(): keySym = keySym.upper() self._Iren.SetEventInformationFlipY(self.__saveX, self.__saveY, ctrl, shift, key, 0, keySym) self._Iren.KeyPressEvent() self._Iren.CharEvent() def keyReleaseEvent(self, ev): ctrl, shift = self._GetCtrlShift(ev) if ev.key() < 256: key = chr(ev.key()) else: key = chr(0) self._Iren.SetEventInformationFlipY(self.__saveX, self.__saveY, ctrl, shift, key, 0, None) self._Iren.KeyReleaseEvent() def wheelEvent(self, ev): if hasattr(ev, 'delta'): self.__wheelDelta += ev.delta() else: self.__wheelDelta += ev.angleDelta().y() if self.__wheelDelta >= 120: self._Iren.MouseWheelForwardEvent() self.__wheelDelta = 0 elif self.__wheelDelta <= -120: self._Iren.MouseWheelBackwardEvent() self.__wheelDelta = 0 def GetRenderWindow(self): return self._RenderWindow def Render(self): self.update()