diff --git a/bec_widgets/examples/developer_view/developer_view.py b/bec_widgets/examples/developer_view/developer_view.py index 6f4febae..17d25bf5 100644 --- a/bec_widgets/examples/developer_view/developer_view.py +++ b/bec_widgets/examples/developer_view/developer_view.py @@ -3,9 +3,14 @@ from typing import List import PySide6QtAds as QtAds from PySide6QtAds import CDockManager, CDockWidget from qtpy.QtCore import Qt, QTimer -from qtpy.QtWidgets import QSplitter, QTreeWidget, QVBoxLayout, QWidget +from qtpy.QtGui import QKeySequence, QShortcut +from qtpy.QtWidgets import QSplitter, QTextEdit, QVBoxLayout, QWidget from bec_widgets import BECWidget +from bec_widgets.utils.error_popups import SafeSlot +from bec_widgets.utils.toolbars.actions import MaterialIconAction +from bec_widgets.utils.toolbars.bundles import ToolbarBundle +from bec_widgets.utils.toolbars.toolbar import ModularToolBar from bec_widgets.widgets.containers.advanced_dock_area.advanced_dock_area import AdvancedDockArea from bec_widgets.widgets.editors.monaco.monaco_tab import MonacoDock from bec_widgets.widgets.editors.web_console.web_console import WebConsole @@ -48,21 +53,36 @@ def set_splitter_weights(splitter: QSplitter, weights: List[float]) -> None: class DeveloperView(BECWidget, QWidget): - def __init__(self, parent=None, *args, **kwargs): - super().__init__(parent, *args, **kwargs) + def __init__(self, parent=None, **kwargs): + super().__init__(parent=parent, **kwargs) # Top-level layout hosting a toolbar and the dock manager self._root_layout = QVBoxLayout(self) self._root_layout.setContentsMargins(0, 0, 0, 0) self._root_layout.setSpacing(0) + self.toolbar = ModularToolBar(self) + self.init_developer_toolbar() + self._root_layout.addWidget(self.toolbar) + self.dock_manager = CDockManager(self) self._root_layout.addWidget(self.dock_manager) # Initialize the widgets - self.explorer = IDEExplorer(self) # TODO will be replaced by explorer widget + self.explorer = IDEExplorer(self) self.console = WebConsole(self) + self.terminal = WebConsole(self, startup_cmd="") self.monaco = MonacoDock(self) + self.monaco.save_enabled.connect(self._on_save_enabled_update) self.plotting_ads = AdvancedDockArea(self, mode="plot", default_add_direction="bottom") + self.signature_help = QTextEdit(self) + self.signature_help.setAcceptRichText(True) + self.signature_help.setReadOnly(True) + self.signature_help.setLineWrapMode(QTextEdit.LineWrapMode.WidgetWidth) + opt = self.signature_help.document().defaultTextOption() + opt.setWrapMode(opt.WrapMode.WrapAnywhere) # wrap everything, including code + self.signature_help.document().setDefaultTextOption(opt) + + self.monaco.signature_help.connect(self.signature_help.setMarkdown) # Create the dock widgets self.explorer_dock = QtAds.CDockWidget("Explorer", self) @@ -74,20 +94,22 @@ class DeveloperView(BECWidget, QWidget): self.monaco_dock = QtAds.CDockWidget("Monaco Editor", self) self.monaco_dock.setWidget(self.monaco) - self.plotting_ads_dock = QtAds.CDockWidget("Plotting Area", self) - self.plotting_ads_dock.setWidget(self.plotting_ads) + self.terminal_dock = QtAds.CDockWidget("Terminal", self) + self.terminal_dock.setWidget(self.terminal) # Monaco will be central widget self.dock_manager.setCentralWidget(self.monaco_dock) # Add the dock widgets to the dock manager - self.dock_manager.addDockWidget( + area_bottom = self.dock_manager.addDockWidget( QtAds.DockWidgetArea.BottomDockWidgetArea, self.console_dock ) - self.dock_manager.addDockWidget(QtAds.DockWidgetArea.LeftDockWidgetArea, self.explorer_dock) - self.dock_manager.addDockWidget( - QtAds.DockWidgetArea.RightDockWidgetArea, self.plotting_ads_dock + self.dock_manager.addDockWidgetTabToArea(self.terminal_dock, area_bottom) + + area_left = self.dock_manager.addDockWidget( + QtAds.DockWidgetArea.LeftDockWidgetArea, self.explorer_dock ) + area_left.titleBar().setVisible(False) for dock in self.dock_manager.dockWidgets(): # dock.setFeature(CDockWidget.DockWidgetDeleteOnClose, True)#TODO implement according to MonacoDock or AdvancedDockArea @@ -96,13 +118,71 @@ class DeveloperView(BECWidget, QWidget): dock.setFeature(CDockWidget.DockWidgetFloatable, False) dock.setFeature(CDockWidget.DockWidgetMovable, False) - # Fetch all dock areas of the dock widgets (on our case always one dock area) - for dock in self.dock_manager.dockWidgets(): - area = dock.dockAreaWidget() - area.titleBar().setVisible(False) + self.plotting_ads_dock = QtAds.CDockWidget("Plotting Area", self) + self.plotting_ads_dock.setWidget(self.plotting_ads) + + self.signature_dock = QtAds.CDockWidget("Signature Help", self) + self.signature_dock.setWidget(self.signature_help) + + area_right = self.dock_manager.addDockWidget( + QtAds.DockWidgetArea.RightDockWidgetArea, self.plotting_ads_dock + ) + self.dock_manager.addDockWidgetTabToArea(self.signature_dock, area_right) # Apply stretch after the layout is done - self.set_default_view([2, 5, 3], [5, 5]) + self.set_default_view([2, 5, 3], [7, 3]) + + # Connect editor signals + self.explorer.file_open_requested.connect(self._open_new_file) + + self.toolbar.show_bundles(["save", "execution", "settings"]) + + def init_developer_toolbar(self): + """Initialize the developer toolbar with necessary actions and widgets.""" + save_button = MaterialIconAction(icon_name="save", tooltip="Save", parent=self) + save_button.action.triggered.connect(self.on_save) + self.toolbar.components.add_safe("save", save_button) + + save_as_button = MaterialIconAction(icon_name="save_as", tooltip="Save As", parent=self) + self.toolbar.components.add_safe("save_as", save_as_button) + + save_bundle = ToolbarBundle("save", self.toolbar.components) + save_bundle.add_action("save") + save_bundle.add_action("save_as") + self.toolbar.add_bundle(save_bundle) + + self.toolbar.components.add_safe( + "run", + MaterialIconAction( + icon_name="play_arrow", tooltip="Run current file", filled=True, parent=self + ), + ) + self.toolbar.components.add_safe( + "stop", + MaterialIconAction( + icon_name="stop", tooltip="Stop current execution", filled=True, parent=self + ), + ) + + execution_bundle = ToolbarBundle("execution", self.toolbar.components) + execution_bundle.add_action("run") + execution_bundle.add_action("stop") + self.toolbar.add_bundle(execution_bundle) + + vim_action = MaterialIconAction( + icon_name="text_ad", tooltip="Vim", filled=True, parent=self, checkable=True + ) + self.toolbar.components.add_safe("vim", vim_action) + vim_action.action.triggered.connect(self.on_vim_triggered) + + settings_bundle = ToolbarBundle("settings", self.toolbar.components) + settings_bundle.add_action("vim") + self.toolbar.add_bundle(settings_bundle) + + save_shortcut = QShortcut(QKeySequence("Ctrl+S"), self) + save_shortcut.activated.connect(self.on_save) + save_as_shortcut = QShortcut(QKeySequence("Ctrl+Shift+S"), self) + save_as_shortcut.activated.connect(self.on_save_as) ####### Default view has to be done with setting up splitters ######## def set_default_view(self, horizontal_weights: list, vertical_weights: list): @@ -166,6 +246,26 @@ class DeveloperView(BECWidget, QWidget): v = [1, 1] self.set_default_view(h, v) + def _open_new_file(self, file_name: str, scope: str): + self.monaco.open_file(file_name) + + @SafeSlot() + def on_save(self): + self.monaco.save_file() + + @SafeSlot() + def on_save_as(self): + self.monaco.save_file(force_save_as=True) + + @SafeSlot() + def on_vim_triggered(self): + self.monaco.set_vim_mode(self.toolbar.components.get_action("vim").action.isChecked()) + + @SafeSlot(bool) + def _on_save_enabled_update(self, enabled: bool): + self.toolbar.components.get_action("save").action.setEnabled(enabled) + self.toolbar.components.get_action("save_as").action.setEnabled(enabled) + if __name__ == "__main__": import sys @@ -176,6 +276,6 @@ if __name__ == "__main__": developer_view = DeveloperView() developer_view.show() developer_view.setWindowTitle("Developer View") - developer_view.resize(1200, 800) + developer_view.resize(1920, 1080) # developer_view.set_stretch(horizontal=[1, 3, 2], vertical=[5, 5]) #can be set during runtime sys.exit(app.exec_()) diff --git a/bec_widgets/widgets/editors/monaco/monaco_tab.py b/bec_widgets/widgets/editors/monaco/monaco_tab.py index be320fcb..4d13cc4c 100644 --- a/bec_widgets/widgets/editors/monaco/monaco_tab.py +++ b/bec_widgets/widgets/editors/monaco/monaco_tab.py @@ -4,6 +4,7 @@ import os from typing import Any, cast import PySide6QtAds as QtAds +from bec_lib.logger import bec_logger from PySide6QtAds import CDockWidget from qtpy.QtCore import QEvent, QTimer, Signal from qtpy.QtWidgets import QFileDialog, QMessageBox, QToolButton, QVBoxLayout, QWidget @@ -11,6 +12,8 @@ from qtpy.QtWidgets import QFileDialog, QMessageBox, QToolButton, QVBoxLayout, Q from bec_widgets import BECWidget from bec_widgets.widgets.editors.monaco.monaco_widget import MonacoWidget +logger = bec_logger.logger + class MonacoDock(BECWidget, QWidget): """ @@ -74,6 +77,8 @@ class MonacoDock(BECWidget, QWidget): return widget = cast(MonacoWidget, editor.widget()) + if widget.modified: + logger.info(f"Editor '{widget.current_file}' has unsaved changes: {widget.get_text()}") self.save_enabled.emit(widget.modified) def _on_signature_change(self, signature: dict): @@ -157,6 +162,7 @@ class MonacoDock(BECWidget, QWidget): idx = tb.indexOf(tb.tabBar()) tb.insertWidget(idx + 1, plus_btn) plus_btn.clicked.connect(lambda: self.add_editor(area)) + # pylint: disable=protected-access area._monaco_plus_btn = plus_btn def _scan_and_fix_areas(self): @@ -244,8 +250,9 @@ class MonacoDock(BECWidget, QWidget): if not widget: return if widget.current_file and not force_save_as: - with open(widget.current_file, "w") as f: + with open(widget.current_file, "w", encoding="utf-8") as f: f.write(widget.get_text()) + # pylint: disable=protected-access widget._original_content = widget.get_text() widget.save_enabled.emit(False) return @@ -268,7 +275,12 @@ class MonacoDock(BECWidget, QWidget): print(f"Save file called, last focused editor: {self.last_focused_editor}") def set_vim_mode(self, enabled: bool): - # Toggle Vim mode for all editor widgets + """ + Set Vim mode for all editor widgets. + + Args: + enabled (bool): Whether to enable or disable Vim mode. + """ for widget in self.dock_manager.dockWidgets(): editor_widget = cast(MonacoWidget, widget.widget()) editor_widget.set_vim_mode_enabled(enabled) @@ -295,6 +307,6 @@ if __name__ == "__main__": from qtpy.QtWidgets import QApplication app = QApplication(sys.argv) - dock = MonacoDock() - dock.show() + _dock = MonacoDock() + _dock.show() sys.exit(app.exec()) diff --git a/bec_widgets/widgets/editors/monaco/monaco_widget.py b/bec_widgets/widgets/editors/monaco/monaco_widget.py index bdda57d3..1f5e757d 100644 --- a/bec_widgets/widgets/editors/monaco/monaco_widget.py +++ b/bec_widgets/widgets/editors/monaco/monaco_widget.py @@ -83,8 +83,9 @@ class MonacoWidget(BECWidget, QWidget): text (str): The text to set in the editor. file_name (str): Set the file name """ - self.editor.set_text(text, uri=file_name) self._current_file = file_name + self._original_content = text + self.editor.set_text(text, uri=file_name) def get_text(self) -> str: """ @@ -125,7 +126,6 @@ class MonacoWidget(BECWidget, QWidget): with open(file_name, "r", encoding="utf-8") as file: content = file.read() - self._original_content = content self.set_text(content, file_name=file_name) @property @@ -277,7 +277,7 @@ if TYPE_CHECKING: scans: Scans ####################################### -########## User Script ##################### +########## User Script ################ ####################################### # This is a comment