Graphviz 12.0.1~dev.20240715.2254
Loading...
Searching...
No Matches
csettings.cpp
Go to the documentation of this file.
1/*************************************************************************
2 * Copyright (c) 2011 AT&T Intellectual Property
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * https://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors: Details at https://graphviz.org
9 *************************************************************************/
10#ifdef _WIN32
11#include "windows.h"
12#endif
13#include "csettings.h"
14#include "mainwindow.h"
15#include "mdichild.h"
16#include "qfiledialog.h"
17#include "qmessagebox.h"
18#include "string.h"
19#include <QTemporaryFile>
20#include <QtWidgets>
21#include <cassert>
22#include <cgraph/rdr.h>
23#include <cstdint>
24#include <qfile.h>
25#include <string>
26#include <vector>
27
28#ifdef __APPLE__
29#include <mach-o/dyld.h>
30#endif
31
32#ifdef __FreeBSD__
33#include <sys/sysctl.h>
34#include <sys/types.h>
35#endif
36
37#if !defined(_WIN32)
38#include <unistd.h>
39#endif
40
41extern int errorPipe(char *errMsg);
42
43#define WIDGET(t, f) ((t *)findChild<t *>(QStringLiteral(#f)))
44
45#ifndef _WIN32
47static std::string readln(const std::string &path) {
48
49 std::vector<char> buf(512, '\0');
50
51 while (true) {
52
53 // expand target buffer
54 buf.resize(buf.size() * 2);
55
56 // attempt to resolve
57 {
58 ssize_t written = readlink(path.c_str(), buf.data(), buf.size());
59 if (written < 0)
60 break;
61 if (static_cast<size_t>(written) < buf.size()) {
62 // success
63 buf[written] = '\0';
64 return buf.data();
65 }
66 }
67 }
68
69 // failed
70 return "";
71}
72#endif
73
75static std::string find_me(void) {
76
77 // macOS
78#ifdef __APPLE__
79 {
80 // determine how many bytes we will need to allocate
81 uint32_t buf_size = 0;
82 int rc = _NSGetExecutablePath(NULL, &buf_size);
83 assert(rc != 0);
84 assert(buf_size > 0);
85
86 std::vector<char> path(buf_size);
87
88 // retrieve the actual path
89 if (_NSGetExecutablePath(path.data(), &buf_size) < 0) {
90 errout << "failed to get path for executable.\n";
91 return "";
92 }
93
94 // try to resolve any levels of symlinks if possible
95 for (std::string p = path.data();;) {
96 const std::string buf = readln(p);
97 if (buf == "")
98 return p;
99
100 p = buf;
101 }
102 }
103#elif defined(_WIN32)
104 {
105 std::vector<char> path;
106 DWORD rc = 0;
107
108 do {
109 {
110 size_t size = path.empty() ? 1024 : (path.size() * 2);
111 path.resize(size);
112 }
113
114 rc = GetModuleFileNameA(NULL, path.data(), path.size());
115 if (rc == 0) {
116 errout << "failed to get path for executable.\n";
117 return "";
118 }
119
120 } while (rc == path.size());
121
122 return path.data();
123 }
124#else
125
126 // Linux
127 std::string path = readln("/proc/self/exe");
128 if (path != "")
129 return path;
130
131 // DragonFly BSD, FreeBSD
132 path = readln("/proc/curproc/file");
133 if (path != "")
134 return path;
135
136 // NetBSD
137 path = readln("/proc/curproc/exe");
138 if (path != "")
139 return path;
140
141// /proc-less FreeBSD
142#ifdef __FreeBSD__
143 {
144 int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
145 static const size_t MIB_LENGTH = sizeof(mib) / sizeof(mib[0]);
146
147 do {
148 // determine how long the path is
149 size_t buf_size = 0;
150 if (sysctl(mib, MIB_LENGTH, NULL, &buf_size, NULL, 0) < 0)
151 break;
152 assert(buf_size > 0);
153
154 // make enough space for the target path
155 std::vector<char> buf(buf_size, '\0');
156
157 // resolve it
158 if (sysctl(mib, MIB_LENGTH, buf.data(), &buf_size, NULL, 0) == 0)
159 return buf.data();
160 } while (0);
161 }
162#endif
163#endif
164
165 errout << "failed to get path for executable.\n";
166 return "";
167}
168
170static std::string find_share(void) {
171
172#ifdef _WIN32
173 const char PATH_SEPARATOR = '\\';
174#else
175 const char PATH_SEPARATOR = '/';
176#endif
177
178 // find the path to the `gvedit` binary
179 std::string gvedit_exe = find_me();
180 if (gvedit_exe == "")
181 return "";
182
183 // assume it is of the form …/bin/gvedit[.exe] and construct
184 // …/share/graphviz/gvedit
185
186 size_t slash = gvedit_exe.rfind(PATH_SEPARATOR);
187 if (slash == std::string::npos) {
188 errout << "no path separator in path to self, " << gvedit_exe.c_str()
189 << '\n';
190 return "";
191 }
192
193 std::string bin = gvedit_exe.substr(0, slash);
194 slash = bin.rfind(PATH_SEPARATOR);
195 if (slash == std::string::npos) {
196 errout << "no path separator in directory containing self, " << bin.c_str()
197 << '\n';
198 return "";
199 }
200
201 std::string install_prefix = bin.substr(0, slash);
202
203 return install_prefix + PATH_SEPARATOR + "share" + PATH_SEPARATOR +
204 "graphviz" + PATH_SEPARATOR + "gvedit";
205}
206
207bool loadAttrs(const QString &fileName, QComboBox *cbNameG, QComboBox *cbNameN,
208 QComboBox *cbNameE) {
209 QFile file(fileName);
210 if (file.open(QIODevice::ReadOnly)) {
211 QTextStream stream(&file);
212 QString line;
213 while (!stream.atEnd()) {
214 line = stream.readLine(); // line of text excluding '\n'
215 if (line.left(1) == QLatin1String(":")) {
216 QString attrName;
217 QStringList sl = line.split(u':');
218 for (int id = 0; id < sl.count(); id++) {
219 if (id == 1)
220 attrName = sl[id];
221 if (id == 2) {
222 if (sl[id].contains(u'G'))
223 cbNameG->addItem(attrName);
224 if (sl[id].contains(u'N'))
225 cbNameN->addItem(attrName);
226 if (sl[id].contains(u'E'))
227 cbNameE->addItem(attrName);
228 }
229 };
230 }
231 }
232 file.close();
233 } else {
234 errout << "Could not open attribute name file \"" << fileName
235 << "\" for reading\n";
236 errout.flush();
237 return true;
238 }
239
240 return false;
241}
242
243QString stripFileExtension(const QString &fileName) {
244 int idx;
245 for (idx = fileName.length(); idx >= 0; idx--) {
246 if (fileName.mid(idx, 1) == u'.')
247 break;
248 }
249 return fileName.left(idx);
250}
251
253 this->gvc = gvContext();
254 Ui_Dialog tempDia;
255 tempDia.setupUi(this);
256 graph = nullptr;
257 activeWindow = nullptr;
258 QString path;
259 char *s = nullptr;
260#ifndef _WIN32
261 s = getenv("GVEDIT_PATH");
262#endif
263 if (s)
264 path = QString::fromUtf8(s);
265 else
266 path = QString::fromStdString(find_share());
267
268 connect(WIDGET(QPushButton, pbAdd), &QPushButton::clicked, this,
269 &CFrmSettings::addSlot);
270 connect(WIDGET(QPushButton, pbNew), &QPushButton::clicked, this,
271 &CFrmSettings::newSlot);
272 connect(WIDGET(QPushButton, pbOpen), &QPushButton::clicked, this,
273 &CFrmSettings::openSlot);
274 connect(WIDGET(QPushButton, pbSave), &QPushButton::clicked, this,
275 &CFrmSettings::saveSlot);
276 connect(WIDGET(QPushButton, btnOK), &QPushButton::clicked, this,
277 &CFrmSettings::okSlot);
278 connect(WIDGET(QPushButton, btnCancel), &QPushButton::clicked, this,
279 &CFrmSettings::cancelSlot);
280 connect(WIDGET(QPushButton, pbOut), &QPushButton::clicked, this,
281 &CFrmSettings::outputSlot);
282 connect(WIDGET(QPushButton, pbHelp), &QPushButton::clicked, this,
283 &CFrmSettings::helpSlot);
284
285 connect(WIDGET(QComboBox, cbScope),
286 QOverload<int>::of(&QComboBox::currentIndexChanged), this,
287 &CFrmSettings::scopeChangedSlot);
288 scopeChangedSlot(0);
289
290 if (!path.isEmpty()) {
291 loadAttrs(path + QLatin1String("/attrs.txt"), WIDGET(QComboBox, cbNameG),
292 WIDGET(QComboBox, cbNameN), WIDGET(QComboBox, cbNameE));
293 }
294 setWindowIcon(QIcon(QStringLiteral(":/images/icon.png")));
295}
296
297void CFrmSettings::outputSlot() {
298 QString _filter = QStringLiteral("Output File(*.%1)")
299 .arg(WIDGET(QComboBox, cbExtension)->currentText());
300 QString fileName = QFileDialog::getSaveFileName(this, tr("Save Graph As.."),
301 QStringLiteral("/"), _filter);
302 if (!fileName.isEmpty())
303 WIDGET(QLineEdit, leOutput)->setText(fileName);
304}
305
306void CFrmSettings::scopeChangedSlot(int id) {
307 WIDGET(QComboBox, cbNameG)->setVisible(id == 0);
308 WIDGET(QComboBox, cbNameN)->setVisible(id == 1);
309 WIDGET(QComboBox, cbNameE)->setVisible(id == 2);
310}
311
312void CFrmSettings::addSlot() {
313 QString _scope = WIDGET(QComboBox, cbScope)->currentText();
314 QString _name;
315 switch (WIDGET(QComboBox, cbScope)->currentIndex()) {
316 case 0:
317 _name = WIDGET(QComboBox, cbNameG)->currentText();
318 break;
319 case 1:
320 _name = WIDGET(QComboBox, cbNameN)->currentText();
321 break;
322 case 2:
323 _name = WIDGET(QComboBox, cbNameE)->currentText();
324 break;
325 }
326 QString _value = WIDGET(QLineEdit, leValue)->text();
327
328 if (_value.trimmed().isEmpty())
329 QMessageBox::warning(this, tr("GvEdit"),
330 tr("Please enter a value for selected attribute!"),
331 QMessageBox::Ok, QMessageBox::Ok);
332 else {
333 QString str = _scope + QLatin1Char(u'[') + _name + QLatin1String("=\"");
334 if (WIDGET(QTextEdit, teAttributes)->toPlainText().contains(str)) {
335 QMessageBox::warning(this, tr("GvEdit"),
336 tr("Attribute is already defined!"), QMessageBox::Ok,
337 QMessageBox::Ok);
338 return;
339 } else {
340 str = str + _value + QLatin1String("\"]");
341 WIDGET(QTextEdit, teAttributes)
342 ->setPlainText(WIDGET(QTextEdit, teAttributes)->toPlainText() + str +
343 QLatin1Char('\n'));
344 }
345 }
346}
347
348void CFrmSettings::helpSlot() {
349 QDesktopServices::openUrl(
350 QUrl(QStringLiteral("http://www.graphviz.org/doc/info/attrs.html")));
351}
352
353void CFrmSettings::cancelSlot() { this->reject(); }
354
355void CFrmSettings::okSlot() {
356 saveContent();
357 this->done(drawGraph());
358}
359
360void CFrmSettings::newSlot() {
361 WIDGET(QTextEdit, teAttributes)->setPlainText(tr(""));
362}
363
364void CFrmSettings::openSlot() {
365 QString fileName = QFileDialog::getOpenFileName(
366 this, tr("Open File"), QStringLiteral("/"), tr("Text file (*.*)"));
367 if (!fileName.isEmpty()) {
368 QFile file(fileName);
369 if (!file.open(QFile::ReadOnly | QFile::Text)) {
370 QMessageBox::warning(this, tr("MDI"),
371 tr("Cannot read file %1:\n%2.")
372 .arg(fileName)
373 .arg(file.errorString()));
374 return;
375 }
376
377 QTextStream in(&file);
378 WIDGET(QTextEdit, teAttributes)->setPlainText(in.readAll());
379 }
380}
381
382void CFrmSettings::saveSlot() {
383
384 if (WIDGET(QTextEdit, teAttributes)->toPlainText().trimmed().isEmpty()) {
385 QMessageBox::warning(this, tr("GvEdit"), tr("Nothing to save!"),
386 QMessageBox::Ok, QMessageBox::Ok);
387 return;
388 }
389
390 QString fileName = QFileDialog::getSaveFileName(
391 this, tr("Open File"), QStringLiteral("/"), tr("Text File(*.*)"));
392 if (!fileName.isEmpty()) {
393
394 QFile file(fileName);
395 if (!file.open(QFile::WriteOnly | QFile::Text)) {
396 QMessageBox::warning(this, tr("MDI"),
397 tr("Cannot write file %1:\n%2.")
398 .arg(fileName)
399 .arg(file.errorString()));
400 return;
401 }
402
403 QTextStream out(&file);
404 out << WIDGET(QTextEdit, teAttributes)->toPlainText();
405 return;
406 }
407}
408
409bool CFrmSettings::loadGraph(MdiChild *m) {
410 if (graph) {
411 agclose(graph);
412 graph = nullptr;
413 }
414 graphData.clear();
415 graphData.append(m->toPlainText());
416 setActiveWindow(m);
417 return true;
418}
419
420bool CFrmSettings::createLayout() {
421 rdr_t rdr;
422 // first attach attributes to graph
423 int _pos = graphData.indexOf(tr("{"));
424 graphData.replace(_pos, 1,
425 QLatin1Char('{') +
426 WIDGET(QTextEdit, teAttributes)->toPlainText());
427
428 /* Reset line number and file name;
429 * If known, might want to use real name
430 */
431 agsetfile("<gvedit>");
432 QByteArray bytes = graphData.toUtf8();
433 rdr.data = bytes.constData();
434 rdr.len = strlen(rdr.data);
435 rdr.cur = 0;
436 graph = agmemread(rdr.data);
437 if (!graph)
438 return false;
439 if (agerrors()) {
440 agclose(graph);
441 graph = nullptr;
442 return false;
443 }
444 Agraph_t *G = this->graph;
445 QString layout;
446
447 layout = WIDGET(QComboBox, cbLayout)->currentText();
448
449 gvLayout(gvc, G, layout.toUtf8().constData()); /* library function */
450 return true;
451}
452
453static QString buildTempFile() {
454 QTemporaryFile tempFile;
455 tempFile.setAutoRemove(false);
456 tempFile.open();
457 QString a = tempFile.fileName();
458 tempFile.close();
459 return a;
460}
461
462void CFrmSettings::doPreview(const QString &fileName) {
463 if (getActiveWindow()->previewFrm != nullptr) {
464 getActiveWindow()->parentFrm->mdiArea->removeSubWindow(
465 getActiveWindow()->previewFrm->subWindowRef);
466 getActiveWindow()->previewFrm = nullptr;
467 }
468
469 if ((fileName.isNull()) ||
470 !(getActiveWindow()->loadPreview(fileName))) { // create preview
471 QString prevFile(buildTempFile());
472 gvRenderFilename(gvc, graph, "png", prevFile.toUtf8().constData());
473 getActiveWindow()->loadPreview(prevFile);
474 }
475}
476
477bool CFrmSettings::renderLayout() {
478 if (!graph)
479 return false;
480 QString sfx = WIDGET(QComboBox, cbExtension)->currentText();
481 QString fileName(WIDGET(QLineEdit, leOutput)->text());
482
483 if (fileName.isEmpty() || sfx == QLatin1String("NONE"))
484 doPreview(QString());
485 else {
487 fileName = fileName + QLatin1Char('.') + sfx;
488 if (fileName != activeWindow->outputFile)
489 activeWindow->outputFile = fileName;
490
491#ifdef _WIN32
492 if (!fileName.contains(u'/') && !fileName.contains(u'\\'))
493#else
494 if (!fileName.contains(u'/'))
495#endif
496 { // no directory info => can we create/write the file?
497 QFile outf(fileName);
498 if (outf.open(QIODevice::WriteOnly))
499 outf.close();
500 else {
501 QString pathName = QDir::homePath();
502 pathName.append(u'/').append(fileName);
503 fileName = QDir::toNativeSeparators(pathName);
504 const QString msg =
505 QStringLiteral("Output written to %1\n").arg(fileName);
506 errorPipe(msg.toLatin1().data());
507 }
508 }
509
510 if (gvRenderFilename(gvc, graph, sfx.toUtf8().constData(),
511 fileName.toUtf8().constData()))
512 return false;
513
514 doPreview(fileName);
515 }
516 return true;
517}
518
519bool CFrmSettings::loadLayouts() { return false; }
520
521bool CFrmSettings::loadRenderers() { return false; }
522
523void CFrmSettings::refreshContent() {
524
525 WIDGET(QComboBox, cbLayout)->setCurrentIndex(activeWindow->layoutIdx);
526 WIDGET(QComboBox, cbExtension)->setCurrentIndex(activeWindow->renderIdx);
527 if (!activeWindow->outputFile.isEmpty())
528 WIDGET(QLineEdit, leOutput)->setText(activeWindow->outputFile);
529 else
530 WIDGET(QLineEdit, leOutput)
531 ->setText(stripFileExtension(activeWindow->currentFile()) +
532 QLatin1Char('.') +
533 WIDGET(QComboBox, cbExtension)->currentText());
534
535 WIDGET(QTextEdit, teAttributes)->setText(activeWindow->attributes);
536
537 WIDGET(QLineEdit, leValue)->clear();
538}
539
540void CFrmSettings::saveContent() {
541 activeWindow->layoutIdx = WIDGET(QComboBox, cbLayout)->currentIndex();
542 activeWindow->renderIdx = WIDGET(QComboBox, cbExtension)->currentIndex();
543 activeWindow->outputFile = WIDGET(QLineEdit, leOutput)->text();
544 activeWindow->attributes = WIDGET(QTextEdit, teAttributes)->toPlainText();
545}
546
548 if (createLayout() && renderLayout()) {
549 getActiveWindow()->settingsSet = false;
550 }
552
553 return QDialog::Accepted;
554}
555
557 if (this->loadGraph(m))
558 return drawGraph();
559
560 if (m && m == getActiveWindow()) {
561 if (this->loadGraph(m))
562 return drawGraph();
563 else
564 return QDialog::Rejected;
565 }
566
567 else
568 return showSettings(m);
569}
570
572
573 if (this->loadGraph(m)) {
574 refreshContent();
575 return this->exec();
576 } else
577 return QDialog::Rejected;
578}
579
580void CFrmSettings::setActiveWindow(MdiChild *m) { this->activeWindow = m; }
581
582MdiChild *CFrmSettings::getActiveWindow() { return activeWindow; }
static void out(agerrlevel_t level, const char *fmt, va_list args)
Report messages using a user-supplied or default write function.
Definition agerror.c:84
QString graphData
Definition csettings.h:31
int runSettings(MdiChild *m)
int showSettings(MdiChild *m)
GVC_t * gvc
Definition csettings.h:32
MdiChild * getActiveWindow()
QMdiArea * mdiArea
Definition mainwindow.h:40
std::unique_ptr< ImageViewer > previewFrm
Definition mdichild.h:38
bool loadPreview(const QString &fileName)
Definition mdichild.cpp:156
QString attributes
Definition mdichild.h:37
QString outputFile
Definition mdichild.h:34
int layoutIdx
Definition mdichild.h:32
bool settingsSet
Definition mdichild.h:42
int renderIdx
Definition mdichild.h:33
CMainWindow * parentFrm
Definition mdichild.h:39
QString currentFile()
Definition mdichild.h:31
void setupUi(QDialog *Dialog)
Definition ui_settings.h:84
#define WIDGET(t, f)
Definition csettings.cpp:43
bool loadAttrs(const QString &fileName, QComboBox *cbNameG, QComboBox *cbNameN, QComboBox *cbNameE)
QString stripFileExtension(const QString &fileName)
static std::string find_me(void)
find an absolute path to the current executable
Definition csettings.cpp:75
int errorPipe(char *errMsg)
static std::string readln(const std::string &path)
readlink-alike but dynamically allocates
Definition csettings.cpp:47
static std::string find_share(void)
find an absolute path to where Smyrna auxiliary files are stored
static QString buildTempFile()
#define G
Definition gdefs.h:7
node NULL
Definition grammar.y:149
int agreseterrors(void)
Definition agerror.c:183
int agerrors(void)
Definition agerror.c:181
int agclose(Agraph_t *g)
deletes a graph, freeing its associated storage
Definition graph.c:96
Agraph_t * agmemread(const char *cp)
reads a graph from the input string
Definition io.c:97
void agsetfile(const char *)
sets the current file name for subsequent error reporting
Definition scan.c:839
int gvLayout(GVC_t *gvc, graph_t *g, const char *engine)
Definition gvc.c:52
GVC_t * gvContext(void)
Definition gvc.c:24
int gvRenderFilename(GVC_t *gvc, graph_t *g, const char *format, const char *filename)
Definition gvc.c:114
static uint64_t id
Definition gv2gml.c:42
agxbuf * str
Definition htmlparse.c:97
char * fileName(ingraph_state *sp)
Return name of current file being processed.
Definition ingraphs.c:156
static int layout(graph_t *g, layout_info *infop)
Definition layout.c:809
QTextStream errout
graph or subgraph
Definition cgraph.h:425
Definition types.h:81
void * data
Definition types.h:86
Definition rdr.h:10
size_t cur
Definition rdr.h:13
const char * data
Definition rdr.h:11
size_t len
Definition rdr.h:12
Definition grammar.c:93