kcgwidget.py 21 KB

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