Source code for src.visuals.windows.analytics_space

"""custom class, which manages an i3 like tiling environment"""
from __future__ import annotations
from loguru import logger

from src.core.config import locPathSet_c, config, Configs

from src.visuals.windows.spaces.templater import NeoDiagrams
from src.visuals.ui.generated.ui_scroll_wrapper import Ui_Scroll_wrapper

from src.utils.io import writeJsonFile, readJsonFile

from PySide6.QtCore import Qt, QKeyCombination, Signal
from PySide6.QtGui import QKeyEvent, QMouseEvent
from PySide6.QtWidgets import (QApplication, QHBoxLayout, QVBoxLayout, QSizePolicy, QVBoxLayout, 
    QWidget, QSplitter, QLabel, QGroupBox, QPushButton, QLineEdit)


[docs] class NeoAnalyticsSingleton(QWidget): """class for each field in the i3 like layout widget : QWidget the widget, that this tile contains """ # signal, for processing the keycombinations in the AnalyticsSpace combinationNoticed = Signal(str, QWidget) def __init__(self, contained: QWidget, parent=None): super().__init__(parent) # attributes self.widget: QWidget = contained # setup visuals layout = QHBoxLayout(self) layout.addWidget(self.widget) self.setLayout(layout) # make the tile focusable self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
[docs] def keyPressEvent(self, e: QKeyEvent): """ overwritten keyPressEvent function, which activates, when a key is pressed for example. Parameters ---------- e : QKeyEvent catches the Keyevents (presses, releases, ...) """ # shift enum value mod: Qt.KeyboardModifier = Qt.KeyboardModifier.ShiftModifier # is shift pressed? if e.modifiers() == mod: # match if the key pressed is: match e.key(): # H case Qt.Key.Key_H: self.combinationNoticed.emit("horizontal", self) # V case Qt.Key.Key_V: self.combinationNoticed.emit("vertical", self) # after custom activation continue with the inherited eventfunction super().keyPressEvent(e)
[docs] def mousePressEvent(self, event: QMouseEvent): """ overwritten mousePressEvent, which activates for example, if a mouse button is pressed. used for highlighting tiles, since keypresses are only noticed on highlighted widgets Parameters ---------- event catches the mousebuttonevents (presses, releases, ...) """ self.setFocus() # continue with inherited function super().mousePressEvent(event)
[docs] class NeoAnalyticsSpace(QWidget): """An i3 like layout handler widget Attributes ---------- widgets : list[QWidget] a list of all widgets, for accessing them efficiently splitter : list[QSplitter] the same concept, as in the widget case treetracker : dict[str, dict[str, list[str] | str]] keeps track of the tree structure, with adjacency lists id --> children: list[str] """ def __init__( self, parent=None, tree: dict = [{ "id": "rootpanel", "parent": "root" }]): super().__init__(parent) # attributes self.widgets: list[QWidget] = [] self.splitter: list[QSplitter] = [] self.treetracker: dict[str, dict[str, list[str] | str]] = dict() # initialize the tree structure initialSplitter: QSplitter = self._build_by_json_tree(tree, "root", 1, []) self.splitter.append( initialSplitter ) # initialize the visuals horizontallayout = QHBoxLayout(self) horizontallayout.addWidget(initialSplitter) self.setLayout(horizontallayout)
[docs] def _make_panel(self) -> NeoAnalyticsSingleton: """this method does make an instance of the widget, that gets pasted into the tiles""" uniqueId = f"id_{len(self.widgets)}" instanceOfWidget: NeoDiagrams = NeoDiagrams(ids=uniqueId) tileWrapper: NeoAnalyticsSingleton = NeoAnalyticsSingleton(instanceOfWidget) self.widgets.append(tileWrapper) instanceOfWidget.ui.commandLink_plot.pressed.connect(self._save_states) tileWrapper.combinationNoticed.connect(self._add_splitter) return tileWrapper
[docs] def _build_by_json_tree( self, childrenDicts: list[dict], currentId: str, altIndex: int, splitterSizes: list[int] ) -> QSplitter: """recursive method, for initializing a jsontree, which was saved before Parameters ---------- childrenDicts : list[dict] the metadicts for the children of the current splitter currentId : str the id of the current Splitter altIndex : int a raising indexnumber, which is used for the alternating split structure splitterSizes : list[int] used for setting the correct proportions for the splitter Returns ------- currentSplitter : QSplitter returns the qsplitter int the current scope """ # the structure is always alternating between vertical and horizontal if altIndex%2 == 1: currentSplitter: QSplitter = QSplitter(orientation=Qt.Orientation.Vertical) else: currentSplitter: QSplitter = QSplitter(orientation=Qt.Orientation.Horizontal) self.splitter.append(currentSplitter) # update property of widget currentSplitter.setProperty("id", currentId) # update the treetracker, for preventing keyerrors self.treetracker[currentId] = dict() self.treetracker[currentId]["children"] = [] for childDict in childrenDicts: # read the id of the child childId: str = childDict["id"] self.treetracker[childId] = dict() isSplitterChild: bool = "children" in childDict if isSplitterChild: # if the child is a splitter, start the method again childSplitter: QSplitter = self._build_by_json_tree( childDict["children"], childId, altIndex+1, childDict["size"] ) currentSplitter.addWidget(childSplitter) else: # if the child is a widget, save it childWidget: NeoAnalyticsSingleton = self._make_panel() childWidget.setProperty("id", childId) currentSplitter.addWidget(childWidget) self.treetracker[currentId]["children"].append(childId) self.treetracker[childId]["parent"] = currentId if splitterSizes: # set sizes currentSplitter.setSizes(splitterSizes) return currentSplitter
[docs] def _save_states(self): """ saves the current layout to the ctr.json in the config folder """ # adjust tree tracker for all widgets for widgetInstances in self.widgets: pass # TODO: later expansion # adjust tree tracker for all splitter for splitterInstance in self.splitter: splitterId: str = splitterInstance.property("id") splitterSize: list[int] = splitterInstance.sizes() self.treetracker[splitterId]["size"] = splitterSize # get the start children from the constant root id RootChildrenIds: list[str] = self.treetracker["root"]["children"] # build the recursive variant of the tree tracker recursiveTreeTracker: dict = self._explicit_to_recursive_dict(self.treetracker, RootChildrenIds) # write the recursive variant to the ctr.json writeJsonFile(recursiveTreeTracker, locPathSet_c + "ctr.json")
[docs] def _explicit_to_recursive_dict( self, explicitTreeTracker: dict, childrenIds: list[str] ) -> dict: """ a recursive function, which converts the explicittreetracker into the recursive one. Parameters: explicitTreeTracker : dict the tree tracker of this class in explicit form childrenIds : list[str] the current children we observe Returns ------- childrenDicts : dict the childrendicts, containing metadata """ # initialize an empty dict, for the return childrenDicts = list() # go through all children for childId in childrenIds: childDict = dict() childDict["id"] = childId explicitChildDict: dict = explicitTreeTracker[childId] # copy all the attribute values of the treetracker for attribute in explicitChildDict: childDict[attribute] = explicitChildDict[attribute] isChildSplitter: bool = ("children" in explicitChildDict) # if encountering a splitter, do the method again if isChildSplitter: childDict["children"] = self._explicit_to_recursive_dict( explicitTreeTracker, explicitChildDict["children"] ) childrenDicts.append(childDict) return childrenDicts
[docs] def _add_splitter(self, orientation: str, focusedWidget: NeoAnalyticsSingleton) -> None: """ slices a focused widget into two new ones Parameters ---------- orientation : str the orientation of the wanted slice focusedWidget : NeoAnalyticsSingleton the tile, which should get sliced """ if orientation == "horizontal": qtOrientation: Qt.Orientation = Qt.Orientation.Horizontal elif orientation == "vertical": qtOrientation: Qt.Orientation = Qt.Orientation.Vertical parentSplitter: QSplitter = focusedWidget.parent() # check if the parent really is a splitter if not isinstance(parentSplitter, QSplitter): return qtParentOrientation: Qt.Orientation = parentSplitter.orientation() # HACK: this is proprietary and needs future change uniqueWidgetId: int = len(self.widgets) uniqueSplitId: int = len(self.splitter) newWidget: NeoAnalyticsSingleton = self._make_panel() # set id newWidgetId: str = f"Widget_{uniqueWidgetId}" newWidget.setProperty("id", newWidgetId) # get the position positionOfFocusedWidget: int = parentSplitter.indexOf(focusedWidget) # if the orientation matches, no splitter is created. instead just add a widget if qtOrientation == qtParentOrientation: # update treetracker self.treetracker[parentSplitter.property("id")]["children"].insert(positionOfFocusedWidget+1, newWidgetId) self.treetracker[newWidgetId] = dict() self.treetracker[newWidgetId]["parent"] = parentSplitter.property("id") print(self.treetracker) # set visible parentSplitter.insertWidget(positionOfFocusedWidget+1, newWidget) return # create the new splitter newSplitterId: str = f"Splitter_{uniqueSplitId}" newSplitter: QSplitter = QSplitter(orientation=qtOrientation) newSplitter.setProperty("id", newSplitterId) self.splitter.append(newSplitter) # remove the widget and place the splitter accordingly focusedWidget.setParent(None) self.treetracker[parentSplitter.property("id")]["children"].remove(focusedWidget.property("id")) parentSplitter.insertWidget(positionOfFocusedWidget, newSplitter) self.treetracker[parentSplitter.property("id")]["children"].insert(positionOfFocusedWidget, newSplitterId) # add the widgets to the new splitter and set correct sizes newSplitter.addWidget(focusedWidget) self.treetracker[focusedWidget.property("id")]["parent"] = newSplitterId newSplitter.addWidget(newWidget) self.treetracker[newWidgetId] = dict() self.treetracker[newWidgetId]["parent"] = newSplitterId newSplitter.setSizes([1, 1]) parentSplitter.setSizes([1 for size in parentSplitter.sizes()]) # adjust the treetracker self.treetracker[newSplitterId] = dict() self.treetracker[newSplitterId]["parent"] = parentSplitter.property("id") self.treetracker[newSplitterId]["children"] = [newWidgetId, focusedWidget.property("id")] print(self.treetracker)
[docs] class NeoAnalyticsSpaceWrapper(QWidget): """wrapper class for analyticsspaces Attributes ---------- ui : Ui_Scroll_wrapper the raw Class, produced by compilation spaceinstance : NeoAnalyticsSpace An instance of the i3 like class """ def __init__(self, parent=None) -> None: super().__init__(parent) self.ui = Ui_Scroll_wrapper() self.ui.setupUi(self) self.spaceInstance: NeoAnalyticsSpace = NeoAnalyticsSpace(self) # initializing a list of singletons, for proper management self.ui.scrollarea_insertlayout.addWidget(self.spaceInstance) self.ui.toolButton_dell.clicked.connect(self._delete_layout) self.ui.toolButton_loadl.clicked.connect(self._load_layout) self.ui.toolButton_savel.clicked.connect(self._save_layout) def _save_layout(self): self.spaceInstance._save_states() def _load_layout(self): pathToRecursiveTree: str = locPathSet_c + "ctr.json" recursiveTree: dict = readJsonFile(pathToRecursiveTree) self.spaceInstance.deleteLater() self.spaceInstance = NeoAnalyticsSpace(self, tree=recursiveTree) self.ui.scrollarea_insertlayout.addWidget(self.spaceInstance) def _delete_layout(self): self.spaceInstance.deleteLater() self.spaceInstance = NeoAnalyticsSpace(self) self.ui.scrollarea_insertlayout.addWidget(self.spaceInstance)