Viewer.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. '''
  2. '''
  3. from PyQt5 import uic
  4. from PyQt5.Qt import Qt, QEvent, QGraphicsScene, QPointF, QFileDialog, \
  5. QApplication, QMessageBox, QTreeWidgetItem, \
  6. QGraphicsTextItem, QGraphicsItem, QGraphicsRectItem, \
  7. QBrush, QColor, QGraphicsLineItem, QLineF, \
  8. QPen, QPainter, QSvgGenerator, QSize, QRect, QGraphicsItemGroup, \
  9. QGraphicsColorizeEffect, QFont
  10. from PyQt5.QtWidgets import QMainWindow, QGraphicsView
  11. from path import Path
  12. import core
  13. Ui_window, _ = uic.loadUiType(Path(__file__).parent / 'qt_viewer.ui')
  14. palette = {
  15. "Table": QColor(240, 240, 20),
  16. "Query": QColor(210, 50, 210),
  17. "Module": QColor(220, 10, 33),
  18. "Relation": QColor(122, 50, 209),
  19. "Report": QColor(25, 10, 220),
  20. "Form": QColor(10, 180, 220),
  21. "Macro": QColor(40, 220, 10),
  22. }
  23. v_spacing = 120
  24. cell_width = 150
  25. cell_spacing = 25
  26. class GraphicsObject(QGraphicsItemGroup):
  27. items = []
  28. def __init__(self, obj, parent=None):
  29. super(GraphicsObject, self).__init__(parent=parent)
  30. self.obj = obj
  31. self.links = []
  32. self._x = 0
  33. self._y = 0
  34. self.setFlag(QGraphicsItem.ItemIsMovable, True)
  35. self.setFlag(QGraphicsItem.ItemIsSelectable, True)
  36. self.setFlag(QGraphicsItem.ItemIsFocusable, True)
  37. self.setAcceptHoverEvents(True)
  38. self.label = QGraphicsTextItem()
  39. self.label.setTextWidth(cell_width)
  40. self.label.setZValue(2)
  41. self.setText()
  42. self.addToGroup(self.label)
  43. pen = QPen(palette[self.obj.type_].darker(150))
  44. pen.setWidth(2)
  45. self.rect = QGraphicsRectItem(self.label.boundingRect())
  46. self.rect.setPen(pen)
  47. self.rect.setBrush(palette[self.obj.type_].lighter(150))
  48. self.rect.setZValue(0)
  49. self.addToGroup(self.rect)
  50. self.topAnchorCoords = ((self.boundingRect().topLeft().x() + self.boundingRect().topRight().x() / 2), self.boundingRect().topLeft().y())
  51. self.bottomAnchorCoords = ((self.boundingRect().topLeft().x() + self.boundingRect().topRight().x() / 2), self.boundingRect().bottomLeft().y())
  52. self.top_anchor = QGraphicsRectItem(self.topAnchorCoords[0] - 3, self.topAnchorCoords[1] - 3, 6, 6)
  53. self.bottom_anchor = QGraphicsRectItem(self.bottomAnchorCoords[0] - 3, self.bottomAnchorCoords[1] - 3, 6, 6)
  54. for anchor in (self.top_anchor, self.bottom_anchor):
  55. anchor.setBrush(QBrush(QColor(255, 153, 51)))
  56. anchor.setZValue(1)
  57. self.addToGroup(anchor)
  58. effect = QGraphicsColorizeEffect()
  59. effect.setEnabled(False)
  60. self.rect.setGraphicsEffect(effect)
  61. GraphicsObject.items.append(self)
  62. def setText(self):
  63. self.label.setHtml(self.html())
  64. def html(self):
  65. return "[{}]<br/><b>{}</b>".format(self.obj.type_,
  66. self.obj.nom)
  67. def topAnchorCenter(self):
  68. return self.mapToScene(QPointF(*self.topAnchorCoords))
  69. def bottomAnchorCenter(self):
  70. return self.mapToScene(QPointF(*self.bottomAnchorCoords))
  71. def update(self):
  72. if self.pos() is not QPointF(self._x, self._y):
  73. self.setPos(QPointF(self._x, self._y))
  74. for l in self.links:
  75. l.update()
  76. def paint(self, *args, **kwargs):
  77. super(GraphicsObject, self).paint(*args, **kwargs)
  78. self._x, self._y = self.pos().x(), self.pos().y()
  79. self.update()
  80. def setShining(self, active):
  81. self.rect.graphicsEffect().setEnabled(active)
  82. def hoverEnterEvent(self, *args, **kwargs):
  83. self.setShining(True)
  84. def hoverLeaveEvent(self, *args, **kwargs):
  85. self.setShining(False)
  86. class GraphicsRootObject(GraphicsObject):
  87. def __init__(self, obj, parent=None):
  88. super(GraphicsRootObject, self).__init__(obj, parent=parent)
  89. self.level = 0
  90. self.deps = []
  91. self.refs = []
  92. self._dep_emprise = 0
  93. self._ref_emprise = 0
  94. pen = QPen(QColor("red"))
  95. pen.setWidth(2)
  96. self.rect.setPen(pen)
  97. def xleft(self):
  98. return 0
  99. def compute_coords(self):
  100. return 0, 0
  101. def dep_emprise(self):
  102. if not self._dep_emprise:
  103. self._dep_emprise = sum([d.dep_emprise() for d in self.deps]) if self.deps else (cell_width + 2 * cell_spacing)
  104. return self._dep_emprise
  105. def ref_emprise(self):
  106. if not self._ref_emprise:
  107. self._ref_emprise = sum([r.ref_emprise() for r in self.refs]) if self.refs else (cell_width + 2 * cell_spacing)
  108. return self._ref_emprise
  109. class GraphicsDepObject(GraphicsObject):
  110. def __init__(self, obj, parentItem, parent=None):
  111. super(GraphicsDepObject, self).__init__(obj, parent=parent)
  112. self.deps = []
  113. self.parentItem = parentItem
  114. if parentItem:
  115. parentItem.deps.append(self)
  116. self.level = parentItem.level + 1
  117. self._dep_emprise = 0
  118. def xleft(self):
  119. x0 = sum([n.dep_emprise() for n in self.parentItem.deps[0:self.parentItem.deps.index(self)]])
  120. return self.parentItem.xleft() + x0
  121. def compute_coords(self):
  122. x0 = sum([n.dep_emprise() for n in self.parentItem.deps[0:self.parentItem.deps.index(self)]])
  123. x = self.parentItem.xleft() + (0.5 * self.dep_emprise() - (cell_width / 2 + cell_spacing)) + x0
  124. y = v_spacing * self.level
  125. return x, y
  126. def dep_emprise(self):
  127. if not self._dep_emprise:
  128. self._dep_emprise = sum([d.dep_emprise() for d in self.deps]) if self.deps else (cell_width + 2 * cell_spacing)
  129. return self._dep_emprise
  130. class GraphicsCloneDepObject(GraphicsDepObject):
  131. def __init__(self, obj, childItem, parent=None):
  132. super(GraphicsCloneDepObject, self).__init__(obj, childItem, parent)
  133. self.bottom_anchor.setBrush(QColor("red"))
  134. self.clone_of = next((item for item in GraphicsObject.items if item.obj == obj
  135. and type(item) is GraphicsDepObject
  136. or type(item) is GraphicsRefObject))
  137. def html(self):
  138. return super(GraphicsCloneDepObject, self).html() + "<br/>(!) Clône"
  139. @property
  140. def deps(self):
  141. return []
  142. @deps.setter
  143. def deps(self, value):
  144. pass
  145. def hoverEnterEvent(self, *args, **kwargs):
  146. self.setShining(True)
  147. self.clone_of.setShining(True)
  148. def hoverLeaveEvent(self, *args, **kwargs):
  149. self.setShining(False)
  150. self.clone_of.setShining(False)
  151. class GraphicsRefObject(GraphicsObject):
  152. def __init__(self, obj, childItem, parent=None):
  153. super(GraphicsRefObject, self).__init__(obj, parent=parent)
  154. self.refs = []
  155. self.childItem = childItem
  156. if childItem:
  157. childItem.refs.append(self)
  158. self.level = childItem.level - 1
  159. self._ref_emprise = 0
  160. def xleft(self):
  161. x0 = sum([n.ref_emprise() for n in self.childItem.refs[0:self.childItem.refs.index(self)]])
  162. return self.childItem.xleft() + x0
  163. def compute_coords(self):
  164. x0 = sum([n.ref_emprise() for n in self.childItem.refs[0:self.childItem.refs.index(self)]])
  165. return self.childItem.xleft() + (0.5 * self.ref_emprise() - (cell_width / 2 + cell_spacing)) + x0, v_spacing * self.level
  166. def ref_emprise(self):
  167. if not self._ref_emprise:
  168. self._ref_emprise = sum([d.ref_emprise() for d in self.refs]) if self.refs else (cell_width + 2 * cell_spacing)
  169. return self._ref_emprise
  170. class GraphicsCloneRefObject(GraphicsRefObject):
  171. def __init__(self, obj, childItem, parent=None):
  172. super(GraphicsCloneRefObject, self).__init__(obj, childItem, parent)
  173. self.top_anchor.setBrush(QColor("red"))
  174. self.clone_of = next((item for item in GraphicsObject.items if item.obj == obj
  175. and type(item) is GraphicsDepObject
  176. or type(item) is GraphicsRefObject))
  177. def html(self):
  178. return super(GraphicsCloneRefObject, self).html() + "<br/>(!) Clône"
  179. @property
  180. def refs(self):
  181. return []
  182. @refs.setter
  183. def refs(self, value):
  184. pass
  185. def hoverEnterEvent(self, *args, **kwargs):
  186. self.clone_of.setShining(True)
  187. def hoverLeaveEvent(self, *args, **kwargs):
  188. self.clone_of.setShining(False)
  189. class GraphicsLink(QGraphicsLineItem):
  190. items = []
  191. def __init__(self, topGraphicsObject, bottomGraphicsObject, parent=None):
  192. self.topGraphicsObject = topGraphicsObject
  193. self.topGraphicsObject.links.append(self)
  194. self.bottomGraphicsObject = bottomGraphicsObject
  195. self.bottomGraphicsObject.links.append(self)
  196. super(QGraphicsLineItem, self).__init__(parent=parent)
  197. self.update()
  198. def update(self):
  199. line = QLineF(self.mapToScene(self.topGraphicsObject.bottomAnchorCenter()), self.mapToScene(self.bottomGraphicsObject.topAnchorCenter()))
  200. self.setLine(line)
  201. def select(self, active):
  202. pen = QPen()
  203. if active:
  204. pen.setColor(QColor("blue"))
  205. pen.setWidth(2)
  206. else:
  207. pen.setColor(QColor("black"))
  208. pen.setWidth(1)
  209. self.setPen(pen)
  210. class Viewer(QMainWindow):
  211. def __init__(self):
  212. super (Viewer, self).__init__()
  213. self.createWidgets()
  214. def createWidgets(self):
  215. self.ui = Ui_window()
  216. self.ui.setupUi(self)
  217. self.ui.progressBar.setVisible(False)
  218. self.ui.stackedWidget.setCurrentIndex(0)
  219. self.ui.btn_test.clicked.connect(self.test)
  220. self.ui.btn_select.clicked.connect(self.selectSourceDir)
  221. self.ui.btn_zoom_plus.clicked.connect(self.zoom_plus)
  222. self.ui.btn_zoom_minus.clicked.connect(self.zoom_minus)
  223. self.ui.btn_zoom_view.clicked.connect(self.fit_in_view)
  224. self.ui.btn_save.clicked.connect(self.save_to_png)
  225. self.ui.treeWidget.itemClicked.connect(self.treeItemSelected)
  226. self._title = "<unknown>"
  227. core.Analyse.report = self.update_progression
  228. self.ui.view.setViewportUpdateMode(QGraphicsView.BoundingRectViewportUpdate)
  229. self.ui.view.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
  230. self.ui.view.viewport().installEventFilter(self)
  231. self._scene = QGraphicsScene()
  232. self._scene.setItemIndexMethod(QGraphicsScene.BspTreeIndex)
  233. self.ui.view.setScene(self._scene)
  234. helpLabel = QGraphicsTextItem()
  235. helpLabel.setPlainText("Sélectionnez le répertoire contenant les sources de la base à analyser,\npuis sélectionnez dans la liste de droite un objet à partir duquel afficher les dépendances...")
  236. helpLabel.setTextWidth(600)
  237. font = QFont()
  238. font.setItalic(True)
  239. font.setWeight(63)
  240. helpLabel.setFont(font)
  241. self._scene.addItem(helpLabel)
  242. def eventFilter(self, _, event):
  243. if event.type() == QEvent.Wheel:
  244. if event.angleDelta().y() > 0:
  245. self.zoom_plus()
  246. elif event.angleDelta().y() < 0:
  247. self.zoom_minus()
  248. return True
  249. return False
  250. def fit_in_view(self):
  251. rect = self._scene.itemsBoundingRect()
  252. rect.adjust(-50, -50, 50, 50)
  253. self._scene.setSceneRect(rect)
  254. self.ui.view.fitInView(self._scene.sceneRect(), Qt.KeepAspectRatio)
  255. def zoom_plus(self):
  256. self.ui.view.scale(1.2, 1.2)
  257. def zoom_minus(self):
  258. self.ui.view.scale(0.8, 0.8)
  259. def save_to_png(self):
  260. fileName, _ = QFileDialog.getSaveFileName(self, "Save File", "", "Svg File (*.svg)")
  261. if not fileName:
  262. return
  263. gen = QSvgGenerator()
  264. gen.setFileName(fileName)
  265. gen.setSize(QSize(1000, 1000))
  266. gen.setViewBox(QRect(0, 0, 1000, 1000))
  267. gen.setTitle("Access Analyser")
  268. gen.setDescription("Access Analyser Report for {}".format(self._title))
  269. painter = QPainter(gen)
  270. self._scene.render(painter)
  271. painter.end()
  272. def update_progression(self, i, total, msg=""):
  273. self.ui.progressBar.setMaximum(total)
  274. self.ui.progressBar.setValue(i)
  275. if msg:
  276. self.ui.txtPanel.append(msg)
  277. QApplication.processEvents()
  278. def selectSourceDir(self):
  279. source_dir = QFileDialog.getExistingDirectory(self, "Sélectionner le répertoire des sources", "", QFileDialog.ShowDirsOnly)
  280. if not source_dir:
  281. return
  282. self.run(source_dir)
  283. def run(self, source_dir):
  284. source_dir = Path(source_dir)
  285. self._title = source_dir
  286. self.ui.progressBar.setVisible(True)
  287. self.ui.lbl_repertoire.setText(source_dir)
  288. self.ui.txtPanel.clear()
  289. self.ui.treeWidget.clear()
  290. if self.ui.radioRefsOnly.isChecked():
  291. mode = core.REFS_ONLY
  292. elif self.ui.radioDepsOnly.isChecked():
  293. mode = core.DEPS_ONLY
  294. else:
  295. mode = core.DEPS_AND_REFS
  296. print(mode)
  297. QApplication.setOverrideCursor(Qt.WaitCursor)
  298. core.Analyse.run(source_dir, mode)
  299. QApplication.restoreOverrideCursor()
  300. if core.Analyse.duplicated_names:
  301. QMessageBox.warning(self, "Risque d'instabilités", "Attention! Des doublons ont été trouvés dans les noms des objets suivants:\n{}".format(", ".join(core.Analyse.duplicated_names)))
  302. QMessageBox.information(self, "test", "{} objets chargés".format(len(core.Analyse.objects)))
  303. self.ui.progressBar.setVisible(False)
  304. self.ui.stackedWidget.setCurrentIndex(1)
  305. self.ui.txtPanel.clear()
  306. topitem = QTreeWidgetItem()
  307. topitem.setText(0, "Objets")
  308. self.ui.treeWidget.addTopLevelItem(topitem)
  309. groupes = {}
  310. for index, obj in enumerate(core.Analyse.objects):
  311. if not obj.type_ in groupes:
  312. item = QTreeWidgetItem()
  313. item.setText(0, obj.type_)
  314. groupes[obj.type_] = item
  315. topitem.addChild(item)
  316. item = QTreeWidgetItem()
  317. item.setText(0, obj.nom)
  318. item.setData(1, 0, index)
  319. groupes[obj.type_].addChild(item)
  320. self.ui.treeWidget.setColumnHidden(1, True)
  321. self.ui.treeWidget.expandToDepth(1)
  322. def clear_scene(self):
  323. self._scene.clear()
  324. GraphicsObject.items = []
  325. def maj_scene_with(self, root_object):
  326. self.clear_scene()
  327. root = GraphicsRootObject(root_object)
  328. self._scene.addItem(root)
  329. def _addDeps(go):
  330. for dep in go.obj.deps:
  331. if dep not in [go.obj for go in GraphicsObject.items]:
  332. gdep = GraphicsDepObject(dep, go)
  333. else:
  334. gdep = GraphicsCloneDepObject(dep, go)
  335. self._scene.addItem(gdep)
  336. link = GraphicsLink(go, gdep)
  337. self._scene.addItem(link)
  338. if dep.deps:
  339. _addDeps(gdep)
  340. _addDeps(root)
  341. def _addRefs(go):
  342. for ref in go.obj.refs:
  343. if ref not in [go.obj for go in GraphicsObject.items]:
  344. gref = GraphicsRefObject(ref, go)
  345. else:
  346. gref = GraphicsCloneRefObject(ref, go)
  347. self._scene.addItem(gref)
  348. link = GraphicsLink(gref, go)
  349. self._scene.addItem(link)
  350. if ref.refs:
  351. _addRefs(gref)
  352. _addRefs(root)
  353. for item in GraphicsObject.items:
  354. item._x, item._y = item.compute_coords()
  355. if isinstance(item, GraphicsDepObject):
  356. item._x -= (root.dep_emprise() - (cell_width + 2 * cell_spacing)) / 2
  357. if isinstance(item, GraphicsRefObject):
  358. item._x -= (root.ref_emprise() - (cell_width + 2 * cell_spacing)) / 2
  359. item.update()
  360. self.ui.btn_save.setEnabled(True)
  361. self.fit_in_view()
  362. def treeItemSelected(self, item):
  363. index = item.data(1, 0)
  364. if not index:
  365. return
  366. obj = core.Analyse.objects[index]
  367. self.maj_scene_with(obj)
  368. def test(self):
  369. self.run(Path(__file__).parent / r"test\source")
  370. def keyPressEvent(self, e):
  371. if e.key() == Qt.Key_Control:
  372. self.ui.view.setDragMode(QGraphicsView.ScrollHandDrag)
  373. e.accept()
  374. def keyReleaseEvent(self, e):
  375. if e.key() == Qt.Key_Control:
  376. self.ui.view.setDragMode(QGraphicsView.RubberBandDrag)