Viewer.py 27 KB

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