leftbar.py 30 KB

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