class _QtMainWindow(QMainWindow): # This was added so that someone can patch # `napari._qt.qt_main_window._QtMainWindow._window_icon` # to their desired window icon _window_icon = NAPARI_ICON_PATH # To track window instances and facilitate getting the "active" viewer... # We use this instead of QApplication.activeWindow for compatibility with # IPython usage. When you activate IPython, it will appear that there are # *no* active windows, so we want to track the most recently active windows _instances: ClassVar[List['_QtMainWindow']] = [] def __init__(self, viewer: 'Viewer', parent=None) -> None: super().__init__(parent) self._ev = None self._qt_viewer = QtViewer(viewer, show_welcome_screen=True) self._quit_app = False self.setWindowIcon(QIcon(self._window_icon)) self.setAttribute(Qt.WA_DeleteOnClose) self.setUnifiedTitleAndToolBarOnMac(True) center = QWidget(self) center.setLayout(QHBoxLayout()) center.layout().addWidget(self._qt_viewer) center.layout().setContentsMargins(4, 0, 4, 0) self.setCentralWidget(center) self.setWindowTitle(self._qt_viewer.viewer.title) self._maximized_flag = False self._window_size = None self._window_pos = None self._old_size = None self._positions = [] act_dlg = QtActivityDialog(self._qt_viewer._canvas_overlay) self._qt_viewer._canvas_overlay.resized.connect( act_dlg.move_to_bottom_right ) act_dlg.hide() self._activity_dialog = act_dlg self.setStatusBar(ViewerStatusBar(self)) settings = get_settings() # TODO: # settings.plugins.defaults.call_order = plugin_manager.call_order() # set the values in plugins to match the ones saved in settings if settings.plugins.call_order is not None: plugin_manager.set_call_order(settings.plugins.call_order) _QtMainWindow._instances.append(self) # since we initialize canvas before window, # we need to manually connect them again. handle = self.windowHandle() if handle is not None: handle.screenChanged.connect( self._qt_viewer.canvas._backend.screen_changed ) def statusBar(self) -> 'ViewerStatusBar': return super().statusBar() @classmethod def current(cls): return cls._instances[-1] if cls._instances else None @classmethod def current_viewer(cls): window = cls.current() return window._qt_viewer.viewer if window else None def event(self, e): if ( e.type() == QEvent.ToolTip and self._qt_viewer.viewer.tooltip.visible ): QToolTip.showText( e.globalPos(), self._qt_viewer.viewer.tooltip.text, self ) if e.type() == QEvent.Close: # when we close the MainWindow, remove it from the instances list try: _QtMainWindow._instances.remove(self) except ValueError: pass if e.type() in {QEvent.WindowActivate, QEvent.ZOrderChange}: # upon activation or raise_, put window at the end of _instances try: inst = _QtMainWindow._instances inst.append(inst.pop(inst.index(self))) except ValueError: pass return super().event(e) def _load_window_settings(self): """ Load window layout settings from configuration. """ settings = get_settings() window_position = settings.application.window_position # It's necessary to verify if the window/position value is valid with # the current screen. if not window_position: window_position = (self.x(), self.y()) else: width, height = window_position screen_geo = QApplication.desktop().geometry() if screen_geo.width() < width or screen_geo.height() < height: window_position = (self.x(), self.y()) return ( settings.application.window_state, settings.application.window_size, window_position, settings.application.window_maximized, settings.application.window_fullscreen, ) def _get_window_settings(self): """Return current window settings. Symmetric to the 'set_window_settings' setter. """ window_fullscreen = self.isFullScreen() if window_fullscreen: window_maximized = self._maximized_flag else: window_maximized = self.isMaximized() window_state = qbytearray_to_str(self.saveState()) return ( window_state, self._window_size or (self.width(), self.height()), self._window_pos or (self.x(), self.y()), window_maximized, window_fullscreen, ) def _set_window_settings( self, window_state, window_size, window_position, window_maximized, window_fullscreen, ): """ Set window settings. Symmetric to the 'get_window_settings' accessor. """ self.setUpdatesEnabled(False) self.setWindowState(Qt.WindowNoState) if window_position: window_position = QPoint(*window_position) self.move(window_position) if window_size: window_size = QSize(*window_size) self.resize(window_size) if window_state: self.restoreState(str_to_qbytearray(window_state)) # Toggling the console visibility is disabled when it is not # available, so ensure that it is hidden. if in_ipython(): self._qt_viewer.dockConsole.setVisible(False) if window_fullscreen: self.setWindowState(Qt.WindowFullScreen) self._maximized_flag = window_maximized elif window_maximized: self.setWindowState(Qt.WindowMaximized) self.setUpdatesEnabled(True) def _save_current_window_settings(self): """Save the current geometry of the main window.""" ( window_state, window_size, window_position, window_maximized, window_fullscreen, ) = self._get_window_settings() settings = get_settings() if settings.application.save_window_geometry: settings.application.window_maximized = window_maximized settings.application.window_fullscreen = window_fullscreen settings.application.window_position = window_position settings.application.window_size = window_size settings.application.window_statusbar = ( not self.statusBar().isHidden() ) if settings.application.save_window_state: settings.application.window_state = window_state def close(self, quit_app=False): """Override to handle closing app or just the window.""" self._quit_app = quit_app return super().close() def close_window(self): """Close active dialog or active window.""" parent = QApplication.focusWidget() while parent is not None: if isinstance(parent, QMainWindow): self.close() break if isinstance(parent, QDialog): parent.close() break try: parent = parent.parent() except Exception: parent = getattr(parent, "_parent", None) def show(self, block=False): super().show() if block: self._ev = QEventLoop() self._ev.exec() def changeEvent(self, event): """Handle window state changes.""" if event.type() == QEvent.WindowStateChange: # TODO: handle maximization issue. When double clicking on the # title bar on Mac the resizeEvent is called an varying amount # of times which makes it hard to track the original size before # maximization. condition = ( self.isMaximized() if os.name == "nt" else self.isFullScreen() ) if condition and self._old_size is not None: if self._positions and len(self._positions) > 1: self._window_pos = self._positions[-2] self._window_size = ( self._old_size.width(), self._old_size.height(), ) else: self._old_size = None self._window_pos = None self._window_size = None self._positions = [] super().changeEvent(event) def resizeEvent(self, event): """Override to handle original size before maximizing.""" # the first resize event will have nonsense positions that we dont # want to store (and potentially restore) if event.oldSize().isValid(): self._old_size = event.oldSize() self._positions.append((self.x(), self.y())) if self._positions and len(self._positions) >= 2: self._window_pos = self._positions[-2] self._positions = self._positions[-2:] super().resizeEvent(event) def closeEvent(self, event): """This method will be called when the main window is closing. Regardless of whether cmd Q, cmd W, or the close button is used... """ if self._ev and self._ev.isRunning(): self._ev.quit() # Close any floating dockwidgets for dock in self.findChildren(QtViewerDockWidget): if dock.isFloating(): dock.setFloating(False) self._save_current_window_settings() # On some versions of Darwin, exiting while fullscreen seems to tickle # some bug deep in NSWindow. This forces the fullscreen keybinding # test to complete its draw cycle, then pop back out of fullscreen. if self.isFullScreen(): self.showNormal() for _i in range(5): time.sleep(0.1) QApplication.processEvents() if self._quit_app: quit_app() event.accept() def restart(self): """Restart the napari application in a detached process.""" process = QProcess() process.setProgram(sys.executable) if not running_as_bundled_app(): process.setArguments(sys.argv) process.startDetached() self.close(quit_app=True) @staticmethod @Slot(Notification) def show_notification(notification: Notification): """Show notification coming from a thread.""" NapariQtNotification.show_notification(notification)
class Editor(QWidget): def __init__(self): super().__init__() self.mainLayout = QGridLayout() # 上方布局 self.bottomLayout = QGridLayout() # 下方布局 self.create_stragety_vbox() self.create_content_vbox() self.mainLayout = QGridLayout() # 主布局为垂直布局 self.mainLayout.setSpacing(5) # 主布局添加补白 self.mainLayout.addWidget(self.strategy_vbox, 0, 0, 1, 1) self.mainLayout.addWidget(self.content_vbox, 0, 1, 1, 1) self.mainLayout.setColumnStretch(0, 2) self.mainLayout.setColumnStretch(1, 6) self.setLayout(self.mainLayout) self.setGeometry(200, 200, 1200, 800) self.setWindowTitle('编辑器') self.show() # 策略信息 self.strategy_path = None with open(r'qdark.qss', encoding='utf-8') as f: self.setStyleSheet(f.read()) def create_stragety_vbox(self): # 策略树 self.strategy_vbox = QGroupBox('策略') self.strategy_layout = QHBoxLayout() self.strategy_tree = QTreeView() self.model = QFileSystemModel() self.model.setRootPath(QtCore.QDir.rootPath()) self.strategy_tree.setModel(self.model) self.strategy_tree.setEditTriggers(QAbstractItemView.NoEditTriggers) self.strategy_tree.setRootIndex(self.model.index(r'/home/hzy/Desktop')) # self.strategy_tree.setColumnCount(1) # self.strategy_tree.setHeaderLabels(['策略']) self.strategy_tree.setDragDropMode(QAbstractItemView.InternalMove) self.model.setReadOnly(False) self.strategy_tree.setHeaderHidden(True) self.strategy_tree.hideColumn(1) self.strategy_tree.hideColumn(2) self.strategy_tree.hideColumn(3) self.strategy_tree.setContextMenuPolicy(Qt.CustomContextMenu) self.strategy_tree.customContextMenuRequested[QPoint].connect(self.strategy_tree_right_menu) # for d in os.listdir(strategy_path): # root = QTreeWidgetItem(self.strategy_tree) # root.setText(0, d) # for file in os.listdir(os.path.join(strategy_path, d)): # child = QTreeWidgetItem(root) # child.setText(0, file) # self.list_strategy(strategy_path, self.strategy_tree) self.strategy_tree.doubleClicked.connect(self.strategy_tree_clicked) self.strategy_layout.addWidget(self.strategy_tree) self.strategy_vbox.setLayout(self.strategy_layout) # 策略右键菜单 def strategy_tree_right_menu(self, point): self.strategy_tree.popMenu = QMenu() self.strategy_tree.addType = QMenu(self.strategy_tree.popMenu) self.strategy_tree.addType.setTitle('新建') rename = QAction('重命名', self.strategy_tree) delete = QAction('删除', self.strategy_tree) add_strategy = QAction('新建策略') add_group = QAction('新建分组') refresh = QAction('刷新', self.strategy_tree) self.strategy_tree.popMenu.addMenu(self.strategy_tree.addType) self.strategy_tree.addType.addAction(add_strategy) self.strategy_tree.addType.addAction(add_group) self.strategy_tree.popMenu.addAction(rename) self.strategy_tree.popMenu.addAction(delete) self.strategy_tree.popMenu.addAction(refresh) # 右键动作 action = self.strategy_tree.popMenu.exec_(self.strategy_tree.mapToGlobal(point)) if action == add_strategy: index = self.strategy_tree.currentIndex() model = index.model() # 请注意这里可以获得model的对象 item_path = model.filePath(index) if item_path and os.path.isdir(item_path): value = '' while True: value, ok = QInputDialog.getText(self, '新建文件', '策略名称', QLineEdit.Normal) path = os.path.join(item_path, value + '.py') if os.path.exists(path) and ok: QMessageBox.warning(self, '提示', '策略名在选择的分组%s已经存在!!!' % value, QMessageBox.Yes) elif not ok: break else: with open(path, 'w', encoding='utf-8') as w: pass break elif not os.path.isdir(item_path): value = '' while True: value, ok = QInputDialog.getText(self, '新建文件', '策略名称', QLineEdit.Normal) path = os.path.join(os.path.split(item_path)[1], value + '.py') if os.path.exists(path) and ok: QMessageBox.warning(self, '提示', '策略名在选择的分组%s已经存在!!!' % value, QMessageBox.Yes) elif not ok: break else: with open(path, 'w', encoding='utf-8') as w: pass break else: QMessageBox.warning(self, '提示', '请选择分组!!!', QMessageBox.Yes) elif action == add_group: value = '' flag = self.strategy_tree.indexAt(point) # 判断鼠标点击位置标志位 while True: if not flag.isValid(): # 鼠标点击位置不在目录树叶子上 item_path = strategy_path # 新建文件夹位置在根目录 else: index = self.strategy_tree.currentIndex() model = index.model() # 请注意这里可以获得model的对象 item_path = model.filePath(index) value, ok = QInputDialog.getText(self, '新建文件夹', '分组名称', QLineEdit.Normal, value) if os.path.isdir(item_path): path = os.path.join(item_path, value) else: path = os.path.join(os.path.split(item_path)[0], value) if os.path.exists(path) and ok: QMessageBox.warning(self, '提示', '分组%s已经存在!!!' % value, QMessageBox.Yes) elif not ok: break else: os.mkdir(path) break elif action == refresh: index = self.strategy_tree.currentIndex() model = index.model() # 请注意这里可以获得model的对象 model.dataChanged.emit(index, index) elif action == rename: index = self.strategy_tree.currentIndex() model = index.model() # 请注意这里可以获得model的对象 item_path = model.filePath(index) if not os.path.isdir(item_path): # 修改策略名 value = '' (file_path, filename) = os.path.split(item_path) while True: value, ok = QInputDialog.getText(self, '修改%s策略名' % filename, '策略名称', QLineEdit.Normal, value) new_path = os.path.join(file_path, value + '.py') if os.path.exists(new_path) and ok: QMessageBox.warning(self, '提示', '策略名在此分组中%s已经存在!!!' % value, QMessageBox.Yes) elif not ok: break else: os.rename(item_path, new_path) break else: value = '' (dir_path, dir_name) = os.path.split(item_path) while True: value, ok = QInputDialog.getText(self, '修改%s文件夹' % dir_name, '分组名称', QLineEdit.Normal, value) new_path = os.path.join(dir_path, value) if os.path.exists(new_path) and ok: QMessageBox.warning(self, '提示', '分组%s已经存在!!!' % value, QMessageBox.Yes) elif not ok: break else: os.rename(item_path, new_path) break elif action == delete: index = self.strategy_tree.currentIndex() model = index.model() # 请注意这里可以获得model的对象 item_path = model.filePath(index) if item_path and os.path.isdir(item_path): reply = QMessageBox.question(self, '提示', '确定删除分组及目录下的所有文件吗?', QMessageBox.Yes | QMessageBox.No) if reply == QMessageBox.Yes: shutil.rmtree(item_path) elif item_path and not os.path.isdir(item_path): reply = QMessageBox.question(self, '提示', '确定删除文件%s吗?' % item_path, QMessageBox.Yes | QMessageBox.No) if reply == QMessageBox.Yes: os.remove(item_path) else: pass else: pass def create_content_vbox(self): self.content_vbox = QGroupBox('内容') self.content_layout = QGridLayout() self.save_btn = QPushButton('保存') self.run_btn = QPushButton('运行') self.update_api = QPushButton('更新api') self.refresh_btn = QPushButton('重新加载') self.new_btn = QPushButton('新建') self.refresh_btn.clicked.connect( lambda: self.contentEdit.load(QUrl.fromLocalFile(os.path.abspath(editor_path)))) self.contentEdit = WebEngineView() self.contentEdit.load(QUrl.fromLocalFile(os.path.abspath(editor_path))) self.save_btn.setMaximumSize(80, 60) self.run_btn.setMaximumSize(80, 60) self.content_layout.addWidget(self.run_btn, 0, 1, 1, 1) self.content_layout.addWidget(self.save_btn, 0, 2, 1, 1) self.content_layout.addWidget(self.refresh_btn, 0, 3, 1, 1) self.content_layout.addWidget(self.new_btn, 0, 4, 1, 1) self.content_layout.addWidget(self.update_api, 0, 5, 1, 1) self.content_layout.addWidget(self.contentEdit, 2, 0, 1, 5) self.content_vbox.setLayout(self.content_layout) self.save_btn.clicked.connect(self.emit_custom_signal) self.new_btn.clicked.connect(lambda: self.contentEdit.new_file()) self.update_api.clicked.connect( lambda: self.contentEdit.signal_set_autocomplete_apis.emit( json.dumps({"python": {"keywords": {"mode": "add", "content": ["import", "def", "class"]}}}))) def emit_custom_signal(self): self.contentEdit.sendSaveSignal() def strategy_tree_clicked(self): # 策略双击槽函数 index = self.strategy_tree.currentIndex() model = index.model() # 请注意这里可以获得model的对象 item_path = model.filePath(index) if not os.path.isdir(item_path): self.contentEdit.open_file(item_path) # sendCustomSignal(item_path) self.strategy_path = item_path def get_text(self) -> str: """ 获取文本内容 :return: """ self.contentEdit.signal_request_text.emit() self.loop = QEventLoop() _text = '' def f(text): self.loop.quit() _text = text self.contentEdit.signal_text_got.connect(f) self.loop.exec_() return _text