table_widgets.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  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. power_order = QtCore.Qt.DescendingOrder
  24. update_order = QtCore.Qt.AscendingOrder
  25. row_height = 30
  26. class VmIconWidget(QtGui.QWidget):
  27. def __init__(self, icon_path, enabled=True, size_multiplier=0.7,
  28. tooltip=None, parent=None, icon_sz=(32, 32)):
  29. super(VmIconWidget, self).__init__(parent)
  30. self.label_icon = QtGui.QLabel()
  31. if icon_path[0] in ':/':
  32. icon = QtGui.QIcon(icon_path)
  33. else:
  34. icon = QtGui.QIcon.fromTheme(icon_path)
  35. icon_sz = QtCore.QSize(row_height * size_multiplier,
  36. row_height * size_multiplier)
  37. icon_pixmap = icon.pixmap(
  38. icon_sz,
  39. QtGui.QIcon.Disabled if not enabled else QtGui.QIcon.Normal)
  40. self.label_icon.setPixmap(icon_pixmap)
  41. self.label_icon.setFixedSize(icon_sz)
  42. if tooltip is not None:
  43. self.label_icon.setToolTip(tooltip)
  44. layout = QtGui.QHBoxLayout()
  45. layout.addWidget(self.label_icon)
  46. layout.setContentsMargins(0, 0, 0, 0)
  47. self.setLayout(layout)
  48. def setToolTip(self, tooltip):
  49. if tooltip is not None:
  50. self.label_icon.setToolTip(tooltip)
  51. else:
  52. self.label_icon.setToolTip('')
  53. class VmTypeWidget(VmIconWidget):
  54. class VmTypeItem(QtGui.QTableWidgetItem):
  55. def __init__(self, value, vm):
  56. super(VmTypeWidget.VmTypeItem, self).__init__()
  57. self.value = value
  58. self.vm = vm
  59. def set_value(self, value):
  60. self.value = value
  61. def __lt__(self, other):
  62. if self.vm.qid == 0:
  63. return True
  64. elif other.vm.qid == 0:
  65. return False
  66. elif self.value == other.value:
  67. return self.vm.name < other.vm.name
  68. else:
  69. return self.value < other.value
  70. def __init__(self, vm, parent=None):
  71. (icon_path, tooltip) = self.get_vm_icon(vm)
  72. super(VmTypeWidget, self).__init__(
  73. icon_path, True, 0.8, tooltip, parent)
  74. self.vm = vm
  75. self.tableItem = self.VmTypeItem(self.value, vm)
  76. self.value = None
  77. # TODO: add "provides network" column
  78. def get_vm_icon(self, vm):
  79. if vm.klass == 'AdminVM':
  80. self.value = 0
  81. icon_name = "dom0"
  82. elif vm.klass == 'TemplateVM':
  83. self.value = 3
  84. icon_name = "templatevm"
  85. elif vm.klass == 'StandaloneVM':
  86. self.value = 4
  87. icon_name = "standalonevm"
  88. else:
  89. self.value = 5 + vm.label.index
  90. icon_name = "appvm"
  91. return ":/" + icon_name + ".png", vm.klass
  92. class VmLabelWidget(VmIconWidget):
  93. class VmLabelItem(QtGui.QTableWidgetItem):
  94. def __init__(self, value, vm):
  95. super(VmLabelWidget.VmLabelItem, self).__init__()
  96. self.value = value
  97. self.vm = vm
  98. def set_value(self, value):
  99. self.value = value
  100. def __lt__(self, other):
  101. if self.vm.qid == 0:
  102. return True
  103. elif other.vm.qid == 0:
  104. return False
  105. elif self.value == other.value:
  106. return self.vm.name < other.vm.name
  107. else:
  108. return self.value < other.value
  109. def __init__(self, vm, parent=None):
  110. icon_path = self.get_vm_icon_path(vm)
  111. super(VmLabelWidget, self).__init__(icon_path, True, 0.8, None, parent)
  112. self.vm = vm
  113. self.tableItem = self.VmLabelItem(self.value, vm)
  114. self.value = None
  115. def get_vm_icon_path(self, vm):
  116. self.value = vm.label.index
  117. return vm.label.icon
  118. class VmNameItem (QtGui.QTableWidgetItem):
  119. def __init__(self, vm):
  120. super(VmNameItem, self).__init__()
  121. self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
  122. self.setText(vm.name)
  123. self.setTextAlignment(QtCore.Qt.AlignVCenter)
  124. self.qid = vm.qid
  125. def __lt__(self, other):
  126. if self.qid == 0:
  127. return True
  128. elif other.qid == 0:
  129. return False
  130. return super(VmNameItem, self).__lt__(other)
  131. class VmStatusIcon(QtGui.QLabel):
  132. def __init__(self, vm, parent=None):
  133. super(VmStatusIcon, self).__init__(parent)
  134. self.vm = vm
  135. self.set_on_icon()
  136. self.previous_power_state = self.vm.get_power_state()
  137. def update(self):
  138. self.previous_power_state = self.vm.get_power_state()
  139. if self.previous_power_state != self.vm.get_power_state():
  140. self.set_on_icon()
  141. self.previous_power_state = self.vm.get_power_state()
  142. def set_on_icon(self):
  143. if self.vm.get_power_state() == "Running":
  144. icon = QtGui.QIcon(":/on.png")
  145. elif self.vm.get_power_state() in ["Paused", "Suspended"]:
  146. icon = QtGui.QIcon(":/paused.png")
  147. elif self.vm.get_power_state() in ["Transient", "Halting", "Dying"]:
  148. icon = QtGui.QIcon(":/transient.png")
  149. else:
  150. icon = QtGui.QIcon(":/off.png")
  151. icon_sz = QtCore.QSize(row_height * 0.5, row_height * 0.5)
  152. icon_pixmap = icon.pixmap(icon_sz)
  153. self.setPixmap(icon_pixmap)
  154. self.setFixedSize(icon_sz)
  155. class VmInfoWidget (QtGui.QWidget):
  156. class VmInfoItem (QtGui.QTableWidgetItem):
  157. def __init__(self, upd_info_item, vm):
  158. super(VmInfoWidget.VmInfoItem, self).__init__()
  159. self.upd_info_item = upd_info_item
  160. self.vm = vm
  161. def __lt__(self, other):
  162. if self.vm.qid == 0:
  163. return True
  164. elif other.vm.qid == 0:
  165. return False
  166. self_val = self.upd_info_item.value
  167. other_val = other.upd_info_item.value
  168. if self.tableWidget().\
  169. horizontalHeader().sortIndicatorOrder() == update_order:
  170. # the result will be sorted by upd, sorting order: Ascending
  171. self_val += 1 if self.vm.is_running() else 0
  172. other_val += 1 if other.vm.is_running() else 0
  173. if self_val == other_val:
  174. return self.vm.name < other.vm.name
  175. else:
  176. return self_val > other_val
  177. elif self.tableWidget().\
  178. horizontalHeader().sortIndicatorOrder() == power_order:
  179. # the result will be sorted by power state,
  180. # sorting order: Descending
  181. self_val = -(self_val/10 +
  182. 10*(1 if self.vm.is_running() else 0))
  183. other_val = -(other_val/10 +
  184. 10*(1 if other.vm.is_running() else 0))
  185. if self_val == other_val:
  186. return self.vm.name < other.vm.name
  187. else:
  188. return self_val > other_val
  189. else:
  190. # it would be strange if this happened
  191. return
  192. def __init__(self, vm, parent=None):
  193. super(VmInfoWidget, self).__init__(parent)
  194. self.vm = vm
  195. layout = QtGui.QHBoxLayout()
  196. self.on_icon = VmStatusIcon(vm)
  197. self.upd_info = VmUpdateInfoWidget(vm, show_text=False)
  198. self.error_icon = VmIconWidget(":/warning.png")
  199. self.blk_icon = VmIconWidget(":/mount.png")
  200. self.rec_icon = VmIconWidget(":/mic.png")
  201. layout.addWidget(self.on_icon)
  202. layout.addWidget(self.upd_info)
  203. layout.addWidget(self.error_icon)
  204. layout.addItem(QtGui.QSpacerItem(0, 10,
  205. QtGui.QSizePolicy.Expanding,
  206. QtGui.QSizePolicy.Expanding))
  207. layout.addWidget(self.blk_icon)
  208. layout.addWidget(self.rec_icon)
  209. layout.setContentsMargins(5, 0, 5, 0)
  210. self.setLayout(layout)
  211. self.rec_icon.setVisible(False)
  212. self.blk_icon.setVisible(False)
  213. self.error_icon.setVisible(False)
  214. self.tableItem = self.VmInfoItem(self.upd_info.tableItem, vm)
  215. def update_vm_state(self, vm):
  216. self.on_icon.update()
  217. self.upd_info.update_outdated(vm)
  218. class VmTemplateItem (QtGui.QTableWidgetItem):
  219. def __init__(self, vm):
  220. super(VmTemplateItem, self).__init__()
  221. self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
  222. self.vm = vm
  223. if getattr(vm, 'template', None) is not None:
  224. self.setText(vm.template.name)
  225. else:
  226. font = QtGui.QFont()
  227. font.setStyle(QtGui.QFont.StyleItalic)
  228. self.setFont(font)
  229. self.setTextColor(QtGui.QColor("gray"))
  230. self.setText(vm.klass)
  231. self.setTextAlignment(QtCore.Qt.AlignVCenter)
  232. def __lt__(self, other):
  233. if self.vm.qid == 0:
  234. return True
  235. elif other.vm.qid == 0:
  236. return False
  237. elif self.text() == other.text():
  238. return self.vm.name < other.vm.name
  239. else:
  240. return super(VmTemplateItem, self).__lt__(other)
  241. class VmNetvmItem (QtGui.QTableWidgetItem):
  242. def __init__(self, vm):
  243. super(VmNetvmItem, self).__init__()
  244. self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
  245. self.vm = vm
  246. if getattr(vm, 'netvm', None) is None:
  247. self.setText("n/a")
  248. else:
  249. self.setText(vm.netvm.name)
  250. self.setTextAlignment(QtCore.Qt.AlignVCenter)
  251. def __lt__(self, other):
  252. if self.vm.qid == 0:
  253. return True
  254. elif other.vm.qid == 0:
  255. return False
  256. elif self.text() == other.text():
  257. return self.vm.name < other.vm.name
  258. else:
  259. return super(VmNetvmItem, self).__lt__(other)
  260. class VmInternalItem(QtGui.QTableWidgetItem):
  261. def __init__(self, vm):
  262. super(VmInternalItem, self).__init__()
  263. self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
  264. self.vm = vm
  265. self.internal = vm.features.get('internal', False)
  266. self.setText("Yes" if self.internal else "")
  267. def __lt__(self, other):
  268. if self.vm.qid == 0:
  269. return True
  270. elif other.vm.qid == 0:
  271. return False
  272. return super(VmInternalItem, self).__lt__(other)
  273. # features man qvm-features
  274. class VmUpdateInfoWidget(QtGui.QWidget):
  275. class VmUpdateInfoItem (QtGui.QTableWidgetItem):
  276. def __init__(self, value, vm):
  277. super(VmUpdateInfoWidget.VmUpdateInfoItem, self).__init__()
  278. self.value = 0
  279. self.vm = vm
  280. self.set_value(value)
  281. def set_value(self, value):
  282. if value in ("outdated", "to-be-outdated"):
  283. self.value = 30
  284. elif value == "update":
  285. self.value = 20
  286. else:
  287. self.value = 0
  288. def __lt__(self, other):
  289. if self.vm.qid == 0:
  290. return True
  291. elif other.vm.qid == 0:
  292. return False
  293. elif self.value == other.value:
  294. return self.vm.name < other.vm.name
  295. else:
  296. return self.value < other.value
  297. def __init__(self, vm, show_text=True, parent=None):
  298. super(VmUpdateInfoWidget, self).__init__(parent)
  299. layout = QtGui.QHBoxLayout()
  300. self.show_text = show_text
  301. if self.show_text:
  302. self.label = QtGui.QLabel("")
  303. layout.addWidget(self.label, alignment=QtCore.Qt.AlignCenter)
  304. else:
  305. self.icon = QtGui.QLabel("")
  306. layout.addWidget(self.icon, alignment=QtCore.Qt.AlignCenter)
  307. self.setLayout(layout)
  308. self.previous_outdated_state = None
  309. self.previous_update_recommended = None
  310. self.value = None
  311. self.tableItem = VmUpdateInfoWidget.VmUpdateInfoItem(self.value, vm)
  312. def update_outdated(self, vm):
  313. outdated_state = False
  314. try:
  315. for vol in vm.volumes:
  316. if vol.is_outdated():
  317. outdated_state = "outdated"
  318. break
  319. except AttributeError:
  320. pass
  321. if not outdated_state and getattr(vm, 'template', None)\
  322. and vm.template.is_running():
  323. outdated_state = "to-be-outdated"
  324. if outdated_state != self.previous_outdated_state:
  325. self.update_status_widget(outdated_state)
  326. self.previous_outdated_state = outdated_state
  327. updates_available = vm.features.get('updates-available', False)
  328. if updates_available != self.previous_update_recommended:
  329. self.update_status_widget("update" if updates_available else None)
  330. self.previous_update_recommended = updates_available
  331. def update_status_widget(self, state):
  332. self.value = state
  333. self.tableItem.set_value(state)
  334. if state == "update":
  335. label_text = "<font color=\"#CCCC00\">Check updates</font>"
  336. icon_path = ":/update-recommended.png"
  337. tooltip_text = self.tr("Updates pending!")
  338. elif state == "outdated":
  339. label_text = "<font color=\"red\">VM outdated</font>"
  340. icon_path = ":/outdated.png"
  341. tooltip_text = self.tr(
  342. "The VM must be restarted for its filesystem to reflect the "
  343. "template's recent committed changes.")
  344. elif state == "to-be-outdated":
  345. label_text = "<font color=\"#800000\">TemplateVM running</font>"
  346. icon_path = ":/to-be-outdated.png"
  347. tooltip_text = self.tr(
  348. "The TemplateVM must be stopped before changes from its "
  349. "current session can be picked up by this VM.")
  350. else:
  351. label_text = ""
  352. icon_path = None
  353. tooltip_text = None
  354. if self.show_text:
  355. self.label.setText(label_text)
  356. else:
  357. self.layout().removeWidget(self.icon)
  358. self.icon.deleteLater()
  359. if icon_path is not None:
  360. self.icon = VmIconWidget(icon_path, True, 0.7)
  361. self.icon.setToolTip(tooltip_text)
  362. else:
  363. self.icon = QtGui.QLabel(label_text)
  364. self.layout().addWidget(self.icon, alignment=QtCore.Qt.AlignCenter)
  365. class VmSizeOnDiskItem (QtGui.QTableWidgetItem):
  366. def __init__(self, vm):
  367. super(VmSizeOnDiskItem, self).__init__()
  368. self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
  369. self.vm = vm
  370. self.value = 0
  371. self.update()
  372. self.setTextAlignment(QtCore.Qt.AlignVCenter)
  373. def update(self):
  374. if self.vm.qid == 0:
  375. self.setText("n/a")
  376. else:
  377. self.value = 10
  378. self.value = self.vm.get_disk_utilization()/(1024*1024)
  379. self.setText(str(self.value) + " MiB")
  380. def __lt__(self, other):
  381. if self.vm.qid == 0:
  382. return True
  383. elif other.vm.qid == 0:
  384. return False
  385. elif self.value == other.value:
  386. return self.vm.name < other.vm.name
  387. else:
  388. return self.value < other.value
  389. class VmIPItem(QtGui.QTableWidgetItem):
  390. def __init__(self, vm):
  391. super(VmIPItem, self).__init__()
  392. self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
  393. self.vm = vm
  394. self.ip = getattr(self.vm, 'ip', None)
  395. self.setText(self.ip if self.ip is not None else 'n/a')
  396. def __lt__(self, other):
  397. if self.vm.qid == 0:
  398. return True
  399. elif other.vm.qid == 0:
  400. return False
  401. return super(VmIPItem, self).__lt__(other)
  402. class VmIncludeInBackupsItem(QtGui.QTableWidgetItem):
  403. def __init__(self, vm):
  404. super(VmIncludeInBackupsItem, self).__init__()
  405. self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
  406. self.vm = vm
  407. if getattr(self.vm, 'include_in_backups', None):
  408. self.setText("Yes")
  409. else:
  410. self.setText("")
  411. def __lt__(self, other):
  412. if self.vm.qid == 0:
  413. return True
  414. elif other.vm.qid == 0:
  415. return False
  416. elif self.vm.include_in_backups == other.vm.include_in_backups:
  417. return self.vm.name < other.vm.name
  418. else:
  419. return self.vm.include_in_backups < other.vm.include_in_backups
  420. class VmLastBackupItem(QtGui.QTableWidgetItem):
  421. def __init__(self, vm):
  422. super(VmLastBackupItem, self).__init__()
  423. self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
  424. self.vm = vm
  425. if getattr(self.vm, 'backup_timestamp', None):
  426. self.setText(self.vm.backup_timestamp)
  427. else:
  428. self.setText("")
  429. def __lt__(self, other):
  430. if self.vm.qid == 0:
  431. return True
  432. elif other.vm.qid == 0:
  433. return False
  434. elif self.vm.backup_timestamp == other.vm.backup_timestamp:
  435. return self.vm.name < other.vm.name
  436. elif not self.vm.backup_timestamp:
  437. return False
  438. elif not other.vm.backup_timestamp:
  439. return True
  440. else:
  441. return self.vm.backup_timestamp < other.vm.backup_timestamp