Пример #1
0
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)
Пример #2
0
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