Graphviz 13.0.0~dev.20250121.0651
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 /* Reset line number and file name;
295 * If known, might want to use real name
296 */
297 agsetfile("<gvedit>");
298 QByteArray bytes = graphData.toUtf8();
299 rdr.data = bytes.constData();
300 rdr.len = strlen(rdr.data);
301 rdr.cur = 0;
302 graph = agmemread(rdr.data);
303 if (!graph)
304 return false;
305 if (agerrors()) {
306 agclose(graph);
307 graph = nullptr;
308 return false;
309 }
310 Agraph_t *G = this->graph;
311 QString layout;
312
313 layout = WIDGET(QComboBox, cbLayout)->currentText();
314
315 gvLayout(gvc, G, layout.toUtf8().constData()); /* library function */
316 return true;
317}
318
319static QString buildTempFile() {
320 QTemporaryFile tempFile;
321 tempFile.setAutoRemove(false);
322 tempFile.open();
323 QString a = tempFile.fileName();
324 return a;
325}
326
327void CFrmSettings::doPreview(const QString &fileName) {
328 if (getActiveWindow()->previewFrm != nullptr) {
329 getActiveWindow()->parentFrm->mdiArea->removeSubWindow(
330 getActiveWindow()->previewFrm->subWindowRef);
331 getActiveWindow()->previewFrm = nullptr;
332 }
333
334 if (fileName.isNull() ||
335 !getActiveWindow()->loadPreview(fileName)) { // create preview
336 QString prevFile(buildTempFile());
337 gvRenderFilename(gvc, graph, "png", prevFile.toUtf8().constData());
338 getActiveWindow()->loadPreview(prevFile);
339 }
340}
341
342bool CFrmSettings::renderLayout() {
343 if (!graph)
344 return false;
345 QString sfx = WIDGET(QComboBox, cbExtension)->currentText();
346 QString fileName(WIDGET(QLineEdit, leOutput)->text());
347
348 if (fileName.isEmpty() || sfx == QLatin1String("NONE"))
349 doPreview(QString());
350 else {
352 fileName = fileName + QLatin1Char('.') + sfx;
353 if (fileName != activeWindow->outputFile)
354 activeWindow->outputFile = fileName;
355
356#ifdef _WIN32
357 if (!fileName.contains(u'/') && !fileName.contains(u'\\'))
358#else
359 if (!fileName.contains(u'/'))
360#endif
361 { // no directory info => can we create/write the file?
362 QFile outf(fileName);
363 if (!outf.open(QIODevice::WriteOnly)) {
364 QString pathName = QDir::homePath();
365 pathName.append(u'/').append(fileName);
366 fileName = QDir::toNativeSeparators(pathName);
367 const QString msg =
368 QStringLiteral("Output written to %1\n").arg(fileName);
369 errorPipe(msg.toLatin1().data());
370 }
371 }
372
373 if (gvRenderFilename(gvc, graph, sfx.toUtf8().constData(),
374 fileName.toUtf8().constData()))
375 return false;
376
377 doPreview(fileName);
378 }
379 return true;
380}
381
382bool CFrmSettings::loadLayouts() { return false; }
383
384bool CFrmSettings::loadRenderers() { return false; }
385
386void CFrmSettings::refreshContent() {
387
388 WIDGET(QComboBox, cbLayout)->setCurrentIndex(activeWindow->layoutIdx);
389 WIDGET(QComboBox, cbExtension)->setCurrentIndex(activeWindow->renderIdx);
390 if (!activeWindow->outputFile.isEmpty())
391 WIDGET(QLineEdit, leOutput)->setText(activeWindow->outputFile);
392 else
393 WIDGET(QLineEdit, leOutput)
394 ->setText(stripFileExtension(activeWindow->currentFile()) +
395 QLatin1Char('.') +
396 WIDGET(QComboBox, cbExtension)->currentText());
397
398 WIDGET(QTextEdit, teAttributes)->setText(activeWindow->attributes);
399
400 WIDGET(QLineEdit, leValue)->clear();
401}
402
403void CFrmSettings::saveContent() {
404 activeWindow->layoutIdx = WIDGET(QComboBox, cbLayout)->currentIndex();
405 activeWindow->renderIdx = WIDGET(QComboBox, cbExtension)->currentIndex();
406 activeWindow->outputFile = WIDGET(QLineEdit, leOutput)->text();
407 activeWindow->attributes = WIDGET(QTextEdit, teAttributes)->toPlainText();
408}
409
411 if (createLayout() && renderLayout()) {
412 getActiveWindow()->settingsSet = false;
413 }
415
416 return QDialog::Accepted;
417}
418
420 if (this->loadGraph(m))
421 return drawGraph();
422
423 if (m && m == getActiveWindow()) {
424 if (this->loadGraph(m))
425 return drawGraph();
426 return QDialog::Rejected;
427 }
428
429 return showSettings(m);
430}
431
433
434 if (this->loadGraph(m)) {
435 refreshContent();
436 return this->exec();
437 }
438 return QDialog::Rejected;
439}
440
441void CFrmSettings::setActiveWindow(MdiChild *m) { this->activeWindow = m; }
442
443MdiChild *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: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: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:99
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
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:156
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:93