PageRenderTime 50ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/src/gui/TabManager.cc

https://github.com/openscad/openscad
C++ | 728 lines | 603 code | 104 blank | 21 comment | 94 complexity | 6d4b4c05a46345bcd609be18a79a00b6 MD5 | raw file
Possible License(s): GPL-2.0, CC0-1.0
  1. #include <QFileInfo>
  2. #include <QFile>
  3. #include <QDir>
  4. #include <QSaveFile>
  5. #include <QShortcut>
  6. #include <QTextStream>
  7. #include <QMessageBox>
  8. #include <QFileDialog>
  9. #include <QClipboard>
  10. #include <QDesktopServices>
  11. #include <Qsci/qscicommand.h>
  12. #include <Qsci/qscicommandset.h>
  13. #include "Editor.h"
  14. #include "TabManager.h"
  15. #include "TabWidget.h"
  16. #include "ScintillaEditor.h"
  17. #include "QSettingsCached.h"
  18. #include "Preferences.h"
  19. #include "MainWindow.h"
  20. TabManager::TabManager(MainWindow *o, const QString& filename)
  21. {
  22. par = o;
  23. tabWidget = new TabWidget();
  24. tabWidget->setAutoHide(true);
  25. tabWidget->setExpanding(false);
  26. tabWidget->setTabsClosable(true);
  27. tabWidget->setMovable(true);
  28. tabWidget->setContextMenuPolicy(Qt::CustomContextMenu);
  29. connect(tabWidget, SIGNAL(currentTabChanged(int)), this, SLOT(tabSwitched(int)));
  30. connect(tabWidget, SIGNAL(tabCloseRequested(int)), this, SLOT(closeTabRequested(int)));
  31. connect(tabWidget, SIGNAL(tabCountChanged(int)), this, SIGNAL(tabCountChanged(int)));
  32. connect(tabWidget, SIGNAL(middleMouseClicked(int)), this, SLOT(middleMouseClicked(int)));
  33. connect(tabWidget, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(showTabHeaderContextMenu(const QPoint&)));
  34. createTab(filename);
  35. connect(tabWidget, SIGNAL(currentTabChanged(int)), this, SLOT(stopAnimation()));
  36. connect(tabWidget, SIGNAL(currentTabChanged(int)), this, SLOT(updateFindState()));
  37. connect(par, SIGNAL(highlightError(int)), this, SLOT(highlightError(int)));
  38. connect(par, SIGNAL(unhighlightLastError()), this, SLOT(unhighlightLastError()));
  39. connect(par->editActionUndo, SIGNAL(triggered()), this, SLOT(undo()));
  40. connect(par->editActionRedo, SIGNAL(triggered()), this, SLOT(redo()));
  41. connect(par->editActionRedo_2, SIGNAL(triggered()), this, SLOT(redo()));
  42. connect(par->editActionCut, SIGNAL(triggered()), this, SLOT(cut()));
  43. connect(par->editActionCopy, SIGNAL(triggered()), this, SLOT(copy()));
  44. connect(par->editActionPaste, SIGNAL(triggered()), this, SLOT(paste()));
  45. connect(par->editActionIndent, SIGNAL(triggered()), this, SLOT(indentSelection()));
  46. connect(par->editActionUnindent, SIGNAL(triggered()), this, SLOT(unindentSelection()));
  47. connect(par->editActionComment, SIGNAL(triggered()), this, SLOT(commentSelection()));
  48. connect(par->editActionUncomment, SIGNAL(triggered()), this, SLOT(uncommentSelection()));
  49. connect(par->editActionToggleBookmark, SIGNAL(triggered()), this, SLOT(toggleBookmark()));
  50. connect(par->editActionNextBookmark, SIGNAL(triggered()), this, SLOT(nextBookmark()));
  51. connect(par->editActionPrevBookmark, SIGNAL(triggered()), this, SLOT(prevBookmark()));
  52. connect(par->editActionJumpToNextError, SIGNAL(triggered()), this, SLOT(jumpToNextError()));
  53. }
  54. QWidget *TabManager::getTabHeader()
  55. {
  56. assert(tabWidget != nullptr);
  57. return tabWidget;
  58. }
  59. QWidget *TabManager::getTabContent()
  60. {
  61. assert(tabWidget != nullptr);
  62. return tabWidget->getContentWidget();
  63. }
  64. void TabManager::tabSwitched(int x)
  65. {
  66. assert(tabWidget != nullptr);
  67. editor = (EditorInterface *)tabWidget->widget(x);
  68. par->activeEditor = editor;
  69. par->parameterDock->setWidget(editor->parameterWidget);
  70. par->editActionUndo->setEnabled(editor->canUndo());
  71. par->changedTopLevelEditor(par->editorDock->isFloating());
  72. par->changedTopLevelConsole(par->consoleDock->isFloating());
  73. par->parameterTopLevelChanged(par->parameterDock->isFloating());
  74. par->setWindowTitle(tabWidget->tabText(x).replace("&&", "&"));
  75. for (int idx = 0; idx < tabWidget->count(); ++idx) {
  76. QWidget *button = tabWidget->tabButton(idx, QTabBar::RightSide);
  77. if (button) {
  78. button->setVisible(idx == x);
  79. }
  80. }
  81. }
  82. void TabManager::middleMouseClicked(int x)
  83. {
  84. if (x < 0) {
  85. createTab("");
  86. } else {
  87. closeTabRequested(x);
  88. }
  89. }
  90. void TabManager::closeTabRequested(int x)
  91. {
  92. assert(tabWidget != nullptr);
  93. if (!maybeSave(x)) return;
  94. EditorInterface *temp = (EditorInterface *)tabWidget->widget(x);
  95. editorList.remove(temp);
  96. tabWidget->removeTab(x);
  97. tabWidget->fireTabCountChanged();
  98. delete temp->parameterWidget;
  99. delete temp;
  100. }
  101. void TabManager::closeCurrentTab()
  102. {
  103. assert(tabWidget != nullptr);
  104. /* Close tab or close the current window if only one tab is open. */
  105. if (tabWidget->count() > 1) this->closeTabRequested(tabWidget->currentIndex());
  106. else par->close();
  107. }
  108. void TabManager::nextTab()
  109. {
  110. assert(tabWidget != nullptr);
  111. tabWidget->setCurrentIndex((tabWidget->currentIndex() + 1) % tabWidget->count());
  112. }
  113. void TabManager::prevTab()
  114. {
  115. assert(tabWidget != nullptr);
  116. tabWidget->setCurrentIndex((tabWidget->currentIndex() + tabWidget->count() - 1) % tabWidget->count());
  117. }
  118. void TabManager::actionNew()
  119. {
  120. if (par->windowActionHideEditor->isChecked()) par->windowActionHideEditor->trigger(); //if editor hidden, make it visible
  121. createTab("");
  122. }
  123. void TabManager::open(const QString& filename)
  124. {
  125. assert(!filename.isEmpty());
  126. for (auto edt: editorList) {
  127. if (filename == edt->filepath) {
  128. tabWidget->setCurrentWidget(tabWidget->indexOf(edt));
  129. return;
  130. }
  131. }
  132. if (editor->filepath.isEmpty() && !editor->isContentModified() && !editor->parameterWidget->isModified()) {
  133. openTabFile(filename);
  134. } else {
  135. createTab(filename);
  136. }
  137. }
  138. void TabManager::createTab(const QString& filename)
  139. {
  140. assert(par != nullptr);
  141. editor = new ScintillaEditor(tabWidget);
  142. par->activeEditor = editor;
  143. editor->parameterWidget = new ParameterWidget(par->parameterDock);
  144. connect(editor->parameterWidget, SIGNAL(parametersChanged()), par, SLOT(actionRenderPreview()));
  145. par->parameterDock->setWidget(editor->parameterWidget);
  146. // clearing default mapping of keyboard shortcut for font size
  147. QsciCommandSet *qcmdset = ((ScintillaEditor *)editor)->qsci->standardCommands();
  148. QsciCommand *qcmd = qcmdset->boundTo(Qt::ControlModifier | Qt::Key_Plus);
  149. qcmd->setKey(0);
  150. qcmd = qcmdset->boundTo(Qt::ControlModifier | Qt::Key_Minus);
  151. qcmd->setKey(0);
  152. Preferences::create(editor->colorSchemes()); // needs to be done only once, however handled
  153. connect(editor, SIGNAL(uriDropped(const QUrl&)), par, SLOT(handleFileDrop(const QUrl&)));
  154. connect(editor, SIGNAL(previewRequest()), par, SLOT(actionRenderPreview()));
  155. connect(editor, SIGNAL(showContextMenuEvent(const QPoint&)), this, SLOT(showContextMenuEvent(const QPoint&)));
  156. connect(Preferences::inst(), SIGNAL(editorConfigChanged()), editor, SLOT(applySettings()));
  157. connect(Preferences::inst(), SIGNAL(autocompleteChanged(bool)), editor, SLOT(onAutocompleteChanged(bool)));
  158. connect(Preferences::inst(), SIGNAL(characterThresholdChanged(int)), editor, SLOT(onCharacterThresholdChanged(int)));
  159. ((ScintillaEditor *)editor)->public_applySettings();
  160. editor->addTemplate();
  161. connect(par->editActionZoomTextIn, SIGNAL(triggered()), editor, SLOT(zoomIn()));
  162. connect(par->editActionZoomTextOut, SIGNAL(triggered()), editor, SLOT(zoomOut()));
  163. connect(editor, SIGNAL(contentsChanged()), this, SLOT(updateActionUndoState()));
  164. connect(editor, SIGNAL(contentsChanged()), par, SLOT(editorContentChanged()));
  165. connect(editor, SIGNAL(contentsChanged()), this, SLOT(setContentRenderState()));
  166. connect(editor, SIGNAL(modificationChanged(EditorInterface*)), this, SLOT(setTabModified(EditorInterface*)));
  167. connect(editor->parameterWidget, &ParameterWidget::modificationChanged, [editor = this->editor, this] {
  168. setTabModified(editor);
  169. });
  170. connect(Preferences::inst(), SIGNAL(fontChanged(const QString&,uint)),
  171. editor, SLOT(initFont(const QString&,uint)));
  172. connect(Preferences::inst(), SIGNAL(syntaxHighlightChanged(const QString&)),
  173. editor, SLOT(setHighlightScheme(const QString&)));
  174. editor->initFont(Preferences::inst()->getValue("editor/fontfamily").toString(), Preferences::inst()->getValue("editor/fontsize").toUInt());
  175. editor->setHighlightScheme(Preferences::inst()->getValue("editor/syntaxhighlight").toString());
  176. connect(editor, SIGNAL(hyperlinkIndicatorClicked(int)), this, SLOT(onHyperlinkIndicatorClicked(int)));
  177. int idx = tabWidget->addTab(editor, _("Untitled.scad"));
  178. if (!editorList.isEmpty()) {
  179. tabWidget->setCurrentWidget(idx); // to prevent emitting of currentTabChanged signal twice for first tab
  180. }
  181. editorList.insert(editor);
  182. if (!filename.isEmpty()) {
  183. openTabFile(filename);
  184. } else {
  185. setTabName("");
  186. }
  187. par->updateRecentFileActions();
  188. }
  189. int TabManager::count()
  190. {
  191. return tabWidget->count();
  192. }
  193. void TabManager::highlightError(int i)
  194. {
  195. editor->highlightError(i);
  196. }
  197. void TabManager::unhighlightLastError()
  198. {
  199. editor->unhighlightLastError();
  200. }
  201. void TabManager::undo()
  202. {
  203. editor->undo();
  204. }
  205. void TabManager::redo()
  206. {
  207. editor->redo();
  208. }
  209. void TabManager::cut()
  210. {
  211. editor->cut();
  212. }
  213. void TabManager::copy()
  214. {
  215. editor->copy();
  216. }
  217. void TabManager::paste()
  218. {
  219. editor->paste();
  220. }
  221. void TabManager::indentSelection()
  222. {
  223. editor->indentSelection();
  224. }
  225. void TabManager::unindentSelection()
  226. {
  227. editor->unindentSelection();
  228. }
  229. void TabManager::commentSelection()
  230. {
  231. editor->commentSelection();
  232. }
  233. void TabManager::uncommentSelection()
  234. {
  235. editor->uncommentSelection();
  236. }
  237. void TabManager::toggleBookmark()
  238. {
  239. editor->toggleBookmark();
  240. }
  241. void TabManager::nextBookmark()
  242. {
  243. editor->nextBookmark();
  244. }
  245. void TabManager::prevBookmark()
  246. {
  247. editor->prevBookmark();
  248. }
  249. void TabManager::jumpToNextError()
  250. {
  251. editor->jumpToNextError();
  252. }
  253. void TabManager::setFocus()
  254. {
  255. editor->setFocus();
  256. }
  257. void TabManager::updateActionUndoState()
  258. {
  259. par->editActionUndo->setEnabled(editor->canUndo());
  260. }
  261. void TabManager::onHyperlinkIndicatorClicked(int val)
  262. {
  263. const QString filename = QString::fromStdString(editor->indicatorData[val].path);
  264. this->open(filename);
  265. }
  266. void TabManager::applyAction(QObject *object, std::function<void(int, EditorInterface *)> func)
  267. {
  268. QAction *action = dynamic_cast<QAction *>(object);
  269. if (action == nullptr) {
  270. return;
  271. }
  272. bool ok;
  273. int idx = action->data().toInt(&ok);
  274. if (!ok) {
  275. return;
  276. }
  277. EditorInterface *edt = (EditorInterface *)tabWidget->widget(idx);
  278. if (edt == nullptr) {
  279. return;
  280. }
  281. func(idx, edt);
  282. }
  283. void TabManager::copyFileName()
  284. {
  285. applyAction(QObject::sender(), [](int, EditorInterface *edt){
  286. QClipboard *clipboard = QApplication::clipboard();
  287. clipboard->setText(QFileInfo(edt->filepath).fileName());
  288. });
  289. }
  290. void TabManager::copyFilePath()
  291. {
  292. applyAction(QObject::sender(), [](int, EditorInterface *edt){
  293. QClipboard *clipboard = QApplication::clipboard();
  294. clipboard->setText(edt->filepath);
  295. });
  296. }
  297. void TabManager::openFolder()
  298. {
  299. applyAction(QObject::sender(), [](int, EditorInterface *edt){
  300. auto dir = QFileInfo(edt->filepath).dir();
  301. if (dir.exists()) {
  302. QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath()));
  303. }
  304. });
  305. }
  306. void TabManager::closeTab()
  307. {
  308. applyAction(QObject::sender(), [this](int idx, EditorInterface *){
  309. closeTabRequested(idx);
  310. });
  311. }
  312. void TabManager::showContextMenuEvent(const QPoint& pos)
  313. {
  314. auto menu = editor->createStandardContextMenu();
  315. menu->addSeparator();
  316. menu->addAction(par->editActionFind);
  317. menu->addAction(par->editActionFindNext);
  318. menu->addAction(par->editActionFindPrevious);
  319. menu->addSeparator();
  320. menu->addAction(par->editActionInsertTemplate);
  321. menu->addAction(par->editActionFoldAll);
  322. menu->exec(editor->mapToGlobal(pos));
  323. delete menu;
  324. }
  325. void TabManager::showTabHeaderContextMenu(const QPoint& pos)
  326. {
  327. int idx = tabWidget->tabAt(pos);
  328. if (idx < 0) {
  329. return;
  330. }
  331. EditorInterface *edt = (EditorInterface *)tabWidget->widget(idx);
  332. QAction *copyFileNameAction = new QAction(tabWidget);
  333. copyFileNameAction->setData(idx);
  334. copyFileNameAction->setEnabled(!edt->filepath.isEmpty());
  335. copyFileNameAction->setText(_("Copy file name"));
  336. connect(copyFileNameAction, SIGNAL(triggered()), SLOT(copyFileName()));
  337. QAction *copyFilePathAction = new QAction(tabWidget);
  338. copyFilePathAction->setData(idx);
  339. copyFilePathAction->setEnabled(!edt->filepath.isEmpty());
  340. copyFilePathAction->setText(_("Copy full path"));
  341. connect(copyFilePathAction, SIGNAL(triggered()), SLOT(copyFilePath()));
  342. QAction *openFolderAction = new QAction(tabWidget);
  343. openFolderAction->setData(idx);
  344. openFolderAction->setEnabled(!edt->filepath.isEmpty());
  345. openFolderAction->setText(_("Open folder"));
  346. connect(openFolderAction, SIGNAL(triggered()), SLOT(openFolder()));
  347. QAction *closeAction = new QAction(tabWidget);
  348. closeAction->setData(idx);
  349. closeAction->setText(_("Close Tab"));
  350. connect(closeAction, SIGNAL(triggered()), SLOT(closeTab()));
  351. QMenu menu;
  352. menu.addAction(copyFileNameAction);
  353. menu.addAction(copyFilePathAction);
  354. menu.addSeparator();
  355. menu.addAction(openFolderAction);
  356. menu.addSeparator();
  357. menu.addAction(closeAction);
  358. int x1, y1, x2, y2;
  359. tabWidget->tabRect(idx).getCoords(&x1, &y1, &x2, &y2);
  360. menu.exec(tabWidget->mapToGlobal(QPoint(x1, y2)));
  361. }
  362. void TabManager::setContentRenderState() //since last render
  363. {
  364. editor->contentsRendered = false; //since last render
  365. editor->parameterWidget->setEnabled(false);
  366. }
  367. void TabManager::stopAnimation()
  368. {
  369. par->animateWidget->pauseAnimation();
  370. par->animateWidget->e_tval->setText("");
  371. }
  372. void TabManager::updateFindState()
  373. {
  374. if (editor->findState == TabManager::FIND_REPLACE_VISIBLE) par->showFindAndReplace();
  375. else if (editor->findState == TabManager::FIND_VISIBLE) par->showFind();
  376. else par->hideFind();
  377. }
  378. void TabManager::setTabModified(EditorInterface *edt)
  379. {
  380. QString fname = _("Untitled.scad");
  381. QString fpath = fname;
  382. if (!edt->filepath.isEmpty()) {
  383. QFileInfo fileinfo(edt->filepath);
  384. fname = fileinfo.fileName();
  385. fpath = fileinfo.filePath();
  386. }
  387. if (edt->isContentModified() || edt->parameterWidget->isModified()) {
  388. fname += "*";
  389. }
  390. if (edt == editor) {
  391. par->setWindowTitle(fname);
  392. }
  393. tabWidget->setTabText(tabWidget->indexOf(edt), fname.replace("&", "&&"));
  394. tabWidget->setTabToolTip(tabWidget->indexOf(edt), fpath);
  395. }
  396. void TabManager::openTabFile(const QString& filename)
  397. {
  398. par->setCurrentOutput();
  399. editor->setPlainText("");
  400. QFileInfo fileinfo(filename);
  401. const auto suffix = fileinfo.suffix().toLower();
  402. const auto knownFileType = par->knownFileExtensions.contains(suffix);
  403. const auto cmd = par->knownFileExtensions[suffix];
  404. if (knownFileType && cmd.isEmpty()) {
  405. setTabName(filename);
  406. editor->parameterWidget->readFile(fileinfo.absoluteFilePath());
  407. par->updateRecentFiles(editor);
  408. } else {
  409. setTabName(nullptr);
  410. editor->setPlainText(cmd.arg(filename));
  411. }
  412. par->fileChangedOnDisk(); // force cached autoReloadId to update
  413. bool opened = refreshDocument();
  414. if (opened) { // only try to parse if the file opened
  415. par->hideCurrentOutput(); // Initial parse for customizer, hide any errors to avoid duplication
  416. try {
  417. par->parseTopLevelDocument();
  418. } catch (const HardWarningException&) {
  419. par->exceptionCleanup();
  420. } catch (...) {
  421. par->UnknownExceptionCleanup();
  422. }
  423. par->last_compiled_doc = ""; // undo the damage so F4 works
  424. par->clearCurrentOutput();
  425. }
  426. }
  427. void TabManager::setTabName(const QString& filename, EditorInterface *edt)
  428. {
  429. if (edt == nullptr) {
  430. edt = editor;
  431. }
  432. QString fname;
  433. if (filename.isEmpty()) {
  434. edt->filepath.clear();
  435. fname = _("Untitled.scad");
  436. tabWidget->setTabText(tabWidget->indexOf(edt), fname);
  437. tabWidget->setTabToolTip(tabWidget->indexOf(edt), fname);
  438. } else {
  439. QFileInfo fileinfo(filename);
  440. edt->filepath = fileinfo.absoluteFilePath();
  441. fname = fileinfo.fileName();
  442. tabWidget->setTabText(tabWidget->indexOf(edt), QString(fname).replace("&", "&&"));
  443. tabWidget->setTabToolTip(tabWidget->indexOf(edt), fileinfo.filePath());
  444. QDir::setCurrent(fileinfo.dir().absolutePath());
  445. }
  446. par->editorTopLevelChanged(par->editorDock->isFloating());
  447. par->changedTopLevelConsole(par->consoleDock->isFloating());
  448. par->parameterTopLevelChanged(par->parameterDock->isFloating());
  449. par->setWindowTitle(fname);
  450. }
  451. bool TabManager::refreshDocument()
  452. {
  453. bool file_opened = false;
  454. par->setCurrentOutput();
  455. if (!editor->filepath.isEmpty()) {
  456. QFile file(editor->filepath);
  457. if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
  458. LOG(message_group::None, Location::NONE, "", "Failed to open file %1$s: %2$s",
  459. editor->filepath.toLocal8Bit().constData(), file.errorString().toLocal8Bit().constData());
  460. } else {
  461. QTextStream reader(&file);
  462. reader.setCodec("UTF-8");
  463. auto text = reader.readAll();
  464. LOG(message_group::None, Location::NONE, "", "Loaded design '%1$s'.", editor->filepath.toLocal8Bit().constData());
  465. if (editor->toPlainText() != text) {
  466. editor->setPlainText(text);
  467. setContentRenderState(); // since last render
  468. }
  469. file_opened = true;
  470. }
  471. }
  472. par->setCurrentOutput();
  473. return file_opened;
  474. }
  475. bool TabManager::maybeSave(int x)
  476. {
  477. EditorInterface *edt = (EditorInterface *) tabWidget->widget(x);
  478. if (edt->isContentModified() || edt->parameterWidget->isModified()) {
  479. QMessageBox box(par);
  480. box.setText(_("The document has been modified."));
  481. box.setInformativeText(_("Do you want to save your changes?"));
  482. box.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
  483. box.setDefaultButton(QMessageBox::Save);
  484. box.setIcon(QMessageBox::Warning);
  485. box.setWindowModality(Qt::ApplicationModal);
  486. #ifdef Q_OS_MAC
  487. // Cmd-D is the standard shortcut for this button on Mac
  488. box.button(QMessageBox::Discard)->setShortcut(QKeySequence("Ctrl+D"));
  489. box.button(QMessageBox::Discard)->setShortcutEnabled(true);
  490. #endif
  491. auto ret = (QMessageBox::StandardButton) box.exec();
  492. if (ret == QMessageBox::Save) {
  493. return save(edt);
  494. } else if (ret == QMessageBox::Cancel) {
  495. return false;
  496. }
  497. }
  498. return true;
  499. }
  500. /*!
  501. * Called for whole window close, returning false will abort the close
  502. * operation.
  503. */
  504. bool TabManager::shouldClose()
  505. {
  506. foreach(EditorInterface * edt, editorList) {
  507. if (!(edt->isContentModified() || edt->parameterWidget->isModified())) continue;
  508. QMessageBox box(par);
  509. box.setText(_("Some tabs have unsaved changes."));
  510. box.setInformativeText(_("Do you want to save all your changes?"));
  511. box.setStandardButtons(QMessageBox::SaveAll | QMessageBox::Discard | QMessageBox::Cancel);
  512. box.setDefaultButton(QMessageBox::SaveAll);
  513. box.setIcon(QMessageBox::Warning);
  514. box.setWindowModality(Qt::ApplicationModal);
  515. #ifdef Q_OS_MAC
  516. // Cmd-D is the standard shortcut for this button on Mac
  517. box.button(QMessageBox::Discard)->setShortcut(QKeySequence("Ctrl+D"));
  518. box.button(QMessageBox::Discard)->setShortcutEnabled(true);
  519. #endif
  520. auto ret = (QMessageBox::StandardButton) box.exec();
  521. if (ret == QMessageBox::Cancel) {
  522. return false;
  523. } else if (ret == QMessageBox::Discard) {
  524. return true;
  525. } else if (ret == QMessageBox::SaveAll) {
  526. return saveAll();
  527. }
  528. }
  529. return true;
  530. }
  531. void TabManager::saveError(const QIODevice& file, const std::string& msg, const QString filepath)
  532. {
  533. const char *fileName = filepath.toLocal8Bit().constData();
  534. LOG(message_group::None, Location::NONE, "", "%1$s %2$s (%3$s)", msg.c_str(), fileName, file.errorString().toLocal8Bit().constData());
  535. const std::string dialogFormatStr = msg + "\n\"%1\"\n(%2)";
  536. const QString dialogFormat(dialogFormatStr.c_str());
  537. QMessageBox::warning(par, par->windowTitle(), dialogFormat.arg(filepath).arg(file.errorString()));
  538. }
  539. /*!
  540. * Save current document.
  541. * Should _always_ write to disk, since this is called by SaveAs - i.e. don't
  542. * try to be smart and check for document modification here.
  543. */
  544. bool TabManager::save(EditorInterface *edt)
  545. {
  546. assert(edt != nullptr);
  547. if (edt->filepath.isEmpty()) {
  548. return saveAs(edt);
  549. } else {
  550. return save(edt, edt->filepath);
  551. }
  552. }
  553. bool TabManager::save(EditorInterface *edt, const QString path)
  554. {
  555. par->setCurrentOutput();
  556. // If available (>= Qt 5.1), use QSaveFile to ensure the file is not
  557. // destroyed if the device is full. Unfortunately this is not working
  558. // as advertised (at least in Qt 5.3) as it does not detect the device
  559. // full properly and happily commits a 0 byte file.
  560. // Checking the QTextStream status flag after flush() seems to catch
  561. // this condition.
  562. QSaveFile file(path);
  563. if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
  564. saveError(file, _("Failed to open file for writing"), path);
  565. return false;
  566. }
  567. QTextStream writer(&file);
  568. writer.setCodec("UTF-8");
  569. writer << edt->toPlainText();
  570. writer.flush();
  571. bool saveOk = writer.status() == QTextStream::Ok;
  572. if (saveOk) {
  573. saveOk = file.commit();
  574. } else {
  575. file.cancelWriting();
  576. }
  577. if (saveOk) {
  578. LOG(message_group::None, Location::NONE, "", "Saved design '%1$s'.", path.toLocal8Bit().constData());
  579. edt->parameterWidget->saveFile(path);
  580. edt->setContentModified(false);
  581. edt->parameterWidget->setModified(false);
  582. par->updateRecentFiles(edt);
  583. } else {
  584. saveError(file, _("Error saving design"), path);
  585. }
  586. return saveOk;
  587. }
  588. bool TabManager::saveAs(EditorInterface *edt)
  589. {
  590. assert(edt != nullptr);
  591. const auto dir = edt->filepath.isEmpty() ? _("Untitled.scad") : edt->filepath;
  592. auto filename = QFileDialog::getSaveFileName(par, _("Save File"), dir, _("OpenSCAD Designs (*.scad)"));
  593. if (filename.isEmpty()) {
  594. return false;
  595. }
  596. if (QFileInfo(filename).suffix().isEmpty()) {
  597. filename.append(".scad");
  598. // Manual overwrite check since Qt doesn't do it, when using the
  599. // defaultSuffix property
  600. const QFileInfo info(filename);
  601. if (info.exists()) {
  602. const auto text = QString(_("%1 already exists.\nDo you want to replace it?")).arg(info.fileName());
  603. if (QMessageBox::warning(par, par->windowTitle(), text, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) {
  604. return false;
  605. }
  606. }
  607. }
  608. bool saveOk = save(edt, filename);
  609. if (saveOk) {
  610. setTabName(filename, edt);
  611. }
  612. return saveOk;
  613. }
  614. bool TabManager::saveAll()
  615. {
  616. foreach(EditorInterface * edt, editorList) {
  617. if (edt->isContentModified() || edt->parameterWidget->isModified()) {
  618. if (!save(edt)) {
  619. return false;
  620. }
  621. }
  622. }
  623. return true;
  624. }