kcgwidget.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  1. """
  2. Base Classes used in KCG.
  3. This module also contains various helpful Classes to make live easier ;)
  4. """
  5. from PyQt4 import QtGui, QtCore, QtSvg
  6. import logging
  7. import sys
  8. from .. import config
  9. def tr(_, x):
  10. return x
  11. class BigIconButton(QtGui.QPushButton):
  12. """
  13. This is a Button with a big Icon (that can fill the whole button)
  14. """
  15. def __init__(self, path, width, height, connect=None, tooltip=None, parent=None):
  16. """
  17. Setting various properties
  18. :param path: (str) the path to the icon
  19. :param width: (int) with of the button
  20. :param height: (int) height of the button
  21. :param connect: (callable) function to call when button is pressed
  22. :param tooltip: (str) tool tip to show
  23. :param parent: (QWidget) parent widget.
  24. :return: -
  25. """
  26. super(BigIconButton, self).__init__(parent)
  27. self.setIcon(QtGui.QIcon(path))
  28. self.setFixedSize(width, height)
  29. self.setIconSize(QtCore.QSize(width*0.7, height*0.7))
  30. if connect:
  31. self.clicked.connect(connect)
  32. if tooltip:
  33. self.setToolTip(tooltip)
  34. class clickLabel(QtGui.QLabel):
  35. """
  36. Clickable Label
  37. """
  38. clicked = QtCore.pyqtSignal()
  39. def mouseReleaseEvent(self, QMouseEvent):
  40. self.clicked.emit()
  41. class ClickableHBoxLayout(QtGui.QHBoxLayout):
  42. clicked = QtCore.pyqtSignal()
  43. def event(self, QEvent):
  44. self.clicked.emit()
  45. if QEvent.type() == QtCore.QEvent.MouseButtonRelease:
  46. self.clicked.emit()
  47. return True
  48. else:
  49. super(ClickableHBoxLayout, self).event(QEvent)
  50. return True
  51. class switchLabel(QtGui.QLabel):
  52. """
  53. This implements a Switch.
  54. It switches between left and right.
  55. """
  56. clicked = QtCore.pyqtSignal()
  57. def __init__(self, startRight=False): # self.state: True->Right False->Left
  58. """
  59. Initialise switchLabel
  60. As a normal Button it emits the clicked event when clicked.
  61. It does NOT have two events for each side at the moment.
  62. :param startRight: (bool) whether the switch is initially set to the right position (default is left (False))
  63. :return: -
  64. """
  65. super(switchLabel, self).__init__()
  66. self.leftSwitch = QtGui.QPixmap(config.install_path+"icons/SwitchButtonLeft.png")
  67. self.leftSwitch = self.leftSwitch.scaled(40, 20, transformMode=QtCore.Qt.SmoothTransformation)
  68. self.rightSwitch = QtGui.QPixmap(config.install_path+"icons/SwitchButtonRight.png")
  69. self.rightSwitch = self.rightSwitch.scaled(40, 20, transformMode=QtCore.Qt.SmoothTransformation)
  70. if startRight:
  71. self.setPixmap(self.rightSwitch)
  72. self.state = True
  73. else:
  74. self.setPixmap(self.leftSwitch)
  75. self.state = False
  76. def mouseReleaseEvent(self, QMouseEvent):
  77. if self.state:
  78. self.state = False
  79. self.setPixmap(self.leftSwitch)
  80. else:
  81. self.state = True
  82. self.setPixmap(self.rightSwitch)
  83. self.clicked.emit()
  84. class Switch(QtGui.QWidget):
  85. clicked = QtCore.pyqtSignal()
  86. def __init__(self, leftLabel, rightLabel, startRight=False):
  87. super(Switch, self).__init__()
  88. self.layout = QtGui.QHBoxLayout()
  89. self.switch = switchLabel(startRight)
  90. self.switch.clicked.connect(self.clicked.emit)
  91. self.setLayout(self.layout)
  92. self.layout.addStretch(1)
  93. self.layout.addWidget(QtGui.QLabel(leftLabel))
  94. self.layout.addWidget(self.switch)
  95. self.layout.addWidget(QtGui.QLabel(rightLabel))
  96. self.layout.addStretch(1)
  97. def state(self):
  98. return self.switch.state
  99. class ClickableSVG(QtGui.QWidget):
  100. """
  101. This implements a clickable SVG Image
  102. """
  103. clicked = QtCore.pyqtSignal()
  104. def __init__(self, path, width, height, wwidth=None, wheight=None, parent=None):
  105. """
  106. Initialisation of ClickabeSVG
  107. :param path: (str) path to the svg file
  108. :param width: (int) width of the svg
  109. :param height: (int) height of the svg
  110. :param wwidth: (int) width of the widget (not the svg)
  111. :param wheight: (int) height of the widget (not the svg)
  112. :param parent: (QWidget) parent widget of the ClickableSVG
  113. :return: -
  114. """
  115. super(ClickableSVG, self).__init__(parent)
  116. self.svg = QtSvg.QSvgWidget(path)
  117. self.svg.setFixedSize(width, height)
  118. layout = QtGui.QHBoxLayout()
  119. layout.addWidget(self.svg)
  120. self.setLayout(layout)
  121. if wwidth:
  122. self.setFixedWidth(wwidth)
  123. if wheight:
  124. self.setFixedHeight(wheight)
  125. def mouseReleaseEvent(self, QMouseEvent):
  126. self.clicked.emit()
  127. def changeSvg(self, path):
  128. """
  129. Change the SVG of this widget
  130. :param path: (str) path to the new svg file
  131. :return: -
  132. """
  133. self.svg.load(path)
  134. class KCGWidgets(QtGui.QWidget):
  135. """
  136. Base Class for alsmost all Widgets used in KCG.
  137. It holds various properties as well as methods that simplify creation of certain objects such as labels, buttons ...
  138. """
  139. closeSignal = QtCore.pyqtSignal()
  140. def __init__(self, name=None, parent=None):
  141. """
  142. Initialise this baseclass
  143. :param name: (str) name of the widget
  144. :param parent: (QWidget) parent of this widget
  145. :return: -
  146. """
  147. super(KCGWidgets, self).__init__(parent)
  148. self._id = None
  149. self._type = None
  150. self._name = None
  151. if name:
  152. self.theName = name
  153. @property
  154. def theType(self):
  155. """
  156. Type of this widget
  157. """
  158. return self._type
  159. @property
  160. def theId(self):
  161. """
  162. ID of this widget
  163. """
  164. return self._id
  165. @property
  166. def theName(self):
  167. """
  168. Name of this widget
  169. """
  170. return self._name
  171. @theType.setter
  172. def theType(self, t):
  173. """
  174. Setter for the type of this widget
  175. :param t: (int) type
  176. """
  177. self._type = t # TODO: ??
  178. @theId.setter
  179. def theId(self, i):
  180. """
  181. Setter for the id of this widget
  182. :param i: (int) id
  183. """
  184. self._id = i # TODO: ??
  185. @theName.setter
  186. def theName(self, n):
  187. """
  188. Setter for the name of this widget
  189. :param n: (str) name
  190. """
  191. self._name = n # TODO: ??
  192. def createButton(self, text="", x=None, y=None, dimensions=None, tooltip="", connect=False, icon=None):
  193. """
  194. Create a Button
  195. :param text: (str) Text to display on the button
  196. :param x: (int) x-position
  197. :param y: (int) y-position
  198. :param dimensions: (QSize) dimensions of the button
  199. :param tooltip: (str) tooltip to display
  200. :param connect: (callable) connect the button pressed event to this callable
  201. :param icon: (QIcon) Icon to display on the button
  202. :return: -
  203. """
  204. button = QtGui.QPushButton(text, self)
  205. if tooltip:
  206. button.setToolTip(tooltip)
  207. if not dimensions:
  208. button.resize(button.sizeHint())
  209. else:
  210. button.resize(dimensions)
  211. if x and y:
  212. button.move(x, y)
  213. if connect:
  214. button.clicked.connect(connect)
  215. if icon:
  216. button.setIcon(icon)
  217. return button
  218. def createLabel(self, text=None, image=None, tooltip=None, click=False, connect=None):
  219. """
  220. Create a Label
  221. :param text: (str) Text to display on this label
  222. :param image: (QPixmap) Image to display on this label
  223. :param tooltip: (str) tooltip to display
  224. :param click: (bool) make this a clickable label?
  225. :param connect: (callable) if click is true, connect the clicked event to this callable
  226. :return: -
  227. """
  228. if click:
  229. label = clickLabel(self)
  230. if connect:
  231. label.clicked.connect(connect)
  232. else:
  233. label = QtGui.QLabel(self)
  234. if text:
  235. label.setText(text)
  236. if image:
  237. label.setPixmap(image)
  238. if tooltip:
  239. label.setToolTip(tooltip)
  240. return label
  241. def createSpinbox(self, min, max, interval=1, start_value=0, connect=None):
  242. """
  243. create a Spinbox
  244. :param min: (int) minimum Value
  245. :param max: (int) maximum Value
  246. :param interval: (int) interval
  247. :param start_value: (int) start Value
  248. :param connect: (callable) function to call on value change
  249. :return: -
  250. """
  251. spinbox = QtGui.QSpinBox()
  252. spinbox.setRange(min, max)
  253. spinbox.setSingleStep(interval)
  254. spinbox.setValue(start_value)
  255. if connect:
  256. spinbox.valueChanged.connect(connect)
  257. return spinbox
  258. def createInput(self, text=None, read_only=False, width=None):
  259. """
  260. Create Input
  261. :param text: (str) Default Text
  262. :param read_only: (bool) set input as read only
  263. :param width: (int) width of the input field in pixels
  264. :return: -
  265. """
  266. input = QtGui.QLineEdit()
  267. if text:
  268. input.setText(text)
  269. if width:
  270. input.setMinimumWidth(width)
  271. input.setReadOnly(read_only)
  272. return input
  273. def createCheckbox(self, text="", tooltip="", checked=False, connect=None):
  274. """
  275. Create Checkbox
  276. :param tooltip: (str) what tooltip text to display
  277. :param checked: (bool) Checkt as default?
  278. :param connect: (callable) function to connect
  279. :return:
  280. """
  281. cb = QtGui.QCheckBox(text)
  282. cb.setToolTip(tooltip)
  283. cb.setChecked(checked)
  284. if connect:
  285. cb.clicked.connect(connect)
  286. return cb
  287. def createSwitch(self, startRight=False, connect=None):
  288. """
  289. Create a Switch
  290. :param startRight: (bool) if this is True the initial setting is set to right (default is left)
  291. :param connect: (callable) connect the switches clicked event to this callable
  292. :return: -
  293. """
  294. sw = switchLabel(startRight)
  295. if connect:
  296. sw.clicked.connect(connect)
  297. return sw
  298. def closeEvent(self, event):
  299. super(KCGWidgets, self).closeEvent(event)
  300. self.closeSignal.emit()
  301. class KCGSubWidget(QtGui.QMdiSubWindow):
  302. """
  303. Base Class for Subwindows in the KCG Gui
  304. """
  305. _id = None
  306. _name = None
  307. _type = None
  308. def __init__(self, name=None, unique_id=None, typ=None, minSize=False):
  309. """
  310. Initialise a Subwindow
  311. :param name: (str) name of this window
  312. :param unique_id: (int) unique id of this window
  313. :param typ: (int) type of this window
  314. :param minSize: (bool) whether the window is to be resized to its minimum size
  315. :return: -
  316. """
  317. super(KCGSubWidget, self).__init__()
  318. if name != None:
  319. self.theName = name
  320. if unique_id != None:
  321. self.theId = unique_id
  322. if typ != None:
  323. self.theType = typ
  324. @property
  325. def theType(self):
  326. return self._type
  327. @property
  328. def theId(self):
  329. return self._id
  330. @property
  331. def theName(self):
  332. return self._name
  333. @theType.setter
  334. def theType(self, t):
  335. self._type = t # TODO: ??
  336. @theId.setter
  337. def theId(self, i):
  338. self._id = i # TODO: ??
  339. @theName.setter
  340. def theName(self, n):
  341. self._name = n # TODO: ??
  342. class AccordionClickLine(QtGui.QWidget):
  343. clicked = QtCore.pyqtSignal()
  344. def __init__(self, text):
  345. super(AccordionClickLine, self).__init__()
  346. self.layout = QtGui.QHBoxLayout()
  347. self.setLayout(self.layout)
  348. self.layout.setSpacing(0)
  349. self.setContentsMargins(0, -5, 0, -5)
  350. self.layout.addWidget(QtGui.QLabel(text))
  351. self.layout.addStretch(1)
  352. self.down_chev = QtSvg.QSvgWidget(config.install_path+"icons/chevron-bottom.svg")
  353. self.down_chev.setFixedSize(10, 10)
  354. self.left_chev = QtSvg.QSvgWidget(config.install_path+"icons/chevron-left.svg")
  355. self.left_chev.setFixedSize(10, 10)
  356. self.down_chev.hide()
  357. self.layout.addWidget(self.left_chev)
  358. self.expanded = True
  359. def mouseReleaseEvent(self, QMouseEvent):
  360. self.clicked.emit()
  361. super(AccordionClickLine, self).mouseReleaseEvent(QMouseEvent)
  362. def expand(self, state):
  363. if state: # if you want to expand
  364. if not self.expanded: # and it is not already expanded
  365. self.layout.removeWidget(self.down_chev)
  366. self.down_chev.hide()
  367. self.left_chev.show()
  368. self.layout.addWidget(self.left_chev)
  369. self.expanded = True
  370. else: # if you want to unexpand
  371. if self.expanded: # and it is epanded
  372. self.layout.removeWidget(self.left_chev)
  373. self.left_chev.hide()
  374. self.down_chev.show()
  375. self.layout.addWidget(self.down_chev)
  376. self.expanded = False
  377. class AccordionWidget(QtGui.QWidget): # TODO: accordion or accordeon?
  378. """
  379. Simple accordion widget similar to QToolBox
  380. """
  381. def __init__(self):
  382. """
  383. Initialise the accordion widget
  384. """
  385. super(AccordionWidget, self).__init__()
  386. self.layout = QtGui.QVBoxLayout()
  387. self.layout.setSpacing(0)
  388. self.setLayout(self.layout)
  389. self.widgets = []
  390. self.headers = []
  391. def resizeEvent(self, QResizeEvent):
  392. self.setMaximumHeight(self.layout.sizeHint().height())
  393. super(AccordionWidget, self).resizeEvent(QResizeEvent)
  394. def addItem(self, widget, text):
  395. """
  396. Add a Widget to the Accordion widget
  397. :param widget: the widget to add
  398. :param text: the text for this widget
  399. """
  400. hline2 = QtGui.QFrame()
  401. hline2.setFrameShape(QtGui.QFrame.HLine)
  402. hline2.setFrameShadow(QtGui.QFrame.Sunken)
  403. cidx = len(self.widgets)
  404. header = AccordionClickLine(text)
  405. header.clicked.connect(lambda: self.toggleIndex(cidx))
  406. self.headers.append(header)
  407. self.layout.addWidget(hline2)
  408. self.layout.addWidget(header)
  409. self.layout.addWidget(widget)
  410. self.widgets.append(widget)
  411. def toggleIndex(self, index):
  412. """
  413. Toggle the visibility of the widget with index index
  414. :param index: the index to toggle
  415. :return:
  416. """
  417. if self.widgets[index].isHidden():
  418. self.expandIndex(index)
  419. else:
  420. self.hideIndex(index)
  421. def expandIndex(self, index):
  422. """
  423. Expand the widget with index index
  424. :param index: the index of the widget to show
  425. """
  426. self.widgets[index].show()
  427. self.headers[index].expand(True)
  428. self.resize(1, 1) # this will trigger a resizeEvent and thus will set the correct size
  429. def hideIndex(self, index):
  430. """
  431. Hide the widget with the index index
  432. :param index: the index of the widget to hide
  433. """
  434. self.widgets[index].hide()
  435. self.headers[index].expand(False)
  436. self.resize(1, 1) # this will trigger a resizeEvent and thus will set the correct size
  437. def hideAll(self):
  438. """
  439. Hide all widgets
  440. """
  441. for wid, header in zip(self.widgets, self.headers):
  442. wid.hide()
  443. header.expand(False)
  444. def showAll(self):
  445. """
  446. Show all widgets
  447. """
  448. for wid, header in zip(self.widgets, self.headers):
  449. wid.show()
  450. header.expand(True)
  451. class MultilineInputDialog(QtGui.QDialog):
  452. """
  453. Multiline Input Dialog
  454. When using this dialog, create is and open it with get_text. this also returns the entered text.
  455. """
  456. def __init__(self):
  457. super(MultilineInputDialog, self).__init__()
  458. self.answer = False
  459. def fill(self, heading, label):
  460. """
  461. Fill the widget with elements
  462. :param heading: (str) the heading of this widget
  463. :param label: (str) the text to show above the input field
  464. :return: -
  465. """
  466. self.setWindowTitle(heading)
  467. self.layout = QtGui.QVBoxLayout()
  468. self.setLayout(self.layout)
  469. self.tl = QtGui.QLabel(label)
  470. self.layout.addWidget(self.tl)
  471. self.ti = QtGui.QPlainTextEdit()
  472. self.layout.addWidget(self.ti)
  473. # Advanced method to activate function call on Ctrl+Return (in this case the ok function aka press ok button)
  474. # def ctrlEnter(event):
  475. # if event.key() == QtCore.Qt.Key_Return:
  476. # if event.modifiers() == QtCore.Qt.ControlModifier:
  477. # self.ok()
  478. # QtGui.QPlainTextEdit.keyPressEvent(self.ti, event)
  479. # self.ti.keyPressEvent = ctrlEnter
  480. self.blayout = QtGui.QHBoxLayout()
  481. self.layout.addLayout(self.blayout)
  482. self.okButton = QtGui.QPushButton(tr("Button", "OK"))
  483. self.okButton.pressed.connect(self.ok)
  484. self.okButton.setShortcut("Ctrl+Return")
  485. self.cancelButton = QtGui.QPushButton(tr("Button", "Cancel"))
  486. self.cancelButton.pressed.connect(self.cancel)
  487. self.cancelButton.setShortcut("Esc")
  488. self.blayout.addWidget(self.okButton)
  489. self.blayout.addWidget(self.cancelButton)
  490. def ok(self):
  491. """
  492. This gets executed when the ok button is pressed
  493. """
  494. self.answer = True
  495. self.close()
  496. def cancel(self):
  497. """
  498. This gets executed when the cancel button is pressed
  499. """
  500. self.answer = False
  501. self.close()
  502. def get_text(self, heading, label):
  503. """
  504. This function is the main entry point.
  505. :param heading: (str) the heading of the widget
  506. :param label: (str) the text to show above the input field
  507. :return: (unicode) the entered text
  508. """
  509. self.fill(heading, label)
  510. self.exec_()
  511. return unicode(self.ti.toPlainText()), self.answer
  512. class IdGenerator():
  513. """
  514. Generate Unique Id for every subwindow
  515. """
  516. highest_id = 0
  517. def genid(self):
  518. self.highest_id += 1
  519. return self.highest_id
  520. idg = IdGenerator() # declare idgenerator instance to use globally
  521. _registered_possible_widgets = []
  522. _registered_possible_widget_functions = []
  523. def register_widget_creation_function(creation_func):
  524. """
  525. Register the function to create a certain widget
  526. :param creation_func: (callable) function to call when the widget is to be created
  527. :return: -
  528. """
  529. _registered_possible_widget_functions.append(creation_func)
  530. def register_widget(icon, text, target, shortcut=None):
  531. """
  532. Register a widget
  533. :param icon: (QIcon) Icon to show on the toolbar button
  534. :param text: (str) tooltip for the toolbar button
  535. :param target: (callable) the function to create the widget
  536. :param shortcut: (str) keyboard shortcut to open this widget
  537. :return: -
  538. """
  539. _registered_possible_widgets.append([icon, text, target, shortcut])
  540. def get_registered_widgets():
  541. """
  542. Get the registered widgets
  543. :return: (list) list of registered widgets
  544. """
  545. return _registered_possible_widgets
  546. def get_registered_widget_functions():
  547. """
  548. Get the functions that are registered
  549. :return: (list) list of functions
  550. """
  551. return _registered_possible_widget_functions
  552. def error(code, text, severe=False):
  553. """
  554. Log an error using the logging module
  555. :param code: the error code
  556. :param text: the text to show
  557. :param severe: if it is a severe error that has to quit the program
  558. :return:
  559. """
  560. # TODO: implement error logging to file
  561. if isinstance(code, str) and code[0:2] == '0x':
  562. cde = code
  563. elif isinstance(code, str):
  564. cde = '0x'+code
  565. else:
  566. cde = '0x'+format(code, '03x')
  567. logging.critical('{code}: {text}'.format(code=cde, text=text))
  568. if severe:
  569. sys.exit(int(cde, 16))