table_widgets.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. #!/usr/bin/python3
  2. # -*- coding: utf8 -*-
  3. #
  4. # The Qubes OS Project, http://www.qubes-os.org
  5. #
  6. # Copyright (C) 2014 Marek Marczykowski-Górecki
  7. # <marmarek@invisiblethingslab.com>
  8. #
  9. # This program is free software; you can redistribute it and/or
  10. # modify it under the terms of the GNU General Public License
  11. # as published by the Free Software Foundation; either version 2
  12. # of the License, or (at your option) any later version.
  13. #
  14. # This program is distributed in the hope that it will be useful,
  15. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. # GNU General Public License for more details.
  18. #
  19. # You should have received a copy of the GNU Lesser General Public License along
  20. # with this program; if not, see <http://www.gnu.org/licenses/>.
  21. from PyQt4 import QtGui
  22. from PyQt4 import QtCore
  23. # TODO: are those needed?
  24. power_order = QtCore.Qt.DescendingOrder
  25. update_order = QtCore.Qt.AscendingOrder
  26. row_height = 30
  27. class VmIconWidget(QtGui.QWidget):
  28. def __init__(self, icon_path, enabled=True, size_multiplier=0.7,
  29. tooltip=None, parent=None, icon_sz=(32, 32)):
  30. super(VmIconWidget, self).__init__(parent)
  31. # TODO: check with Marek how icons should be done
  32. self.label_icon = QtGui.QLabel()
  33. if icon_path[0] in ':/':
  34. icon = QtGui.QIcon(icon_path)
  35. else:
  36. icon = QtGui.QIcon.fromTheme(icon_path)
  37. icon_sz = QtCore.QSize(row_height * size_multiplier,
  38. row_height * size_multiplier)
  39. icon_pixmap = icon.pixmap(
  40. icon_sz,
  41. QtGui.QIcon.Disabled if not enabled else QtGui.QIcon.Normal)
  42. self.label_icon.setPixmap(icon_pixmap)
  43. self.label_icon.setFixedSize(icon_sz)
  44. if tooltip is not None:
  45. self.label_icon.setToolTip(tooltip)
  46. layout = QtGui.QHBoxLayout()
  47. layout.addWidget(self.label_icon)
  48. layout.setContentsMargins(0, 0, 0, 0)
  49. self.setLayout(layout)
  50. def setToolTip(self, tooltip):
  51. if tooltip is not None:
  52. self.label_icon.setToolTip(tooltip)
  53. else:
  54. self.label_icon.setToolTip('')
  55. class VmTypeWidget(VmIconWidget):
  56. class VmTypeItem(QtGui.QTableWidgetItem):
  57. def __init__(self, value, vm):
  58. super(VmTypeWidget.VmTypeItem, self).__init__()
  59. self.value = value
  60. self.vm = vm
  61. def set_value(self, value):
  62. self.value = value
  63. def __lt__(self, other):
  64. if self.vm.qid == 0:
  65. return True
  66. elif other.vm.qid == 0:
  67. return False
  68. elif self.value == other.value:
  69. return self.vm.name < other.vm.name
  70. else:
  71. return self.value < other.value
  72. def __init__(self, vm, parent=None):
  73. (icon_path, tooltip) = self.get_vm_icon(vm)
  74. super(VmTypeWidget, self).__init__(
  75. icon_path, True, 0.8, tooltip, parent)
  76. self.vm = vm
  77. self.tableItem = self.VmTypeItem(self.value, vm)
  78. self.value = None
  79. # TODO: seriously, are numbers the best idea here?
  80. # TODO: add "provides network column
  81. # TODO: in type make vmtype
  82. # 'AdminVM': '0',
  83. # 'TemplateVM': 't',
  84. # 'AppVM': 'a',
  85. # 'StandaloneVM': 's',
  86. # 'DispVM': 'd',
  87. def get_vm_icon(self, vm):
  88. if vm.klass == 'AdminVM':
  89. self.value = 0
  90. icon_name = "dom0"
  91. elif vm.klass == 'TemplateVM':
  92. self.value = 3
  93. icon_name = "templatevm"
  94. elif vm.klass == 'StandaloneVM':
  95. self.value = 4
  96. icon_name = "standalonevm"
  97. else:
  98. self.value = 5 + vm.label.index
  99. icon_name = "appvm"
  100. return ":/" + icon_name + ".png", vm.klass
  101. class VmLabelWidget(VmIconWidget):
  102. class VmLabelItem(QtGui.QTableWidgetItem):
  103. def __init__(self, value, vm):
  104. super(VmLabelWidget.VmLabelItem, self).__init__()
  105. self.value = value
  106. self.vm = vm
  107. def set_value(self, value):
  108. self.value = value
  109. # TODO: figure a prettier sorting method?
  110. def __lt__(self, other):
  111. if self.vm.qid == 0:
  112. return True
  113. elif other.vm.qid == 0:
  114. return False
  115. elif self.value == other.value:
  116. return self.vm.name < other.vm.name
  117. else:
  118. return self.value < other.value
  119. def __init__(self, vm, parent=None):
  120. icon_path = self.get_vm_icon_path(vm)
  121. super(VmLabelWidget, self).__init__(icon_path, True, 0.8, None, parent)
  122. self.vm = vm
  123. self.tableItem = self.VmLabelItem(self.value, vm)
  124. self.value = None
  125. def get_vm_icon_path(self, vm):
  126. self.value = vm.label.index
  127. return vm.label.icon
  128. class VmNameItem (QtGui.QTableWidgetItem):
  129. def __init__(self, vm):
  130. super(VmNameItem, self).__init__()
  131. # TODO: is this needed
  132. self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
  133. self.setText(vm.name)
  134. self.setTextAlignment(QtCore.Qt.AlignVCenter)
  135. self.qid = vm.qid
  136. def __lt__(self, other):
  137. if self.qid == 0:
  138. return True
  139. elif other.qid == 0:
  140. return False
  141. return super(VmNameItem, self).__lt__(other)
  142. # TODO: current status - it should work...
  143. # TODO: maybe dbus events?
  144. class VmStatusIcon(QtGui.QLabel):
  145. def __init__(self, vm, parent=None):
  146. super(VmStatusIcon, self).__init__(parent)
  147. self.vm = vm
  148. self.set_on_icon()
  149. # TODO: rename previous power state to something better?
  150. self.previous_power_state = self.vm.get_power_state()
  151. def update(self):
  152. self.previous_power_state = self.vm.get_power_state()
  153. if self.previous_power_state != self.vm.get_power_state():
  154. self.set_on_icon()
  155. self.previous_power_state = self.vm.get_power_state()
  156. def set_on_icon(self):
  157. if self.vm.get_power_state() == "Running":
  158. icon = QtGui.QIcon(":/on.png")
  159. elif self.vm.get_power_state() in ["Paused", "Suspended"]:
  160. icon = QtGui.QIcon(":/paused.png")
  161. elif self.vm.get_power_state() in ["Transient", "Halting", "Dying"]:
  162. icon = QtGui.QIcon(":/transient.png")
  163. else:
  164. icon = QtGui.QIcon(":/off.png")
  165. icon_sz = QtCore.QSize(row_height * 0.5, row_height * 0.5)
  166. icon_pixmap = icon.pixmap(icon_sz)
  167. self.setPixmap(icon_pixmap)
  168. self.setFixedSize(icon_sz)
  169. class VmInfoWidget (QtGui.QWidget):
  170. class VmInfoItem (QtGui.QTableWidgetItem):
  171. def __init__(self, upd_info_item, vm):
  172. super(VmInfoWidget.VmInfoItem, self).__init__()
  173. self.upd_info_item = upd_info_item
  174. self.vm = vm
  175. def __lt__(self, other):
  176. if self.vm.qid == 0:
  177. return True
  178. elif other.vm.qid == 0:
  179. return False
  180. self_val = self.upd_info_item.value
  181. other_val = other.upd_info_item.value
  182. if self.tableWidget().\
  183. horizontalHeader().sortIndicatorOrder() == update_order:
  184. # the result will be sorted by upd, sorting order: Ascending
  185. self_val += 1 if self.vm.is_running() else 0
  186. other_val += 1 if other.vm.is_running() else 0
  187. if self_val == other_val:
  188. return self.vm.name < other.vm.name
  189. else:
  190. return self_val > other_val
  191. elif self.tableWidget().\
  192. horizontalHeader().sortIndicatorOrder() == power_order:
  193. # the result will be sorted by power state,
  194. # sorting order: Descending
  195. self_val = -(self_val/10 +
  196. 10*(1 if self.vm.is_running() else 0))
  197. other_val = -(other_val/10 +
  198. 10*(1 if other.vm.is_running() else 0))
  199. if self_val == other_val:
  200. return self.vm.name < other.vm.name
  201. else:
  202. return self_val > other_val
  203. else:
  204. # it would be strange if this happened
  205. return
  206. def __init__(self, vm, parent=None):
  207. super(VmInfoWidget, self).__init__(parent)
  208. self.vm = vm
  209. layout = QtGui.QHBoxLayout()
  210. self.on_icon = VmStatusIcon(vm)
  211. self.upd_info = VmUpdateInfoWidget(vm, show_text=False)
  212. self.error_icon = VmIconWidget(":/warning.png")
  213. self.blk_icon = VmIconWidget(":/mount.png")
  214. self.rec_icon = VmIconWidget(":/mic.png")
  215. layout.addWidget(self.on_icon)
  216. layout.addWidget(self.upd_info)
  217. layout.addWidget(self.error_icon)
  218. layout.addItem(QtGui.QSpacerItem(0, 10,
  219. QtGui.QSizePolicy.Expanding,
  220. QtGui.QSizePolicy.Expanding))
  221. layout.addWidget(self.blk_icon)
  222. layout.addWidget(self.rec_icon)
  223. layout.setContentsMargins(5, 0, 5, 0)
  224. self.setLayout(layout)
  225. self.rec_icon.setVisible(False)
  226. self.blk_icon.setVisible(False)
  227. self.error_icon.setVisible(False)
  228. self.tableItem = self.VmInfoItem(self.upd_info.tableItem, vm)
  229. def update_vm_state(self, vm):
  230. self.on_icon.update()
  231. self.upd_info.update_outdated(vm)
  232. # TODO: add updating things like label? name? evrything? size?
  233. # TODO add main to git history as a saner name and with a decent comment
  234. # TODO and rename that shit
  235. class VmTemplateItem (QtGui.QTableWidgetItem):
  236. def __init__(self, vm):
  237. super(VmTemplateItem, self).__init__()
  238. self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
  239. self.vm = vm
  240. if getattr(vm, 'template', None) is not None:
  241. self.setText(vm.template.name)
  242. else:
  243. font = QtGui.QFont()
  244. font.setStyle(QtGui.QFont.StyleItalic)
  245. self.setFont(font)
  246. self.setTextColor(QtGui.QColor("gray"))
  247. self.setText(vm.klass)
  248. self.setTextAlignment(QtCore.Qt.AlignVCenter)
  249. def __lt__(self, other):
  250. if self.vm.qid == 0:
  251. return True
  252. elif other.vm.qid == 0:
  253. return False
  254. elif self.text() == other.text():
  255. return self.vm.name < other.vm.name
  256. else:
  257. return super(VmTemplateItem, self).__lt__(other)
  258. class VmNetvmItem (QtGui.QTableWidgetItem):
  259. def __init__(self, vm):
  260. super(VmNetvmItem, self).__init__()
  261. self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
  262. self.vm = vm
  263. # TODO: differentiate without no net vm/ no networking?
  264. # TODO: mark provides network somehow?
  265. if getattr(vm, 'netvm', None) is None:
  266. self.setText("n/a")
  267. else:
  268. self.setText(vm.netvm.name)
  269. self.setTextAlignment(QtCore.Qt.AlignVCenter)
  270. def __lt__(self, other):
  271. if self.vm.qid == 0:
  272. return True
  273. elif other.vm.qid == 0:
  274. return False
  275. elif self.text() == other.text():
  276. return self.vm.name < other.vm.name
  277. else:
  278. return super(VmNetvmItem, self).__lt__(other)
  279. class VmInternalItem(QtGui.QTableWidgetItem):
  280. def __init__(self, vm):
  281. super(VmInternalItem, self).__init__()
  282. self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
  283. self.vm = vm
  284. self.internal = vm.features.get('internal', False)
  285. self.setText("Yes" if self.internal else "")
  286. def __lt__(self, other):
  287. if self.vm.qid == 0:
  288. return True
  289. elif other.vm.qid == 0:
  290. return False
  291. return super(VmInternalItem, self).__lt__(other)
  292. # features man qvm-features
  293. class VmUpdateInfoWidget(QtGui.QWidget):
  294. class VmUpdateInfoItem (QtGui.QTableWidgetItem):
  295. def __init__(self, value, vm):
  296. super(VmUpdateInfoWidget.VmUpdateInfoItem, self).__init__()
  297. self.value = 0
  298. self.vm = vm
  299. self.set_value(value)
  300. def set_value(self, value):
  301. if value in ("outdated", "to-be-outdated"):
  302. self.value = 30
  303. elif value == "update":
  304. self.value = 20
  305. else:
  306. self.value = 0
  307. def __lt__(self, other):
  308. if self.vm.qid == 0:
  309. return True
  310. elif other.vm.qid == 0:
  311. return False
  312. elif self.value == other.value:
  313. return self.vm.name < other.vm.name
  314. else:
  315. return self.value < other.value
  316. def __init__(self, vm, show_text=True, parent=None):
  317. super(VmUpdateInfoWidget, self).__init__(parent)
  318. layout = QtGui.QHBoxLayout()
  319. self.show_text = show_text
  320. if self.show_text:
  321. self.label = QtGui.QLabel("")
  322. layout.addWidget(self.label, alignment=QtCore.Qt.AlignCenter)
  323. else:
  324. self.icon = QtGui.QLabel("")
  325. layout.addWidget(self.icon, alignment=QtCore.Qt.AlignCenter)
  326. self.setLayout(layout)
  327. self.previous_outdated_state = None
  328. self.previous_update_recommended = None
  329. self.value = None
  330. self.tableItem = VmUpdateInfoWidget.VmUpdateInfoItem(self.value, vm)
  331. def update_outdated(self, vm):
  332. outdated_state = False
  333. try:
  334. for vol in vm.volumes:
  335. if vol.is_outdated():
  336. outdated_state = "outdated"
  337. break
  338. except AttributeError:
  339. pass
  340. if not outdated_state and getattr(vm, 'template', None)\
  341. and vm.template.is_running():
  342. outdated_state = "to-be-outdated"
  343. if outdated_state != self.previous_outdated_state:
  344. self.update_status_widget(outdated_state)
  345. self.previous_outdated_state = outdated_state
  346. updates_available = vm.features.get('updates-available', False)
  347. if updates_available != self.previous_update_recommended:
  348. self.update_status_widget("update" if updates_available else None)
  349. self.previous_update_recommended = updates_available
  350. def update_status_widget(self, state):
  351. self.value = state
  352. self.tableItem.set_value(state)
  353. if state == "update":
  354. label_text = "<font color=\"#CCCC00\">Check updates</font>"
  355. icon_path = ":/update-recommended.png"
  356. tooltip_text = self.tr("Updates pending!")
  357. elif state == "outdated":
  358. label_text = "<font color=\"red\">VM outdated</font>"
  359. icon_path = ":/outdated.png"
  360. tooltip_text = self.tr(
  361. "The VM must be restarted for its filesystem to reflect the "
  362. "template's recent committed changes.")
  363. elif state == "to-be-outdated":
  364. label_text = "<font color=\"#800000\">TemplateVM running</font>"
  365. icon_path = ":/to-be-outdated.png"
  366. tooltip_text = self.tr(
  367. "The TemplateVM must be stopped before changes from its "
  368. "current session can be picked up by this VM.")
  369. else:
  370. label_text = ""
  371. icon_path = None
  372. tooltip_text = None
  373. if self.show_text:
  374. self.label.setText(label_text)
  375. else:
  376. self.layout().removeWidget(self.icon)
  377. self.icon.deleteLater()
  378. if icon_path is not None:
  379. self.icon = VmIconWidget(icon_path, True, 0.7)
  380. self.icon.setToolTip(tooltip_text)
  381. else:
  382. self.icon = QtGui.QLabel(label_text)
  383. self.layout().addWidget(self.icon, alignment=QtCore.Qt.AlignCenter)
  384. class VmSizeOnDiskItem (QtGui.QTableWidgetItem):
  385. def __init__(self, vm):
  386. super(VmSizeOnDiskItem, self).__init__()
  387. self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
  388. self.vm = vm
  389. self.value = 0
  390. self.update()
  391. self.setTextAlignment(QtCore.Qt.AlignVCenter)
  392. def update(self):
  393. if self.vm.qid == 0:
  394. self.setText("n/a")
  395. else:
  396. self.value = 10
  397. self.value = self.vm.get_disk_utilization()/(1024*1024)
  398. self.setText(str(self.value) + " MiB")
  399. def __lt__(self, other):
  400. if self.vm.qid == 0:
  401. return True
  402. elif other.vm.qid == 0:
  403. return False
  404. elif self.value == other.value:
  405. return self.vm.name < other.vm.name
  406. else:
  407. return self.value < other.value
  408. # TODO: replace these widgets with a generic widgets
  409. class VmIPItem(QtGui.QTableWidgetItem):
  410. def __init__(self, vm):
  411. super(VmIPItem, self).__init__()
  412. self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
  413. self.vm = vm
  414. self.ip = getattr(self.vm, 'ip', None)
  415. self.setText(self.ip if self.ip is not None else 'n/a')
  416. def __lt__(self, other):
  417. if self.vm.qid == 0:
  418. return True
  419. elif other.vm.qid == 0:
  420. return False
  421. return super(VmIPItem, self).__lt__(other)
  422. class VmIncludeInBackupsItem(QtGui.QTableWidgetItem):
  423. def __init__(self, vm):
  424. super(VmIncludeInBackupsItem, self).__init__()
  425. self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
  426. self.vm = vm
  427. if getattr(self.vm, 'include_in_backups', None):
  428. self.setText("Yes")
  429. else:
  430. self.setText("")
  431. self.setText("")
  432. def __lt__(self, other):
  433. if self.vm.qid == 0:
  434. return True
  435. elif other.vm.qid == 0:
  436. return False
  437. elif self.vm.include_in_backups == other.vm.include_in_backups:
  438. return self.vm.name < other.vm.name
  439. else:
  440. return self.vm.include_in_backups < other.vm.include_in_backups
  441. class VmLastBackupItem(QtGui.QTableWidgetItem):
  442. def __init__(self, vm):
  443. super(VmLastBackupItem, self).__init__()
  444. self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
  445. self.vm = vm
  446. if getattr(self.vm, 'backup_timestamp', None):
  447. self.setText(self.vm.backup_timestamp)
  448. else:
  449. self.setText("")
  450. self.setText("")
  451. def __lt__(self, other):
  452. if self.vm.qid == 0:
  453. return True
  454. elif other.vm.qid == 0:
  455. return False
  456. elif self.vm.backup_timestamp == other.vm.backup_timestamp:
  457. return self.vm.name < other.vm.name
  458. elif not self.vm.backup_timestamp:
  459. return False
  460. elif not other.vm.backup_timestamp:
  461. return True
  462. else:
  463. return self.vm.backup_timestamp < other.vm.backup_timestamp