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