Graphviz 13.0.0~dev.20241220.2304
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) (findChild<t *>(QStringLiteral(#f)))
44
45#ifndef _WIN32
47static std::string readln(const std::string &pathname) {
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(pathname.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> pathname(buf_size);
87
88 // retrieve the actual path
89 if (_NSGetExecutablePath(pathname.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 = pathname.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> pathname;
106 DWORD rc = 0;
107
108 do {
109 {
110 size_t size = pathname.empty() ? 1024 : (pathname.size() * 2);
111 pathname.resize(size);
112 }
113
114 rc = GetModuleFileNameA(NULL, pathname.data(), pathname.size());
115 if (rc == 0) {
116 errout << "failed to get path for executable.\n";
117 return "";
118 }
119
120 } while (rc == pathname.size());
121
122 return pathname.data();
123 }
124#else
125
126 // Linux
127 std::string pathname = readln("/proc/self/exe");
128 if (pathname != "")
129 return pathname;
130
131 // DragonFly BSD, FreeBSD
132 pathname = readln("/proc/curproc/file");
133 if (pathname != "")
134 return pathname;
135
136 // NetBSD
137 pathname = readln("/proc/curproc/exe");
138 if (pathname != "")
139 return pathname;
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 } else {
233 errout << "Could not open attribute name file \"" << fileName
234 << "\" for reading\n";
235 errout.flush();
236 return true;
237 }
238
239 return false;
240}
241
242QString stripFileExtension(const QString &fileName) {
243 // `lastIndexOf` returns -1 if not found and `left` takes a negative number to
244 // mean “the entire string”, so this is a no-op if the filename has no
245 // extension
246 return fileName.left(fileName.lastIndexOf(u'.', fileName.size() - 1));
247}
248
250 this->gvc = gvContext();
251 Ui_Dialog tempDia;
252 tempDia.setupUi(this);
253 graph = nullptr;
254 activeWindow = nullptr;
255 QString pathname;
256 char *s = nullptr;
257#ifndef _WIN32
258 s = getenv("GVEDIT_PATH");
259#endif
260 if (s)
261 pathname = QString::fromUtf8(s);
262 else
263 pathname = QString::fromStdString(find_share());
264
265 connect(WIDGET(QPushButton, pbAdd), &QPushButton::clicked, this,
266 &CFrmSettings::addSlot);
267 connect(WIDGET(QPushButton, pbNew), &QPushButton::clicked, this,
268 &CFrmSettings::newSlot);
269 connect(WIDGET(QPushButton, pbOpen), &QPushButton::clicked, this,
270 &CFrmSettings::openSlot);
271 connect(WIDGET(QPushButton, pbSave), &QPushButton::clicked, this,
272 &CFrmSettings::saveSlot);
273 connect(WIDGET(QPushButton, btnOK), &QPushButton::clicked, this,
274 &CFrmSettings::okSlot);
275 connect(WIDGET(QPushButton, btnCancel), &QPushButton::clicked, this,
276 &CFrmSettings::cancelSlot);
277 connect(WIDGET(QPushButton, pbOut), &QPushButton::clicked, this,
278 &CFrmSettings::outputSlot);
279 connect(WIDGET(QPushButton, pbHelp), &QPushButton::clicked, this,
280 &CFrmSettings::helpSlot);
281
282 connect(WIDGET(QComboBox, cbScope),
283 QOverload<int>::of(&QComboBox::currentIndexChanged), this,
284 &CFrmSettings::scopeChangedSlot);
285 scopeChangedSlot(0);
286
287 if (!pathname.isEmpty()) {
288 loadAttrs(pathname + QLatin1String("/attrs.txt"),
289 WIDGET(QComboBox, cbNameG), WIDGET(QComboBox, cbNameN),
290 WIDGET(QComboBox, cbNameE));
291 }
292 setWindowIcon(QIcon(QStringLiteral(":/images/icon.png")));
293}
294
295void CFrmSettings::outputSlot() {
296 QString _filter = QStringLiteral("Output File(*.%1)")
297 .arg(WIDGET(QComboBox, cbExtension)->currentText());
298 QString fileName = QFileDialog::getSaveFileName(this, tr("Save Graph As.."),
299 QStringLiteral("/"), _filter);
300 if (!fileName.isEmpty())
301 WIDGET(QLineEdit, leOutput)->setText(fileName);
302}
303
304void CFrmSettings::scopeChangedSlot(int id) {
305 WIDGET(QComboBox, cbNameG)->setVisible(id == 0);
306 WIDGET(QComboBox, cbNameN)->setVisible(id == 1);
307 WIDGET(QComboBox, cbNameE)->setVisible(id == 2);
308}
309
310void CFrmSettings::addSlot() {
311 QString _scope = WIDGET(QComboBox, cbScope)->currentText();
312 QString _name;
313 switch (WIDGET(QComboBox, cbScope)->currentIndex()) {
314 case 0:
315 _name = WIDGET(QComboBox, cbNameG)->currentText();
316 break;
317 case 1:
318 _name = WIDGET(QComboBox, cbNameN)->currentText();
319 break;
320 case 2:
321 _name = WIDGET(QComboBox, cbNameE)->currentText();
322 break;
323 }
324 QString _value = WIDGET(QLineEdit, leValue)->text();
325
326 if (_value.trimmed().isEmpty())
327 QMessageBox::warning(this, tr("GvEdit"),
328 tr("Please enter a value for selected attribute!"),
329 QMessageBox::Ok, QMessageBox::Ok);
330 else {
331 QString str = _scope + QLatin1Char(u'[') + _name + QLatin1String("=\"");
332 if (WIDGET(QTextEdit, teAttributes)->toPlainText().contains(str)) {
333 QMessageBox::warning(this, tr("GvEdit"),
334 tr("Attribute is already defined!"), QMessageBox::Ok,
335 QMessageBox::Ok);
336 return;
337 }
338 str = str + _value + QLatin1String("\"]");
339 WIDGET(QTextEdit, teAttributes)
340 ->setPlainText(WIDGET(QTextEdit, teAttributes)->toPlainText() + str +
341 QLatin1Char('\n'));
342 }
343}
344
345void CFrmSettings::helpSlot() {
346 QDesktopServices::openUrl(
347 QUrl(QStringLiteral("http://www.graphviz.org/doc/info/attrs.html")));
348}
349
350void CFrmSettings::cancelSlot() { this->reject(); }
351
352void CFrmSettings::okSlot() {
353 saveContent();
354 this->done(drawGraph());
355}
356
357void CFrmSettings::newSlot() {
358 WIDGET(QTextEdit, teAttributes)->setPlainText(tr(""));
359}
360
361void CFrmSettings::openSlot() {
362 QString fileName = QFileDialog::getOpenFileName(
363 this, tr("Open File"), QStringLiteral("/"), tr("Text file (*.*)"));
364 if (!fileName.isEmpty()) {
365 QFile file(fileName);
366 if (!file.open(QFile::ReadOnly | QFile::Text)) {
367 QMessageBox::warning(this, tr("MDI"),
368 tr("Cannot read file %1:\n%2.")
369 .arg(fileName)
370 .arg(file.errorString()));
371 return;
372 }
373
374 QTextStream in(&file);
375 WIDGET(QTextEdit, teAttributes)->setPlainText(in.readAll());
376 }
377}
378
379void CFrmSettings::saveSlot() {
380
381 if (WIDGET(QTextEdit, teAttributes)->toPlainText().trimmed().isEmpty()) {
382 QMessageBox::warning(this, tr("GvEdit"), tr("Nothing to save!"),
383 QMessageBox::Ok, QMessageBox::Ok);
384 return;
385 }
386
387 QString fileName = QFileDialog::getSaveFileName(
388 this, tr("Open File"), QStringLiteral("/"), tr("Text File(*.*)"));
389 if (!fileName.isEmpty()) {
390
391 QFile file(fileName);
392 if (!file.open(QFile::WriteOnly | QFile::Text)) {
393 QMessageBox::warning(this, tr("MDI"),
394 tr("Cannot write file %1:\n%2.")
395 .arg(fileName)
396 .arg(file.errorString()));
397 return;
398 }
399
400 QTextStream out(&file);
401 out << WIDGET(QTextEdit, teAttributes)->toPlainText();
402 }
403}
404
405bool CFrmSettings::loadGraph(MdiChild *m) {
406 if (graph) {
407 agclose(graph);
408 graph = nullptr;
409 }
410 graphData.clear();
411 graphData.append(m->toPlainText());
412 setActiveWindow(m);
413 return true;
414}
415
416bool CFrmSettings::createLayout() {
417 rdr_t rdr;
418 // first attach attributes to graph
419 int _pos = graphData.indexOf(tr("{"));
420 graphData.replace(_pos, 1,
421 QLatin1Char('{') +
422 WIDGET(QTextEdit, teAttributes)->toPlainText());
423
424 /* Reset line number and file name;
425 * If known, might want to use real name
426 */
427 agsetfile("<gvedit>");
428 QByteArray bytes = graphData.toUtf8();
429 rdr.data = bytes.constData();
430 rdr.len = strlen(rdr.data);
431 rdr.cur = 0;
432 graph = agmemread(rdr.data);
433 if (!graph)
434 return false;
435 if (agerrors()) {
436 agclose(graph);
437 graph = nullptr;
438 return false;
439 }
440 Agraph_t *G = this->graph;
441 QString layout;
442
443 layout = WIDGET(QComboBox, cbLayout)->currentText();
444
445 gvLayout(gvc, G, layout.toUtf8().constData()); /* library function */
446 return true;
447}
448
449static QString buildTempFile() {
450 QTemporaryFile tempFile;
451 tempFile.setAutoRemove(false);
452 tempFile.open();
453 QString a = tempFile.fileName();
454 return a;
455}
456
457void CFrmSettings::doPreview(const QString &fileName) {
458 if (getActiveWindow()->previewFrm != nullptr) {
459 getActiveWindow()->parentFrm->mdiArea->removeSubWindow(
460 getActiveWindow()->previewFrm->subWindowRef);
461 getActiveWindow()->previewFrm = nullptr;
462 }
463
464 if (fileName.isNull() ||
465 !getActiveWindow()->loadPreview(fileName)) { // create preview
466 QString prevFile(buildTempFile());
467 gvRenderFilename(gvc, graph, "png", prevFile.toUtf8().constData());
468 getActiveWindow()->loadPreview(prevFile);
469 }
470}
471
472bool CFrmSettings::renderLayout() {
473 if (!graph)
474 return false;
475 QString sfx = WIDGET(QComboBox, cbExtension)->currentText();
476 QString fileName(WIDGET(QLineEdit, leOutput)->text());
477
478 if (fileName.isEmpty() || sfx == QLatin1String("NONE"))
479 doPreview(QString());
480 else {
482 fileName = fileName + QLatin1Char('.') + sfx;
483 if (fileName != activeWindow->outputFile)
484 activeWindow->outputFile = fileName;
485
486#ifdef _WIN32
487 if (!fileName.contains(u'/') && !fileName.contains(u'\\'))
488#else
489 if (!fileName.contains(u'/'))
490#endif
491 { // no directory info => can we create/write the file?
492 QFile outf(fileName);
493 if (!outf.open(QIODevice::WriteOnly)) {
494 QString pathName = QDir::homePath();
495 pathName.append(u'/').append(fileName);
496 fileName = QDir::toNativeSeparators(pathName);
497 const QString msg =
498 QStringLiteral("Output written to %1\n").arg(fileName);
499 errorPipe(msg.toLatin1().data());
500 }
501 }
502
503 if (gvRenderFilename(gvc, graph, sfx.toUtf8().constData(),
504 fileName.toUtf8().constData()))
505 return false;
506
507 doPreview(fileName);
508 }
509 return true;
510}
511
512bool CFrmSettings::loadLayouts() { return false; }
513
514bool CFrmSettings::loadRenderers() { return false; }
515
516void CFrmSettings::refreshContent() {
517
518 WIDGET(QComboBox, cbLayout)->setCurrentIndex(activeWindow->layoutIdx);
519 WIDGET(QComboBox, cbExtension)->setCurrentIndex(activeWindow->renderIdx);
520 if (!activeWindow->outputFile.isEmpty())
521 WIDGET(QLineEdit, leOutput)->setText(activeWindow->outputFile);
522 else
523 WIDGET(QLineEdit, leOutput)
524 ->setText(stripFileExtension(activeWindow->currentFile()) +
525 QLatin1Char('.') +
526 WIDGET(QComboBox, cbExtension)->currentText());
527
528 WIDGET(QTextEdit, teAttributes)->setText(activeWindow->attributes);
529
530 WIDGET(QLineEdit, leValue)->clear();
531}
532
533void CFrmSettings::saveContent() {
534 activeWindow->layoutIdx = WIDGET(QComboBox, cbLayout)->currentIndex();
535 activeWindow->renderIdx = WIDGET(QComboBox, cbExtension)->currentIndex();
536 activeWindow->outputFile = WIDGET(QLineEdit, leOutput)->text();
537 activeWindow->attributes = WIDGET(QTextEdit, teAttributes)->toPlainText();
538}
539
541 if (createLayout() && renderLayout()) {
542 getActiveWindow()->settingsSet = false;
543 }
545
546 return QDialog::Accepted;
547}
548
550 if (this->loadGraph(m))
551 return drawGraph();
552
553 if (m && m == getActiveWindow()) {
554 if (this->loadGraph(m))
555 return drawGraph();
556 return QDialog::Rejected;
557 }
558
559 return showSettings(m);
560}
561
563
564 if (this->loadGraph(m)) {
565 refreshContent();
566 return this->exec();
567 }
568 return QDialog::Rejected;
569}
570
571void CFrmSettings::setActiveWindow(MdiChild *m) { this->activeWindow = m; }
572
573MdiChild *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:140
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:81
#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
static std::string readln(const std::string &pathname)
readlink-alike but dynamically allocates
Definition csettings.cpp:47
int errorPipe(char *errMsg)
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:163
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:102
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:2415
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
textitem scanner parser str
Definition htmlparse.y:224
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:811
QTextStream errout
graph or subgraph
Definition cgraph.h:425
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