NumeRe v1.1.4
NumeRe: Framework für Numerische Rechnungen
dependencydialog.cpp
Go to the documentation of this file.
1/*****************************************************************************
2 NumeRe: Framework fuer Numerische Rechnungen
3 Copyright (C) 2019 Erik Haenel et al.
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17******************************************************************************/
18
19#include "dependencydialog.hpp"
20#include "../NumeReWindow.h"
21#include "../../kernel/core/utils/tools.hpp"
22#include "../../kernel/core/ui/language.hpp"
23
24extern Language _guilang;
25
26using namespace std;
27
28BEGIN_EVENT_TABLE(DependencyDialog, wxDialog)
29 EVT_TREE_ITEM_ACTIVATED(-1, DependencyDialog::OnItemActivate)
30 EVT_TREE_ITEM_RIGHT_CLICK(-1, DependencyDialog::OnItemRightClick)
37
38
39
51DependencyDialog::DependencyDialog(wxWindow* parent, wxWindowID id, const wxString& title, const string& mainfile, ProcedureLibrary& lib, long style) : wxDialog(parent, id, title, wxDefaultPosition, wxSize(-1, 450), style)
52{
53 wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL);
54
55 // Create the UI elements
56 m_dependencyTree = new wxcode::wxTreeListCtrl(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTR_TWIST_BUTTONS | wxTR_FULL_ROW_HIGHLIGHT);
57 m_dependencyTree->AddColumn(_guilang.get("GUI_DEPDLG_TREE"), GetClientSize().GetWidth());
58 vsizer->Add(m_dependencyTree, 1, wxEXPAND | wxALL, 5);
59 vsizer->Add(CreateButtonSizer(wxOK), 0, wxALL | wxALIGN_CENTER_HORIZONTAL, 5);
60
61 SetSizer(vsizer);
62
63 // Calculate the dependencies
64 calculateDependencies(lib, mainfile);
65
66 // Fill the dependency tree with the calculated
67 // dependencies
68 fillDependencyTree();
69}
70
71
84{
85 // Get the dependencies
88
89 // Insert the dependencies into the main map
90 m_deps.insert(dep->getDependencyMap().begin(), dep->getDependencyMap().end());
91
92 // Get the iterator to the begin of the map
93 auto iter = m_deps.begin();
94
95 // Go through the map
96 while (iter != m_deps.end())
97 {
98 bool restart = false;
99
100 // Go through all dependencies
101 for (auto listiter = iter->second.begin(); listiter != iter->second.end(); ++listiter)
102 {
103 if (m_deps.find(listiter->getProcedureName()) == m_deps.end() && listiter->getProcedureName().find("thisfile~") == string::npos)
104 {
105 dep = lib.getProcedureContents(listiter->getFileName())->getDependencies();
106
107 if (dep->getDependencyMap().size())
108 {
109 m_deps.insert(dep->getDependencyMap().begin(), dep->getDependencyMap().end());
110 iter = m_deps.begin();
111 restart = true;
112 break;
113 }
114 }
115 }
116
117 // Only increment the iterator, if we do not
118 // need to restart the process
119 if (!restart)
120 ++iter;
121 }
122}
123
124
136{
137 // Find the current main procedure
138 auto iter = m_deps.find(m_mainProcedure);
139
140 // Ensure that the main procedure exists and that it is found
141 // in the dependency maps
142 if (iter == m_deps.end())
143 {
144 m_dependencyTree->AddRoot("ERROR: No main procedure found.");
145 return;
146 }
147
148 wxTreeItemId root = m_dependencyTree->AddRoot(m_mainProcedure + "()");
149
150 // Go through the list of calls
151 for (auto listiter = iter->second.begin(); listiter != iter->second.end(); ++listiter)
152 {
153 wxTreeItemId item = m_dependencyTree->AppendItem(root, listiter->getProcedureName() + "()");
154
155 // Colour thisfile namespace calls in grey
156 if (listiter->getProcedureName().find("thisfile~") != string::npos)
157 {
158 m_dependencyTree->SetItemTextColour(item, wxColour(128, 128, 128));
159 m_dependencyTree->SetItemFont(item, GetFont().MakeItalic());
160 }
161
162 // Insert the child calls to the current procedure call
163 insertChilds(item, listiter->getProcedureName());
164 }
165
166 // Expand the root node
167 m_dependencyTree->Expand(root);
168}
169
170
183void DependencyDialog::insertChilds(wxTreeItemId item, const string& sParentProcedure)
184{
185 // Find the current main procedure
186 auto iter = m_deps.find(sParentProcedure);
187
188 // Return, if the current procedure is not found
189 if (iter == m_deps.end())
190 return;
191
192 for (auto listiter = iter->second.begin(); listiter != iter->second.end(); ++listiter)
193 {
194 wxTreeItemId currItem;
195
196 // If the current procedure is already part of the branch, then
197 // simply add this call. Otherwise recurse to append its childs
198 if (findInParents(item, listiter->getProcedureName() + "()"))
199 currItem = m_dependencyTree->AppendItem(item, listiter->getProcedureName() + "()");
200 else
201 {
202 currItem = m_dependencyTree->AppendItem(item, listiter->getProcedureName() + "()");
203 insertChilds(currItem, listiter->getProcedureName());
204 }
205
206 // Colour thisfile namespace calls in grey
207 if (listiter->getProcedureName().find("thisfile~") != string::npos)
208 {
209 m_dependencyTree->SetItemTextColour(currItem, wxColour(128, 128, 128));
210 m_dependencyTree->SetItemFont(currItem, GetFont().MakeItalic());
211 }
212
213 }
214}
215
216
227bool DependencyDialog::findInParents(wxTreeItemId item, const string& sCurrProc)
228{
229 // Is the current node already equal?
230 if (m_dependencyTree->GetItemText(item) == sCurrProc)
231 return true;
232
233 // As long as there are further parents
234 // try to match the current string
235 while (m_dependencyTree->GetItemParent(item).IsOk())
236 {
237 item = m_dependencyTree->GetItemParent(item);
238
239 if (m_dependencyTree->GetItemText(item) == sCurrProc)
240 return true;
241 }
242
243 return false;
244}
245
246
256void DependencyDialog::CollapseAll(wxTreeItemId item)
257{
258 wxTreeItemIdValue cookie;
259 wxTreeItemId child = m_dependencyTree->GetFirstChild(item, cookie);
260
261 while (child.IsOk())
262 {
263 CollapseAll(child);
264 child = m_dependencyTree->GetNextSibling(child);
265 }
266
267 m_dependencyTree->Collapse(item);
268}
269
270
279std::vector<std::string> DependencyDialog::parseNameSpace(std::string sNameSpace) const
280{
281 std::vector<std::string> vNameSpace;
282
283 if (sNameSpace.front() == '$')
284 sNameSpace.erase(0, 1);
285
286 while (sNameSpace.length())
287 {
288 size_t pos = sNameSpace.find('~');
289 vNameSpace.push_back(sNameSpace.substr(0, pos));
290 sNameSpace.erase(0, pos);
291
292 if (pos != std::string::npos)
293 sNameSpace.erase(0, 1);
294 }
295
296 return vNameSpace;
297}
298
299
310int DependencyDialog::calculateClusterLevel(const std::vector<std::string>& sCurrentNameSpace, const std::vector<std::string>& sNewNameSpace)
311{
312 int nNameSpaces = 0;
313
314 // Go through the namespaces and compare them
315 for (; nNameSpaces < (int)sCurrentNameSpace.size(); nNameSpaces++)
316 {
317 // New one is shorter? Return
318 if ((int)sNewNameSpace.size() <= nNameSpaces)
319 return nNameSpaces+1;
320
321 // Characters differ? Return
322 if (sCurrentNameSpace[nNameSpaces] != sNewNameSpace[nNameSpaces])
323 return nNameSpaces+1;
324 }
325
326 // New one is longer? Return the namespace counter + 1
327 if (sNewNameSpace.size() > sCurrentNameSpace.size())
328 return sNewNameSpace.size();
329
330 return nNameSpaces;
331}
332
333
342{
343 std::string sClusterDefinition;
344 std::string sDotFileContent;
345 std::map<std::string, std::list<std::string> > mProcedures;
346
347 // Fill the cluster map with the procedures and prepare the graph
348 // list for DOT
349 for (auto caller = m_deps.begin(); caller != m_deps.end(); ++caller)
350 {
351 for (auto called = caller->second.begin(); called != caller->second.end(); ++called)
352 {
353 if (caller->first.find_last_of("~/") != std::string::npos)
354 mProcedures[caller->first.substr(0, caller->first.find_last_of("~/")+1)].push_back(caller->first);
355 else
356 mProcedures[caller->first].push_back(caller->first);
357
358 mProcedures[called->getProcedureName().substr(0, called->getProcedureName().find_last_of("~/")+1)].push_back(called->getProcedureName());
359
360 sDotFileContent += "\t\"" + caller->first + "()\" -> \"" + called->getProcedureName() + "()\"\n";
361 }
362 }
363
364 std::vector<std::string> vCurrentNameSpace;
365 std::vector<std::string> vNameSpace;
366 int nClusterindex = 0;
367 int nCurrentClusterLevel = 0;
368
369 // Prepare the cluster list by calculating the cluster level of
370 // the current namespace and nest it in a previous cluster
371 for (auto iter = mProcedures.begin(); iter != mProcedures.end(); ++iter)
372 {
373 std::string sNameSpace = iter->first;
374
375 if (sNameSpace.find_last_of("~/") != std::string::npos)
376 sNameSpace.erase(sNameSpace.find_last_of("~/")+1);
377
378 vNameSpace = parseNameSpace(sNameSpace);
379
380 int nNewClusterLevel = calculateClusterLevel(vCurrentNameSpace, vNameSpace);
381
382 // Deduce, whether the new cluster is nested or
383 // an independent one
384 if (nNewClusterLevel < nCurrentClusterLevel)
385 {
386 sClusterDefinition += "\n";
387
388 for (int i = nCurrentClusterLevel-nNewClusterLevel; i >= 0; i--)
389 sClusterDefinition += std::string(i+nNewClusterLevel, '\t') + "}\n";
390
391 nCurrentClusterLevel = nNewClusterLevel-1;
392
393 // Minimal level is 0 in this case
394 if (nCurrentClusterLevel < 0)
395 nCurrentClusterLevel = 0;
396 }
397 else if (nNewClusterLevel == nCurrentClusterLevel)
398 {
399 sClusterDefinition += "\n" + std::string(nNewClusterLevel, '\t') + "}\n";
400 nCurrentClusterLevel--;
401 }
402 else
403 sClusterDefinition += "\n";
404
405 vCurrentNameSpace = vNameSpace;
406
407 for (int i = nCurrentClusterLevel; i < (int)vCurrentNameSpace.size(); i++)
408 {
409 // Write the cluster header
410 if (vCurrentNameSpace[i].find("::thisfile") != std::string::npos)
411 sClusterDefinition += "\n" + std::string(i+1, '\t')
412 + "subgraph cluster_" + toString(nClusterindex) + "\n"
413 + std::string(i+1, '\t') + "{\n"
414 + std::string(i+2, '\t') + "style=filled\n"
415 + std::string(i+2, '\t') + "color=lightsteelblue\n"
416 + std::string(i+2, '\t') + "fillcolor=floralwhite\n"
417 + std::string(i+2, '\t') + "node [fillcolor=\"cornsilk\"]\n"
418 + std::string(i+2, '\t') + "label=\"" + vCurrentNameSpace[i] + "\"\n";
419 else
420 sClusterDefinition += "\n" + std::string(i+1, '\t')
421 + "subgraph cluster_" + toString(nClusterindex) + "\n"
422 + std::string(i+1, '\t') + "{\n"
423 + std::string(i+2, '\t') + "style=rounded\n"
424 + std::string(i+2, '\t') + "color=mediumslateblue\n"
425 + std::string(i+2, '\t') + "label=\"" + vCurrentNameSpace[i] + "\"\n";
426
427 nClusterindex++;
428 }
429
430 sClusterDefinition += std::string(vCurrentNameSpace.size()+1, '\t');
431 nCurrentClusterLevel = vCurrentNameSpace.size();
432
433 // Ensure that the procedure list is unique
434 iter->second.sort();
435 iter->second.unique();
436
437 // Add the procedures to the current namespace
438 for (auto procedure = iter->second.begin(); procedure != iter->second.end(); ++procedure)
439 sClusterDefinition += "\"" + *procedure + "()\" ";
440
441 }
442
443 // Close all opened clusters
444 for (int i = nCurrentClusterLevel; i > 0; i--)
445 sClusterDefinition += "\n" + std::string(i, '\t') + "}";
446
447 // Present a file save dialog to the user
448 wxFileDialog saveDialog(this, _guilang.get("GUI_DLG_SAVE"), "", "dependency.dot", _guilang.get("COMMON_FILETYPE_DOT") + " (*.dot)|*.dot", wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
449
450 if (saveDialog.ShowModal() == wxID_CANCEL)
451 return;
452
453 // Open the file stream
454 ofstream file(saveDialog.GetPath().ToStdString().c_str());
455
456 // If the stream could be opened, stream the prepared contents
457 // of the DOT file to the opened file
458 if (file.good())
459 {
460 file << "digraph ProcedureDependency\n{\n\tratio=0.4\n\tfontname=\"Consolas\"\n\tnode [fontname=\"Consolas\" fontcolor=\"maroon\" style=\"filled\" fillcolor=\"palegoldenrod\"]";
461 file << sClusterDefinition << "\n\n";
462 file << sDotFileContent << "}\n";
463 }
464
465 file.close();
466}
467
468
478void DependencyDialog::OnItemActivate(wxTreeEvent& event)
479{
480 NumeReWindow* main = static_cast<NumeReWindow*>(this->GetParent());
481
482 wxString procedureName = m_dependencyTree->GetItemText(event.GetItem());
483 procedureName.erase(procedureName.find('('));
484
485 // Procedures in the "thisfile" namespace can be found by first calling the main
486 // procedure and then jumping to the correct local routine
487 if (procedureName.find("::thisfile~") != string::npos)
488 {
489 main->FindAndOpenProcedure(procedureName.substr(0, procedureName.find("::thisfile~")));
490 main->FindAndOpenProcedure("$" + procedureName.substr(procedureName.find("::thisfile~")+2));
491 }
492 else
493 main->FindAndOpenProcedure(procedureName);
494}
495
496
506{
507 m_selectedItem = event.GetItem();
508
509 wxMenu popupMenu;
510
511 popupMenu.Append(ID_DEPENDENCYDIALOG_FOLDALL, _guilang.get("GUI_DEPDLG_FOLDALL"));
512 popupMenu.Append(ID_DEPENDENCYDIALOG_UNFOLDALL, _guilang.get("GUI_DEPDLG_UNFOLDALL"));
513 popupMenu.Append(ID_DEPENDENCYDIALOG_FOLDITEM, _guilang.get("GUI_DEPDLG_FOLDITEM"));
514 popupMenu.Append(ID_DEPENDENCYDIALOG_UNFOLDITEM, _guilang.get("GUI_DEPDLG_UNFOLDITEM"));
515 popupMenu.AppendSeparator();
516 popupMenu.Append(ID_DEPENDENCYDIALOG_EXPORTDOT, _guilang.get("GUI_DEPDLG_EXPORTDOT"));
517
518 m_dependencyTree->PopupMenu(&popupMenu, event.GetPoint());
519}
520
521
531void DependencyDialog::OnMenuEvent(wxCommandEvent& event)
532{
533 switch (event.GetId())
534 {
536 CollapseAll(m_dependencyTree->GetRootItem());
537 m_dependencyTree->Expand(m_dependencyTree->GetRootItem());
538 break;
540 m_dependencyTree->ExpandAll(m_dependencyTree->GetRootItem());
541 break;
544 break;
547 break;
550 break;
551 }
552}
553
554
This class handles the dependencies of the current procedure file (passed as pointer to a ProcedureEl...
Definition: dependency.hpp:81
std::map< std::string, DependencyList > & getDependencyMap()
Definition: dependency.hpp:98
std::string getMainProcedure() const
Definition: dependency.hpp:103
This class represents a dialog showing the dependencies of a selected procedure file.
std::string m_mainProcedure
void OnMenuEvent(wxCommandEvent &event)
This private member function handles all menu events yielded by clicking on an item of the popup menu...
wxTreeItemId m_selectedItem
void OnItemRightClick(wxTreeEvent &event)
This private member function shows the popup menu on a right click on any tree item.
std::map< std::string, DependencyList > m_deps
void calculateDependencies(ProcedureLibrary &lib, const std::string &mainfile)
This private member function calculates the dependencies of the current selected main file....
std::vector< std::string > parseNameSpace(std::string sNameSpace) const
Convert a name space into a vector of single namespaces for easier comparison.
void CreateDotFile()
This member function handles the creation of GraphViz DOT files.
void CollapseAll(wxTreeItemId item)
This private member function will collapse the current item and all corresponding subitems.
void OnItemActivate(wxTreeEvent &event)
This private member function is the event handler for double clicking on an item in the dependency tr...
int calculateClusterLevel(const std::vector< std::string > &sCurrentNameSpace, const std::vector< std::string > &sNewNameSpace)
This private member function calculates the level of nested clusters needed to correctly visualize th...
void fillDependencyTree()
This private member function fills the tree in the UI with the calculated dependencies....
wxcode::wxTreeListCtrl * m_dependencyTree
bool findInParents(wxTreeItemId item, const std::string &sCurrProc)
This private member function searches the procedure call in the parents of the current branch.
void insertChilds(wxTreeItemId item, const std::string &sParentProcedure)
This private member function is called recursively to fill the childs of a procedure call....
This class handles the internal language system and returns the language strings of the selected lang...
Definition: language.hpp:38
std::string get(const std::string &sMessage, const std::vector< std::string > &vTokens) const
This member function returns the language string for the passed language identifier and replaces all ...
Definition: language.cpp:292
This class is the actual NumeRe main frame. The application's logic is implemented here.
Definition: NumeReWindow.h:177
void FindAndOpenProcedure(const wxString &procedureName)
Wrapper for the corresponding function of the editor.
Dependencies * getDependencies()
This member function returns the first-level dependencies of the current procedure file....
This class manages all already read and possibly pre-parsed procedure files for easier and faster acc...
ProcedureElement * getProcedureContents(const std::string &sProcedureFileName)
Returns the ProcedureElement pointer to the desired procedure file. It also creates the element,...
@ ID_DEPENDENCYDIALOG_FOLDALL
@ ID_DEPENDENCYDIALOG_UNFOLDALL
@ ID_DEPENDENCYDIALOG_EXPORTDOT
@ ID_DEPENDENCYDIALOG_UNFOLDITEM
@ ID_DEPENDENCYDIALOG_FOLDITEM
Language _guilang
std::string replacePathSeparator(const std::string &)
This function replaces the Windows style path sparators to UNIX style.
std::string toString(int)
Converts an integer to a string without the Settings bloat.
END_EVENT_TABLE()