leftbar.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681
  1. # -*- coding: utf-8 -*-
  2. from PyQt4 import QtGui, QtCore
  3. import kcgwidget as kcgw
  4. from callbacks import callbacks
  5. from plotWidget import PlotWidget
  6. from backend.board import available_boards
  7. from backend import io
  8. import storage
  9. from ..widgets import acquiresettings as acqs
  10. from groupedelements import Elements
  11. import backendinterface as bif
  12. import log
  13. tr = kcgw.tr
  14. from .. import config
  15. LIVE = 1
  16. FILE = 2
  17. class BoardSpecificInformation(kcgw.KCGWidgets):
  18. def __init__(self, board_id):
  19. """
  20. This creates the information part for board with id board_id.
  21. This is used to be able to create a variable number of board information areas.
  22. :param board_id: the id of the board
  23. """
  24. super(BoardSpecificInformation, self).__init__()
  25. self.temperature = self.createLabel(tr("Label", "Temp:"))
  26. def update_temp():
  27. if bif.bk_get_board_status(board_id, 'board_connected'):
  28. self.temperature.setText("Temp: " + bif.bk_get_temperature(board_id) + u" °C")
  29. try:
  30. update_temp()
  31. except Exception: # TODO: Find correct exception to catch
  32. print "cannot get temperature"
  33. self.timer = QtCore.QTimer()
  34. self.timer.timeout.connect(update_temp)
  35. self.timer.start(30000)
  36. self.skipturns = self.createLabel(
  37. tr("Label", "O_skip:") + " " + str(bif.bk_get_config(board_id, 'orbits_skip')),
  38. tooltip=tr("Tooltip", "Skipped orbits\nKlick to open acquire settings"),
  39. click=True,
  40. connect=lambda: acqs.add_acquire_settings_widget(board_id=board_id)
  41. )
  42. bif.bk_get_board_config(board_id).observe(self.skipturns,
  43. lambda x: self.skipturns.setText(tr("Label", "O_skip:")+" "+str(x)),
  44. 'orbits_skip')
  45. self.orbitsobserved = self.createLabel(
  46. tr("Label", "O_obs:") + " " + str(bif.bk_get_config(board_id, 'orbits_observe')),
  47. tooltip=tr("Tooltip", "Number of observed Orbits\nKlick to open acquire settings"),
  48. click=True,
  49. connect=lambda: acqs.add_acquire_settings_widget(board_id=board_id)
  50. )
  51. bif.bk_get_board_config(board_id).observe(
  52. self.orbitsobserved,
  53. lambda x: self.orbitsobserved.setText(tr("Label", "O_obs:") + " " + str(x)), 'orbits_observe')
  54. self.orbitscount = self.createLabel(
  55. tr("Label", "Count:") + " " + str(bif.bk_get_config(board_id, 'acquisition_count')),
  56. tooltip=tr("Tooltip", "Number of acquisitions\nKlick to open acquire settings"),
  57. click=True,
  58. connect=lambda: acqs.add_acquire_settings_widget(board_id=board_id)
  59. )
  60. bif.bk_get_board_config(board_id).observe(
  61. self.orbitscount,
  62. lambda x: self.orbitscount.setText(tr("Label", "Count:") + " " + str(x)), 'acquisition_count')
  63. self.orbitswait = self.createLabel(
  64. tr("Label", "T_wait:") + " " + str(bif.bk_get_config(board_id, 'orbits_wait_time')),
  65. tooltip=tr("Tooltip", "Time in seconds to wait between acquisitions\nKlick to open acquire settings"),
  66. click=True,
  67. connect=lambda: acqs.add_acquire_settings_widget(board_id=board_id)
  68. )
  69. bif.bk_get_board_config(board_id).observe(
  70. self.orbitswait,
  71. lambda x: self.orbitswait.setText(tr("Label", "T_wait:") + " " + str(x)), 'orbits_wait_time')
  72. self.boardSpecificLayout = QtGui.QVBoxLayout()
  73. self.setLayout(self.boardSpecificLayout)
  74. self.layout = QtGui.QGridLayout() # TODO: add text for build_spectrograms ? was???
  75. self.layout.addWidget(self.temperature, 0, 0)
  76. self.layout.addWidget(self.skipturns, 1, 1)
  77. self.layout.addWidget(self.orbitsobserved, 1, 0)
  78. self.layout.addWidget(self.orbitscount, 2, 0)
  79. self.layout.addWidget(self.orbitswait, 2, 1)
  80. self.boardSpecificLayout.addLayout(self.layout)
  81. class AcquisitionAndInfo(kcgw.KCGWidgets):
  82. """
  83. Widget to show below the plot list.
  84. Show certain information.
  85. Provide Start Acquisition button and settings.
  86. """
  87. # consistency_updated = QtCore.pyqtSignal(bool)
  88. # acquisition_started_signal = QtCore.pyqtSignal(int)
  89. def __init__(self):
  90. super(AcquisitionAndInfo, self).__init__()
  91. self.layout = QtGui.QVBoxLayout()
  92. self.layout.setContentsMargins(0, 0, 0, 0)
  93. self.setLayout(self.layout)
  94. if available_boards.multi_board:
  95. self.scroll_area = QtGui.QScrollArea()
  96. self.layout.addWidget(self.scroll_area)
  97. self.information_box = kcgw.AccordionWidget()
  98. for brd in available_boards.board_ids:
  99. self.information_box.addItem(BoardSpecificInformation(brd),
  100. available_boards.get_board_name_from_id(brd))
  101. self.scroll_area.setWidget(self.information_box)
  102. self.scroll_area.setWidgetResizable(True)
  103. self.information_box.hideAll()
  104. self.information_box.expandIndex(0)
  105. else:
  106. self.layout.addWidget(BoardSpecificInformation(available_boards.board_ids[0]))
  107. # -----[ Constistency ]-----------
  108. self.consistency = QtGui.QHBoxLayout()
  109. self.layout.addLayout(self.consistency)
  110. self.consistency_label = self.createLabel("Consistency:")
  111. self.consistency_content = self.createLabel(u"●")
  112. self.consistency_content.setStyleSheet("color: lightgrey;")
  113. self.consistency.addWidget(self.consistency_label)
  114. self.consistency.addWidget(self.consistency_content)
  115. # -------[ acquire ]--------------
  116. self.acquire = self.createButton(tr("Button", "Start Acquisition"),
  117. icon=QtGui.QIcon(config.install_path + config.startIcon),
  118. connect=self.toggle_acquisition)
  119. if not available_boards.multi_board:
  120. Elements.addItem('acquireTrigger_{}'.format(available_boards.board_ids[0]), self.acquire)
  121. if available_boards.multi_board:
  122. self.list_to_acquire = {
  123. i: self.createCheckbox(
  124. str(i),
  125. tooltip=str(tr("Tooltip",
  126. "Check this checkbox if you want to acquire with board {board}")
  127. ). format(board=i),
  128. connect=self.update_acq_tickboxes) for i in available_boards}
  129. self.list_of_active_boards = {i: False for i in available_boards}
  130. self.acq_list_layout = QtGui.QHBoxLayout()
  131. for b_id, cb in self.list_to_acquire.iteritems():
  132. self.acq_list_layout.addWidget(cb)
  133. Elements.addItem('acquireTrigger_{}'.format(b_id), cb)
  134. self.acquisition_stopped(b_id)
  135. # -----[ log ]-------
  136. self.log = self.createButton(tr("Button", "Log"),
  137. icon=QtGui.QIcon(config.install_path + config.logIcon),
  138. connect=lambda: log.log(additional="Manual Log"),
  139. tooltip="Rightclick to Configure")
  140. self.log_w_comment = self.createButton(tr("Button", "Log"),
  141. icon=QtGui.QIcon(config.install_path + config.logCommentIcon),
  142. connect=self.do_log_w_comment,
  143. tooltip="Log with Comment\nRightclick to Configure")
  144. self.log.contextMenuEvent = self.log_configure_context_menu
  145. self.log_w_comment.contextMenuEvent = self.log_configure_context_menu
  146. # self.layout.addLayout(self.consistency, 0, 0) # TODO: für welches board? zum board???
  147. if available_boards.multi_board:
  148. self.layout.addLayout(self.acq_list_layout)
  149. self.layout.addWidget(self.acquire)
  150. self.logLayout = QtGui.QHBoxLayout()
  151. self.logLayout.addWidget(self.log)
  152. self.logLayout.addWidget(self.log_w_comment)
  153. self.layout.addLayout(self.logLayout)
  154. callbacks.add_callback('update_consistency', self.update_consistency)
  155. callbacks.add_callback('acquisition_started', self.acquisition_started)
  156. callbacks.add_callback('acquisition_stopped', self.acquisition_stopped)
  157. def toggle_acquisition(self):
  158. """
  159. Turn acquisition on/off. This gets the information about which board to use by checking state of the checkboxes.
  160. :return:
  161. """
  162. if not available_boards.multi_board:
  163. bif.bk_acquire(available_boards.board_ids[0])
  164. return
  165. for b_id, cb in self.list_to_acquire.iteritems():
  166. if cb.isChecked():
  167. bif.bk_acquire(b_id)
  168. for cb in self.list_to_acquire.values():
  169. cb.setChecked(False)
  170. self.update_acq_tickboxes()
  171. def update_acq_tickboxes(self):
  172. """
  173. If at least one checkbox is checked, set all checkboxes with other state (acquiring or not)
  174. to disabled. This prevents the user to start with one board and stop with the other simultaneously.
  175. TODO: add option in settings to allow the now eliminated behaviour
  176. """
  177. active = False
  178. inactive = False
  179. for b_id, cb in self.list_to_acquire.iteritems():
  180. if cb.isChecked(): # search for first checked box
  181. if self.list_of_active_boards[b_id] is True: # if board is acquiring
  182. active = True
  183. break
  184. else:
  185. inactive = True
  186. break
  187. if active: # if checked box is for acquiring boards (meaning to stop them)
  188. for b_id, state in self.list_of_active_boards.iteritems():
  189. if Elements.isEnabled('acquireTrigger_{}'.format(b_id)):
  190. self.list_to_acquire[b_id].setEnabled(state) # set all active boards to enabled
  191. self.acquire.setIcon(QtGui.QIcon(config.install_path + config.stopIcon))
  192. self.acquire.setText(tr("Button", "Stop Acquisition"))
  193. elif inactive:
  194. for b_id, state in self.list_of_active_boards.iteritems():
  195. if Elements.isEnabled('acquireTrigger_{}'.format(b_id)) or bif.bk_get_board_status(b_id, 'acquisition'):
  196. self.list_to_acquire[b_id].setEnabled(not state) # set all active boards to not enabled
  197. self.acquire.setIcon(QtGui.QIcon(config.install_path + config.startIcon))
  198. self.acquire.setText(tr("Button", "Start Acquisition"))
  199. else: # if no board is selected
  200. for b_id, cb in self.list_to_acquire.items():
  201. if Elements.isEnabled('acquireTrigger_{}'.format(b_id)):
  202. cb.setEnabled(True)
  203. self.acquire.setIcon(QtGui.QIcon(config.install_path + config.startIcon))
  204. self.acquire.setText(tr("Button", "Start Acquisition"))
  205. def acquisition_stopped(self, board_id):
  206. """
  207. This is called when a acquisition is stopped.
  208. (has to be registered in Callbacks under 'acquisition_stopped'
  209. :param board_id: id of the board
  210. """
  211. if not available_boards.multi_board:
  212. for elem in Elements.getElements("acquireTrigger_{}".format(board_id)):
  213. if isinstance(elem, QtGui.QShortcut) or isinstance(elem, QtGui.QCheckBox):
  214. continue
  215. elem.setIcon(QtGui.QIcon(config.install_path + config.startIcon))
  216. elem.setText(tr("Button", "Start Acquisition"))
  217. return
  218. self.list_to_acquire[board_id].setStyleSheet('')
  219. self.list_of_active_boards[board_id] = False
  220. self.update_acq_tickboxes()
  221. def acquisition_started(self, board_id):
  222. """
  223. This is called when a acquisition is started.
  224. (has to be registered in Callbacks under 'acquisition_started'
  225. :param board_id: id of the board
  226. """
  227. if not available_boards.multi_board:
  228. for elem in Elements.getElements("acquireTrigger_{}".format(board_id)):
  229. if isinstance(elem, QtGui.QShortcut) or isinstance(elem, QtGui.QCheckBox):
  230. continue
  231. elem.setIcon(QtGui.QIcon(config.install_path + config.stopIcon))
  232. elem.setText(tr("Button", "Stop Acquisition"))
  233. return
  234. self.list_to_acquire[board_id].setStyleSheet("background-color: lightgreen;")
  235. self.list_of_active_boards[board_id] = True
  236. self.update_acq_tickboxes()
  237. def do_log_w_comment(self):
  238. """
  239. Function to handle logging with comments
  240. """
  241. # text, ok = QtGui.QInputDialog.getText(self, tr("Heading", 'Log Comment'), tr("Label", 'Log Comment:'))
  242. text, ok = kcgw.MultilineInputDialog().get_text(tr("Heading", "Log Comment"), tr("Label", "Log Comment:"))
  243. if ok:
  244. log.log(additional=text)
  245. def log_configure_context_menu(self, event):
  246. """
  247. Function that creates the context menu for the log buttons
  248. :param event: (QEvent) the event
  249. :return: -
  250. """
  251. pos = event.globalPos()
  252. def configure_log():
  253. c = log.ConfigureLog(self)
  254. c.exec_()
  255. del c
  256. if pos is not None:
  257. menu = QtGui.QMenu(self)
  258. configure = menu.addAction(tr("Button", "Configure Log"))
  259. configure.triggered.connect(configure_log)
  260. menu.popup(pos)
  261. event.accept()
  262. def update_consistency(self, status):
  263. """
  264. Set consistency indicator
  265. :param status: True if consistent, False if inconsistent, None is nothing
  266. :return: -
  267. """
  268. if status is True:
  269. self.consistency_content.setStyleSheet("color: green;")
  270. elif status is False:
  271. self.consistency_content.setStyleSheet("color: red;")
  272. else:
  273. self.consistency_content.setStyleSheet("color: lightgrey;")
  274. class LeftBar(kcgw.KCGWidgets):
  275. """
  276. Left bar in the main view of the gui.
  277. Shows plot list and acquisition and info panel
  278. """
  279. # general notes:
  280. # uid = unique_id
  281. # usid = unique_sub_id
  282. # upid = unique_parent_id
  283. possiblePlots = ["Heatmap", "FFT", "Trains", "Combined", "Compare"]
  284. def __init__(self, parent):
  285. super(LeftBar, self).__init__("LeftBar")
  286. self.parent = parent
  287. self.openData = {}
  288. self.openDataNames = {}
  289. self.openPlots = {}
  290. self.openPlotWindows = {}
  291. self.Data = {}
  292. self.initUI()
  293. def initUI(self):
  294. self.layout = QtGui.QVBoxLayout()
  295. self.layout.setContentsMargins(0, 0, 5, 0)
  296. self.setMinimumWidth(230)
  297. self.treeWidget = QtGui.QTreeWidget()
  298. self.treeWidget.setObjectName("tree_view")
  299. self.treeWidget.setColumnCount(2)
  300. self.treeWidget.resizeColumnToContents(1)
  301. # self.treeWidget.header().resizeSection(0, 110)
  302. self.treeWidget.setHeaderHidden(True)
  303. self.treeWidget.itemClicked.connect(self.plot_clicked)
  304. self.treeWidget.contextMenuEvent = self.contextMenuEventListView
  305. self.rootItem = self.treeWidget.invisibleRootItem()
  306. self.infoAndAcquisitionWidget = AcquisitionAndInfo()
  307. self.layout.addWidget(self.treeWidget)
  308. self.layout.addWidget(self.infoAndAcquisitionWidget)
  309. self.setLayout(self.layout)
  310. def _generate_menu(self, menu, item):
  311. heatmap = menu.addAction(tr("Button", "Heatmap"))
  312. fft = menu.addAction(tr("Button", "FFT"))
  313. trains = menu.addAction(tr("Button", "Trains"))
  314. combined = menu.addAction(tr("Button", "Combined"))
  315. compare = menu.addAction(tr("Button", "Compare"))
  316. heatmap.triggered.connect(lambda: self.add_plot_node(
  317. text=tr("Label", 'Heatmap'), unique_id=item.uid, d_type=0, datatype=item.datatype))
  318. fft.triggered.connect(lambda: self.add_plot_node(
  319. text=tr("Label", 'FFT'), unique_id=item.uid, d_type=1, datatype=item.datatype))
  320. trains.triggered.connect(lambda: self.add_plot_node(
  321. text=tr("Label", 'Trains'), unique_id=item.uid, d_type=2, datatype=item.datatype))
  322. combined.triggered.connect(lambda: self.add_plot_node(
  323. text=tr("Label", 'Combined'), unique_id=item.uid, d_type=3, datatype=item.datatype))
  324. compare.triggered.connect(lambda: self.add_plot_node(
  325. text=tr("Label", 'Combined'), unique_id=item.uid, d_type=4, datatype=item.datatype))
  326. def contextMenuEventListView(self, event):
  327. """
  328. Gets called when right mouse button is clicked
  329. :param event: the event that causes the call of this function
  330. :return: -
  331. """
  332. pos = event.globalPos()
  333. try:
  334. item = self.treeWidget.selectedItems()[0]
  335. except IndexError:
  336. return
  337. if pos is not None:
  338. menu = QtGui.QMenu(self)
  339. close = menu.addAction(tr("Button", "Close"))
  340. if item.is_child:
  341. close.triggered.connect(lambda: self.remove_plot(item.usid, silent=False, close_plot_window=True))
  342. else:
  343. close.triggered.connect(lambda: self.remove_plot_tree(item.uid))
  344. self._generate_menu(menu, item)
  345. menu.popup(pos)
  346. event.accept()
  347. def plot_clicked(self, item, i):
  348. """
  349. Function to handle when a plot item is clicked.
  350. :param item: what item is clicked
  351. :param i: what column
  352. :return: -
  353. """
  354. if item.is_child:
  355. self.openPlotWindows[item.usid].setFocus()
  356. else:
  357. if not item.ready:
  358. return
  359. if i == 1:
  360. position = QtGui.QCursor.pos()
  361. menu = QtGui.QMenu(self)
  362. self._generate_menu(menu, item)
  363. menu.popup(position)
  364. def add_plot_tree(self, text=False, unique_id=None, datatype=None):
  365. """
  366. Add a top node to the plot list.
  367. :param text: (str) text to be displayed in the plot list
  368. :param unique_id: (int) unique id of this top node
  369. :param datatype: (int) live or data from file
  370. :return: -
  371. """
  372. if datatype == FILE:
  373. file_name = QtGui.QFileDialog.getOpenFileName()
  374. if file_name == "":
  375. return
  376. if file_name in self.openDataNames.itervalues(): # is this to prevent double opening?
  377. return
  378. self.openDataNames[unique_id] = file_name
  379. else:
  380. if text in self.openDataNames.itervalues():
  381. return
  382. self.openDataNames[unique_id] = QtCore.QString(text) # Add a qstring for comparison
  383. self.openData[unique_id] = QtGui.QTreeWidgetItem()
  384. self.openData[unique_id].uid = unique_id
  385. self.openData[unique_id].is_child = False
  386. self.openData[unique_id].datatype = datatype
  387. if datatype == FILE:
  388. self.openData[unique_id].file_name = file_name
  389. self.openData[unique_id].setText(0, "Live" if datatype == LIVE else file_name.split('/')[-1])
  390. self.openData[unique_id].setText(1, "Reading")
  391. if datatype == LIVE: # Insert LIVE on top
  392. self.treeWidget.insertTopLevelItem(0, self.openData[unique_id])
  393. else:
  394. self.treeWidget.addTopLevelItem(self.openData[unique_id])
  395. self.treeWidget.expandItem(self.openData[unique_id])
  396. self.treeWidget.resizeColumnToContents(0)
  397. self.openData[unique_id].ready = False # indicates whether data was completely read or not
  398. QtGui.qApp.processEvents()
  399. try:
  400. self.read_data(datatype, unique_id, file_name if datatype == FILE else None)
  401. except Exception:
  402. self.Data[unique_id] = None
  403. self.remove_plot_tree(unique_id, silent=True)
  404. QtGui.QMessageBox.critical(
  405. self,
  406. tr("Heading", "Error Reading Data"),
  407. tr("Dialog", "There was an error reading the datafile, make shure it is valid and try again."))
  408. self.openData[unique_id].ready = True # indicates whether data was completely read or not
  409. self.openData[unique_id].setText(1, "+")
  410. self.add_plot_node(d_type=0, unique_id=unique_id, datatype=datatype)
  411. def read_data(self, d_type, uid, file_name):
  412. """
  413. Reads datafile
  414. :param d_type: FILE or LIVE - type of datasource
  415. :param uid: unique id for the treeelement
  416. :param file_name: filename if type is FILE
  417. :return: -
  418. """
  419. if d_type == FILE:
  420. self.Data[uid] = io.read_from_file(file_name, header=storage.storage.header)
  421. else:
  422. self.Data[uid] = bif.livePlotData
  423. def add_plot_node(self, board_id=None, text=False, unique_id=None, d_type=None, datatype=None):
  424. """
  425. Actually open a new plot window.
  426. :param board_id: the id of the board to which the plot corresponds
  427. :param text: (str) text that is displayed in the plot list
  428. :param unique_id: (int) unique id of this plot window
  429. :param d_type: (int) type of this plot window
  430. :param datatype: (int) live or data from file
  431. :return: -
  432. """
  433. if datatype == LIVE and board_id is None: # get board_id for live plot from popup menu
  434. # board_id = 0
  435. if available_boards.multi_board:
  436. # raise NotImplementedError("Dialog to ask for board is not implemented yet.")
  437. position = QtGui.QCursor.pos()
  438. menu = QtGui.QMenu(self)
  439. for bid in available_boards:
  440. tmp = menu.addAction(available_boards.get_board_name_from_id(bid))
  441. tmp.triggered.connect(lambda _, b=bid: self.add_plot_node(board_id=b,
  442. text=text,
  443. unique_id=unique_id,
  444. d_type=d_type,
  445. datatype=datatype))
  446. # NOTE to the above: the lambda needs b=bid to capture the variable for the lambda
  447. # additionally the _ is needed because the call to the lambda includes an optional parameter
  448. # that is true or false, passed by pyqt
  449. menu.popup(position)
  450. return
  451. else:
  452. board_id = available_boards.board_ids[0]
  453. # if not board_id:
  454. # TODO: Activate this (No Board Id Given)
  455. # pass
  456. # raise NoBoardId("No Board Id was given.")
  457. unique_sub_id = kcgw.idg.genid()
  458. nr = self.openData[unique_id].childCount()
  459. if datatype == FILE:
  460. file_name = self.openData[unique_id].file_name
  461. else:
  462. file_name = None
  463. name = "Live" if not file_name else file_name.split('/')[-1]
  464. self.openPlotWindows[unique_sub_id] = PlotWidget(
  465. board_id=board_id,
  466. parent=self,
  467. name=name,
  468. unique_id=unique_sub_id,
  469. type=d_type,
  470. datatype=datatype,
  471. prefix=nr,
  472. fName=file_name,
  473. data=self.Data[unique_id]
  474. )
  475. self.openPlotWindows[unique_sub_id].change_type_signal.connect(self.update_plot)
  476. self.openPlotWindows[unique_sub_id].upid = unique_id
  477. self.parent.area.newWidget(
  478. self.openPlotWindows[unique_sub_id],
  479. name=text,
  480. unique_id=unique_sub_id,
  481. widget_type=d_type
  482. )
  483. self.openPlots[unique_sub_id] = QtGui.QTreeWidgetItem()
  484. if datatype == LIVE:
  485. brd = " B: "+available_boards.get_board_name_from_id(board_id)
  486. else:
  487. brd = ""
  488. self.openPlots[unique_sub_id].setText(0, str(nr) + "." + self.possiblePlots[d_type] + " - ADC 1"+brd)
  489. if d_type == 4: # Compare Plot
  490. self.openPlots[unique_sub_id].setText(0, str(nr) + "." + self.possiblePlots[d_type] + " - ADC 1+2"+brd)
  491. self.openPlots[unique_sub_id].is_child = True
  492. self.openPlots[unique_sub_id].usid = unique_sub_id
  493. self.openPlots[unique_sub_id].nr = nr
  494. self.openData[unique_id].addChild(self.openPlots[unique_sub_id])
  495. self.treeWidget.resizeColumnToContents(0)
  496. @QtCore.pyqtSlot()
  497. def remove_plot(self, unique_id, silent=False, close_plot_window=False):
  498. """
  499. Removes a plot from the plot list.
  500. :param unique_id: (int) the unique id of the plot to remove
  501. :param silent: (bool) Ask to close the plot source node in the list if the last plot window is closed
  502. (if False the source node will not be closed)
  503. :param close_plot_window: (bool) close the corresponding plot window
  504. :return: -
  505. """
  506. parent = self.openData[self.openPlotWindows[unique_id].upid]
  507. if not silent:
  508. reply = QtGui.QMessageBox()
  509. reply.setIcon(QtGui.QMessageBox.Question)
  510. reply.setWindowTitle(tr("Heading", 'Close Plot'))
  511. reply.setText(tr("Dialog", "Close this plot?"))
  512. reply.addButton(QtGui.QMessageBox.Yes)
  513. reply.addButton(QtGui.QMessageBox.No)
  514. reply.setDefaultButton(QtGui.QMessageBox.No)
  515. if parent.childCount() == 1:
  516. reply.addButton("Close Plot Source", QtGui.QMessageBox.ResetRole)
  517. reply.exec_()
  518. if reply.buttonRole(reply.clickedButton()) == QtGui.QMessageBox.ResetRole:
  519. self.remove_plot_tree(parent.uid, silent=True)
  520. return True
  521. elif reply.buttonRole(reply.clickedButton()) == QtGui.QMessageBox.NoRole:
  522. return False
  523. if close_plot_window:
  524. self.openPlotWindows[unique_id].close_silent = True
  525. self.openPlotWindows[unique_id].close()
  526. parent.removeChild(self.openPlots[unique_id])
  527. self.openPlotWindows[unique_id] = None
  528. self.openPlots[unique_id] = None
  529. del self.openPlotWindows[unique_id]
  530. del self.openPlots[unique_id]
  531. return True
  532. def remove_plot_tree(self, unique_id, silent=False):
  533. """
  534. Remove the top node from the plot list
  535. :param unique_id: (int) the id of the top node
  536. :param silent: (bool) ask to close even if corresponding plot windows are open
  537. :return: -
  538. """
  539. if self.openData[unique_id].childCount() != 0 and not silent:
  540. reply = QtGui.QMessageBox.question(
  541. self, tr("Heading", 'Close Data Source'), tr(
  542. "Dialog", "There are open plot windows.\n"
  543. "Close this plot source and all corresponding plot windows?"),
  544. QtGui.QMessageBox.Yes | QtGui.QMessageBox.No,
  545. QtGui.QMessageBox.No)
  546. if reply == QtGui.QMessageBox.No:
  547. return
  548. for plot in self.openData[unique_id].takeChildren():
  549. self.openPlotWindows[plot.usid].close_silent = True
  550. self.openPlotWindows[plot.usid].close()
  551. self.openData[unique_id].removeChild(plot)
  552. self.rootItem.removeChild(self.openData[unique_id])
  553. del self.openData[unique_id]
  554. del self.openDataNames[unique_id]
  555. del self.Data[unique_id]
  556. @QtCore.pyqtSlot(int, int, str)
  557. def update_plot(self, uid, b_type, adc):
  558. """
  559. Updates the plot list when the type of a plot window changes
  560. :param uid: (int) unique id of the plot window to update
  561. :param b_type: (int) type of that plot window
  562. :param int adc: the adc to update
  563. :return: -
  564. """
  565. self.openPlots[uid].setText(
  566. 0, str(self.openPlots[uid].nr) + "." + self.possiblePlots[b_type] + " - ADC " + str(adc)
  567. )
  568. self.treeWidget.resizeColumnToContents(0)
  569. def handle_dialog(self, value, diag=None,):
  570. """
  571. Function that handles the return of the dialog that asks for live or data from file
  572. :param value: (int) the value of the pressed button in the dialog
  573. :param diag: (QDialog) the dialog that is to be closed (optional)
  574. :return: -
  575. """
  576. if diag:
  577. diag.close()
  578. s = kcgw.idg.genid()
  579. if value == 1:
  580. if tr("Label", "Live") in self.openDataNames.itervalues():
  581. x = [bid for bid, obj in self.openData.iteritems() if obj.datatype == LIVE][0]
  582. self.add_plot_node(unique_id=x, d_type=0, datatype=LIVE)
  583. else:
  584. self.add_plot_tree(text=tr("Label", "Live"), unique_id=s, datatype=LIVE)
  585. elif value == 2:
  586. self.add_plot_tree(text=tr("Label", "File"), unique_id=s, datatype=FILE)
  587. else:
  588. pass
  589. def add_plot(self, d_type=None):
  590. """
  591. Handles the creation of a plot.
  592. Also shows the dialog that asks for the data source.
  593. :param d_type: the type of the plot to add
  594. :return: -
  595. """
  596. if d_type == LIVE or d_type == FILE:
  597. self.handle_dialog(value=d_type)
  598. return
  599. ask = QtGui.QDialog()
  600. window = self.parent.geometry()
  601. ask_layout = QtGui.QVBoxLayout()
  602. ask_layout.addWidget(QtGui.QLabel(tr("Dialog", "Open file from disk or read live data?")))
  603. button_layout = QtGui.QHBoxLayout()
  604. ask_button_live = QtGui.QPushButton(tr("Button", "Live"))
  605. ask_button_file = QtGui.QPushButton(tr("Button", "Open File"))
  606. ask_button_cancel = QtGui.QPushButton(tr("Button", "Cancel"))
  607. ask_button_live.pressed.connect(lambda: self.handle_dialog(diag=ask, value=1))
  608. ask_button_file.pressed.connect(lambda: self.handle_dialog(diag=ask, value=2))
  609. ask_button_cancel.pressed.connect(lambda: self.handle_dialog(diag=ask, value=3))
  610. button_layout.addWidget(ask_button_live)
  611. button_layout.addWidget(ask_button_file)
  612. button_layout.addWidget(ask_button_cancel)
  613. ask_layout.addLayout(button_layout)
  614. ask.setGeometry(window.width()/2.-100, window.height()/2.-50, 200, 100)
  615. ask.setLayout(ask_layout)
  616. ask.exec_()