leftbar.py 31 KB

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