csettings.cpp 16 KB

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