Merge branch 'window_mode' into 'master'

Window mode / RPC utility functions

See merge request augustin_s/grum!4
This commit is contained in:
2023-01-23 09:45:17 +00:00
6 changed files with 113 additions and 17 deletions

View File

@ -5,6 +5,7 @@ from PyQt5.QtWidgets import QApplication
from . import ctrl_c, theme
from .mainwin import MainWindow
from .mdi import MDIWindowMode
def main():
@ -26,7 +27,18 @@ def handle_clargs():
parser.add_argument("-e", "--examples", dest="add_examples", action="store_true", help="Add example data")
parser.add_argument("-w", "--window-mode", default="multi", choices=MDIWindowMode.values(), type=unambiguous_window_mode, help="Set the initial window mode")
return parser.parse_args().__dict__
def unambiguous_window_mode(arg):
cfarg = arg.casefold()
values = MDIWindowMode.values()
matches = [i for i in values if i.casefold().startswith(cfarg)]
if len(matches) == 1:
return matches[0]
return arg

View File

@ -4,7 +4,7 @@ from PyQt5.QtWidgets import QMainWindow, QSplitter
from . import assets
from .dictlist import DictList
from .exampledata import exampledata
from .mdi import MDIArea, MDISubMultiPlot, MDISubPlot
from .mdi import MDIArea, MDISubMultiPlot, MDISubPlot, MDIWindowMode
from .menus import BarMenu
from .plotdesc import PlotDescription
from .rpc import RPCServerThread
@ -15,7 +15,7 @@ class MainWindow(QMainWindow):
sig_make_new_plot = pyqtSignal(str, PlotDescription)
def __init__(self, *args, title="grum", host="localhost", port=8000, add_examples=False, **kwargs):
def __init__(self, *args, title="grum", host="localhost", port=8000, add_examples=False, window_mode=MDIWindowMode.MULTI, **kwargs):
super().__init__(*args, **kwargs)
self.setWindowTitle(title)
self.setWindowIcon(assets.icon())
@ -38,7 +38,7 @@ class MainWindow(QMainWindow):
self.menu_settings = menu = BarMenu(bar, "&Settings")
menu.addCheckbox("Open new plots", state=True)
self.mdi = mdi = MDIArea(bar)
self.mdi = mdi = MDIArea(bar, window_mode=window_mode)
splitter = QSplitter(Qt.Horizontal)
splitter.addWidget(lst)
@ -47,7 +47,7 @@ class MainWindow(QMainWindow):
self.setCentralWidget(splitter)
rst = RPCServerThread(host, port)
rst = RPCServerThread(host, port, doc_title_suffix="grum")
rst.start()
rst.server.register_function(self.new_plot)
rst.server.register_function(self.append_data)

View File

@ -1,5 +1,5 @@
from .mdiarea import MDIArea
from .mdiarea import MDIArea, MDIWindowMode
from .mdisubplot import MDISubPlot, MDISubMultiPlot

View File

@ -1,4 +1,5 @@
from PyQt5.QtWidgets import QMdiArea, QAction
import enum
from PyQt5.QtWidgets import QMdiArea
from PyQt5.QtGui import QPainter
from .. import assets
@ -6,14 +7,26 @@ from ..theme import MDI_BKG
from ..menus import BarMenu
class MDIWindowMode(str, enum.Enum):
MULTI = "multi"
SINGLE = "single"
TABS = "tabs"
@classmethod
def values(cls):
return tuple(i.value for i in cls)
class MDIArea(QMdiArea):
def __init__(self, bar, *args, **kwargs):
def __init__(self, bar, window_mode=MDIWindowMode.MULTI, *args, **kwargs):
super().__init__(*args, **kwargs)
self.logo = assets.logo()
self.setTabsClosable(True)
self.setTabsMovable(True)
self._add_menu(bar)
self.set_window_mode(window_mode)
def _add_menu(self, bar):
@ -22,25 +35,41 @@ class MDIArea(QMdiArea):
menu.addAction("Tile", self.on_tile)
menu.addSeparator()
group = menu.addGroup()
group.addCheckbox("Multiple windows", triggered=self.enable_subwindow_view, state=True)
group.addCheckbox("Multiple windows", triggered=self.enable_multiple_windows_mode, state=True)
group.addCheckbox("Single window", triggered=self.enable_single_window_mode)
group.addCheckbox("Tabbed", triggered=self.enable_tabbed_view)
group.addCheckbox("Tabbed", triggered=self.enable_tabbed_mode)
menu.addSeparator()
menu.addAction("Close all", self.closeAllSubWindows)
menu.addAction("Close inactive", self.closeInactiveSubWindows)
def set_window_mode(self, mode: MDIWindowMode) -> None:
mode_enablers = {
MDIWindowMode.SINGLE: self.enable_single_window_mode,
MDIWindowMode.MULTI: self.enable_multiple_windows_mode,
MDIWindowMode.TABS: self.enable_tabbed_mode
}
enable_mode = mode_enablers[mode]
enable_mode()
def on_cascade(self):
self.menu.checkboxes["Multiple windows"].setChecked(True)
self.enable_subwindow_view()
self.enable_multiple_windows_mode()
self.cascadeSubWindows()
def on_tile(self):
self.menu.checkboxes["Multiple windows"].setChecked(True)
self.enable_subwindow_view()
self.enable_multiple_windows_mode()
self.tileSubWindows()
def enable_multiple_windows_mode(self):
self.menu.checkboxes["Multiple windows"].setChecked(True)
self.enable_subwindow_view()
for sub in self.subWindowList():
sub.frame_on()
def enable_single_window_mode(self):
self.menu.checkboxes["Single window"].setChecked(True)
self.enable_subwindow_view()
self.closeInactiveSubWindows()
active = self.activeSubWindow()
@ -48,10 +77,13 @@ class MDIArea(QMdiArea):
active.showMaximized()
active.frame_off()
def enable_tabbed_mode(self):
self.menu.checkboxes["Tabbed"].setChecked(True)
self.enable_tabbed_view()
def enable_subwindow_view(self):
self.setViewMode(QMdiArea.SubWindowView)
for sub in self.subWindowList():
sub.frame_on()
def enable_tabbed_view(self):
self.setViewMode(QMdiArea.TabbedView)

View File

@ -8,4 +8,12 @@ class RPCClient(xrc.ServerProxy):
super().__init__(uri, *args, **kwargs)
def __repr__(self):
orig = super().__repr__()
head = orig.strip("<>").rstrip("/")
head += " exposing:\n\n"
help = self.utils.help()
return head + help

View File

@ -1,12 +1,56 @@
import xmlrpc.server as xrs
from inspect import getdoc, signature
class RPCServer(xrs.SimpleXMLRPCServer):
class RPCServer(xrs.DocXMLRPCServer):
def __init__(self, host, port, *args, **kwargs):
def __init__(self, host, port, doc_title_suffix="", *args, **kwargs):
addr = (host, port)
kwargs.setdefault("allow_none", True)
super().__init__(addr, *args, **kwargs)
if doc_title_suffix:
self.server_name = doc_title_suffix + " " + self.server_name
self.server_title = doc_title_suffix + " " + self.server_title
self.register_function(self.ping, name="utils.ping")
self.register_function(self.help, name="utils.help")
self.register_function(self.info, name="utils.info")
def ping(self):
"""
Returns "pong". May be used for testing the connection.
"""
return "pong"
def help(self):
"""
Returns an overview of exposed functions incl. signatures and docstrings.
"""
info = self.info()
lines = []
for name, item in info.items():
sig = item["signature"]
head = f"{name}{sig}:"
underline = "-" * len(head)
doc = item["docstring"]
doc = str(doc) # take care of None
lines += [head, underline, doc, ""]
return "\n".join(lines)
def info(self):
"""
Returns a dict mapping names of exposed functions to dicts holding their signature and docstring.
"""
res = {}
for name, func in self.funcs.items():
doc = getdoc(func)
sig = str(signature(func))
res[name] = dict(signature=sig, docstring=doc)
return res