forked from M-Labs/artiq
Compare commits
10 Commits
8755439481
...
b2924ef9bb
Author | SHA1 | Date |
---|---|---|
Simon Renblad | b2924ef9bb | |
Simon Renblad | 91241bd5c2 | |
Simon Renblad | 1c7f2dd148 | |
Simon Renblad | 48d5b19e4f | |
Simon Renblad | 95b67d43af | |
Simon Renblad | bd276ffd68 | |
Simon Renblad | a1d23b271e | |
Simon Renblad | e9703518af | |
Simon Renblad | 16c9724035 | |
Simon Renblad | 80c3acad17 |
|
@ -2,13 +2,15 @@ import asyncio
|
||||||
import logging
|
import logging
|
||||||
import textwrap
|
import textwrap
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtWidgets
|
from PyQt5 import QtCore, QtWidgets
|
||||||
|
|
||||||
from artiq.coredevice.comm_moninj import CommMonInj, TTLOverride, TTLProbe
|
from artiq.coredevice.comm_moninj import CommMonInj, TTLOverride, TTLProbe
|
||||||
from artiq.coredevice.ad9912_reg import AD9912_SER_CONF
|
from artiq.coredevice.ad9912_reg import AD9912_SER_CONF
|
||||||
from artiq.gui.tools import LayoutWidget
|
from artiq.gui.tools import LayoutWidget, QDockWidgetCloseDetect, DoubleClickLineEdit
|
||||||
from artiq.gui.flowlayout import FlowLayout
|
from artiq.gui.dndwidgets import VDragScrollArea, DragDropFlowLayoutWidget
|
||||||
|
from artiq.gui.models import DictSyncTreeSepModel
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -149,6 +151,9 @@ class _TTLWidget(QtWidgets.QFrame):
|
||||||
def uid(self):
|
def uid(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
|
def to_model_path(self):
|
||||||
|
return "ttl/{}".format(self.title)
|
||||||
|
|
||||||
|
|
||||||
class _DDSModel:
|
class _DDSModel:
|
||||||
def __init__(self, dds_type, ref_clk, cpld=None, pll=1, clk_div=0):
|
def __init__(self, dds_type, ref_clk, cpld=None, pll=1, clk_div=0):
|
||||||
|
@ -322,6 +327,9 @@ class _DDSWidget(QtWidgets.QFrame):
|
||||||
def uid(self):
|
def uid(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
|
def to_model_path(self):
|
||||||
|
return "dds/{}".format(self.title)
|
||||||
|
|
||||||
|
|
||||||
class _DACWidget(QtWidgets.QFrame):
|
class _DACWidget(QtWidgets.QFrame):
|
||||||
def __init__(self, dm, spi_channel, channel, title):
|
def __init__(self, dm, spi_channel, channel, title):
|
||||||
|
@ -363,6 +371,9 @@ class _DACWidget(QtWidgets.QFrame):
|
||||||
def uid(self):
|
def uid(self):
|
||||||
return (self.title, self.channel)
|
return (self.title, self.channel)
|
||||||
|
|
||||||
|
def to_model_path(self):
|
||||||
|
return "dac/{} ch{}".format(self.title, self.channel)
|
||||||
|
|
||||||
|
|
||||||
_WidgetDesc = namedtuple("_WidgetDesc", "uid comment cls arguments")
|
_WidgetDesc = namedtuple("_WidgetDesc", "uid comment cls arguments")
|
||||||
|
|
||||||
|
@ -735,34 +746,223 @@ class _DeviceManager:
|
||||||
await self.mi_connection.close()
|
await self.mi_connection.close()
|
||||||
|
|
||||||
|
|
||||||
class _MonInjDock(QtWidgets.QDockWidget):
|
class Model(DictSyncTreeSepModel):
|
||||||
def __init__(self, name):
|
def __init__(self, init):
|
||||||
QtWidgets.QDockWidget.__init__(self, name)
|
DictSyncTreeSepModel.__init__(self, "/", ["Channels"], init)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
for k in self.backing_store:
|
||||||
|
self._del_item(self, k.split(self.separator))
|
||||||
|
self.backing_store.clear()
|
||||||
|
|
||||||
|
def update(self, d):
|
||||||
|
for k, v in d.items():
|
||||||
|
self[v.to_model_path()] = v
|
||||||
|
|
||||||
|
|
||||||
|
class _AddChannelDialog(QtWidgets.QDialog):
|
||||||
|
def __init__(self, parent, model):
|
||||||
|
QtWidgets.QDialog.__init__(self, parent=parent)
|
||||||
|
self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
|
||||||
|
self.setWindowTitle("Add channels")
|
||||||
|
|
||||||
|
layout = QtWidgets.QVBoxLayout()
|
||||||
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
self._model = model
|
||||||
|
self._tree_view = QtWidgets.QTreeView()
|
||||||
|
self._tree_view.setHeaderHidden(True)
|
||||||
|
self._tree_view.setSelectionBehavior(
|
||||||
|
QtWidgets.QAbstractItemView.SelectItems)
|
||||||
|
self._tree_view.setSelectionMode(
|
||||||
|
QtWidgets.QAbstractItemView.ExtendedSelection)
|
||||||
|
self._tree_view.setModel(self._model)
|
||||||
|
layout.addWidget(self._tree_view)
|
||||||
|
|
||||||
|
self._button_box = QtWidgets.QDialogButtonBox(
|
||||||
|
QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel
|
||||||
|
)
|
||||||
|
self._button_box.setCenterButtons(True)
|
||||||
|
self._button_box.accepted.connect(self.add_channels)
|
||||||
|
self._button_box.rejected.connect(self.reject)
|
||||||
|
layout.addWidget(self._button_box)
|
||||||
|
|
||||||
|
def add_channels(self):
|
||||||
|
selection = self._tree_view.selectedIndexes()
|
||||||
|
channels = []
|
||||||
|
for select in selection:
|
||||||
|
key = self._model.index_to_key(select)
|
||||||
|
if key is not None:
|
||||||
|
channels.append(self._model[key].ref)
|
||||||
|
self.channels = channels
|
||||||
|
self.accept()
|
||||||
|
|
||||||
|
|
||||||
|
class _MonInjDock(QDockWidgetCloseDetect):
|
||||||
|
def __init__(self, name, manager):
|
||||||
|
QtWidgets.QDockWidget.__init__(self, "MonInj")
|
||||||
self.setObjectName(name)
|
self.setObjectName(name)
|
||||||
self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable |
|
self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable |
|
||||||
QtWidgets.QDockWidget.DockWidgetFloatable)
|
QtWidgets.QDockWidget.DockWidgetFloatable)
|
||||||
|
grid = LayoutWidget()
|
||||||
|
self.setWidget(grid)
|
||||||
|
self.manager = manager
|
||||||
|
self.widget_uids = None
|
||||||
|
|
||||||
|
newdock = QtWidgets.QToolButton()
|
||||||
|
newdock.setToolTip("Create new moninj dock")
|
||||||
|
newdock.setIcon(QtWidgets.QApplication.style().standardIcon(
|
||||||
|
QtWidgets.QStyle.SP_FileDialogNewFolder))
|
||||||
|
newdock.clicked.connect(lambda: self.manager.create_new_dock())
|
||||||
|
grid.addWidget(newdock, 0, 0)
|
||||||
|
|
||||||
|
self.channel_dialog = _AddChannelDialog(self, self.manager.channel_model)
|
||||||
|
self.channel_dialog.accepted.connect(self.add_channels)
|
||||||
|
|
||||||
|
dialog_btn = QtWidgets.QToolButton()
|
||||||
|
dialog_btn.setToolTip("Add channels")
|
||||||
|
dialog_btn.setIcon(
|
||||||
|
QtWidgets.QApplication.style().standardIcon(
|
||||||
|
QtWidgets.QStyle.SP_FileDialogListView))
|
||||||
|
dialog_btn.clicked.connect(self.channel_dialog.open)
|
||||||
|
grid.addWidget(dialog_btn, 0, 1)
|
||||||
|
|
||||||
|
self.label = DoubleClickLineEdit(name)
|
||||||
|
grid.addWidget(self.label, 0, 2)
|
||||||
|
|
||||||
|
scroll_area = VDragScrollArea(self)
|
||||||
|
grid.addWidget(scroll_area, 1, 0, 1, 10)
|
||||||
|
self.flow = DragDropFlowLayoutWidget()
|
||||||
|
scroll_area.setWidgetResizable(True)
|
||||||
|
scroll_area.setWidget(self.flow)
|
||||||
|
self.flow.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||||
|
self.flow.customContextMenuRequested.connect(self.custom_context_menu)
|
||||||
|
|
||||||
|
def custom_context_menu(self, pos):
|
||||||
|
index = self.flow._get_index(pos)
|
||||||
|
if index == -1:
|
||||||
|
return
|
||||||
|
menu = QtWidgets.QMenu()
|
||||||
|
delete_action = QtWidgets.QAction("Delete widget", menu)
|
||||||
|
delete_action.triggered.connect(partial(self.delete_widget, index))
|
||||||
|
menu.addAction(delete_action)
|
||||||
|
menu.exec_(self.flow.mapToGlobal(pos))
|
||||||
|
|
||||||
|
def delete_widget(self, index, checked):
|
||||||
|
widget = self.flow.itemAt(index).widget()
|
||||||
|
widget.hide()
|
||||||
|
self.flow.layout.takeAt(index)
|
||||||
|
|
||||||
|
def add_channels(self):
|
||||||
|
channels = self.channel_dialog.channels
|
||||||
|
self.layout_widgets(channels)
|
||||||
|
|
||||||
def layout_widgets(self, widgets):
|
def layout_widgets(self, widgets):
|
||||||
scroll_area = QtWidgets.QScrollArea()
|
|
||||||
self.setWidget(scroll_area)
|
|
||||||
|
|
||||||
grid = FlowLayout()
|
|
||||||
grid_widget = QtWidgets.QWidget()
|
|
||||||
grid_widget.setLayout(grid)
|
|
||||||
|
|
||||||
for widget in sorted(widgets, key=lambda w: w.sort_key()):
|
for widget in sorted(widgets, key=lambda w: w.sort_key()):
|
||||||
grid.addWidget(widget)
|
widget.show()
|
||||||
|
self.flow.addWidget(widget)
|
||||||
|
|
||||||
scroll_area.setWidgetResizable(True)
|
def restore_widgets(self):
|
||||||
scroll_area.setWidget(grid_widget)
|
if self.widget_uids is not None:
|
||||||
|
widgets_by_uid = self.manager.dm.widgets_by_uid
|
||||||
|
widgets = list()
|
||||||
|
for uid in self.widget_uids:
|
||||||
|
if uid in widgets_by_uid:
|
||||||
|
widgets.append(widgets_by_uid[uid])
|
||||||
|
else:
|
||||||
|
logger.warning("removing moninj widget {}".format(uid))
|
||||||
|
self.layout_widgets(widgets)
|
||||||
|
self.widget_uids = None
|
||||||
|
|
||||||
|
def _save_widget_uids(self):
|
||||||
|
uids = []
|
||||||
|
for i in range(self.flow.count()):
|
||||||
|
uids.append(self.flow.itemAt(i).widget().uid())
|
||||||
|
return uids
|
||||||
|
|
||||||
|
def save_state(self):
|
||||||
|
return {
|
||||||
|
"dock_label": self.label.text(),
|
||||||
|
"widget_uids": self._save_widget_uids()
|
||||||
|
}
|
||||||
|
|
||||||
|
def restore_state(self, state):
|
||||||
|
try:
|
||||||
|
label = state["dock_label"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.label._text = label
|
||||||
|
self.label.setText(label)
|
||||||
|
try:
|
||||||
|
self.widget_uids = state["widget_uids"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class MonInj:
|
class MonInj:
|
||||||
def __init__(self, schedule_ctl):
|
def __init__(self, schedule_ctl, main_window):
|
||||||
self.dock = _MonInjDock("MonInj")
|
self.docks = dict()
|
||||||
|
self.main_window = main_window
|
||||||
self.dm = _DeviceManager(schedule_ctl)
|
self.dm = _DeviceManager(schedule_ctl)
|
||||||
self.dm.channels_cb = lambda: self.dock.layout_widgets(self.dm.widgets_by_uid.values())
|
self.dm.channels_cb = self.add_channels
|
||||||
|
self.channel_model = Model({})
|
||||||
|
|
||||||
|
def add_channels(self):
|
||||||
|
self.channel_model.clear()
|
||||||
|
self.channel_model.update(self.dm.widgets_by_uid)
|
||||||
|
for dock in self.docks.values():
|
||||||
|
dock.restore_widgets()
|
||||||
|
|
||||||
|
def create_new_dock(self, add_to_area=True):
|
||||||
|
n = 0
|
||||||
|
name = "moninj0"
|
||||||
|
while name in self.docks:
|
||||||
|
n += 1
|
||||||
|
name = "moninj" + str(n)
|
||||||
|
|
||||||
|
dock = _MonInjDock(name, self)
|
||||||
|
self.docks[name] = dock
|
||||||
|
if add_to_area:
|
||||||
|
self.main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, dock)
|
||||||
|
dock.setFloating(True)
|
||||||
|
dock.sigClosed.connect(partial(self.on_dock_closed, name))
|
||||||
|
self.update_closable()
|
||||||
|
return dock
|
||||||
|
|
||||||
|
def on_dock_closed(self, name):
|
||||||
|
dock = self.docks[name]
|
||||||
|
dock.hide() # dock may be parent, only delete on exit
|
||||||
|
del self.docks[name]
|
||||||
|
self.update_closable()
|
||||||
|
|
||||||
|
def update_closable(self):
|
||||||
|
flags = (QtWidgets.QDockWidget.DockWidgetMovable |
|
||||||
|
QtWidgets.QDockWidget.DockWidgetFloatable)
|
||||||
|
if len(self.docks) > 1:
|
||||||
|
flags |= QtWidgets.QDockWidget.DockWidgetClosable
|
||||||
|
for dock in self.docks.values():
|
||||||
|
dock.setFeatures(flags)
|
||||||
|
|
||||||
|
def first_moninj_dock(self):
|
||||||
|
if self.docks:
|
||||||
|
return None
|
||||||
|
dock = self.create_new_dock(False)
|
||||||
|
return dock
|
||||||
|
|
||||||
|
def save_state(self):
|
||||||
|
return {name: dock.save_state() for name, dock in self.docks.items()}
|
||||||
|
|
||||||
|
def restore_state(self, state):
|
||||||
|
if self.docks:
|
||||||
|
raise NotImplementedError
|
||||||
|
for name, dock_state in state.items():
|
||||||
|
dock = _MonInjDock(name, self)
|
||||||
|
self.docks[name] = dock
|
||||||
|
dock.restore_state(dock_state)
|
||||||
|
self.main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, dock)
|
||||||
|
dock.sigClosed.connect(partial(self.on_dock_closed, name))
|
||||||
|
self.update_closable()
|
||||||
|
|
||||||
async def stop(self):
|
async def stop(self):
|
||||||
if self.dm is not None:
|
if self.dm is not None:
|
||||||
|
|
|
@ -226,7 +226,8 @@ def main():
|
||||||
smgr.register(d_applets)
|
smgr.register(d_applets)
|
||||||
broadcast_clients["ccb"].notify_cbs.append(d_applets.ccb_notify)
|
broadcast_clients["ccb"].notify_cbs.append(d_applets.ccb_notify)
|
||||||
|
|
||||||
d_ttl_dds = moninj.MonInj(rpc_clients["schedule"])
|
d_ttl_dds = moninj.MonInj(rpc_clients["schedule"], main_window)
|
||||||
|
smgr.register(d_ttl_dds)
|
||||||
atexit_register_coroutine(d_ttl_dds.stop, loop=loop)
|
atexit_register_coroutine(d_ttl_dds.stop, loop=loop)
|
||||||
|
|
||||||
d_waveform = waveform.WaveformDock(
|
d_waveform = waveform.WaveformDock(
|
||||||
|
@ -236,14 +237,6 @@ def main():
|
||||||
)
|
)
|
||||||
atexit_register_coroutine(d_waveform.stop, loop=loop)
|
atexit_register_coroutine(d_waveform.stop, loop=loop)
|
||||||
|
|
||||||
def init_cbs(ddb):
|
|
||||||
d_ttl_dds.dm.init_ddb(ddb)
|
|
||||||
d_waveform.init_ddb(ddb)
|
|
||||||
return ddb
|
|
||||||
devices_sub = Subscriber("devices", init_cbs, [d_ttl_dds.dm.notify_ddb, d_waveform.notify_ddb])
|
|
||||||
loop.run_until_complete(devices_sub.connect(args.server, args.port_notify))
|
|
||||||
atexit_register_coroutine(devices_sub.close, loop=loop)
|
|
||||||
|
|
||||||
d_interactive_args = interactive_args.InteractiveArgsDock(
|
d_interactive_args = interactive_args.InteractiveArgsDock(
|
||||||
sub_clients["interactive_args"],
|
sub_clients["interactive_args"],
|
||||||
rpc_clients["interactive_arg_db"]
|
rpc_clients["interactive_arg_db"]
|
||||||
|
@ -261,7 +254,7 @@ def main():
|
||||||
# lay out docks
|
# lay out docks
|
||||||
right_docks = [
|
right_docks = [
|
||||||
d_explorer, d_shortcuts,
|
d_explorer, d_shortcuts,
|
||||||
d_ttl_dds.dock, d_datasets, d_applets,
|
d_datasets, d_applets,
|
||||||
d_waveform, d_interactive_args
|
d_waveform, d_interactive_args
|
||||||
]
|
]
|
||||||
main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, right_docks[0])
|
main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, right_docks[0])
|
||||||
|
@ -276,16 +269,25 @@ def main():
|
||||||
# QDockWidgets fail to be embedded.
|
# QDockWidgets fail to be embedded.
|
||||||
main_window.show()
|
main_window.show()
|
||||||
smgr.load()
|
smgr.load()
|
||||||
|
|
||||||
|
def init_cbs(ddb):
|
||||||
|
d_ttl_dds.dm.init_ddb(ddb)
|
||||||
|
d_waveform.init_ddb(ddb)
|
||||||
|
return ddb
|
||||||
|
devices_sub = Subscriber("devices", init_cbs, [d_ttl_dds.dm.notify_ddb, d_waveform.notify_ddb])
|
||||||
|
loop.run_until_complete(devices_sub.connect(args.server, args.port_notify))
|
||||||
|
atexit_register_coroutine(devices_sub.close, loop=loop)
|
||||||
|
|
||||||
smgr.start(loop=loop)
|
smgr.start(loop=loop)
|
||||||
atexit_register_coroutine(smgr.stop, loop=loop)
|
atexit_register_coroutine(smgr.stop, loop=loop)
|
||||||
|
|
||||||
# work around for https://github.com/m-labs/artiq/issues/1307
|
|
||||||
d_ttl_dds.dock.show()
|
|
||||||
|
|
||||||
# create first log dock if not already in state
|
# create first log dock if not already in state
|
||||||
d_log0 = logmgr.first_log_dock()
|
d_log0 = logmgr.first_log_dock()
|
||||||
if d_log0 is not None:
|
if d_log0 is not None:
|
||||||
main_window.tabifyDockWidget(d_schedule, d_log0)
|
main_window.tabifyDockWidget(d_schedule, d_log0)
|
||||||
|
d_moninj0 = d_ttl_dds.first_moninj_dock()
|
||||||
|
if d_moninj0 is not None:
|
||||||
|
main_window.tabifyDockWidget(right_docks[-1], d_moninj0)
|
||||||
|
|
||||||
if server_name is not None:
|
if server_name is not None:
|
||||||
server_description = server_name + " ({})".format(args.server)
|
server_description = server_name + " ({})".format(args.server)
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
from PyQt5 import QtCore, QtWidgets
|
from PyQt5 import QtCore, QtWidgets, QtGui
|
||||||
|
|
||||||
|
from artiq.gui.flowlayout import FlowLayout
|
||||||
|
|
||||||
|
|
||||||
class VDragDropSplitter(QtWidgets.QSplitter):
|
class VDragDropSplitter(QtWidgets.QSplitter):
|
||||||
|
@ -98,3 +100,65 @@ class VDragScrollArea(QtWidgets.QScrollArea):
|
||||||
dy = self._direction * self._speed
|
dy = self._direction * self._speed
|
||||||
new_val = min(max_, max(min_, val + dy))
|
new_val = min(max_, max(min_, val + dy))
|
||||||
self.verticalScrollBar().setValue(new_val)
|
self.verticalScrollBar().setValue(new_val)
|
||||||
|
|
||||||
|
|
||||||
|
# Widget with FlowLayout and drag and drop support between widgets
|
||||||
|
class DragDropFlowLayoutWidget(QtWidgets.QWidget):
|
||||||
|
def __init__(self):
|
||||||
|
QtWidgets.QWidget.__init__(self)
|
||||||
|
self.layout = FlowLayout()
|
||||||
|
self.setLayout(self.layout)
|
||||||
|
self.setAcceptDrops(True)
|
||||||
|
|
||||||
|
def _get_index(self, pos):
|
||||||
|
for i in range(self.layout.count()):
|
||||||
|
if self.itemAt(i).geometry().contains(pos):
|
||||||
|
return i
|
||||||
|
return -1
|
||||||
|
|
||||||
|
def mousePressEvent(self, event):
|
||||||
|
if event.buttons() == QtCore.Qt.LeftButton \
|
||||||
|
and event.modifiers() == QtCore.Qt.ShiftModifier:
|
||||||
|
index = self._get_index(event.pos())
|
||||||
|
if index == -1:
|
||||||
|
return
|
||||||
|
drag = QtGui.QDrag(self)
|
||||||
|
mime = QtCore.QMimeData()
|
||||||
|
mime.setData("index", str(index).encode())
|
||||||
|
drag.setMimeData(mime)
|
||||||
|
pixmapi = QtWidgets.QApplication.style().standardIcon(
|
||||||
|
QtWidgets.QStyle.SP_FileIcon)
|
||||||
|
drag.setPixmap(pixmapi.pixmap(32))
|
||||||
|
drag.exec_(QtCore.Qt.MoveAction)
|
||||||
|
event.accept()
|
||||||
|
|
||||||
|
def dragEnterEvent(self, event):
|
||||||
|
event.accept()
|
||||||
|
|
||||||
|
def dropEvent(self, event):
|
||||||
|
index = self._get_index(event.pos())
|
||||||
|
source_layout = event.source()
|
||||||
|
source_index = int(bytes(event.mimeData().data("index")).decode())
|
||||||
|
if source_layout == self:
|
||||||
|
if index == source_index:
|
||||||
|
return
|
||||||
|
widget = self.layout.itemAt(source_index).widget()
|
||||||
|
self.layout.removeWidget(widget)
|
||||||
|
self.layout.addWidget(widget)
|
||||||
|
self.layout.itemList.insert(index, self.layout.itemList.pop())
|
||||||
|
else:
|
||||||
|
widget = source_layout.layout.itemAt(source_index).widget()
|
||||||
|
source_layout.layout.removeWidget(widget)
|
||||||
|
self.layout.addWidget(widget)
|
||||||
|
if index != -1:
|
||||||
|
self.layout.itemList.insert(index, self.layout.itemList.pop())
|
||||||
|
event.accept()
|
||||||
|
|
||||||
|
def addWidget(self, widget):
|
||||||
|
self.layout.addWidget(widget)
|
||||||
|
|
||||||
|
def count(self):
|
||||||
|
return self.layout.count()
|
||||||
|
|
||||||
|
def itemAt(self, i):
|
||||||
|
return self.layout.itemAt(i)
|
||||||
|
|
|
@ -4,6 +4,40 @@ import logging
|
||||||
from PyQt5 import QtCore, QtWidgets
|
from PyQt5 import QtCore, QtWidgets
|
||||||
|
|
||||||
|
|
||||||
|
class DoubleClickLineEdit(QtWidgets.QLineEdit):
|
||||||
|
finished = QtCore.pyqtSignal()
|
||||||
|
|
||||||
|
def __init__(self, init):
|
||||||
|
QtWidgets.QLineEdit.__init__(self, init)
|
||||||
|
self.setFrame(False)
|
||||||
|
self.setReadOnly(True)
|
||||||
|
self.returnPressed.connect(self._return_pressed)
|
||||||
|
self.editingFinished.connect(self._editing_finished)
|
||||||
|
self._text = init
|
||||||
|
|
||||||
|
def mouseDoubleClickEvent(self, event):
|
||||||
|
if self.isReadOnly():
|
||||||
|
self.setReadOnly(False)
|
||||||
|
self.setFrame(True)
|
||||||
|
QtWidgets.QLineEdit.mouseDoubleClickEvent(self, event)
|
||||||
|
|
||||||
|
def _return_pressed(self):
|
||||||
|
self._text = self.text()
|
||||||
|
|
||||||
|
def _editing_finished(self):
|
||||||
|
self.setReadOnly(True)
|
||||||
|
self.setFrame(False)
|
||||||
|
self.setText(self._text)
|
||||||
|
self.finished.emit()
|
||||||
|
|
||||||
|
def keyPressEvent(self, event):
|
||||||
|
key = event.key()
|
||||||
|
if key == QtCore.Qt.Key_Escape and not self.isReadOnly():
|
||||||
|
self.editingFinished.emit()
|
||||||
|
else:
|
||||||
|
QtWidgets.QLineEdit.keyPressEvent(self, event)
|
||||||
|
|
||||||
|
|
||||||
def log_level_to_name(level):
|
def log_level_to_name(level):
|
||||||
if level >= logging.CRITICAL:
|
if level >= logging.CRITICAL:
|
||||||
return "CRITICAL"
|
return "CRITICAL"
|
||||||
|
|
Loading…
Reference in New Issue