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