Graphviz 14.1.2~dev.20260118.1035
Loading...
Searching...
No Matches
mainwindow.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#include "csettings.h"
14#include "mainwindow.h"
15#include "mdichild.h"
16#include <QStringList>
17#include <QtWidgets>
18#include <optional>
19#include <qframe.h>
20#include <string_view>
21
22QTextEdit *globTextEdit;
23
24int errorPipe(char *errMsg) {
25 globTextEdit->setText(globTextEdit->toPlainText() +
26 QString::fromUtf8(errMsg));
27 return 0;
28}
29
30static void freeList(char **lp, int count) {
31 for (int i = 0; i < count; i++)
32 free(lp[i]);
33 free(lp);
34}
35
36static int LoadPlugins(QComboBox &cb, GVC_t *gvc, const char *kind,
37 const QStringList &more, std::string_view prefer) {
38 int count;
39 char **lp = gvPluginList(gvc, kind, &count);
40 std::optional<int> idx;
41
42 cb.clear();
43 for (int id = 0; id < count; id++) {
44 cb.addItem(QString::fromUtf8(lp[id]));
45 if (!idx.has_value() && prefer == lp[id])
46 idx = id;
47 }
48 freeList(lp, count);
49
50 /* Add additional items if supplied */
51 cb.addItems(more);
52
53 if (idx.has_value())
54 cb.setCurrentIndex(*idx);
55
56 return idx.value_or(0);
57}
58
59void CMainWindow::createConsole() {
60 QDockWidget *dock = new QDockWidget(tr("Output Console"), nullptr);
61 QTextEdit *textEdit = new QTextEdit(dock);
62
63 dock->setAllowedAreas(Qt::BottomDockWidgetArea | Qt::TopDockWidgetArea);
64 addDockWidget(Qt::BottomDockWidgetArea, dock);
65 QVBoxLayout *vL = new QVBoxLayout();
66
67 textEdit->setObjectName(QStringLiteral("textEdit"));
68 globTextEdit = textEdit;
70
71 vL->addWidget(textEdit);
72 vL->setContentsMargins(1, 1, 1, 1);
73
74 QFrame *fr = new QFrame(dock);
75 vL->addWidget(fr);
76
77 QPushButton *logNewBtn =
78 new QPushButton(QIcon(QStringLiteral(":/images/new.png")), {}, fr);
79 QPushButton *logSaveBtn =
80 new QPushButton(QIcon(QStringLiteral(":/images/save.png")), {}, fr);
81 QHBoxLayout *consoleLayout = new QHBoxLayout();
82 consoleLayout->addWidget(logNewBtn);
83 connect(logNewBtn, &QPushButton::clicked, this, &CMainWindow::slotNewLog);
84 connect(logSaveBtn, &QPushButton::clicked, this, &CMainWindow::slotSaveLog);
85 consoleLayout->addWidget(logSaveBtn);
86 consoleLayout->addStretch();
87
88 consoleLayout->setContentsMargins(1, 1, 1, 1);
89
90 fr->setLayout(consoleLayout);
91
92 QFrame *mainFrame = new QFrame(dock);
93 mainFrame->setLayout(vL);
94
95 dock->setWidget(mainFrame);
96}
97
98static const QStringList xtra = {QStringLiteral("NONE")};
99
100CMainWindow::CMainWindow(const QStringList &files) {
101
102 QWidget *centralwidget = new QWidget(this);
103 centralwidget->setObjectName(QStringLiteral("centralwidget"));
104 QVBoxLayout *verticalLayout_2 = new QVBoxLayout(centralwidget);
105 verticalLayout_2->setObjectName(QStringLiteral("verticalLayout_2"));
106 QVBoxLayout *verticalLayout = new QVBoxLayout();
107 verticalLayout->setObjectName(QStringLiteral("verticalLayout"));
108 mdiArea = new QMdiArea(centralwidget);
109 mdiArea->setObjectName(QStringLiteral("mdiArea"));
110
111 verticalLayout->addWidget(mdiArea);
112 verticalLayout_2->setContentsMargins(1, 1, 1, 1);
113 verticalLayout_2->addLayout(verticalLayout);
114 setCentralWidget(centralwidget);
115 centralwidget->layout()->setContentsMargins(1, 1, 1, 1);
116 prevChild = nullptr;
117
118 createConsole();
119
120 connect(mdiArea, &QMdiArea::subWindowActivated, this,
121 &CMainWindow::slotRefreshMenus);
122
123 frmSettings = new CFrmSettings();
124
125 actions();
126 menus();
127 toolBars();
128 statusBar();
129 updateMenus();
130
131 readSettings();
132
133 setWindowTitle(tr("GVEdit"));
134 this->resize(1024, 900);
135 this->move(0, 0);
136 setUnifiedTitleAndToolBarOnMac(true);
137 QComboBox *cb =
138 frmSettings->findChild<QComboBox *>(QStringLiteral("cbLayout"));
139 dfltLayoutIdx = LoadPlugins(*cb, frmSettings->gvc, "layout", {}, "dot");
140 cb = frmSettings->findChild<QComboBox *>(QStringLiteral("cbExtension"));
141 dfltRenderIdx = LoadPlugins(*cb, frmSettings->gvc, "device", xtra, "png");
142 statusBar()->showMessage(tr("Ready"));
143 setWindowIcon(QIcon(QStringLiteral(":/images/icon.png")));
144 // load files specified in command line , one time task
145 for (const QString &file : files) {
146 addFile(file);
147 }
148}
149
150void CMainWindow::closeEvent(QCloseEvent *event) {
151 mdiArea->closeAllSubWindows();
152 if (mdiArea->currentSubWindow()) {
153 event->ignore();
154 } else {
155 writeSettings();
156 event->accept();
157 }
158}
159
160void CMainWindow::slotNew() {
161 MdiChild *child = createMdiChild();
162 child->newFile();
163 child->show();
164}
165
166void CMainWindow::addFile(const QString &fileName) {
167 if (!fileName.isEmpty()) {
168 QMdiSubWindow *existing = findMdiChild(fileName);
169 if (existing) {
170 mdiArea->setActiveSubWindow(existing);
171 return;
172 }
173
174 MdiChild *child = createMdiChild();
175 if (child->loadFile(fileName)) {
176 statusBar()->showMessage(tr("File loaded"), 2000);
177 child->show();
178 slotRun(child);
179 } else {
180 child->close();
181 }
182 }
183}
184
185void CMainWindow::slotOpen() {
186 QStringList filters{
187 QStringLiteral("*.cpp"),
188 QStringLiteral("*.cxx"),
189 QStringLiteral("*.cc"),
190 };
191
192 QFileDialog fd;
193 fd.setNameFilter(QStringLiteral("XML (*.xml)"));
194 QString fileName = fd.getOpenFileName(this);
195
197}
198
199void CMainWindow::slotSave() {
200 if (activeMdiChild() && activeMdiChild()->save())
201 statusBar()->showMessage(tr("File saved"), 2000);
202}
203
204void CMainWindow::slotSaveAs() {
205 if (activeMdiChild() && activeMdiChild()->saveAs())
206 statusBar()->showMessage(tr("File saved"), 2000);
207}
208
209void CMainWindow::slotCut() {
210 if (activeMdiChild())
211 activeMdiChild()->cut();
212}
213
214void CMainWindow::slotCopy() {
215 if (activeMdiChild())
216 activeMdiChild()->copy();
217}
218
219void CMainWindow::slotPaste() {
220 if (activeMdiChild())
221 activeMdiChild()->paste();
222}
223
224void CMainWindow::slotAbout() {
225 QString abs(tr("<b>GVEdit</b> Graph File Editor For Graphviz"
226 " version: 1.02\n"
227 "Graphviz version: "));
228
229 abs += tr(gvcVersion(frmSettings->gvc));
230 QMessageBox::about(this, tr("About GVEdit"), abs);
231}
232
233void CMainWindow::setChild() {
234 if (prevChild != activeMdiChild()) {
235 const QString msg =
236 QStringLiteral("working on %1\n").arg(activeMdiChild()->currentFile());
237 errorPipe(msg.toLatin1().data());
238 prevChild = activeMdiChild();
239 }
240}
241
242void CMainWindow::slotSettings() {
243 setChild();
244 frmSettings->showSettings(activeMdiChild());
245}
246
247void CMainWindow::slotRun(MdiChild *m) {
248 setChild();
249
250 if (m)
251 frmSettings->runSettings(m);
252 else
253 frmSettings->runSettings(activeMdiChild());
254}
255
256void CMainWindow::slotNewLog() { globTextEdit->clear(); }
257
258void CMainWindow::slotSaveLog() {
259
260 if (globTextEdit->toPlainText().trimmed().isEmpty()) {
261 QMessageBox::warning(this, tr("GvEdit"), tr("Nothing to save!"),
262 QMessageBox::Ok, QMessageBox::Ok);
263 return;
264 }
265
266 QString fileName = QFileDialog::getSaveFileName(
267 this, tr("Open File"), QStringLiteral("/"), tr("Text File(*.*)"));
268 if (!fileName.isEmpty()) {
269
270 QFile file(fileName);
271 if (!file.open(QFile::WriteOnly | QFile::Text)) {
272 QMessageBox::warning(this, tr("MDI"),
273 tr("Cannot write file %1:\n%2.")
274 .arg(fileName)
275 .arg(file.errorString()));
276 return;
277 }
278
279 QTextStream out(&file);
280 out << globTextEdit->toPlainText();
281 }
282}
283
284void CMainWindow::updateFileMenu() {
285 if (!activeMdiChild()) {
286 saveAct->setEnabled(false);
287 saveAsAct->setEnabled(false);
288 pasteAct->setEnabled(false);
289 closeAct->setEnabled(false);
290 closeAllAct->setEnabled(false);
291 tileAct->setEnabled(false);
292 cascadeAct->setEnabled(false);
293 nextAct->setEnabled(false);
294 previousAct->setEnabled(false);
295 separatorAct->setVisible(false);
296 settingsAct->setEnabled(false);
297 layoutAct->setEnabled(false);
298 } else {
299 saveAct->setEnabled(true);
300 saveAsAct->setEnabled(true);
301 pasteAct->setEnabled(true);
302 closeAct->setEnabled(true);
303 closeAllAct->setEnabled(true);
304 tileAct->setEnabled(true);
305 cascadeAct->setEnabled(true);
306 nextAct->setEnabled(true);
307 previousAct->setEnabled(true);
308 separatorAct->setVisible(true);
309 settingsAct->setEnabled(true);
310 layoutAct->setEnabled(true);
311
312 if (activeMdiChild()->textCursor().hasSelection()) {
313 cutAct->setEnabled(true);
314 copyAct->setEnabled(true);
315
316 } else {
317 cutAct->setEnabled(false);
318 copyAct->setEnabled(false);
319 }
320 }
321}
322
323void CMainWindow::slotRefreshMenus() { updateMenus(); }
324
325void CMainWindow::updateMenus() {
326 this->updateFileMenu();
327 this->updateWindowMenu();
328}
329
330void CMainWindow::updateWindowMenu() {
331 mWindow->clear();
332 mWindow->addAction(closeAct);
333 mWindow->addAction(closeAllAct);
334 mWindow->addSeparator();
335 mWindow->addAction(tileAct);
336 mWindow->addAction(cascadeAct);
337 mWindow->addSeparator();
338 mWindow->addAction(nextAct);
339 mWindow->addAction(previousAct);
340 mWindow->addAction(separatorAct);
341
342 QList<QMdiSubWindow *> windows = mdiArea->subWindowList();
343 separatorAct->setVisible(!windows.isEmpty());
344
345 for (int i = 0; i < windows.size(); ++i) {
346 QMdiSubWindow *window = windows.at(i);
347 if (window->widget()->inherits("MdiChild")) {
348 MdiChild *child = qobject_cast<MdiChild *>(window->widget());
349 QString text;
350 if (i < 9) {
351 text = tr("&%1 %2").arg(i + 1).arg(child->userFriendlyCurrentFile());
352 } else {
353 text = tr("%1 %2").arg(i + 1).arg(child->userFriendlyCurrentFile());
354 }
355 QAction *action = mWindow->addAction(text);
356 action->setCheckable(true);
357 action->setChecked(child == activeMdiChild());
358 connect(action, &QAction::triggered, this,
359 [this, window] { activateChild(window); });
360 }
361 }
362}
363
364MdiChild *CMainWindow::createMdiChild() {
365 MdiChild *child = new MdiChild;
366 child->parentFrm = this;
367 QMdiSubWindow *s = mdiArea->addSubWindow(child);
368 s->resize(800, 600);
369 s->move(mdiArea->subWindowList().count() * 5,
370 mdiArea->subWindowList().count() * 5);
371 connect(child, &MdiChild::copyAvailable, cutAct, &QAction::setEnabled);
372 connect(child, &MdiChild::copyAvailable, copyAct, &QAction::setEnabled);
373 child->layoutIdx = dfltLayoutIdx;
374 child->renderIdx = dfltRenderIdx;
375
376 return child;
377}
378
379void CMainWindow::actions() {
380 newAct =
381 new QAction(QIcon(QStringLiteral(":/images/new.png")), tr("&New"), this);
382 newAct->setShortcuts(QKeySequence::New);
383 newAct->setStatusTip(tr("Create a new file"));
384 connect(newAct, &QAction::triggered, this, &CMainWindow::slotNew);
385
386 openAct = new QAction(QIcon(QStringLiteral(":/images/open.png")),
387 tr("&Open..."), this);
388 openAct->setShortcuts(QKeySequence::Open);
389 openAct->setStatusTip(tr("Open an existing file"));
390 connect(openAct, &QAction::triggered, this, &CMainWindow::slotOpen);
391
392 saveAct = new QAction(QIcon(QStringLiteral(":/images/save.png")), tr("&Save"),
393 this);
394 saveAct->setShortcuts(QKeySequence::Save);
395 saveAct->setStatusTip(tr("Save the document to disk"));
396 connect(saveAct, &QAction::triggered, this, &CMainWindow::slotSave);
397
398 saveAsAct = new QAction(tr("Save &As..."), this);
399 saveAsAct->setShortcuts(QKeySequence::SaveAs);
400 saveAsAct->setStatusTip(tr("Save the document under a new name"));
401 connect(saveAsAct, &QAction::triggered, this, &CMainWindow::slotSaveAs);
402
403 exitAct = new QAction(tr("E&xit"), this);
404 exitAct->setShortcuts(QKeySequence::Quit);
405 exitAct->setStatusTip(tr("Exit the application"));
406 connect(exitAct, &QAction::triggered, qApp, &QApplication::closeAllWindows);
407
408 cutAct =
409 new QAction(QIcon(QStringLiteral(":/images/cut.png")), tr("Cu&t"), this);
410 cutAct->setShortcuts(QKeySequence::Cut);
411 cutAct->setStatusTip(tr("Cut the current selection's contents to the "
412 "clipboard"));
413 connect(cutAct, &QAction::triggered, this, &CMainWindow::slotCut);
414
415 copyAct = new QAction(QIcon(QStringLiteral(":/images/copy.png")), tr("&Copy"),
416 this);
417 copyAct->setShortcuts(QKeySequence::Copy);
418 copyAct->setStatusTip(tr("Copy the current selection's contents to the "
419 "clipboard"));
420 connect(copyAct, &QAction::triggered, this, &CMainWindow::slotCopy);
421
422 pasteAct = new QAction(QIcon(QStringLiteral(":/images/paste.png")),
423 tr("&Paste"), this);
424 pasteAct->setShortcuts(QKeySequence::Paste);
425 pasteAct->setStatusTip(tr("Paste the clipboard's contents into the current "
426 "selection"));
427 connect(pasteAct, &QAction::triggered, this, &CMainWindow::slotPaste);
428
429 closeAct = new QAction(tr("Cl&ose"), this);
430 closeAct->setStatusTip(tr("Close the active window"));
431 connect(closeAct, &QAction::triggered, mdiArea,
432 &QMdiArea::closeActiveSubWindow);
433
434 closeAllAct = new QAction(tr("Close &All"), this);
435 closeAllAct->setStatusTip(tr("Close all the windows"));
436 connect(closeAllAct, &QAction::triggered, mdiArea,
437 &QMdiArea::closeAllSubWindows);
438
439 tileAct = new QAction(tr("&Tile"), this);
440 tileAct->setStatusTip(tr("Tile the windows"));
441 connect(tileAct, &QAction::triggered, mdiArea, &QMdiArea::tileSubWindows);
442
443 cascadeAct = new QAction(tr("&Cascade"), this);
444 cascadeAct->setStatusTip(tr("Cascade the windows"));
445 connect(cascadeAct, &QAction::triggered, mdiArea,
446 &QMdiArea::cascadeSubWindows);
447
448 nextAct = new QAction(tr("Ne&xt"), this);
449 nextAct->setShortcuts(QKeySequence::NextChild);
450 nextAct->setStatusTip(tr("Move the focus to the next window"));
451 connect(nextAct, &QAction::triggered, mdiArea,
452 &QMdiArea::activateNextSubWindow);
453
454 previousAct = new QAction(tr("Pre&vious"), this);
455 previousAct->setShortcuts(QKeySequence::PreviousChild);
456 previousAct->setStatusTip(tr("Move the focus to the previous window"));
457 connect(previousAct, &QAction::triggered, mdiArea,
458 &QMdiArea::activatePreviousSubWindow);
459
460 separatorAct = new QAction(this);
461 separatorAct->setSeparator(true);
462
463 aboutAct = new QAction(tr("&About"), this);
464 aboutAct->setStatusTip(tr("Show the application's About box"));
465 connect(aboutAct, &QAction::triggered, this, &CMainWindow::slotAbout);
466
467 settingsAct = new QAction(QIcon(QStringLiteral(":/images/settings.png")),
468 tr("Settings"), this);
469 settingsAct->setStatusTip(tr("Show Graphviz Settings"));
470 connect(settingsAct, &QAction::triggered, this, &CMainWindow::slotSettings);
471 settingsAct->setShortcut(QKeySequence(Qt::SHIFT | Qt::Key_F5));
472
473 layoutAct = new QAction(QIcon(QStringLiteral(":/images/run.png")),
474 tr("Layout"), this);
475 layoutAct->setStatusTip(tr("Layout the active graph"));
476 connect(layoutAct, &QAction::triggered, this, [this] { slotRun(); });
477 layoutAct->setShortcut(QKeySequence(Qt::Key_F5));
478}
479
480void CMainWindow::menus() {
481 mFile = menuBar()->addMenu(tr("&File"));
482 mEdit = menuBar()->addMenu(tr("&Edit"));
483 mWindow = menuBar()->addMenu(tr("&Window"));
484 mGraph = menuBar()->addMenu(tr("&Graph"));
485 mHelp = menuBar()->addMenu(tr("&Help"));
486
487 mFile->addAction(newAct);
488 mFile->addAction(openAct);
489 mFile->addAction(saveAct);
490 mFile->addAction(saveAsAct);
491 mFile->addSeparator();
492
493 mFile->addAction(exitAct);
494
495 mEdit->addAction(cutAct);
496 mEdit->addAction(copyAct);
497 mEdit->addAction(pasteAct);
498
499 mGraph->addAction(settingsAct);
500 mGraph->addAction(layoutAct);
501 mGraph->addSeparator();
502
503 updateWindowMenu();
504 connect(mWindow, &QMenu::aboutToShow, this, &CMainWindow::slotRefreshMenus);
505 mHelp->addAction(aboutAct);
506}
507
508void CMainWindow::toolBars() {
509 tbFile = addToolBar(tr("File"));
510 tbFile->addAction(newAct);
511 tbFile->addAction(openAct);
512 tbFile->addAction(saveAct);
513
514 tbEdit = addToolBar(tr("Edit"));
515 tbEdit->addAction(cutAct);
516 tbEdit->addAction(copyAct);
517 tbEdit->addAction(pasteAct);
518
519 tbGraph = addToolBar(tr("Graph"));
520 tbGraph->addAction(settingsAct);
521 tbGraph->addAction(layoutAct);
522}
523
524void CMainWindow::readSettings() {
525 // first try new settings
526 {
527 QSettings settings(QStringLiteral("Graphviz"), QStringLiteral("gvedit"));
528 if (settings.contains(QStringLiteral("pos")) &&
529 settings.contains(QStringLiteral("size"))) {
530 QPoint pos =
531 settings.value(QStringLiteral("pos"), QPoint(200, 200)).toPoint();
532 QSize size =
533 settings.value(QStringLiteral("size"), QSize(400, 400)).toSize();
534 move(pos);
535 resize(size);
536 return;
537 }
538 }
539
540 // fall back to old settings
541 QSettings settings(QStringLiteral("Trolltech"),
542 QStringLiteral("MDI Example"));
543 QPoint pos =
544 settings.value(QStringLiteral("pos"), QPoint(200, 200)).toPoint();
545 QSize size = settings.value(QStringLiteral("size"), QSize(400, 400)).toSize();
546 move(pos);
547 resize(size);
548}
549
550void CMainWindow::writeSettings() {
551 QSettings settings(QStringLiteral("Graphviz"), QStringLiteral("gvedit"));
552 settings.setValue(QStringLiteral("pos"), pos());
553 settings.setValue(QStringLiteral("size"), size());
554}
555
556MdiChild *CMainWindow::activeMdiChild() {
557 if (QMdiSubWindow *activeSubWindow = mdiArea->activeSubWindow()) {
558 if (activeSubWindow->widget()->inherits("MdiChild"))
559 return qobject_cast<MdiChild *>(activeSubWindow->widget());
560 return qobject_cast<ImageViewer *>(activeSubWindow->widget())->graphWindow;
561 }
562 return 0;
563}
564
565QMdiSubWindow *CMainWindow::findMdiChild(const QString &fileName) {
566 QString canonicalFilePath = QFileInfo(fileName).canonicalFilePath();
567
568 foreach (QMdiSubWindow *window, mdiArea->subWindowList()) {
569 if (window->widget()->inherits("MdiChild")) {
570
571 MdiChild *mdiChild = qobject_cast<MdiChild *>(window->widget());
572 if (mdiChild->currentFile() == canonicalFilePath)
573 return window;
574 } else {
575
576 MdiChild *mdiChild =
577 qobject_cast<ImageViewer *>(window->widget())->graphWindow;
578 if (mdiChild->currentFile() == canonicalFilePath)
579 return window;
580 }
581 }
582 return 0;
583}
584
585void CMainWindow::activateChild(QWidget *window) {
586 if (!window)
587 return;
588 mdiArea->setActiveSubWindow(qobject_cast<QMdiSubWindow *>(window));
589}
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
int runSettings(MdiChild *m)
int showSettings(MdiChild *m)
GVC_t * gvc
Definition csettings.h:32
QMdiArea * mdiArea
Definition mainwindow.h:40
void closeEvent(QCloseEvent *event)
void addFile(const QString &fileName)
CMainWindow(const QStringList &files)
QString userFriendlyCurrentFile()
Definition mdichild.cpp:99
bool loadFile(const QString &fileName)
Definition mdichild.cpp:40
int layoutIdx
Definition mdichild.h:32
void newFile()
Definition mdichild.cpp:29
int renderIdx
Definition mdichild.h:33
CMainWindow * parentFrm
Definition mdichild.h:39
QString currentFile()
Definition mdichild.h:31
int errorPipe(char *errMsg)
void free(void *)
agusererrf agseterrf(agusererrf newf)
Definition agerror.c:34
char ** gvPluginList(GVC_t *gvc, const char *kind, int *sz)
Definition gvplugin.c:426
char * gvcVersion(GVC_t *gvc)
Definition gvc.c:234
static uint64_t id
Definition gv2gml.c:42
static GVC_t * gvc
Definition gv.cpp:27
char * fileName(ingraph_state *sp)
Return name of current file being processed.
Definition ingraphs.c:156
QTextEdit * globTextEdit
int errorPipe(char *errMsg)
static int LoadPlugins(QComboBox &cb, GVC_t *gvc, const char *kind, const QStringList &more, std::string_view prefer)
static const QStringList xtra
static void freeList(char **lp, int count)
Definition gvcint.h:81
Definition grammar.c:90