Viewer.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789
  1. '''
  2. @author: olivier.massot
  3. '''
  4. from PyQt5 import uic, QtCore
  5. from PyQt5.Qt import Qt, QEvent, QGraphicsScene, QPointF, QFileDialog, \
  6. QApplication, QMessageBox, QTreeWidgetItem, \
  7. QGraphicsTextItem, QGraphicsItem, QGraphicsRectItem, \
  8. QBrush, QColor, QGraphicsLineItem, QLineF, \
  9. QPen, QPainter, QSvgGenerator, QSize, QRect, QGraphicsItemGroup, \
  10. QGraphicsColorizeEffect, QFont, QDialog, QTableWidgetItem, \
  11. QListWidgetItem, QInputDialog, QIcon, QPixmap, pyqtSignal, QObject, \
  12. QTreeWidgetItemIterator
  13. from PyQt5.QtWidgets import QMainWindow, QGraphicsView
  14. from path import Path
  15. from core import ModuleObject
  16. import core
  17. Ui_window, _ = uic.loadUiType(Path(__file__).parent / 'qt_viewer.ui')
  18. Ui_details, _ = uic.loadUiType(Path(__file__).parent / 'qt_details.ui')
  19. Ui_select, _ = uic.loadUiType(Path(__file__).parent / 'qt_select_object.ui')
  20. palette = {
  21. "Table": QColor(240, 240, 20),
  22. "Query": QColor(210, 50, 210),
  23. "Module": QColor(220, 10, 33),
  24. "Relation": QColor(122, 50, 209),
  25. "Report": QColor(25, 10, 220),
  26. "Form": QColor(10, 180, 220),
  27. "Macro": QColor(40, 220, 10),
  28. }
  29. v_spacing = 120
  30. cell_width = 150
  31. cell_spacing = 25
  32. class GraphicsObject(QGraphicsItemGroup):
  33. items = []
  34. def __init__(self, obj, parent=None):
  35. super(GraphicsObject, self).__init__(parent=parent)
  36. self.obj = obj
  37. self.links = []
  38. self._x = 0
  39. self._y = 0
  40. self.setFlag(QGraphicsItem.ItemIsMovable, True)
  41. self.setFlag(QGraphicsItem.ItemIsSelectable, True)
  42. self.setFlag(QGraphicsItem.ItemIsFocusable, True)
  43. self.setAcceptHoverEvents(True)
  44. self.label = QGraphicsTextItem()
  45. self.label.setTextWidth(cell_width)
  46. self.label.setZValue(2)
  47. self.setText()
  48. self.addToGroup(self.label)
  49. pen = QPen(palette[self.obj.type_].darker(150))
  50. pen.setWidth(2)
  51. self.rect = QGraphicsRectItem(self.label.boundingRect())
  52. self.rect.setPen(pen)
  53. self.rect.setBrush(palette[self.obj.type_].lighter(150))
  54. self.rect.setZValue(0)
  55. self.addToGroup(self.rect)
  56. self.topAnchorCoords = ((self.boundingRect().topLeft().x() + self.boundingRect().topRight().x() / 2), self.boundingRect().topLeft().y())
  57. self.bottomAnchorCoords = ((self.boundingRect().topLeft().x() + self.boundingRect().topRight().x() / 2), self.boundingRect().bottomLeft().y())
  58. self.top_anchor = QGraphicsRectItem(self.topAnchorCoords[0] - 3, self.topAnchorCoords[1] - 3, 6, 6)
  59. self.bottom_anchor = QGraphicsRectItem(self.bottomAnchorCoords[0] - 3, self.bottomAnchorCoords[1] - 3, 6, 6)
  60. for anchor in (self.top_anchor, self.bottom_anchor):
  61. anchor.setBrush(QBrush(QColor(255, 153, 51)))
  62. anchor.setZValue(1)
  63. self.addToGroup(anchor)
  64. effect = QGraphicsColorizeEffect()
  65. effect.setColor(QColor(255, 255, 255))
  66. effect.setStrength(0.35)
  67. effect.setEnabled(False)
  68. self.rect.setGraphicsEffect(effect)
  69. self.setToolTip("[{}] '{}'".format(self.obj.type_, self.obj.name_))
  70. GraphicsObject.items.append(self)
  71. def setText(self):
  72. self.label.setHtml(self.html())
  73. def html(self):
  74. return "[{}]<br/><b>{}</b>".format(self.obj.type_,
  75. self.obj.name_)
  76. def topAnchorCenter(self):
  77. return self.mapToScene(QPointF(*self.topAnchorCoords))
  78. def bottomAnchorCenter(self):
  79. return self.mapToScene(QPointF(*self.bottomAnchorCoords))
  80. def update(self):
  81. if self.pos() is not QPointF(self._x, self._y):
  82. self.setPos(QPointF(self._x, self._y))
  83. for l in self.links:
  84. l.update()
  85. def paint(self, *args, **kwargs):
  86. super(GraphicsObject, self).paint(*args, **kwargs)
  87. self._x, self._y = self.pos().x(), self.pos().y()
  88. self.update()
  89. def setShining(self, active):
  90. self.rect.graphicsEffect().setEnabled(active)
  91. def hoverEnterEvent(self, _):
  92. self.setShining(True)
  93. def hoverLeaveEvent(self, _):
  94. self.setShining(False)
  95. def mouseDoubleClickEvent(self, _):
  96. self.scene().itemDoubleClicked.emit(self)
  97. class GraphicsRootObject(GraphicsObject):
  98. def __init__(self, obj, parent=None):
  99. super(GraphicsRootObject, self).__init__(obj, parent=parent)
  100. self.level = 0
  101. self.deps = []
  102. self.refs = []
  103. self._dep_emprise = 0
  104. self._ref_emprise = 0
  105. pen = QPen(QColor("red"))
  106. pen.setWidth(2)
  107. self.rect.setPen(pen)
  108. def xleft(self):
  109. return 0
  110. def compute_coords(self):
  111. return 0, 0
  112. def dep_emprise(self):
  113. if not self._dep_emprise:
  114. self._dep_emprise = sum([d.dep_emprise() for d in self.deps]) if self.deps else (cell_width + 2 * cell_spacing)
  115. return self._dep_emprise
  116. def ref_emprise(self):
  117. if not self._ref_emprise:
  118. self._ref_emprise = sum([r.ref_emprise() for r in self.refs]) if self.refs else (cell_width + 2 * cell_spacing)
  119. return self._ref_emprise
  120. class GraphicsDepObject(GraphicsObject):
  121. def __init__(self, obj, parentItem, parent=None):
  122. super(GraphicsDepObject, self).__init__(obj, parent=parent)
  123. self.deps = []
  124. self.parentItem = parentItem
  125. if parentItem:
  126. parentItem.deps.append(self)
  127. self.level = parentItem.level + 1
  128. self._dep_emprise = 0
  129. def xleft(self):
  130. x0 = sum([n.dep_emprise() for n in self.parentItem.deps[0:self.parentItem.deps.index(self)]])
  131. return self.parentItem.xleft() + x0
  132. def compute_coords(self):
  133. x0 = sum([n.dep_emprise() for n in self.parentItem.deps[0:self.parentItem.deps.index(self)]])
  134. x = self.parentItem.xleft() + (0.5 * self.dep_emprise() - (cell_width / 2 + cell_spacing)) + x0
  135. y = v_spacing * self.level
  136. return x, y
  137. def dep_emprise(self):
  138. if not self._dep_emprise:
  139. self._dep_emprise = sum([d.dep_emprise() for d in self.deps]) if self.deps else (cell_width + 2 * cell_spacing)
  140. return self._dep_emprise
  141. class GraphicsCloneDepObject(GraphicsDepObject):
  142. def __init__(self, obj, childItem, parent=None):
  143. super(GraphicsCloneDepObject, self).__init__(obj, childItem, parent)
  144. self.bottom_anchor.setBrush(QColor("red"))
  145. self.clone_of = next((item for item in GraphicsObject.items if item.obj is obj
  146. and (type(item) in (GraphicsRootObject, GraphicsDepObject, GraphicsRefObject))))
  147. self.setToolTip("L'objet [{}] '{}' a déjà été placé sur l'arbre".format(self.obj.type_, self.obj.name_))
  148. def html(self):
  149. return "* Clone *<br/>" + super(GraphicsCloneDepObject, self).html()
  150. @property
  151. def deps(self):
  152. return []
  153. @deps.setter
  154. def deps(self, value):
  155. pass
  156. def hoverEnterEvent(self, _):
  157. self.setShining(True)
  158. self.clone_of.setShining(True)
  159. def hoverLeaveEvent(self, _):
  160. self.setShining(False)
  161. self.clone_of.setShining(False)
  162. class GraphicsRefObject(GraphicsObject):
  163. def __init__(self, obj, childItem, parent=None):
  164. super(GraphicsRefObject, self).__init__(obj, parent=parent)
  165. self.refs = []
  166. self.childItem = childItem
  167. if childItem:
  168. childItem.refs.append(self)
  169. self.level = childItem.level - 1
  170. self._ref_emprise = 0
  171. def xleft(self):
  172. x0 = sum([n.ref_emprise() for n in self.childItem.refs[0:self.childItem.refs.index(self)]])
  173. return self.childItem.xleft() + x0
  174. def compute_coords(self):
  175. x0 = sum([n.ref_emprise() for n in self.childItem.refs[0:self.childItem.refs.index(self)]])
  176. return self.childItem.xleft() + (0.5 * self.ref_emprise() - (cell_width / 2 + cell_spacing)) + x0, v_spacing * self.level
  177. def ref_emprise(self):
  178. if not self._ref_emprise:
  179. self._ref_emprise = sum([d.ref_emprise() for d in self.refs]) if self.refs else (cell_width + 2 * cell_spacing)
  180. return self._ref_emprise
  181. class GraphicsCloneRefObject(GraphicsRefObject):
  182. def __init__(self, obj, childItem, parent=None):
  183. super(GraphicsCloneRefObject, self).__init__(obj, childItem, parent)
  184. self.top_anchor.setBrush(QColor("red"))
  185. self.clone_of = next((item for item in GraphicsObject.items if item.obj is obj
  186. and (type(item) in (GraphicsRootObject, GraphicsDepObject, GraphicsRefObject))))
  187. self.setToolTip("L'objet [{}] '{}' a déjà été placé sur l'arbre".format(self.obj.type_, self.obj.name_))
  188. def html(self):
  189. return "* Clone *<br/>" + super(GraphicsCloneRefObject, self).html()
  190. @property
  191. def refs(self):
  192. return []
  193. @refs.setter
  194. def refs(self, value):
  195. pass
  196. def hoverEnterEvent(self, _):
  197. self.setShining(True)
  198. self.clone_of.setShining(True)
  199. def hoverLeaveEvent(self, _):
  200. self.setShining(False)
  201. self.clone_of.setShining(False)
  202. class GraphicsLink(QGraphicsLineItem):
  203. items = []
  204. def __init__(self, topGraphicsObject, bottomGraphicsObject, parent=None):
  205. self.topGraphicsObject = topGraphicsObject
  206. self.topGraphicsObject.links.append(self)
  207. self.bottomGraphicsObject = bottomGraphicsObject
  208. self.bottomGraphicsObject.links.append(self)
  209. super(QGraphicsLineItem, self).__init__(parent=parent)
  210. self.update()
  211. def update(self):
  212. line = QLineF(self.mapToScene(self.topGraphicsObject.bottomAnchorCenter()), self.mapToScene(self.bottomGraphicsObject.topAnchorCenter()))
  213. self.setLine(line)
  214. class GraphicsScene(QGraphicsScene):
  215. itemDoubleClicked = pyqtSignal(object)
  216. class Viewer(QMainWindow):
  217. def __init__(self):
  218. super (Viewer, self).__init__()
  219. self.createWidgets()
  220. def createWidgets(self):
  221. self.ui = Ui_window()
  222. self.ui.setupUi(self)
  223. self.ui.progressBar.setVisible(False)
  224. self.ui.stackedWidget.setCurrentIndex(0)
  225. self.ui.stackedView.setCurrentIndex(0)
  226. self.ui.btn_select.clicked.connect(self.selectSourceDir)
  227. self.ui.btn_zoom_plus.clicked.connect(self.zoom_plus)
  228. self.ui.btn_zoom_minus.clicked.connect(self.zoom_minus)
  229. self.ui.btn_zoom_view.clicked.connect(self.fit_in_view)
  230. self.ui.btn_zoom_center.clicked.connect(self.zoom_center)
  231. self.ui.btn_svg.clicked.connect(self.to_svg)
  232. self.ui.btn_save.clicked.connect(self.save)
  233. self.ui.btn_load.clicked.connect(self.open)
  234. self.ui.treeWidget.itemClicked.connect(self.treeItemSelected)
  235. self.ui.btn_edit_item.clicked.connect(self.edit_selected_item)
  236. self.ui.btn_graphic_view.clicked.connect(self.switch_graphic_view)
  237. self.ui.btn_table_view.clicked.connect(self.switch_table_view)
  238. self.ui.searchBoxDeps.textChanged.connect(self.filterTblDeps)
  239. self.ui.searchBoxRefs.textChanged.connect(self.filterTblRefs)
  240. self.ui.btnSearchTree.clicked.connect(self.searchTree)
  241. self.ui.searchBoxTree.returnPressed.connect(self.searchTree)
  242. self._title = "<unknown>"
  243. core.Analyse.report = self.update_progression
  244. self.ui.view.setViewportUpdateMode(QGraphicsView.BoundingRectViewportUpdate)
  245. self.ui.view.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
  246. self.ui.view.viewport().installEventFilter(self)
  247. self._scene = GraphicsScene()
  248. self._scene.setItemIndexMethod(QGraphicsScene.BspTreeIndex)
  249. self._scene.itemDoubleClicked.connect(self.graphicsObjectDoubleClicked)
  250. self.ui.view.setScene(self._scene)
  251. helpLabel = QGraphicsTextItem()
  252. 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...")
  253. helpLabel.setTextWidth(600)
  254. font = QFont()
  255. font.setItalic(True)
  256. font.setWeight(63)
  257. helpLabel.setFont(font)
  258. self._scene.addItem(helpLabel)
  259. def eventFilter(self, _, event):
  260. if event.type() == QEvent.Wheel:
  261. if event.angleDelta().y() > 0:
  262. self.zoom_plus()
  263. elif event.angleDelta().y() < 0:
  264. self.zoom_minus()
  265. return True
  266. return False
  267. def fit_in_view(self):
  268. rect = self._scene.itemsBoundingRect()
  269. rect.adjust(-50, -50, 50, 50)
  270. self._scene.setSceneRect(rect)
  271. self.ui.view.fitInView(self._scene.sceneRect(), Qt.KeepAspectRatio)
  272. def zoom_plus(self):
  273. self.ui.view.scale(1.2, 1.2)
  274. def zoom_minus(self):
  275. self.ui.view.scale(0.8, 0.8)
  276. def zoom_center(self):
  277. root = next((item for item in GraphicsObject.items if type(item) is GraphicsRootObject))
  278. rect = root.boundingRect()
  279. rect.adjust(-200, -200, 200, 200)
  280. self.ui.view.fitInView(rect, Qt.KeepAspectRatio)
  281. def switch_graphic_view(self):
  282. self.ui.stackedView.setCurrentIndex(0)
  283. def switch_table_view(self):
  284. self.ui.stackedView.setCurrentIndex(1)
  285. def to_svg(self):
  286. fileName, _ = QFileDialog.getSaveFileName(self, "Save File", "", "Svg File (*.svg)")
  287. if not fileName:
  288. return
  289. gen = QSvgGenerator()
  290. gen.setFileName(fileName)
  291. gen.setSize(QSize(1000, 1000))
  292. gen.setViewBox(QRect(0, 0, 1000, 1000))
  293. gen.setTitle("Access Analyser")
  294. gen.setDescription("Access Analyser Report for {}".format(self._title))
  295. painter = QPainter(gen)
  296. self._scene.render(painter)
  297. painter.end()
  298. def save(self):
  299. if not core.Analyse.objects:
  300. return
  301. filepath, _ = QFileDialog.getSaveFileName(self, 'Enregistrer sous', '', "Access Analyser Files (*.acan)")
  302. if filepath:
  303. core.Analyse.dump_to(filepath)
  304. def open(self):
  305. filepath, _ = QFileDialog.getOpenFileName(self, 'Charger les données de', '', "Access Analyser Files (*.acan)")
  306. if filepath:
  307. core.Analyse.load_from(filepath)
  308. self._title = Path(filepath).name
  309. self.load()
  310. def update_progression(self, i, total, msg=""):
  311. self.ui.progressBar.setMaximum(total)
  312. self.ui.progressBar.setValue(i)
  313. if msg:
  314. self.ui.txtPanel.append(msg)
  315. QApplication.processEvents()
  316. def selectSourceDir(self):
  317. source_dir = QFileDialog.getExistingDirectory(self, "Sélectionner le répertoire des sources", "", QFileDialog.ShowDirsOnly)
  318. if not source_dir:
  319. return
  320. self.run(source_dir)
  321. def run(self, source_dir):
  322. source_dir = Path(source_dir)
  323. self._title = source_dir
  324. self.ui.stackedWidget.setCurrentIndex(0)
  325. self.ui.progressBar.setVisible(True)
  326. self.ui.txtPanel.clear()
  327. core.Analyse.run(source_dir)
  328. self.ui.progressBar.setVisible(False)
  329. self.ui.txtPanel.clear()
  330. self.load()
  331. QMessageBox.information(self, "Analyse", "Analyse terminée: {} objets chargés en mémoire".format(len(core.Analyse.objects)))
  332. def load(self):
  333. self.clear_scene()
  334. self.ui.treeWidget.clear()
  335. self.ui.stackedWidget.setCurrentIndex(1)
  336. topitem = QTreeWidgetItem()
  337. topitem.setText(0, "Objets")
  338. self.ui.treeWidget.addTopLevelItem(topitem)
  339. groupes = {}
  340. for index, obj in enumerate(core.Analyse.objects):
  341. if not obj.type_ in groupes:
  342. item = QTreeWidgetItem()
  343. item.setText(0, obj.type_)
  344. groupes[obj.type_] = item
  345. topitem.addChild(item)
  346. item = QTreeWidgetItem()
  347. if any(len(m.warnings) > 0 for m in obj.mentions):
  348. item.setIcon(0, QIcon(QPixmap(core.here / "rsc\\warning_16.png")))
  349. item.setText(1, obj.name_)
  350. item.setData(2, 0, index)
  351. groupes[obj.type_].addChild(item)
  352. self.ui.treeWidget.setColumnHidden(2, True)
  353. self.ui.treeWidget.expandToDepth(1)
  354. self.ui.btn_save.setEnabled(True)
  355. def enable_view_buttons(self, enable):
  356. self.ui.btn_zoom_plus.setEnabled(enable)
  357. self.ui.btn_zoom_center.setEnabled(enable)
  358. self.ui.btn_zoom_view.setEnabled(enable)
  359. self.ui.btn_zoom_minus.setEnabled(enable)
  360. self.ui.btn_graphic_view.setEnabled(enable)
  361. self.ui.btn_table_view.setEnabled(enable)
  362. def clear_scene(self):
  363. self._scene.clear()
  364. GraphicsObject.items = []
  365. self.enable_view_buttons(False)
  366. def update_view(self, root_object=None):
  367. if root_object is None:
  368. root_object = next((go.obj for go in GraphicsObject.items if type(go) is GraphicsRootObject))
  369. self.switch_graphic_view()
  370. self.clear_scene()
  371. root = GraphicsRootObject(root_object)
  372. self._scene.addItem(root)
  373. def _addDeps(go):
  374. for dep in go.obj.deps:
  375. if dep not in [go.obj for go in GraphicsObject.items]:
  376. gdep = GraphicsDepObject(dep, go)
  377. if dep.deps:
  378. _addDeps(gdep)
  379. else:
  380. gdep = GraphicsCloneDepObject(dep, go)
  381. self._scene.addItem(gdep)
  382. link = GraphicsLink(go, gdep)
  383. self._scene.addItem(link)
  384. _addDeps(root)
  385. def _addRefs(go):
  386. for ref in go.obj.refs:
  387. if ref not in [go.obj for go in GraphicsObject.items]:
  388. gref = GraphicsRefObject(ref, go)
  389. if ref.refs:
  390. _addRefs(gref)
  391. else:
  392. gref = GraphicsCloneRefObject(ref, go)
  393. self._scene.addItem(gref)
  394. link = GraphicsLink(gref, go)
  395. self._scene.addItem(link)
  396. _addRefs(root)
  397. for item in GraphicsObject.items:
  398. item._x, item._y = item.compute_coords()
  399. if isinstance(item, GraphicsDepObject):
  400. item._x -= (root.dep_emprise() - (cell_width + 2 * cell_spacing)) / 2
  401. if isinstance(item, GraphicsRefObject):
  402. item._x -= (root.ref_emprise() - (cell_width + 2 * cell_spacing)) / 2
  403. item.update()
  404. self.ui.tblDeps.clearContents()
  405. self.ui.tblRefs.clearContents()
  406. self.ui.tblDeps.setRowCount(0)
  407. self.ui.tblRefs.setRowCount(0)
  408. self.ui.lblRootTitle.setText("{}: {}".format(root_object.type_, root_object.name_))
  409. for item in GraphicsObject.items:
  410. if type(item) is GraphicsDepObject:
  411. row = self.ui.tblDeps.rowCount()
  412. self.ui.tblDeps.insertRow(row)
  413. self.ui.tblDeps.setItem(row, 0, QTableWidgetItem(item.obj.type_))
  414. self.ui.tblDeps.setItem(row, 1, QTableWidgetItem(item.obj.name_))
  415. if type(item) is GraphicsRefObject:
  416. row = self.ui.tblRefs.rowCount()
  417. self.ui.tblRefs.insertRow(row)
  418. self.ui.tblRefs.setItem(row, 0, QTableWidgetItem(item.obj.type_))
  419. self.ui.tblRefs.setItem(row, 1, QTableWidgetItem(item.obj.name_))
  420. self.enable_view_buttons(True)
  421. self.fit_in_view()
  422. def treeItemSelected(self, item):
  423. index = item.data(2, 0)
  424. if index is None:
  425. self.ui.btn_edit_item.setEnabled(False)
  426. return
  427. obj = core.Analyse.objects[index]
  428. self.update_view(obj)
  429. self.ui.btn_edit_item.setEnabled(True)
  430. def filterTblDeps(self, filterStr):
  431. for row in range(self.ui.tblDeps.rowCount()):
  432. content = "{}{}".format(self.ui.tblDeps.item(row, 0).text(), self.ui.tblDeps.item(row, 1).text())
  433. self.ui.tblDeps.setRowHidden(row, filterStr.lower() not in content.lower())
  434. def filterTblRefs(self, filterStr):
  435. for row in range(self.ui.tblRefs.rowCount()):
  436. content = "{}{}".format(self.ui.tblRefs.item(row, 0).text(), self.ui.tblRefs.item(row, 1).text())
  437. self.ui.tblRefs.setRowHidden(row, filterStr.lower() not in content.lower())
  438. def searchTree(self):
  439. filterStr = self.ui.searchBoxTree.text()
  440. if not filterStr:
  441. return
  442. items = self.ui.treeWidget.findItems(filterStr, Qt.MatchContains | Qt.MatchRecursive, 1)
  443. if not items:
  444. QMessageBox.warning(self, "Recherche", "Aucun résultat pour '{}'".format(filterStr))
  445. try:
  446. start = items.index(self.ui.treeWidget.currentItem()) + 1
  447. except ValueError:
  448. start = 0
  449. match = (items[start:] + items[:start])[0]
  450. self.ui.treeWidget.setCurrentItem(match)
  451. self.ui.treeWidget.scrollTo(self.ui.treeWidget.indexFromItem(match, 1))
  452. def edit(self, obj):
  453. dlg = DetailsDialog(obj, self)
  454. dlg.show()
  455. r = dlg.exec_()
  456. if r:
  457. core.Analyse.build_trees()
  458. self.update_view()
  459. item = self.find_tree_item(obj)
  460. if any(len(m.warnings) > 0 for m in obj.mentions):
  461. item.setIcon(0, QIcon(QPixmap(core.here / "rsc\\warning_16.png")))
  462. else:
  463. item.setIcon(0, QIcon())
  464. def find_tree_item(self, obj):
  465. return next((item for item in self.ui.treeWidget.findItems("", Qt.MatchContains | Qt.MatchRecursive, 1) if item.data(2, 0) == core.Analyse.objects.index(obj)))
  466. def edit_selected_item(self):
  467. index = self.ui.treeWidget.currentItem().data(2, 0)
  468. obj = core.Analyse.objects[index]
  469. self.edit(obj)
  470. def graphicsObjectDoubleClicked(self, go):
  471. self.edit(go.obj)
  472. def keyPressEvent(self, e):
  473. if e.key() == Qt.Key_S and e.modifiers() & Qt.ControlModifier:
  474. self.save()
  475. e.accept()
  476. if e.key() == Qt.Key_Control:
  477. self.ui.view.setDragMode(QGraphicsView.ScrollHandDrag)
  478. e.accept()
  479. def keyReleaseEvent(self, e):
  480. if e.key() == Qt.Key_Control:
  481. self.ui.view.setDragMode(QGraphicsView.RubberBandDrag)
  482. class DetailsDialog(QDialog):
  483. def __init__(self, obj, parent=None):
  484. self.obj = obj
  485. super (DetailsDialog, self).__init__(parent)
  486. self.createWidgets()
  487. def createWidgets(self):
  488. self.ui = Ui_details()
  489. self.ui.setupUi(self)
  490. self.ui.tbl_mentions.verticalHeader().sectionDoubleClicked.connect(self.verticalHeaderDoubleClicked)
  491. self.ui.tbl_mentions.itemClicked.connect(self.itemClicked)
  492. self.ui.btn_add.clicked.connect(self.add_mention)
  493. self.ui.btn_edit.clicked.connect(self.edit_mention)
  494. self.ui.btn_del.clicked.connect(self.del_mention)
  495. self.ui.btn_ok.clicked.connect(self.ok)
  496. self.ui.btn_cancel.clicked.connect(self.ok)
  497. self.ui.lbl_title.setText("{}: {}".format(self.obj.type_, self.obj.name_))
  498. self.ui.tbl_mentions.hideColumn(0)
  499. self.ui.tbl_mentions.setColumnWidth(1, 50)
  500. self.ui.tbl_mentions.setColumnWidth(2, 70)
  501. self.ui.tbl_mentions.setColumnWidth(3, 150)
  502. self.load_table()
  503. def load_table(self):
  504. self.ui.tbl_mentions.setSortingEnabled(False)
  505. self.ui.tbl_mentions.clearContents()
  506. self.ui.tbl_mentions.setRowCount(len(self.obj.mentions))
  507. for index, mention in enumerate(self.obj.mentions):
  508. item = QTableWidgetItem("")
  509. if len(mention.warnings) > 0:
  510. item.setIcon(QIcon(QPixmap(core.here / "rsc\\warning_16.png")))
  511. item.setToolTip("\n".join([w.text for w in mention.warnings]) + "\n\nDoucle-cliquer pour supprimer l'avertissement.")
  512. self.ui.tbl_mentions.setVerticalHeaderItem(index, item)
  513. item = QTableWidgetItem("")
  514. item.setData(0, index)
  515. self.ui.tbl_mentions.setItem(index, 0, item)
  516. item = QTableWidgetItem("{}".format(mention.line))
  517. self.ui.tbl_mentions.setItem(index, 1, item)
  518. item = QTableWidgetItem("{}".format(mention.obj.type_))
  519. self.ui.tbl_mentions.setItem(index, 2, item)
  520. item = QTableWidgetItem("{}".format(mention.objname) if mention.obj.type_ != "Module" else "{}.{}".format(mention.obj.name_, mention.objname))
  521. self.ui.tbl_mentions.setItem(index, 3, item)
  522. item = QTableWidgetItem("{}".format(mention.quote))
  523. item.setFont(QFont("Consolas", 8))
  524. self.ui.tbl_mentions.setItem(index, 4, item)
  525. self.ui.tbl_mentions.setSortingEnabled(True)
  526. def itemClicked(self, _):
  527. self.ui.btn_edit.setEnabled(True)
  528. self.ui.btn_del.setEnabled(True)
  529. def verticalHeaderDoubleClicked(self, row):
  530. index = self.ui.tbl_mentions.item(row, 0).data(0)
  531. mention = self.obj.mentions[index]
  532. if mention.warnings:
  533. if QMessageBox.question(self, "Confirmation", "Voulez-vous supprimer cet avertissement?") == QMessageBox.Yes:
  534. del mention.warnings[:]
  535. self.ui.tbl_mentions.verticalHeaderItem(row).setIcon(QIcon())
  536. def add_mention(self):
  537. dlg = ObjectSelectorDialog(parent=self)
  538. dlg.show()
  539. r = dlg.exec_()
  540. if not r:
  541. return
  542. mention = core.Mention(0, dlg.obj.name_, "(ajouté manuellement)", dlg.obj)
  543. linenum, _ = QInputDialog.getInt(self, "Ligne", "Numéro de ligne correspondant dans le code-source? (facultatif)", 0)
  544. if linenum > 0:
  545. mention.line = linenum
  546. try:
  547. with open(self.obj.sourcefile, "r", encoding='utf-8') as f:
  548. mention.quote = next((line.strip() for i, line in enumerate(f) if i == linenum - 1)).strip()
  549. except FileNotFoundError:
  550. pass
  551. self.obj.mentions.append(mention)
  552. self.load_table()
  553. def selected_index(self):
  554. row = self.ui.tbl_mentions.currentRow()
  555. if row < 0:
  556. return None
  557. return self.ui.tbl_mentions.item(row, 0).data(0)
  558. def edit_mention(self):
  559. row = self.ui.tbl_mentions.currentRow()
  560. if row < 0:
  561. return
  562. item = QTableWidgetItem("")
  563. item.setIcon(QIcon(QPixmap(core.here / "rsc\\edit_16.png")))
  564. self.ui.tbl_mentions.setVerticalHeaderItem(row, item)
  565. index = self.ui.tbl_mentions.item(row, 0).data(0)
  566. mention = self.obj.mentions[index]
  567. dlg = ObjectSelectorDialog(filterStr=mention.objname, parent=self)
  568. dlg.show()
  569. r = dlg.exec_()
  570. if r:
  571. mention.obj = dlg.obj
  572. mention.warning = ""
  573. self.load_table()
  574. def del_mention(self):
  575. index = self.selected_index()
  576. if not QMessageBox.question(self, "Confirmation", "Etes-vous sûr de vouloir supprimer la référence sélectionnée?") == QMessageBox.Yes:
  577. return
  578. del self.obj.mentions[index]
  579. self.load_table()
  580. def ok(self):
  581. self.done(1)
  582. def cancel(self):
  583. self.done(0)
  584. def keyPressEvent(self, e):
  585. if e.key() == Qt.Key_Delete:
  586. if self.ui.tbl_mentions.currentRow() < 0:
  587. return
  588. self.del_mention()
  589. e.accept()
  590. class ObjectSelectorDialog(QDialog):
  591. def __init__(self, filterStr="", parent=None):
  592. self.obj = None
  593. super (ObjectSelectorDialog, self).__init__(parent)
  594. self.createWidgets()
  595. self.ui.searchBox.setText(filterStr)
  596. def createWidgets(self):
  597. self.ui = Ui_select()
  598. self.ui.setupUi(self)
  599. for obj in core.Analyse.objects:
  600. item = QListWidgetItem("{} - {}".format(obj.type_, obj.name_))
  601. item.obj = obj
  602. self.ui.listWidget.addItem(item)
  603. self.ui.searchBox.textChanged.connect(self.filter)
  604. self.ui.listWidget.itemClicked.connect(self.itemClicked)
  605. self.ui.listWidget.itemDoubleClicked.connect(self.itemDoubleClicked)
  606. self.ui.btn_ok.clicked.connect(self.ok)
  607. def itemClicked(self, item):
  608. self.obj = item.obj
  609. self.ui.btn_ok.setEnabled(True)
  610. def itemDoubleClicked(self, item):
  611. self.itemClicked(item)
  612. self.ok()
  613. def filter(self, filterStr):
  614. for i in range(self.ui.listWidget.count()):
  615. item = self.ui.listWidget.item(i)
  616. item.setHidden(filterStr.lower() not in item.text().lower())
  617. def ok(self):
  618. self.done(1)
  619. def done(self, status):
  620. if not status:
  621. self.obj = None
  622. super(ObjectSelectorDialog, self).done(status)