Salome HOME
updated copyright message
[modules/gui.git] / src / LightApp / LightApp_ExtInfoDlg.cxx
1 // Copyright (C) 2007-2023  CEA, EDF, OPEN CASCADE
2 //
3 // Copyright (C) 2003-2007  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
4 // CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
5 //
6 // This library is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU Lesser General Public
8 // License as published by the Free Software Foundation; either
9 // version 2.1 of the License, or (at your option) any later version.
10 //
11 // This library is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 // Lesser General Public License for more details.
15 //
16 // You should have received a copy of the GNU Lesser General Public
17 // License along with this library; if not, write to the Free Software
18 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
19 //
20 // See https://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
21 //
22
23 // File:      LightApp_ExtInfoDlg.cxx
24 // Author:    Konstantin Leontev
25 //
26 #include "LightApp_ExtInfoDlg.h"
27 #include "utilities.h"
28
29 // Prevent slot compilation error
30 #pragma push_macro("slots")
31 #undef slots
32 #include "PyInterp_Utils.h"
33 #pragma pop_macro("slots")
34
35 #include <QHBoxLayout>
36
37 // Show extensions info
38 #include <QTableWidget>
39 #include <QTableWidgetItem>
40 #include <QHeaderView>
41
42 // Render dependency tree
43 #include <QSvgWidget>
44 #include <QByteArray>
45 #include <graphviz/gvc.h>
46
47
48 /*! RAII wrapper to graphviz Agraph_t.*/
49 class GraphWrapper
50 {
51   public:
52     virtual ~GraphWrapper();
53
54     void init(char* name, Agdesc_t desc = Agstrictdirected, Agdisc_t* disc = nullptr);
55
56     Agraph_t* getGraph() const { return m_graph; }
57     GVC_t* getGvc() const { return m_gvc; }
58
59   private:
60     Agraph_t* m_graph = nullptr;
61     GVC_t* m_gvc = nullptr;
62 };
63
64 GraphWrapper::~GraphWrapper()
65 {
66   if (m_graph)
67   {
68     agclose(m_graph);
69     m_graph = nullptr;
70   }
71
72   if (m_gvc)
73   {
74     gvFreeContext(m_gvc);
75     m_gvc = nullptr;
76   }
77 }
78
79 void GraphWrapper::init(char *name, Agdesc_t desc /* = Agstrictdirected */, Agdisc_t* disc /* = nullptr */)
80 {
81   m_graph = agopen(name, desc, disc);
82   m_gvc = gvContext();
83 }
84
85 /*!Constructor.*/
86 LightApp_ExtInfoDlg::LightApp_ExtInfoDlg(QWidget* parent)
87 : QtxDialog(parent, true, true, ButtonFlags::OK)
88 {
89   MESSAGE("Start creating a dialog...\n");
90
91   setObjectName("salome_ext_info_dialog");
92   setWindowTitle(tr("EXT_INFO_CAPTION"));
93   setSizeGripEnabled(true);
94   setButtonPosition(ButtonPosition::Center, ButtonFlags::OK);
95
96   auto extInfoWiget = getExtListWidget(mainFrame());
97   auto extTreeWiget = getExtTreeWidget(mainFrame());
98
99   auto layout = new QHBoxLayout(mainFrame());
100   layout->addWidget(extInfoWiget);
101   layout->addWidget(extTreeWiget);
102 }
103
104 /*!Destructor.*/
105 LightApp_ExtInfoDlg::~LightApp_ExtInfoDlg()
106 {
107   //! Do nothing.
108 }
109
110 /*! Fill the given widget with info about installed extensions */
111 bool LightApp_ExtInfoDlg::fillExtListWidget(QTableWidget* extListWidget) const
112 {
113   MESSAGE("Getting info from SalomeOnDemandTK.extension_query...\n");
114
115   // Import Python module that manages SALOME extensions
116   PyLockWrapper lck; // acquire GIL
117   PyObjWrapper extensionQuery = PyImport_ImportModule((char*)"SalomeOnDemandTK.extension_query");
118   auto extRootDir = getenv("SALOME_APPLICATION_DIR");
119   PyObjWrapper extInfoDict = PyObject_CallMethod(extensionQuery, (char*)"ext_info_dict", (char*)"s", extRootDir);
120   if (!extInfoDict)
121   {
122     PyErr_Print();
123     return false;
124   }
125
126   // Check if we have any info to display
127   Py_ssize_t rowCount = PyDict_Size(extInfoDict);
128   if (!rowCount)
129   {
130     MESSAGE("Didn't find any extensions! Return.\n");
131     return false;
132   }
133
134   extListWidget->setRowCount(rowCount);
135   const int columnCount = extListWidget->columnCount();
136
137   auto makeTableWidgetItem = [](PyObject* itemText) -> QTableWidgetItem*
138   {
139     const char* itemTextStr = PyUnicode_AsUTF8(itemText);
140     SCRUTE(itemTextStr);
141
142     return new QTableWidgetItem(itemTextStr);
143   };
144
145   PyObject* keyName = nullptr;
146   PyObject* infoList = nullptr;
147   Py_ssize_t keyNamePos = 0;
148
149   // Iterate name:info_list dictionary
150   while (PyDict_Next(extInfoDict, &keyNamePos, &keyName, &infoList))
151   {
152     auto widgetItem = makeTableWidgetItem(keyName);
153
154     // keyNamePos is already 1 on the first iteration, so we need to decrease it 
155     extListWidget->setItem(keyNamePos - 1, 0, widgetItem);
156
157     // Iterate an extension info list
158     for (Py_ssize_t infoPos = 0; infoPos < PyList_Size(infoList); ++infoPos)
159     {
160       if (infoPos >= columnCount)
161       {
162         MESSAGE("Number of info items is greater than column count! Skip.\n");
163         break;
164       }
165
166       auto info = PyList_GetItem(infoList, infoPos);
167       widgetItem = makeTableWidgetItem(info);
168
169       // keyNamePos started from 1 instead of 0, so decrease
170       // info need to be filled from column 1, so increase
171       extListWidget->setItem(keyNamePos - 1, infoPos + 1, widgetItem);
172     }
173   }
174
175   return true;
176 }
177
178 /*! Return widget with info about installed extensions */
179 QWidget* LightApp_ExtInfoDlg::getExtListWidget(QWidget* parent) const
180 {
181   MESSAGE("Make a widget to display extensions info...\n");
182
183   auto extListWidget = new QTableWidget(parent);
184
185   // Setup the table params
186   const QStringList headerLabels = {
187     "Name", "Description", "Author", "Components", "Size"
188     };
189
190   extListWidget->setColumnCount(headerLabels.count());
191   extListWidget->setHorizontalHeaderLabels(headerLabels);
192
193   // Fill it with data about extensions
194   if (fillExtListWidget(extListWidget))
195   {
196     // Tune an appearance
197     extListWidget->sortItems(0);
198     extListWidget->horizontalHeader()->setStretchLastSection(true);
199     extListWidget->resizeColumnsToContents();
200     extListWidget->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
201   }
202
203   return extListWidget;
204 }
205
206 /*! Fill a given extension tree graph with nodes and edges */
207 bool LightApp_ExtInfoDlg::fillExtTreeGraph(const GraphWrapper& graph) const
208 {
209   MESSAGE("Start to get info from SalomeOnDemandTK.extension_query...\n");
210
211   // Import Python module that manages SALOME extensions
212   PyLockWrapper lck; // acquire GIL
213   PyObjWrapper extensionQuery = PyImport_ImportModule((char*)"SalomeOnDemandTK.extension_query");
214   auto extRootDir = getenv("SALOME_APPLICATION_DIR");
215   PyObjWrapper dependencyTree = PyObject_CallMethod(extensionQuery, (char*)"dependency_tree", (char*)"s", extRootDir);
216   if (!dependencyTree)
217   {
218     PyErr_Print();
219     return false;
220   }
221
222   Agraph_t* extTreeGraph = graph.getGraph();
223
224   // Python declarations
225   PyObject* extName = nullptr;
226   PyObject* dependantsList = nullptr;
227   Py_ssize_t pos = 0;
228
229   // Iterate the tree
230   while (PyDict_Next(dependencyTree, &pos, &extName, &dependantsList))
231   {
232     // Create a parent node if it doesn't already exist
233     auto parentName = PyUnicode_AsUTF8(extName);
234     Agnode_t* parentNode = agnode(extTreeGraph, const_cast<char*>(parentName), true);
235     SCRUTE(parentName);
236
237     // Iterate a list of dependants
238     for (Py_ssize_t depPos = 0; depPos < PyList_Size(dependantsList); ++depPos)
239     {
240       auto dependant = PyList_GetItem(dependantsList, depPos);
241
242       // Create a child node if it doesn't already exist
243       auto dependantName = PyUnicode_AsUTF8(dependant);
244       Agnode_t* dependantNode = agnode(extTreeGraph, const_cast<char*>(dependantName), true);
245       SCRUTE(dependantName);
246
247       // Make an edge
248       std::string edgeNameStr(parentName);
249       edgeNameStr += dependantName;
250       auto edgeName = edgeNameStr.c_str();
251       agedge(extTreeGraph, parentNode, dependantNode, const_cast<char*>(edgeName), true);
252       SCRUTE(edgeName);
253     }
254   }
255
256   return true;
257 }
258
259 /*! Render dependency tree to array of bytes */
260 QByteArray LightApp_ExtInfoDlg::renderExtTreeGraph(const GraphWrapper& graph) const
261 {
262   Agraph_t* extTreeGraph = graph.getGraph();
263   GVC_t* gvc = graph.getGvc();
264
265   // Layout and render to buffer
266   MESSAGE("Layout dependency tree...\n");
267   int res = gvLayout(gvc, extTreeGraph, "dot");
268   if (res)
269   {
270     MESSAGE("gvLayout failed!\n");
271     return {};
272   }
273
274   MESSAGE("Render dependency tree...\n");
275   char* buffer = nullptr;
276   unsigned int bufferLength = 0;
277   res = gvRenderData(gvc, extTreeGraph, "svg", &buffer, &bufferLength);
278   if (res)
279   {
280     MESSAGE("gvRenderData failed!\n");
281     return {};
282   }
283
284   QByteArray renderedGraph(buffer, bufferLength);
285   
286   // Clean up layout and buffer
287   gvFreeLayout(gvc, extTreeGraph);
288   gvFreeRenderData(buffer);
289
290   return renderedGraph;
291 }
292
293 /*! Return widget with an image of dependency tree fot installed extensions */
294 QWidget* LightApp_ExtInfoDlg::getExtTreeWidget(QWidget* parent) const
295 {
296   MESSAGE("Start to make a widget to show dependency tree...\n");
297
298   auto extTreeWidget = new QSvgWidget(parent);
299
300   // Graph to be filled up from python data
301   GraphWrapper extTreeGraph;
302   char graphName[] = "extTreeGraph";
303   extTreeGraph.init(graphName);
304   if (fillExtTreeGraph(extTreeGraph))
305   {
306     extTreeWidget->load(renderExtTreeGraph(extTreeGraph));
307   }
308
309   return extTreeWidget;
310 }