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