Graphviz 13.0.0~dev.20250607.1528
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 <cgraph/rdr.h>
22#include <cstdlib>
23#include <qfile.h>
24#include <string>
25#include <util/gv_find_me.h>
26
27extern int errorPipe(char *errMsg);
28
29#define WIDGET(t, f) (findChild<t *>(QStringLiteral(#f)))
30
32static std::string find_me() {
33 char *const me = gv_find_me();
34 const std::string me_s = me == NULL ? "" : me;
35 free(me);
36 return me_s;
37}
38
40static std::string find_share(void) {
41
42#ifdef _WIN32
43 const char PATH_SEPARATOR = '\\';
44#else
45 const char PATH_SEPARATOR = '/';
46#endif
47
48 // find the path to the `gvedit` binary
49 std::string gvedit_exe = find_me();
50 if (gvedit_exe == "")
51 return "";
52
53 // assume it is of the form …/bin/gvedit[.exe] and construct
54 // …/share/graphviz/gvedit
55
56 size_t slash = gvedit_exe.rfind(PATH_SEPARATOR);
57 if (slash == std::string::npos) {
58 errout << "no path separator in path to self, " << gvedit_exe.c_str()
59 << '\n';
60 return "";
61 }
62
63 std::string bin = gvedit_exe.substr(0, slash);
64 slash = bin.rfind(PATH_SEPARATOR);
65 if (slash == std::string::npos) {
66 errout << "no path separator in directory containing self, " << bin.c_str()
67 << '\n';
68 return "";
69 }
70
71 std::string install_prefix = bin.substr(0, slash);
72
73 return install_prefix + PATH_SEPARATOR + "share" + PATH_SEPARATOR +
74 "graphviz" + PATH_SEPARATOR + "gvedit";
75}
76
77bool loadAttrs(const QString &fileName, QComboBox *cbNameG, QComboBox *cbNameN,
78 QComboBox *cbNameE) {
79 QFile file(fileName);
80 if (file.open(QIODevice::ReadOnly)) {
81 QTextStream stream(&file);
82 QString line;
83 while (!stream.atEnd()) {
84 line = stream.readLine(); // line of text excluding '\n'
85 if (line.left(1) == QLatin1String(":")) {
86 QString attrName;
87 QStringList sl = line.split(u':');
88 for (int id = 0; id < sl.count(); id++) {
89 if (id == 1)
90 attrName = sl[id];
91 if (id == 2) {
92 if (sl[id].contains(u'G'))
93 cbNameG->addItem(attrName);
94 if (sl[id].contains(u'N'))
95 cbNameN->addItem(attrName);
96 if (sl[id].contains(u'E'))
97 cbNameE->addItem(attrName);
98 }
99 }
100 }
101 }
102 } else {
103 errout << "Could not open attribute name file \"" << fileName
104 << "\" for reading\n";
105 errout.flush();
106 return true;
107 }
108
109 return false;
110}
111
112QString stripFileExtension(const QString &fileName) {
113 // `lastIndexOf` returns -1 if not found and `left` takes a negative number to
114 // mean “the entire string”, so this is a no-op if the filename has no
115 // extension
116 return fileName.left(fileName.lastIndexOf(u'.', fileName.size() - 1));
117}
118
120 this->gvc = gvContext();
121 Ui_Dialog tempDia;
122 tempDia.setupUi(this);
123 graph = nullptr;
124 activeWindow = nullptr;
125 QString pathname;
126 char *s = nullptr;
127#ifndef _WIN32
128 s = getenv("GVEDIT_PATH");
129#endif
130 if (s)
131 pathname = QString::fromUtf8(s);
132 else
133 pathname = QString::fromStdString(find_share());
134
135 connect(WIDGET(QPushButton, pbAdd), &QPushButton::clicked, this,
136 &CFrmSettings::addSlot);
137 connect(WIDGET(QPushButton, pbNew), &QPushButton::clicked, this,
138 &CFrmSettings::newSlot);
139 connect(WIDGET(QPushButton, pbOpen), &QPushButton::clicked, this,
140 &CFrmSettings::openSlot);
141 connect(WIDGET(QPushButton, pbSave), &QPushButton::clicked, this,
142 &CFrmSettings::saveSlot);
143 connect(WIDGET(QPushButton, btnOK), &QPushButton::clicked, this,
144 &CFrmSettings::okSlot);
145 connect(WIDGET(QPushButton, btnCancel), &QPushButton::clicked, this,
146 &CFrmSettings::cancelSlot);
147 connect(WIDGET(QPushButton, pbOut), &QPushButton::clicked, this,
148 &CFrmSettings::outputSlot);
149 connect(WIDGET(QPushButton, pbHelp), &QPushButton::clicked, this,
150 &CFrmSettings::helpSlot);
151
152 connect(WIDGET(QComboBox, cbScope),
153 QOverload<int>::of(&QComboBox::currentIndexChanged), this,
154 &CFrmSettings::scopeChangedSlot);
155 scopeChangedSlot(0);
156
157 if (!pathname.isEmpty()) {
158 loadAttrs(pathname + QLatin1String("/attrs.txt"),
159 WIDGET(QComboBox, cbNameG), WIDGET(QComboBox, cbNameN),
160 WIDGET(QComboBox, cbNameE));
161 }
162 setWindowIcon(QIcon(QStringLiteral(":/images/icon.png")));
163}
164
165void CFrmSettings::outputSlot() {
166 QString _filter = QStringLiteral("Output File(*.%1)")
167 .arg(WIDGET(QComboBox, cbExtension)->currentText());
168 QString fileName = QFileDialog::getSaveFileName(this, tr("Save Graph As.."),
169 QStringLiteral("/"), _filter);
170 if (!fileName.isEmpty())
171 WIDGET(QLineEdit, leOutput)->setText(fileName);
172}
173
174void CFrmSettings::scopeChangedSlot(int id) {
175 WIDGET(QComboBox, cbNameG)->setVisible(id == 0);
176 WIDGET(QComboBox, cbNameN)->setVisible(id == 1);
177 WIDGET(QComboBox, cbNameE)->setVisible(id == 2);
178}
179
180void CFrmSettings::addSlot() {
181 QString _scope = WIDGET(QComboBox, cbScope)->currentText();
182 QString _name;
183 switch (WIDGET(QComboBox, cbScope)->currentIndex()) {
184 case 0:
185 _name = WIDGET(QComboBox, cbNameG)->currentText();
186 break;
187 case 1:
188 _name = WIDGET(QComboBox, cbNameN)->currentText();
189 break;
190 case 2:
191 _name = WIDGET(QComboBox, cbNameE)->currentText();
192 break;
193 }
194 QString _value = WIDGET(QLineEdit, leValue)->text();
195
196 if (_value.trimmed().isEmpty())
197 QMessageBox::warning(this, tr("GvEdit"),
198 tr("Please enter a value for selected attribute!"),
199 QMessageBox::Ok, QMessageBox::Ok);
200 else {
201 QString str = _scope + QLatin1Char(u'[') + _name + QLatin1String("=\"");
202 if (WIDGET(QTextEdit, teAttributes)->toPlainText().contains(str)) {
203 QMessageBox::warning(this, tr("GvEdit"),
204 tr("Attribute is already defined!"), QMessageBox::Ok,
205 QMessageBox::Ok);
206 return;
207 }
208 str = str + _value + QLatin1String("\"]");
209 WIDGET(QTextEdit, teAttributes)
210 ->setPlainText(WIDGET(QTextEdit, teAttributes)->toPlainText() + str +
211 QLatin1Char('\n'));
212 }
213}
214
215void CFrmSettings::helpSlot() {
216 QDesktopServices::openUrl(
217 QUrl(QStringLiteral("http://www.graphviz.org/doc/info/attrs.html")));
218}
219
220void CFrmSettings::cancelSlot() { this->reject(); }
221
222void CFrmSettings::okSlot() {
223 saveContent();
224 this->done(drawGraph());
225}
226
227void CFrmSettings::newSlot() {
228 WIDGET(QTextEdit, teAttributes)->setPlainText(tr(""));
229}
230
231void CFrmSettings::openSlot() {
232 QString fileName = QFileDialog::getOpenFileName(
233 this, tr("Open File"), QStringLiteral("/"), tr("Text file (*.*)"));
234 if (!fileName.isEmpty()) {
235 QFile file(fileName);
236 if (!file.open(QFile::ReadOnly | QFile::Text)) {
237 QMessageBox::warning(this, tr("MDI"),
238 tr("Cannot read file %1:\n%2.")
239 .arg(fileName)
240 .arg(file.errorString()));
241 return;
242 }
243
244 QTextStream in(&file);
245 WIDGET(QTextEdit, teAttributes)->setPlainText(in.readAll());
246 }
247}
248
249void CFrmSettings::saveSlot() {
250
251 if (WIDGET(QTextEdit, teAttributes)->toPlainText().trimmed().isEmpty()) {
252 QMessageBox::warning(this, tr("GvEdit"), tr("Nothing to save!"),
253 QMessageBox::Ok, QMessageBox::Ok);
254 return;
255 }
256
257 QString fileName = QFileDialog::getSaveFileName(
258 this, tr("Open File"), QStringLiteral("/"), tr("Text File(*.*)"));
259 if (!fileName.isEmpty()) {
260
261 QFile file(fileName);
262 if (!file.open(QFile::WriteOnly | QFile::Text)) {
263 QMessageBox::warning(this, tr("MDI"),
264 tr("Cannot write file %1:\n%2.")
265 .arg(fileName)
266 .arg(file.errorString()));
267 return;
268 }
269
270 QTextStream out(&file);
271 out << WIDGET(QTextEdit, teAttributes)->toPlainText();
272 }
273}
274
275bool CFrmSettings::loadGraph(MdiChild *m) {
276 if (graph) {
277 agclose(graph);
278 graph = nullptr;
279 }
280 graphData.clear();
281 graphData.append(m->toPlainText());
282 setActiveWindow(m);
283 return true;
284}
285
286bool CFrmSettings::createLayout() {
287 rdr_t rdr;
288 // first attach attributes to graph
289 int _pos = graphData.indexOf(tr("{"));
290 graphData.replace(_pos, 1,
291 QLatin1Char('{') +
292 WIDGET(QTextEdit, teAttributes)->toPlainText());
293
294 QByteArray bytes = graphData.toUtf8();
295 rdr.data = bytes.constData();
296 rdr.len = strlen(rdr.data);
297 rdr.cur = 0;
298 graph = agmemread(rdr.data);
299 if (!graph)
300 return false;
301 if (agerrors()) {
302 agclose(graph);
303 graph = nullptr;
304 return false;
305 }
306 Agraph_t *G = this->graph;
307 QString layout;
308
309 layout = WIDGET(QComboBox, cbLayout)->currentText();
310
311 gvLayout(gvc, G, layout.toUtf8().constData()); /* library function */
312 return true;
313}
314
315static QString buildTempFile() {
316 QTemporaryFile tempFile;
317 tempFile.setAutoRemove(false);
318 tempFile.open();
319 QString a = tempFile.fileName();
320 return a;
321}
322
323void CFrmSettings::doPreview(const QString &fileName) {
324 if (getActiveWindow()->previewFrm != nullptr) {
325 getActiveWindow()->parentFrm->mdiArea->removeSubWindow(
326 getActiveWindow()->previewFrm->subWindowRef);
327 getActiveWindow()->previewFrm = nullptr;
328 }
329
330 if (fileName.isNull() ||
331 !getActiveWindow()->loadPreview(fileName)) { // create preview
332 QString prevFile(buildTempFile());
333 gvRenderFilename(gvc, graph, "png", prevFile.toUtf8().constData());
334 getActiveWindow()->loadPreview(prevFile);
335 }
336}
337
338bool CFrmSettings::renderLayout() {
339 if (!graph)
340 return false;
341 QString sfx = WIDGET(QComboBox, cbExtension)->currentText();
342 QString fileName(WIDGET(QLineEdit, leOutput)->text());
343
344 if (fileName.isEmpty() || sfx == QLatin1String("NONE"))
345 doPreview(QString());
346 else {
348 fileName = fileName + QLatin1Char('.') + sfx;
349 if (fileName != activeWindow->outputFile)
350 activeWindow->outputFile = fileName;
351
352#ifdef _WIN32
353 if (!fileName.contains(u'/') && !fileName.contains(u'\\'))
354#else
355 if (!fileName.contains(u'/'))
356#endif
357 { // no directory info => can we create/write the file?
358 QFile outf(fileName);
359 if (!outf.open(QIODevice::WriteOnly)) {
360 QString pathName = QDir::homePath();
361 pathName.append(u'/').append(fileName);
362 fileName = QDir::toNativeSeparators(pathName);
363 const QString msg =
364 QStringLiteral("Output written to %1\n").arg(fileName);
365 errorPipe(msg.toLatin1().data());
366 }
367 }
368
369 if (gvRenderFilename(gvc, graph, sfx.toUtf8().constData(),
370 fileName.toUtf8().constData()))
371 return false;
372
373 doPreview(fileName);
374 }
375 return true;
376}
377
378bool CFrmSettings::loadLayouts() { return false; }
379
380bool CFrmSettings::loadRenderers() { return false; }
381
382void CFrmSettings::refreshContent() {
383
384 WIDGET(QComboBox, cbLayout)->setCurrentIndex(activeWindow->layoutIdx);
385 WIDGET(QComboBox, cbExtension)->setCurrentIndex(activeWindow->renderIdx);
386 if (!activeWindow->outputFile.isEmpty())
387 WIDGET(QLineEdit, leOutput)->setText(activeWindow->outputFile);
388 else
389 WIDGET(QLineEdit, leOutput)
390 ->setText(stripFileExtension(activeWindow->currentFile()) +
391 QLatin1Char('.') +
392 WIDGET(QComboBox, cbExtension)->currentText());
393
394 WIDGET(QTextEdit, teAttributes)->setText(activeWindow->attributes);
395
396 WIDGET(QLineEdit, leValue)->clear();
397}
398
399void CFrmSettings::saveContent() {
400 activeWindow->layoutIdx = WIDGET(QComboBox, cbLayout)->currentIndex();
401 activeWindow->renderIdx = WIDGET(QComboBox, cbExtension)->currentIndex();
402 activeWindow->outputFile = WIDGET(QLineEdit, leOutput)->text();
403 activeWindow->attributes = WIDGET(QTextEdit, teAttributes)->toPlainText();
404}
405
407 if (createLayout() && renderLayout()) {
408 getActiveWindow()->settingsSet = false;
409 }
411
412 return QDialog::Accepted;
413}
414
416 if (this->loadGraph(m))
417 return drawGraph();
418
419 if (m && m == getActiveWindow()) {
420 if (this->loadGraph(m))
421 return drawGraph();
422 return QDialog::Rejected;
423 }
424
425 return showSettings(m);
426}
427
429
430 if (this->loadGraph(m)) {
431 refreshContent();
432 return this->exec();
433 }
434 return QDialog::Rejected;
435}
436
437void CFrmSettings::setActiveWindow(MdiChild *m) { this->activeWindow = m; }
438
439MdiChild *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
static int in(Extype_t lhs, Exid_t *rhs, Exdisc_t *disc)
Definition compile.c:1638
#define WIDGET(t, f)
Definition csettings.cpp:29
bool loadAttrs(const QString &fileName, QComboBox *cbNameG, QComboBox *cbNameN, QComboBox *cbNameE)
Definition csettings.cpp:77
QString stripFileExtension(const QString &fileName)
int errorPipe(char *errMsg)
static std::string find_me()
wrapper around gv_find_me to convert to a C++ type
Definition csettings.cpp:32
static std::string find_share(void)
find an absolute path to where Gvedit auxiliary files are stored
Definition csettings.cpp:40
static QString buildTempFile()
#define G
Definition gdefs.h:7
void free(void *)
node NULL
Definition grammar.y:180
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:95
Agraph_t * agmemread(const char *cp)
reads a graph from the input string
Definition io.c:90
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:40
char * gv_find_me(void)
Definition gv_find_me.c:65
platform abstraction for finding the path to yourself
textitem scanner parser str
Definition htmlparse.y:224
char * fileName(ingraph_state *sp)
Return name of current file being processed.
Definition ingraphs.c:154
static int layout(graph_t *g, layout_info *infop)
Definition layout.c:814
QTextStream errout
graph or subgraph
Definition cgraph.h:424
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:89