forked from M-Labs/artiq
browser: experiment dock stubs
This commit is contained in:
parent
77b84a7979
commit
ee8160863a
|
@ -0,0 +1,359 @@
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
|
from artiq import __artiq_dir__ as artiq_dir
|
||||||
|
from artiq.gui.tools import LayoutWidget, log_level_to_name
|
||||||
|
from artiq.gui.entries import argty_to_entry
|
||||||
|
from artiq.protocols import pyon
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class _WheelFilter(QtCore.QObject):
|
||||||
|
def eventFilter(self, obj, event):
|
||||||
|
if (event.type() == QtCore.QEvent.Wheel and
|
||||||
|
event.modifiers() != QtCore.Qt.NoModifier):
|
||||||
|
event.ignore()
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class _ArgumentEditor(QtWidgets.QTreeWidget):
|
||||||
|
def __init__(self, expurl):
|
||||||
|
QtWidgets.QTreeWidget.__init__(self)
|
||||||
|
self.setColumnCount(3)
|
||||||
|
self.header().setStretchLastSection(False)
|
||||||
|
if hasattr(self.header(), "setSectionResizeMode"):
|
||||||
|
set_resize_mode = self.header().setSectionResizeMode
|
||||||
|
else:
|
||||||
|
set_resize_mode = self.header().setResizeMode
|
||||||
|
set_resize_mode(0, QtWidgets.QHeaderView.ResizeToContents)
|
||||||
|
set_resize_mode(1, QtWidgets.QHeaderView.Stretch)
|
||||||
|
set_resize_mode(2, QtWidgets.QHeaderView.ResizeToContents)
|
||||||
|
self.header().setVisible(False)
|
||||||
|
self.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
|
||||||
|
self.setHorizontalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
|
||||||
|
self.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
|
||||||
|
|
||||||
|
self.viewport().installEventFilter(_WheelFilter(self.viewport()))
|
||||||
|
|
||||||
|
self.expurl = expurl
|
||||||
|
|
||||||
|
self._groups = dict()
|
||||||
|
self._arg_to_entry_widgetitem = dict()
|
||||||
|
|
||||||
|
arguments = self.get_submission_arguments() # TODO
|
||||||
|
|
||||||
|
if not arguments:
|
||||||
|
self.addTopLevelItem(QtWidgets.QTreeWidgetItem(["No arguments"]))
|
||||||
|
|
||||||
|
for name, argument in arguments.items():
|
||||||
|
entry = argty_to_entry[argument["desc"]["ty"]](argument)
|
||||||
|
widget_item = QtWidgets.QTreeWidgetItem([name])
|
||||||
|
self._arg_to_entry_widgetitem[name] = entry, widget_item
|
||||||
|
|
||||||
|
if argument["group"] is None:
|
||||||
|
self.addTopLevelItem(widget_item)
|
||||||
|
else:
|
||||||
|
self._get_group(argument["group"]).addChild(widget_item)
|
||||||
|
self.setItemWidget(widget_item, 1, entry)
|
||||||
|
recompute_argument = QtWidgets.QToolButton()
|
||||||
|
recompute_argument.setToolTip("Re-run the experiment's build "
|
||||||
|
"method and take the default value")
|
||||||
|
recompute_argument.setIcon(QtWidgets.QApplication.style().standardIcon(
|
||||||
|
QtWidgets.QStyle.SP_BrowserReload))
|
||||||
|
recompute_argument.clicked.connect(
|
||||||
|
partial(self._recompute_argument_clicked, name))
|
||||||
|
fix_layout = LayoutWidget()
|
||||||
|
fix_layout.addWidget(recompute_argument)
|
||||||
|
self.setItemWidget(widget_item, 2, fix_layout)
|
||||||
|
|
||||||
|
widget_item = QtWidgets.QTreeWidgetItem()
|
||||||
|
self.addTopLevelItem(widget_item)
|
||||||
|
recompute_arguments = QtWidgets.QPushButton("Recompute all arguments")
|
||||||
|
recompute_arguments.setIcon(QtWidgets.QApplication.style().standardIcon(
|
||||||
|
QtWidgets.QStyle.SP_BrowserReload))
|
||||||
|
recompute_arguments.clicked.connect(self._recompute_arguments_clicked)
|
||||||
|
|
||||||
|
buttons = LayoutWidget()
|
||||||
|
buttons.addWidget(recompute_arguments, 1, 1)
|
||||||
|
buttons.layout.setColumnStretch(0, 1)
|
||||||
|
buttons.layout.setColumnStretch(1, 0)
|
||||||
|
buttons.layout.setColumnStretch(2, 0)
|
||||||
|
buttons.layout.setColumnStretch(3, 1)
|
||||||
|
self.setItemWidget(widget_item, 1, buttons)
|
||||||
|
|
||||||
|
def _get_group(self, name):
|
||||||
|
if name in self._groups:
|
||||||
|
return self._groups[name]
|
||||||
|
group = QtWidgets.QTreeWidgetItem([name])
|
||||||
|
for c in 0, 1:
|
||||||
|
group.setBackground(c, QtGui.QBrush(QtGui.QColor(100, 100, 100)))
|
||||||
|
group.setForeground(c, QtGui.QBrush(QtGui.QColor(220, 220, 255)))
|
||||||
|
font = group.font(c)
|
||||||
|
font.setBold(True)
|
||||||
|
group.setFont(c, font)
|
||||||
|
self.addTopLevelItem(group)
|
||||||
|
self._groups[name] = group
|
||||||
|
return group
|
||||||
|
|
||||||
|
def get_submission_arguments(self):
|
||||||
|
return {} # TODO
|
||||||
|
|
||||||
|
def _recompute_arguments_clicked(self):
|
||||||
|
pass # TODO
|
||||||
|
|
||||||
|
def _recompute_argument_clicked(self, name):
|
||||||
|
asyncio.ensure_future(self._recompute_argument(name))
|
||||||
|
|
||||||
|
async def _recompute_argument(self, name):
|
||||||
|
try:
|
||||||
|
arginfo = await self.compute_arginfo()
|
||||||
|
except:
|
||||||
|
logger.error("Could not recompute argument '%s' of '%s'",
|
||||||
|
name, self.expurl, exc_info=True)
|
||||||
|
return
|
||||||
|
argument = self.get_submission_arguments()[name]
|
||||||
|
|
||||||
|
procdesc = arginfo[name][0]
|
||||||
|
state = argty_to_entry[procdesc["ty"]].default_state(procdesc)
|
||||||
|
argument["desc"] = procdesc
|
||||||
|
argument["state"] = state
|
||||||
|
|
||||||
|
old_entry, widget_item = self._arg_to_entry_widgetitem[name]
|
||||||
|
old_entry.deleteLater()
|
||||||
|
|
||||||
|
entry = argty_to_entry[procdesc["ty"]](argument)
|
||||||
|
self._arg_to_entry_widgetitem[name] = entry, widget_item
|
||||||
|
self.setItemWidget(widget_item, 1, entry)
|
||||||
|
|
||||||
|
async def compute_arginfo(self):
|
||||||
|
return {} # TODO
|
||||||
|
|
||||||
|
def save_state(self):
|
||||||
|
expanded = []
|
||||||
|
for k, v in self._groups.items():
|
||||||
|
if v.isExpanded():
|
||||||
|
expanded.append(k)
|
||||||
|
return {"expanded": expanded}
|
||||||
|
|
||||||
|
def restore_state(self, state):
|
||||||
|
for e in state["expanded"]:
|
||||||
|
try:
|
||||||
|
self._groups[e].setExpanded(True)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
log_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
||||||
|
|
||||||
|
|
||||||
|
class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
||||||
|
sigClosed = QtCore.pyqtSignal()
|
||||||
|
|
||||||
|
def __init__(self, expurl):
|
||||||
|
QtWidgets.QMdiSubWindow.__init__(self)
|
||||||
|
self.setWindowTitle(expurl)
|
||||||
|
self.setWindowIcon(QtWidgets.QApplication.style().standardIcon(
|
||||||
|
QtWidgets.QStyle.SP_FileDialogContentsView))
|
||||||
|
|
||||||
|
self.layout = QtWidgets.QGridLayout()
|
||||||
|
top_widget = QtWidgets.QWidget()
|
||||||
|
top_widget.setLayout(self.layout)
|
||||||
|
self.setWidget(top_widget)
|
||||||
|
self.layout.setSpacing(5)
|
||||||
|
self.layout.setContentsMargins(5, 5, 5, 5)
|
||||||
|
|
||||||
|
self.expurl = expurl
|
||||||
|
|
||||||
|
self.argeditor = _ArgumentEditor(self)
|
||||||
|
self.layout.addWidget(self.argeditor, 0, 0, 1, 5)
|
||||||
|
self.layout.setRowStretch(0, 1)
|
||||||
|
|
||||||
|
options = self.get_submission_options()
|
||||||
|
|
||||||
|
log_level = QtWidgets.QComboBox()
|
||||||
|
log_level.addItems(log_levels)
|
||||||
|
log_level.setCurrentIndex(1)
|
||||||
|
log_level.setToolTip("Minimum level for log entry production")
|
||||||
|
log_level_label = QtWidgets.QLabel("Logging level:")
|
||||||
|
log_level_label.setToolTip("Minimum level for log message production")
|
||||||
|
self.layout.addWidget(log_level_label, 3, 0)
|
||||||
|
self.layout.addWidget(log_level, 3, 1)
|
||||||
|
|
||||||
|
log_level.setCurrentIndex(log_levels.index(
|
||||||
|
log_level_to_name(options["log_level"])))
|
||||||
|
def update_log_level(index):
|
||||||
|
options["log_level"] = getattr(logging, log_level.currentText())
|
||||||
|
log_level.currentIndexChanged.connect(update_log_level)
|
||||||
|
self.log_level = log_level
|
||||||
|
|
||||||
|
if "repo_rev" in options:
|
||||||
|
repo_rev = QtWidgets.QLineEdit()
|
||||||
|
repo_rev.setPlaceholderText("current")
|
||||||
|
repo_rev_label = QtWidgets.QLabel("Revision:")
|
||||||
|
repo_rev_label.setToolTip("Experiment repository revision "
|
||||||
|
"(commit ID) to use")
|
||||||
|
self.layout.addWidget(repo_rev_label, 3, 2)
|
||||||
|
self.layout.addWidget(repo_rev, 3, 3)
|
||||||
|
|
||||||
|
if options["repo_rev"] is not None:
|
||||||
|
repo_rev.setText(options["repo_rev"])
|
||||||
|
def update_repo_rev(text):
|
||||||
|
if text:
|
||||||
|
options["repo_rev"] = text
|
||||||
|
else:
|
||||||
|
options["repo_rev"] = None
|
||||||
|
repo_rev.textChanged.connect(update_repo_rev)
|
||||||
|
self.repo_rev = repo_rev
|
||||||
|
|
||||||
|
submit = QtWidgets.QPushButton("Submit")
|
||||||
|
submit.setIcon(QtWidgets.QApplication.style().standardIcon(
|
||||||
|
QtWidgets.QStyle.SP_DialogOkButton))
|
||||||
|
submit.setToolTip("Schedule the experiment (Ctrl+Return)")
|
||||||
|
submit.setShortcut("CTRL+RETURN")
|
||||||
|
submit.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
|
||||||
|
QtWidgets.QSizePolicy.Expanding)
|
||||||
|
self.layout.addWidget(submit, 1, 4, 2, 1)
|
||||||
|
submit.clicked.connect(self.submit_clicked)
|
||||||
|
|
||||||
|
reqterm = QtWidgets.QPushButton("Terminate instances")
|
||||||
|
reqterm.setIcon(QtWidgets.QApplication.style().standardIcon(
|
||||||
|
QtWidgets.QStyle.SP_DialogCancelButton))
|
||||||
|
reqterm.setToolTip("Request termination of instances (Ctrl+Backspace)")
|
||||||
|
reqterm.setShortcut("CTRL+BACKSPACE")
|
||||||
|
reqterm.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
|
||||||
|
QtWidgets.QSizePolicy.Expanding)
|
||||||
|
self.layout.addWidget(reqterm, 3, 4)
|
||||||
|
reqterm.clicked.connect(self.reqterm_clicked)
|
||||||
|
|
||||||
|
def get_submission_options(self):
|
||||||
|
return {"log_level": 10} # TODO
|
||||||
|
|
||||||
|
def submit_clicked(self):
|
||||||
|
try:
|
||||||
|
pass # TODO
|
||||||
|
except:
|
||||||
|
# May happen when experiment has been removed
|
||||||
|
# from repository/explist
|
||||||
|
logger.error("Failed to submit '%s'",
|
||||||
|
self.expurl, exc_info=True)
|
||||||
|
|
||||||
|
def reqterm_clicked(self):
|
||||||
|
try:
|
||||||
|
pass # TODO
|
||||||
|
except:
|
||||||
|
# May happen when experiment has been removed
|
||||||
|
# from repository/explist
|
||||||
|
logger.error("Failed to request termination of instances of '%s'",
|
||||||
|
self.expurl, exc_info=True)
|
||||||
|
|
||||||
|
async def _load_hdf5_task(self, filename):
|
||||||
|
try:
|
||||||
|
with h5py.File(filename, "r") as f:
|
||||||
|
expid = f["expid"][()]
|
||||||
|
expid = pyon.decode(expid)
|
||||||
|
arguments = expid["arguments"]
|
||||||
|
except:
|
||||||
|
logger.error("Could not retrieve expid from HDF5 file",
|
||||||
|
exc_info=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.log_level.setCurrentIndex(log_levels.index(
|
||||||
|
log_level_to_name(expid["log_level"])))
|
||||||
|
if ("repo_rev" in expid and expid["repo_rev"] != "N/A" and
|
||||||
|
hasattr(self, "repo_rev")):
|
||||||
|
self.repo_rev.setText(expid["repo_rev"])
|
||||||
|
except:
|
||||||
|
logger.error("Could not set submission options from HDF5 expid",
|
||||||
|
exc_info=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
await self._recompute_arguments_task(arguments)
|
||||||
|
|
||||||
|
def closeEvent(self, event):
|
||||||
|
self.sigClosed.emit()
|
||||||
|
QtWidgets.QMdiSubWindow.closeEvent(self, event)
|
||||||
|
|
||||||
|
def save_state(self):
|
||||||
|
return {
|
||||||
|
"argeditor": self.argeditor.save_state(),
|
||||||
|
"geometry": bytes(self.saveGeometry()),
|
||||||
|
#"arguments": self.arguments,
|
||||||
|
"expurl": self.expurl
|
||||||
|
}
|
||||||
|
|
||||||
|
def restore_state(self, state):
|
||||||
|
self.argeditor.restore_state(state["argeditor"])
|
||||||
|
self.restoreGeometry(QtCore.QByteArray(state["geometry"]))
|
||||||
|
#self.arguments = state["arguments"]
|
||||||
|
self.expurl = state["expurl"]
|
||||||
|
|
||||||
|
|
||||||
|
class ExperimentsArea(QtWidgets.QMdiArea):
|
||||||
|
def __init__(self, root):
|
||||||
|
QtWidgets.QMdiArea.__init__(self)
|
||||||
|
self.pixmap = QtGui.QPixmap(os.path.join(
|
||||||
|
artiq_dir, "gui", "logo20.svg"))
|
||||||
|
self.current_dir = root
|
||||||
|
self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
|
||||||
|
self.setFocusPolicy(QtCore.Qt.StrongFocus)
|
||||||
|
|
||||||
|
action = QtWidgets.QAction("&Open experiment", self)
|
||||||
|
action.setShortcut(QtGui.QKeySequence("CTRL+o"))
|
||||||
|
action.setShortcutContext(QtCore.Qt.WidgetShortcut)
|
||||||
|
action.triggered.connect(self.open_experiment)
|
||||||
|
self.addAction(action)
|
||||||
|
|
||||||
|
self.open_experiments = []
|
||||||
|
|
||||||
|
def paintEvent(self, event):
|
||||||
|
QtWidgets.QMdiArea.paintEvent(self, event)
|
||||||
|
painter = QtGui.QPainter(self.viewport())
|
||||||
|
x = (self.width() - self.pixmap.width())//2
|
||||||
|
y = (self.height() - self.pixmap.height())//2
|
||||||
|
painter.setOpacity(0.5)
|
||||||
|
painter.drawPixmap(x, y, self.pixmap)
|
||||||
|
|
||||||
|
def save_state(self):
|
||||||
|
return {"experiments": [experiment.save_state()
|
||||||
|
for experiment in self.open_experiments]}
|
||||||
|
|
||||||
|
def restore_state(self, state):
|
||||||
|
if self.open_experiments:
|
||||||
|
raise NotImplementedError
|
||||||
|
for ex_state in state["experiments"]:
|
||||||
|
ex = self.load_experiment(ex_state["file"])
|
||||||
|
ex.restore_state(ex_state)
|
||||||
|
|
||||||
|
def open_experiment(self):
|
||||||
|
file, filter = QtWidgets.QFileDialog.getOpenFileName(
|
||||||
|
self, "Open experiment", self.current_dir, "Experiments (*.py)")
|
||||||
|
if not file:
|
||||||
|
return
|
||||||
|
logger.info("opening experiment %s", file)
|
||||||
|
self.load_experiment(file)
|
||||||
|
|
||||||
|
def load_experiment(self, expurl):
|
||||||
|
try:
|
||||||
|
dock = _ExperimentDock(expurl)
|
||||||
|
except:
|
||||||
|
logger.warning("Failed to create experiment dock for %s, "
|
||||||
|
"attempting to reset arguments", expurl,
|
||||||
|
exc_info=True)
|
||||||
|
del self.submission_arguments[expurl]
|
||||||
|
dock = _ExperimentDock(expurl)
|
||||||
|
self.open_experiments.append(dock)
|
||||||
|
self.addSubWindow(dock)
|
||||||
|
dock.show()
|
||||||
|
dock.sigClosed.connect(partial(self.on_dock_closed, expurl))
|
||||||
|
return dock
|
||||||
|
|
||||||
|
def on_dock_closed(self, expurl):
|
||||||
|
del self.open_experiments[expurl]
|
|
@ -5,7 +5,6 @@ import asyncio
|
||||||
import atexit
|
import atexit
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
from functools import partial
|
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
from quamash import QEventLoop
|
from quamash import QEventLoop
|
||||||
|
@ -14,7 +13,7 @@ from artiq import __artiq_dir__ as artiq_dir
|
||||||
from artiq.tools import verbosity_args, init_logger, atexit_register_coroutine
|
from artiq.tools import verbosity_args, init_logger, atexit_register_coroutine
|
||||||
from artiq.gui import state, applets, models
|
from artiq.gui import state, applets, models
|
||||||
from artiq.browser import datasets, files
|
from artiq.browser import datasets, files
|
||||||
from artiq.dashboard import experiments
|
from artiq.browser import experiments
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -66,69 +65,6 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||||
self.restoreState(QtCore.QByteArray(state["state"]))
|
self.restoreState(QtCore.QByteArray(state["state"]))
|
||||||
|
|
||||||
|
|
||||||
class ExperimentsArea(QtWidgets.QMdiArea):
|
|
||||||
def __init__(self, root):
|
|
||||||
QtWidgets.QMdiArea.__init__(self)
|
|
||||||
self.pixmap = QtGui.QPixmap(os.path.join(
|
|
||||||
artiq_dir, "gui", "logo20.svg"))
|
|
||||||
self.current_dir = root
|
|
||||||
self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
|
|
||||||
self.setFocusPolicy(QtCore.Qt.StrongFocus)
|
|
||||||
|
|
||||||
action = QtWidgets.QAction("&Open experiment", self)
|
|
||||||
action.setShortcut(QtGui.QKeySequence("CTRL+o"))
|
|
||||||
action.setShortcutContext(QtCore.Qt.WidgetShortcut)
|
|
||||||
action.triggered.connect(self.open_experiment)
|
|
||||||
self.addAction(action)
|
|
||||||
|
|
||||||
self.open_experiments = []
|
|
||||||
|
|
||||||
def paintEvent(self, event):
|
|
||||||
QtWidgets.QMdiArea.paintEvent(self, event)
|
|
||||||
painter = QtGui.QPainter(self.viewport())
|
|
||||||
x = (self.width() - self.pixmap.width())//2
|
|
||||||
y = (self.height() - self.pixmap.height())//2
|
|
||||||
painter.setOpacity(0.5)
|
|
||||||
painter.drawPixmap(x, y, self.pixmap)
|
|
||||||
|
|
||||||
def save_state(self):
|
|
||||||
return {"experiments": [experiment.save_state()
|
|
||||||
for experiment in self.open_experiments]}
|
|
||||||
|
|
||||||
def restore_state(self, state):
|
|
||||||
if self.open_experiments:
|
|
||||||
raise NotImplementedError
|
|
||||||
for ex_state in state["experiments"]:
|
|
||||||
ex = self.load_experiment(ex_state["file"])
|
|
||||||
ex.restore_state(ex_state)
|
|
||||||
|
|
||||||
def open_experiment(self):
|
|
||||||
file, filter = QtWidgets.QFileDialog.getOpenFileName(
|
|
||||||
self, "Open experiment", self.current_dir, "Experiments (*.py)")
|
|
||||||
if not file:
|
|
||||||
return
|
|
||||||
logger.info("opening experiment %s", file)
|
|
||||||
self.load_experiment(file)
|
|
||||||
|
|
||||||
def load_experiment(self, expurl):
|
|
||||||
try:
|
|
||||||
dock = _ExperimentDock(self, expurl)
|
|
||||||
except:
|
|
||||||
logger.warning("Failed to create experiment dock for %s, "
|
|
||||||
"attempting to reset arguments", expurl,
|
|
||||||
exc_info=True)
|
|
||||||
del self.submission_arguments[expurl]
|
|
||||||
dock = _ExperimentDock(self, expurl)
|
|
||||||
self.open_experiments.append(dock)
|
|
||||||
self.addSubWindow(dock)
|
|
||||||
dock.show()
|
|
||||||
dock.sigClosed.connect(partial(self.on_dock_closed, expurl))
|
|
||||||
return dock
|
|
||||||
|
|
||||||
def on_dock_closed(self, expurl):
|
|
||||||
del self.open_experiments[expurl]
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
# initialize application
|
# initialize application
|
||||||
args = get_argparser().parse_args()
|
args = get_argparser().parse_args()
|
||||||
|
@ -149,7 +85,7 @@ def main():
|
||||||
status_bar = QtWidgets.QStatusBar()
|
status_bar = QtWidgets.QStatusBar()
|
||||||
main_window.setStatusBar(status_bar)
|
main_window.setStatusBar(status_bar)
|
||||||
|
|
||||||
mdi_area = ExperimentsArea(args.browse_root)
|
mdi_area = experiments.ExperimentsArea(args.browse_root)
|
||||||
mdi_area.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
|
mdi_area.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
|
||||||
mdi_area.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
|
mdi_area.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
|
||||||
main_window.setCentralWidget(mdi_area)
|
main_window.setCentralWidget(mdi_area)
|
||||||
|
|
Loading…
Reference in New Issue