1 /*
2  main_window_presenter.cpp     MindForger thinking notebook
3 
4  Copyright (C) 2016-2020 Martin Dvorak <martin.dvorak@mindforger.com>
5 
6  This program is free software; you can redistribute it and/or
7  modify it under the terms of the GNU General Public License
8  as published by the Free Software Foundation; either version 2
9  of the License, or (at your option) any later version.
10 
11  This program is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19 #include "main_window_presenter.h"
20 
21 using namespace std;
22 
23 namespace m8r {
24 
MainWindowPresenter(MainWindowView & view)25 MainWindowPresenter::MainWindowPresenter(MainWindowView& view)
26     : view(view),
27       config(Configuration::getInstance())
28 {
29     mind = new Mind{config};
30 
31     // representations
32     this->htmlRepresentation
33         = mind->getHtmlRepresentation();
34     this->mdRepresentation
35         = &htmlRepresentation->getMarkdownRepresentation();
36     this->mdConfigRepresentation
37         = new MarkdownConfigurationRepresentation{};
38 
39     // assemble presenters w/ UI
40     statusBar = new StatusBarPresenter{view.getStatusBar(), mind};
41     mainMenu = new MainMenuPresenter{this}; view.getOrloj()->setMainMenu(mainMenu->getView());
42     cli = new CliAndBreadcrumbsPresenter{this, view.getCli(), mind};
43     orloj = new OrlojPresenter{this, view.getOrloj(), mind};
44 
45     // initialize components
46     scopeDialog = new ScopeDialog{mind->getOntology(), &view};
47     newOutlineDialog = new OutlineNewDialog{QString::fromStdString(config.getMemoryPath()), mind->getOntology(), &view};
48     newNoteDialog = new NoteNewDialog{mind->remind().getOntology(), &view};
49     ftsDialog = new FtsDialog{&view};
50     ftsDialogPresenter = new FtsDialogPresenter(ftsDialog, mind, orloj);
51     findOutlineByNameDialog = new FindOutlineByNameDialog{&view};
52     findThingByNameDialog = new FindOutlineByNameDialog{&view};
53     findNoteByNameDialog = new FindNoteByNameDialog{&view};
54     findOutlineByTagDialog = new FindOutlineByTagDialog{mind->remind().getOntology(), &view};
55     findNoteByTagDialog = new FindNoteByTagDialog{mind->remind().getOntology(), &view};
56     refactorNoteToOutlineDialog = new RefactorNoteToOutlineDialog{&view};
57     configDialog = new ConfigurationDialog{&view};
58     insertImageDialog = new InsertImageDialog{&view};
59     insertLinkDialog = new InsertLinkDialog{&view};
60     rowsAndDepthDialog = new RowsAndDepthDialog(&view);
61     newRepositoryDialog = new NewRepositoryDialog(&view);
62     newFileDialog = new NewFileDialog(&view);
63     exportOutlineToHtmlDialog
64        = new ExportFileDialog(tr("Export Notebook to HTML"),tr("Export"),QString::fromStdString(FILE_EXTENSION_HTML),&view);
65     exportMindToCsvDialog
66        = new ExportFileDialog(tr("Export Mind to CSV"),tr("Export"),QString::fromStdString(FILE_EXTENSION_CSV),&view);
67 #ifdef MF_NER
68     nerChooseTagsDialog = new NerChooseTagTypesDialog(&view);
69     nerResultDialog = new NerResultDialog(&view);
70 #endif
71     // show/hide widgets based on configuration
72     handleMindPreferences();
73 
74     // wire signals
75     QObject::connect(scopeDialog->getSetButton(), SIGNAL(clicked()), this, SLOT(handleMindScope()));
76     QObject::connect(newOutlineDialog, SIGNAL(accepted()), this, SLOT(handleOutlineNew()));
77     QObject::connect(newNoteDialog, SIGNAL(accepted()), this, SLOT(handleNoteNew()));
78     QObject::connect(findOutlineByNameDialog, SIGNAL(searchFinished()), this, SLOT(handleFindOutlineByName()));
79     QObject::connect(findThingByNameDialog, SIGNAL(searchFinished()), this, SLOT(handleFindThingByName()));
80     QObject::connect(findNoteByNameDialog, SIGNAL(searchFinished()), this, SLOT(handleFindNoteByName()));
81     QObject::connect(findOutlineByTagDialog, SIGNAL(searchFinished()), this, SLOT(handleFindOutlineByTag()));
82     QObject::connect(findOutlineByTagDialog, SIGNAL(switchDialogs(bool)), this, SLOT(doSwitchFindByTagDialog(bool)));
83     QObject::connect(findNoteByTagDialog, SIGNAL(searchFinished()), this, SLOT(handleFindNoteByTag()));
84     QObject::connect(findNoteByTagDialog, SIGNAL(switchDialogs(bool)), this, SLOT(doSwitchFindByTagDialog(bool)));
85     QObject::connect(refactorNoteToOutlineDialog, SIGNAL(searchFinished()), this, SLOT(handleRefactorNoteToOutline()));
86     QObject::connect(insertImageDialog->getInsertButton(), SIGNAL(clicked()), this, SLOT(handleFormatImage()));
87     QObject::connect(insertLinkDialog->getInsertButton(), SIGNAL(clicked()), this, SLOT(handleFormatLink()));
88     QObject::connect(rowsAndDepthDialog->getGenerateButton(), SIGNAL(clicked()), this, SLOT(handleRowsAndDepth()));
89     QObject::connect(newRepositoryDialog->getNewButton(), SIGNAL(clicked()), this, SLOT(handleMindNewRepository()));
90     QObject::connect(newFileDialog->getNewButton(), SIGNAL(clicked()), this, SLOT(handleMindNewFile()));
91     QObject::connect(exportOutlineToHtmlDialog->getNewButton(), SIGNAL(clicked()), this, SLOT(handleOutlineHtmlExport()));
92     QObject::connect(exportMindToCsvDialog->getNewButton(), SIGNAL(clicked()), this, SLOT(handleMindCsvExport()));
93     QObject::connect(
94         orloj->getDashboard()->getView()->getNavigatorDashboardlet(), SIGNAL(clickToSwitchFacet()),
95         this, SLOT(doActionViewKnowledgeGraphNavigator())
96     );
97     QObject::connect(
98         orloj->getNoteEdit()->getView()->getNoteEditor(), SIGNAL(signalDnDropUrl(QString)),
99         this, SLOT(doActionFormatLinkOrImage(QString))
100     );
101     QObject::connect(
102         orloj->getOutlineHeaderEdit()->getView()->getHeaderEditor(), SIGNAL(signalDnDropUrl(QString)),
103         this, SLOT(doActionFormatLinkOrImage(QString))
104     );
105     QObject::connect(
106         orloj->getNoteEdit()->getView()->getNoteEditor(), SIGNAL(signalPasteImageData(QImage)),
107         this, SLOT(doActionEditPasteImageData(QImage))
108     );
109     QObject::connect(
110         orloj->getOutlineHeaderEdit()->getView()->getHeaderEditor(), SIGNAL(signalPasteImageData(QImage)),
111         this, SLOT(doActionEditPasteImageData(QImage))
112     );
113     // wire toolbar signals
114     QObject::connect(view.getToolBar()->actionNewOutlineOrNote, SIGNAL(triggered()), this, SLOT(doActionOutlineOrNoteNew()));
115     QObject::connect(view.getToolBar()->actionOpenRepository, SIGNAL(triggered()), this, SLOT(doActionMindLearnRepository()));
116     QObject::connect(view.getToolBar()->actionOpenFile, SIGNAL(triggered()), this, SLOT(doActionMindLearnFile()));
117     QObject::connect(view.getToolBar()->actionViewDashboard, SIGNAL(triggered()), this, SLOT(doActionViewDashboard()));
118     QObject::connect(view.getToolBar()->actionViewEisenhower, SIGNAL(triggered()), this, SLOT(doActionViewOrganizer()));
119     QObject::connect(view.getToolBar()->actionViewOutlines, SIGNAL(triggered()), this, SLOT(doActionViewOutlines()));
120     QObject::connect(view.getToolBar()->actionViewNavigator, SIGNAL(triggered()), this, SLOT(doActionViewKnowledgeGraphNavigator()));
121     QObject::connect(view.getToolBar()->actionViewTags, SIGNAL(triggered()), this, SLOT(doActionViewTagCloud()));
122     QObject::connect(view.getToolBar()->actionViewRecentNotes, SIGNAL(triggered()), this, SLOT(doActionViewRecentNotes()));
123     QObject::connect(view.getToolBar()->actionFindFts, SIGNAL(triggered()), this, SLOT(doActionFts()));
124     QObject::connect(view.getToolBar()->actionHomeOutline, SIGNAL(triggered()), this, SLOT(doActionViewHome()));
125     QObject::connect(view.getToolBar()->actionThink, SIGNAL(triggered()), this, SLOT(doActionMindToggleThink()));
126     QObject::connect(view.getToolBar()->actionScope, SIGNAL(triggered()), this, SLOT(doActionMindTimeTagScope()));
127     QObject::connect(view.getToolBar()->actionAdapt, SIGNAL(triggered()), this, SLOT(doActionMindPreferences()));
128     QObject::connect(view.getToolBar()->actionHelp, SIGNAL(triggered()), this, SLOT(doActionHelpDocumentation()));
129 
130 #ifdef MF_NER
131     QObject::connect(nerChooseTagsDialog->getChooseButton(), SIGNAL(clicked()), this, SLOT(handleFindNerEntities()));
132     QObject::connect(nerResultDialog, SIGNAL(choiceFinished()), this, SLOT(handleFtsNerEntity()));
133 #endif
134 
135     // async task 2 GUI events distributor
136     distributor = new AsyncTaskNotificationsDistributor(this);
137     // setup callback for cleanup when it finishes
138     QObject::connect(distributor, SIGNAL(finished()), distributor, SLOT(deleteLater()));
139     distributor->start();
140 #ifdef MF_NER
141     // NER worker
142     nerWorker = nullptr;
143 #endif
144 
145     // send signal to components to be updated on a configuration change
146     QObject::connect(configDialog, SIGNAL(saveConfigSignal()), this, SLOT(handleMindPreferences()));
147     QObject::connect(configDialog, SIGNAL(saveConfigSignal()), orloj->getOutlineHeaderEdit()->getView()->getHeaderEditor(), SLOT(slotConfigurationUpdated()));
148     QObject::connect(configDialog, SIGNAL(saveConfigSignal()), orloj->getNoteEdit()->getView()->getNoteEditor(), SLOT(slotConfigurationUpdated()));
149     QObject::connect(configDialog, SIGNAL(saveConfigSignal()), distributor, SLOT(slotConfigurationUpdated()));
150 
151     // let Mind to learn active repository & preserve desired state
152     mind->learn();
153 }
154 
~MainWindowPresenter()155 MainWindowPresenter::~MainWindowPresenter()
156 {
157     if(mind) delete mind;
158     if(mainMenu) delete mainMenu;
159     if(statusBar) delete statusBar;
160     if(newOutlineDialog) delete newOutlineDialog;
161     if(ftsDialogPresenter) delete ftsDialogPresenter;
162     if(ftsDialog) delete ftsDialog;
163     if(findOutlineByNameDialog) delete findOutlineByNameDialog;
164     if(findThingByNameDialog) delete findThingByNameDialog;
165     if(findNoteByNameDialog) delete findNoteByNameDialog;
166     if(findOutlineByTagDialog) delete findOutlineByTagDialog;
167     if(configDialog) delete configDialog;
168     //if(findNoteByNameDialog) delete findNoteByNameDialog;
169     if(insertImageDialog) delete insertImageDialog;
170 
171     // TODO deletes
172 }
173 
showInitialView()174 void MainWindowPresenter::showInitialView()
175 {
176     MF_DEBUG("Initial view to show " << mind->getOutlines().size() << " Os (scope is applied if active)" << endl);
177 
178     // UI
179     if(mind->getOutlines().size()) {
180         if(config.getActiveRepository()->getMode()==Repository::RepositoryMode::REPOSITORY) {
181             if(config.getActiveRepository()->isGithubRepository()) {
182                 string key{config.getActiveRepository()->getDir()};
183                 key += FILE_PATH_SEPARATOR;
184                 key += "README.md";
185                 Outline* o = mind->remind().getOutline(key);
186                 if(o) {
187                     orloj->showFacetOutline(o);
188                 } else {
189                     orloj->showFacetOutlineList(mind->getOutlines());
190                 }
191             } else if(config.getActiveRepository()->getType()==Repository::RepositoryType::MINDFORGER) {
192                 if(!string{START_TO_DASHBOARD}.compare(config.getStartupView())) {
193                     orloj->showFacetDashboard();
194                 } else if(!string{START_TO_OUTLINES}.compare(config.getStartupView())) {
195                     orloj->showFacetOutlineList(mind->getOutlines());
196                 } else if(!string{START_TO_TAGS}.compare(config.getStartupView())) {
197                     orloj->showFacetTagCloud();
198                 } else if(!string{START_TO_RECENT}.compare(config.getStartupView())) {
199                     vector<Note*> notes{};
200                     orloj->showFacetRecentNotes(mind->getAllNotes(notes));
201                 } else if(!string{START_TO_EISENHOWER_MATRIX}.compare(config.getStartupView())) {
202                     orloj->showFacetOrganizer(mind->getOutlines());
203                 } else if(!string{START_TO_HOME_OUTLINE}.compare(config.getStartupView())) {
204                     if(!doActionViewHome()) {
205                         // fallback
206                         orloj->showFacetOutlineList(mind->getOutlines());
207                     }
208                 } else {
209                     orloj->showFacetOutlineList(mind->getOutlines());
210                 }
211             } else {
212                 view.getCli()->setBreadcrumbPath("/outlines");
213                 orloj->showFacetOutlineList(mind->getOutlines());
214             }
215         } else { // file
216             // IMPROVE move this method to breadcrumps
217             QString m{"/outlines/"};
218             m += QString::fromStdString((*mind->getOutlines().begin())->getName());
219             view.getCli()->setBreadcrumbPath(m);
220 
221             orloj->showFacetOutline(*mind->getOutlines().begin());
222         }
223     } else {
224         // NO Os > nothing to show
225         // IMPROVE show homepage once it's implemented
226         mind->amnesia();
227         orloj->showFacetOutlineList(mind->getOutlines());
228     }
229 
230     view.setFileOrDirectory(QString::fromStdString(config.getActiveRepository()->getPath()));
231 
232     // config > menu
233     mainMenu->showFacetMindAutolink(config.isAutolinking());
234     mainMenu->showFacetLiveNotePreview(config.isUiLiveNotePreview());
235     orloj->setAspect(config.isUiLiveNotePreview()?OrlojPresenterFacetAspect::ASPECT_LIVE_PREVIEW:OrlojPresenterFacetAspect::ASPECT_NONE);
236 
237     // move Mind to configured state
238     if(config.getDesiredMindState()==Configuration::MindState::THINKING) {
239         MF_DEBUG("InitialView: asking Mind to THINK..." << endl);
240         shared_future<bool> f = mind->think(); // move
241         if(f.wait_for(chrono::microseconds(0)) == future_status::ready) {
242             if(!f.get()) {
243                 mainMenu->showFacetMindSleep();
244                 statusBar->showError(tr("Cannot think - either Mind already dreaming or repository too big"));
245             }
246             statusBar->showMindStatistics();
247         } else {
248             statusBar->showMindStatistics();
249             // ask notifications distributor to repaint status bar later
250             AsyncTaskNotificationsDistributor::Task* task
251                 = new AsyncTaskNotificationsDistributor::Task{f,AsyncTaskNotificationsDistributor::TaskType::DREAM_TO_THINK};
252             distributor->add(task);
253         }
254     }
255 }
256 
257 /* Link handling hints
258  *
259  * PROBLEM:
260  *
261  *   QWebView RESOLVES clicked link and then delegates it's handling to this
262  *   method. The problem is that this handler does NOT get original link, but
263  *   RESOLVED link - which might be resolved differently than I expected.
264  *     For link resolution is IMPORTANT baseUrl specified within HTML source
265  *   passed to QWebView for rendering.
266  *
267  * Input:
268  *
269  *   Qt URL    ... URL resolved by QWebView using a.href and html@baseUrl
270  *   Current O ... link clicked in description of a N from O
271  *   Current N ... link clicked in description of a N from O
272  *
273  * Outline link types:
274  *
275  *   ABSOLUTE link
276  *     - a.href: /home/user/mf/memory/d/o.md
277  *     - Qt URL: file:///home/user/mf/memory/d/o.md
278  *   RELATIVE link SAME directory:
279  *     - a.href: o.md
280  *     - Qt URL: file:///home/user/mf/memory/d/o.md
281  *               HTML.baseUrl + a.href
282  *   RELATIVE link DIFFERENT directory:
283  *     - a.href: ../d/o.md
284  *     - Qt URL: file:///home/user/mf/memory/d/o.md
285  *               HTML.baseUrl + a.href
286  *
287  * Note link types
288  *
289  *   RELATIVE LINKS:
290  *     - a.href: #mangled-note-name
291  *     - Qt URL:
292  *   ... and all O links above w/ #mangled-note-name suffix
293  *
294  */
handleNoteViewLinkClicked(const QUrl & url)295 void MainWindowPresenter::handleNoteViewLinkClicked(const QUrl& url)
296 {
297 #ifdef DO_MF_DEBUG
298     MF_DEBUG("HTML clickHandler: " << endl);
299     MF_DEBUG("  Qt URL     : " << url.toString().toStdString() << endl);
300     MF_DEBUG("  Memory path: " << config.getMemoryPath() << endl);
301     MF_DEBUG("  Current O  : " << orloj->getOutlineView()->getCurrentOutline()->getKey() << endl);
302 #endif
303 
304     statusBar->showInfo(QString(tr("Hyperlink %1 clicked...")).arg(url.toString()));
305     Outline* currentOutline = orloj->getOutlineView()->getCurrentOutline();
306     if(url.toString().size()) {
307         if(url.toString().startsWith(QString::fromStdString(AutolinkingPreprocessor::MF_URL_PREFIX))) {
308             MF_DEBUG("  URL type   : MindForger" << endl);
309             findThingByNameDialog->setWindowTitle(tr("Autolinked Notebooks and Notes"));
310             findThingByNameDialog->getKeywordsCheckbox()->setChecked(false);
311             findThingByNameDialog->setSearchedString(
312                 QString::fromStdString(url.toString().toStdString().substr(AutolinkingPreprocessor::MF_URL_PREFIX.size())));
313 
314             vector<Thing*> allThings{};
315             vector<string>* thingsNames = new vector<string>{};
316             mind->getAllThings(allThings, thingsNames);
317 
318             findThingByNameDialog->show(allThings, thingsNames, false, false);
319             return;
320         }
321 
322         if(url.toString().startsWith(QString::fromStdString(AutolinkingPreprocessor::FILE_URL_PROTOCOL))) {
323             string key{url.toString().toStdString()};
324 #if defined(WIN32) || defined(WIN64)
325             key.erase(0,8); // remove file prefix
326             std::replace(key.begin(), key.end(), '/', '\\');
327 #else
328             key.erase(0,7); // remove file prefix
329 #endif
330             size_t offset;
331             if((offset = key.find("#")) != string::npos) {
332                 // it CAN be Note
333 
334                 // HANDLE relative N link: #mangled-section-name
335                 string currentDir{}, currentFile{};
336                 pathToDirectoryAndFile(currentOutline->getKey(), currentDir, currentFile);
337                 string relativeLinkPrefix{currentDir};
338                 relativeLinkPrefix.append(FILE_PATH_SEPARATOR);
339                 relativeLinkPrefix.append("#");
340                 MF_DEBUG("  Relative prefix: " << relativeLinkPrefix << endl);
341                 if(stringStartsWith(key, relativeLinkPrefix)) {
342                     // it's a relative link within current O
343                     string mangledNoteName = key.substr(offset+1);
344                     MF_DEBUG("HTML clickHandler - N lookup using: " << mangledNoteName << endl);
345                     Outline* o=orloj->getMind()->remind().getOutline(orloj->getOutlineView()->getCurrentOutline()->getKey());
346                     if(o) {
347                         Note* n = o->getNoteByMangledName(mangledNoteName);
348                         if(n) {
349                             orloj->showFacetNoteView(n);
350                             return;
351                         }
352                     }
353                     // if N not found, then link is broken - do nothing
354                     statusBar->showInfo(QString(tr("Link target not found for relative link %1")).arg(QString::fromStdString(mangledNoteName)));
355                     return;
356                 } else {
357                     // HANDLE O#N link - O can be in a memory SUBDIRECTORY
358                     string mangledNoteName = key.substr(offset+1);
359                     key.erase(offset);
360                     MF_DEBUG("HTML clickHandler - O lookup using key: " << key << endl);
361 
362                     // IMPROVE find note within outline
363                     Outline* o=orloj->getMind()->remind().getOutline(key);
364                     if(o) {
365                         Note* n = o->getNoteByMangledName(mangledNoteName);
366                         if(n) {
367                             orloj->showFacetNoteView(n);
368                             return;
369                         } else {
370                             // fallback to Notebook for hyperlink found
371                             orloj->showFacetOutline(o);
372                             return;
373                         }
374                     } // else fallback to open using desktop services
375                 }
376             } else {
377                 // it CAN be Outline
378 
379                 // QWebView resolves URL (it is NEVER relative) - use resolved URL as is
380                 MF_DEBUG("  O lookup using path: " << key << std::endl);
381                 Outline* o=orloj->getMind()->remind().getOutline(key);
382                 if(o) {
383                     orloj->showFacetOutline(o);
384                     return;
385                 } // else fallback to open using desktop services
386             }
387 
388             // IMPROVE let Qt to open also directories and external files
389             MF_DEBUG("Unable to find Notebook/Note for hyperlink: " << url.toString().toStdString() << " > delegating to OS" << std::endl);
390             if(!QDesktopServices::openUrl(url)) {
391                 MF_DEBUG("FAILED to open hyperlink: " << url.toString().toStdString() << std::endl);
392             }
393         } else {
394             // launch URL in browser
395             QDesktopServices::openUrl(url);
396         }
397     }
398 }
399 
400 #ifdef DO_MF_DEBUG
doActionMindHack()401 void MainWindowPresenter::doActionMindHack()
402 {
403     MF_DEBUG("[MindHack] Current facet: " << orloj->getFacet() << endl);
404 }
405 #endif
406 
doActionMindNewRepository()407 void MainWindowPresenter::doActionMindNewRepository()
408 {
409     newRepositoryDialog->show();
410 }
411 
handleMindNewRepository()412 void MainWindowPresenter::handleMindNewRepository()
413 {
414     // if directory exists, then fail
415     if(isDirectoryOrFileExists(newRepositoryDialog->getRepositoryPath().toStdString().c_str())) {
416         QMessageBox::critical(&view, tr("New Repository Error"), tr("Specified repository path already exists!"));
417         return;
418     }
419 
420     // create repository
421     if(!config.getInstaller()->createEmptyMindForgerRepository(newRepositoryDialog->getRepositoryPath().toStdString())) {
422         QMessageBox::critical(&view, tr("New Repository Error"), tr("Failed to create empty repository!"));
423         return;
424     }
425 
426     // copy doc and stencils
427     if(!config.getInstaller()->initMindForgerRepository(
428         newRepositoryDialog->isCopyDoc(),
429         newRepositoryDialog->isCopyStencils(),
430         newRepositoryDialog->getRepositoryPath().toStdString().c_str()
431     )) {
432         statusBar->showError(tr("ERROR: repository created, but attempt to copy documentation and/or stencils failed"));
433     }
434 
435     // open new repository
436     doActionMindRelearn(newRepositoryDialog->getRepositoryPath());
437     mainMenu->addRecentDirectoryOrFile(newRepositoryDialog->getRepositoryPath());
438 }
439 
doActionMindNewFile()440 void MainWindowPresenter::doActionMindNewFile()
441 {
442     newFileDialog->show();
443 }
444 
handleMindNewFile()445 void MainWindowPresenter::handleMindNewFile()
446 {
447     if(isDirectoryOrFileExists(newFileDialog->getFilePath().toStdString().c_str())) {
448         QMessageBox::critical(&view, tr("New Markdown File Error"), tr("Specified file path already exists!"));
449         return;
450     }
451 
452     // create foo file ...
453     stringToFile(
454         newFileDialog->getFilePath().toStdString(),
455         DEFAULT_NEW_OUTLINE);
456 
457     // ... and open it
458     doActionMindRelearn(newFileDialog->getFilePath());
459     mainMenu->addRecentDirectoryOrFile(newFileDialog->getFilePath());
460 }
461 
doActionMindThink()462 void MainWindowPresenter::doActionMindThink()
463 {
464     shared_future<bool> f = mind->think(); // move
465     if(f.wait_for(chrono::microseconds(0)) == future_status::ready) {
466         // sync
467         if(f.get()) {
468             mainMenu->showFacetMindThink();
469             if(config.getActiveRepository()->getMode()==Repository::RepositoryMode::REPOSITORY) {
470                 orloj->showFacetOutlineList(mind->getOutlines());
471             } else {
472                 if(mind->getOutlines().size()>0) {
473                     orloj->showFacetOutline(*mind->getOutlines().begin());
474                 }
475             }
476             statusBar->showMindStatistics();
477         } else {
478             mainMenu->showFacetMindSleep();
479             statusBar->showError(tr("Cannot think - either Mind already dreaming or repository too big"));
480         }
481     } else {
482         statusBar->showMindStatistics();
483         // ask notifications distributor to repaint status bar later
484         AsyncTaskNotificationsDistributor::Task* task
485             = new AsyncTaskNotificationsDistributor::Task{f,AsyncTaskNotificationsDistributor::TaskType::DREAM_TO_THINK};
486         distributor->add(task);
487     }
488 }
489 
doActionMindSleep()490 void MainWindowPresenter::doActionMindSleep()
491 {
492     if(mind->sleep()) {
493         mainMenu->showFacetMindSleep();
494         statusBar->showMindStatistics();
495     } else {
496         statusBar->showMindStatistics();
497         statusBar->showError(tr("Cannot start sleeping - please wait until dreaming finishes and then try again"));
498     }
499 
500     orloj->getOutlineView()->getAssocLeaderboard()->getView()->hide();
501 }
502 
doActionMindToggleThink()503 void MainWindowPresenter::doActionMindToggleThink()
504 {
505     if(config.getMindState()==Configuration::MindState::THINKING) {
506         doActionMindSleep();
507     } else {
508         doActionMindThink();
509     }
510 }
511 
doActionMindToggleAutolink()512 void MainWindowPresenter::doActionMindToggleAutolink()
513 {
514     if(config.isAutolinking()) {
515         config.setAutolinking(false);
516     } else {
517         config.setAutolinking(true);
518     }
519     mainMenu->showFacetMindAutolink(config.isAutolinking());
520     mdConfigRepresentation->save(config);
521 
522     // refresh view
523     if(orloj->isFacetActive(OrlojPresenterFacets::FACET_VIEW_OUTLINE_HEADER)
524          ||
525        orloj->isFacetActive(OrlojPresenterFacets::FACET_VIEW_OUTLINE))
526     {
527         orloj->showFacetOutline(orloj->getOutlineView()->getCurrentOutline());
528     } else if(orloj->isFacetActive(OrlojPresenterFacets::FACET_VIEW_NOTE))
529     {
530         orloj->showFacetNoteView(orloj->getOutlineView()->getOutlineTree()->getCurrentNote());
531     }
532 }
533 
doActionNameDescFocusSwap()534 void MainWindowPresenter::doActionNameDescFocusSwap()
535 {
536     if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_OUTLINE_HEADER)) {
537         if(orloj->getOutlineHeaderEdit()->getView()->getHeaderEditor()->hasFocus()) {
538             orloj->getOutlineHeaderEdit()->getView()->focusName();
539         } else {
540             orloj->getOutlineHeaderEdit()->getView()->getHeaderEditor()->setFocus();
541         }
542     } else if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_NOTE)) {
543         if(orloj->getNoteEdit()->getView()->getNoteEditor()->hasFocus()) {
544             orloj->getNoteEdit()->getView()->focusName();
545         } else {
546             orloj->getNoteEdit()->getView()->getNoteEditor()->setFocus();
547         }
548     }
549 }
550 
doActionToggleLiveNotePreview()551 void MainWindowPresenter::doActionToggleLiveNotePreview()
552 {
553     MF_DEBUG("Toggling live N preview" << endl);
554 
555     // toggle config
556     if(config.isUiLiveNotePreview()) {
557         config.setUiLiveNotePreview(false);
558     } else {
559         config.setUiLiveNotePreview(true);
560     }
561     mdConfigRepresentation->save(config);
562 
563     // menu
564     mainMenu->showFacetLiveNotePreview(config.isUiLiveNotePreview());
565 
566     // aspect
567     if(config.isUiLiveNotePreview()) {
568         orloj->setAspect(OrlojPresenterFacetAspect::ASPECT_LIVE_PREVIEW);
569     } else {
570         orloj->setAspect(OrlojPresenterFacetAspect::ASPECT_NONE);
571     }
572 
573     // view
574     orloj->refreshLiveNotePreview();
575 
576     if(config.isUiLiveNotePreview()) {
577         statusInfoPreviewFlickering();
578     }
579 }
580 
doActionMindLearnRepository()581 void MainWindowPresenter::doActionMindLearnRepository()
582 {
583     QString homeDirectory
584         = QStandardPaths::locate(QStandardPaths::HomeLocation, QString(), QStandardPaths::LocateDirectory);
585 
586     QFileDialog learnDialog{&view};
587     learnDialog.setWindowTitle(tr("Learn Directory or MindForger Repository"));
588     // learnDialog.setFileMode(QFileDialog::Directory|QFileDialog::ExistingFiles); not supported, therefore
589     // >
590     // ASK user: directory/repository or file (choice) > open dialog configured as required
591     learnDialog.setFileMode(QFileDialog::Directory);
592     learnDialog.setDirectory(homeDirectory);
593     learnDialog.setViewMode(QFileDialog::Detail);
594 
595     QStringList directoryNames{};
596     if(learnDialog.exec()) {
597         directoryNames = learnDialog.selectedFiles();
598         if(directoryNames.size()==1) {
599             mainMenu->addRecentDirectoryOrFile(directoryNames[0]);
600             doActionMindRelearn(directoryNames[0]);
601         } // else too many files
602     } // else directory closed / nothing choosen
603 }
604 
doActionMindLearnFile()605 void MainWindowPresenter::doActionMindLearnFile()
606 {
607     QString homeDirectory
608         = QStandardPaths::locate(QStandardPaths::HomeLocation, QString(), QStandardPaths::LocateDirectory);
609 
610     QFileDialog learnDialog{&view};
611     learnDialog.setWindowTitle(tr("Learn Markdown File"));
612     learnDialog.setFileMode(QFileDialog::ExistingFile);
613     learnDialog.setDirectory(homeDirectory);
614     learnDialog.setViewMode(QFileDialog::Detail);
615 
616     QStringList directoryNames{};
617     if(learnDialog.exec()) {
618         directoryNames = learnDialog.selectedFiles();
619         if(directoryNames.size()==1) {
620             mainMenu->addRecentDirectoryOrFile(directoryNames[0]);
621             doActionMindRelearn(directoryNames[0]);
622         } // else too many files
623     } // else directory closed / nothing choosen
624 }
625 
doActionMindRelearn(QString path)626 void MainWindowPresenter::doActionMindRelearn(QString path)
627 {
628     Repository* r = RepositoryIndexer::getRepositoryForPath(path.toStdString());
629     if(r) {
630         config.setActiveRepository(config.addRepository(r));
631         // remember new repository
632         mdConfigRepresentation->save(config);
633         // learn and show
634         mind->learn();
635         showInitialView();
636     } else {
637         QMessageBox::critical(
638             &view,
639             tr("Learn"),
640             tr("This is neither valid MindForger/Markdown repository nor file."));
641     }
642 }
643 
doActionExit()644 void MainWindowPresenter::doActionExit()
645 {
646     QApplication::quit();
647 }
648 
doActionFts()649 void MainWindowPresenter::doActionFts()
650 {
651     doFts(QString{}, false);
652 }
653 
doFts(const QString & pattern,bool doSearch)654 void MainWindowPresenter::doFts(const QString& pattern, bool doSearch)
655 {
656     if(pattern.size()) {
657         ftsDialog->setSearchPattern(pattern);
658     }
659 
660     if(orloj->isFacetActiveOutlineOrNoteView()) {
661         ftsDialog->setWindowTitle(tr("Notebook Full-text Search"));
662         ftsDialog->setScope(
663             ResourceType::OUTLINE,
664             orloj->getOutlineView()->getCurrentOutline());
665     } else if(orloj->isFacetActiveOutlineOrNoteEdit()) {
666         ftsDialog->setWindowTitle(tr("Note Full-text Search"));
667         ftsDialog->setScope(
668             ResourceType::NOTE,
669             orloj->getOutlineView()->getCurrentOutline());
670     } else {
671         ftsDialog->setWindowTitle(tr("Full-text Search"));
672         ftsDialog->clearScope();
673     }
674     ftsDialog->show();
675 
676     if(doSearch) {
677         ftsDialogPresenter->doSearch();
678     }
679 }
680 
slotHandleFts()681 void MainWindowPresenter::slotHandleFts()
682 {
683     ftsDialog->hide();
684 
685     QString searchedString = ftsDialog->getSearchPattern();
686     switch(ftsDialog->getScopeType()) {
687     case ResourceType::NOTE:
688         if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_NOTE)) {
689             orloj->getNoteEdit()->getView()->getNoteEditor()->findString(
690                 searchedString,
691                 ftsDialog->isEditorReverse(),
692                 ftsDialog->isEditorCaseInsensitive(),
693                 ftsDialog->isEditorWords());
694         } else if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_OUTLINE_HEADER)) {
695             orloj->getOutlineHeaderEdit()->getView()->getHeaderEditor()->findString(
696                 searchedString,
697                 ftsDialog->isEditorReverse(),
698                 ftsDialog->isEditorCaseInsensitive(),
699                 ftsDialog->isEditorWords());
700         }
701         break;
702     default:
703         // repository or O FTS
704         if(ftsDialogPresenter->getSelectedNote()) {
705             orloj->showFacetOutline(ftsDialogPresenter->getSelectedNote()->getOutline());
706             orloj->showFacetNoteView(ftsDialogPresenter->getSelectedNote());
707             orloj->getNoteView()->getView()->getViever()->findText(searchedString);
708         }
709     }
710 }
711 
doActionFindOutlineByName()712 void MainWindowPresenter::doActionFindOutlineByName()
713 {
714     // IMPROVE rebuild model ONLY if dirty i.e. an outline name was changed on save
715     vector<Outline*> os{mind->getOutlines()};
716     mind->remind().sortByName(os);
717     vector<Thing*> es{os.begin(),os.end()};
718 
719     findOutlineByNameDialog->show(es);
720 }
721 
handleFindOutlineByName()722 void MainWindowPresenter::handleFindOutlineByName()
723 {
724     if(findOutlineByNameDialog->getChoice()) {
725         orloj->showFacetOutline((Outline*)findOutlineByNameDialog->getChoice());
726         // IMPROVE make this more efficient
727         statusBar->showInfo(QString(tr("Notebook "))+QString::fromStdString(findOutlineByNameDialog->getChoice()->getName()));
728     } else {
729         statusBar->showInfo(QString(tr("Notebook not found")+": ") += findOutlineByNameDialog->getSearchedString());
730     }
731 }
732 
handleFindThingByName()733 void MainWindowPresenter::handleFindThingByName()
734 {
735     if(findThingByNameDialog->getChoice()) {
736         if(mind->remind().getOutline(findThingByNameDialog->getChoice()->getKey())) {
737             orloj->showFacetOutline((Outline*)findThingByNameDialog->getChoice());
738             statusBar->showInfo(QString(tr("Notebook "))+QString::fromStdString(findThingByNameDialog->getChoice()->getKey()));
739         } else {
740             orloj->showFacetNoteView((Note*)findThingByNameDialog->getChoice());
741             statusBar->showInfo(QString(tr("Note "))+QString::fromStdString(findThingByNameDialog->getChoice()->getKey()));
742         }
743     } else {
744         statusBar->showInfo(QString(tr("Thing not found")+": ") += findThingByNameDialog->getSearchedString());
745     }
746 }
747 
doActionFindOutlineByTag()748 void MainWindowPresenter::doActionFindOutlineByTag()
749 {
750     // IMPROVE rebuild model ONLY if dirty i.e. an outline name was changed on save
751     vector<Outline*> os{mind->getOutlines()};
752     mind->remind().sortByName(os);
753     vector<Thing*> outlines{os.begin(),os.end()};
754 
755     findOutlineByTagDialog->show(outlines);
756 }
757 
handleFindOutlineByTag()758 void MainWindowPresenter::handleFindOutlineByTag()
759 {
760     if(findOutlineByTagDialog->getChoice()) {
761         orloj->showFacetOutline((Outline*)findOutlineByTagDialog->getChoice());
762         // IMPROVE make this more efficient
763         statusBar->showInfo(QString(tr("Notebook "))+QString::fromStdString(findOutlineByTagDialog->getChoice()->getName()));
764     } else {
765         statusBar->showInfo(QString(tr("Notebook not found")));
766     }
767 }
768 
doActionFindNoteByTag()769 void MainWindowPresenter::doActionFindNoteByTag()
770 {
771     // IMPROVE rebuild model ONLY if dirty i.e. an outline name was changed on save
772     if(orloj->isFacetActiveOutlineOrNoteView() || orloj->isFacetActiveOutlineOrNoteEdit()) {
773         findNoteByTagDialog->setWindowTitle(tr("Find Note by Tags in Notebook"));
774         findNoteByTagDialog->setScope(orloj->getOutlineView()->getCurrentOutline());
775         vector<Note*> allNotes(findNoteByTagDialog->getScope()->getNotes());
776         findNoteByTagDialog->show(allNotes);
777     } else {
778         findNoteByTagDialog->setWindowTitle(tr("Find Note by Tags"));
779         findNoteByTagDialog->clearScope();
780         vector<Note*> allNotes{};
781         mind->getAllNotes(allNotes);
782         findNoteByTagDialog->show(allNotes);
783     }
784 }
785 
doTriggerFindNoteByTag(const Tag * tag)786 void MainWindowPresenter::doTriggerFindNoteByTag(const Tag* tag)
787 {
788     findNoteByTagDialog->setWindowTitle(tr("Find Note by Tags"));
789     findNoteByTagDialog->clearScope();
790     vector<Note*> allNotes{};
791     mind->getAllNotes(allNotes);
792     vector<const Tag*> tags{};
793     tags.push_back(tag);
794     findNoteByTagDialog->show(allNotes, &tags);
795 }
796 
doSwitchFindByTagDialog(bool toFindNotesByTag)797 void MainWindowPresenter::doSwitchFindByTagDialog(bool toFindNotesByTag)
798 {
799     // switch dialogs and transfer selected tags
800     vector<const Tag*>* tags = new vector<const Tag*>{};
801     if(toFindNotesByTag) {
802         findOutlineByTagDialog->hide();
803         findOutlineByTagDialog->getChosenTags(tags);
804 
805         vector<Note*> allNotes{};
806         mind->getAllNotes(allNotes);
807         findNoteByTagDialog->show(allNotes, tags);
808 
809     } else {
810         findNoteByTagDialog->hide();
811         findNoteByTagDialog->getChosenTags(tags);
812 
813         vector<Outline*> os{mind->getOutlines()};
814         mind->remind().sortByName(os);
815         vector<Thing*> outlines{os.begin(),os.end()};
816         findOutlineByTagDialog->show(outlines, tags);
817     }
818     delete tags;
819 }
820 
handleFindNoteByTag()821 void MainWindowPresenter::handleFindNoteByTag()
822 {
823     if(findNoteByTagDialog->getChoice()) {
824         Note* choice = (Note*)findNoteByTagDialog->getChoice();
825 
826         choice->incReads();
827         choice->makeDirty();
828 
829         orloj->showFacetOutline(choice->getOutline());
830         orloj->getNoteView()->refresh(choice);
831         orloj->showFacetNoteView();
832         orloj->getOutlineView()->selectRowByNote(choice);
833         // IMPROVE make this more efficient
834         statusBar->showInfo(QString(tr("Note "))+QString::fromStdString(choice->getName()));
835     } else {
836         statusBar->showInfo(QString(tr("Note not found")+": ") += findNoteByNameDialog->getSearchedString());
837     }
838 }
839 
doActionRefactorNoteToOutline()840 void MainWindowPresenter::doActionRefactorNoteToOutline()
841 {
842     // IMPROVE rebuild model ONLY if dirty i.e. an outline name was changed on save
843     vector<Outline*> os{mind->getOutlines()};
844     mind->remind().sortByName(os);
845     vector<Thing*> es{os.begin(),os.end()};
846 
847     refactorNoteToOutlineDialog->show(es);
848 }
849 
handleRefactorNoteToOutline()850 void MainWindowPresenter::handleRefactorNoteToOutline()
851 {
852     // IMPROVE check current view to be VIEW or EDIT NOTE
853     Note* noteToRefactor = orloj->getOutlineView()->getOutlineTree()->getCurrentNote();
854     if(noteToRefactor) {
855         if(refactorNoteToOutlineDialog->getChoice()) {
856             Outline* dstOutline = (Outline*)refactorNoteToOutlineDialog->getChoice();
857             mind->noteRefactor(noteToRefactor, dstOutline->getKey());
858 
859             orloj->showFacetOutline((Outline*)refactorNoteToOutlineDialog->getChoice());
860 
861             // IMPROVE make this more efficient .arg() + add Note's name
862             statusBar->showInfo(QString(tr("Refactored Note to Notebook '"))+QString::fromStdString(refactorNoteToOutlineDialog->getChoice()->getName())+"'...");
863         } else {
864             statusBar->showInfo(QString(tr("Target Notebook not found")+": ") += refactorNoteToOutlineDialog->getSearchedString());
865         }
866     } else {
867         QMessageBox::critical(&view, tr("Refactor Note"), tr("Note to be refactored not specified!"));
868     }
869 }
870 
doActionFindNoteByName()871 void MainWindowPresenter::doActionFindNoteByName()
872 {
873     // IMPROVE rebuild model ONLY if dirty i.e. an outline name was changed on save
874     if(orloj->isFacetActiveOutlineOrNoteView() || orloj->isFacetActiveOutlineOrNoteEdit()) {
875         findNoteByNameDialog->setWindowTitle(tr("Find Note by Name in Notebook"));
876         findNoteByNameDialog->setScope(orloj->getOutlineView()->getCurrentOutline());
877         vector<Note*> allNotes(findNoteByNameDialog->getScope()->getNotes());
878         findNoteByNameDialog->show(allNotes);
879     } else {
880         findNoteByNameDialog->setWindowTitle(tr("Find Note by Name"));
881         findNoteByNameDialog->clearScope();
882         vector<Note*> allNotes{};
883         mind->getAllNotes(allNotes);
884         findNoteByNameDialog->show(allNotes);
885     }
886 }
887 
handleFindNoteByName()888 void MainWindowPresenter::handleFindNoteByName()
889 {
890     if(findNoteByNameDialog->getChoice()) {
891         Note* choice = (Note*)findNoteByNameDialog->getChoice();
892 
893         choice->incReads();
894         choice->makeDirty();
895 
896         orloj->showFacetOutline(choice->getOutline());
897         orloj->getNoteView()->refresh(choice);
898         orloj->showFacetNoteView();
899         orloj->getOutlineView()->selectRowByNote(choice);
900         // IMPROVE make this more efficient
901         statusBar->showInfo(QString(tr("Note "))+QString::fromStdString(choice->getName()));
902     } else {
903         statusBar->showInfo(QString(tr("Note not found")+": ") += findNoteByNameDialog->getSearchedString());
904     }
905 }
906 
907 #ifdef MF_NER
908 
doActionFindNerPersons()909 void MainWindowPresenter::doActionFindNerPersons()
910 {
911     if(orloj->isFacetActiveOutlineManagement()) {
912         nerChooseTagsDialog->clearCheckboxes();
913         nerChooseTagsDialog->getPersonsCheckbox()->setChecked(true);
914         nerChooseTagsDialog->show();
915     } else {
916         statusBar->showInfo(tr("Initializing NER and predicting..."));
917         QMessageBox::critical(&view, tr("NER"), tr("Memory NER not implemented yet."));
918     }
919 }
doActionFindNerLocations()920 void MainWindowPresenter::doActionFindNerLocations()
921 {
922     if(orloj->isFacetActiveOutlineManagement()) {
923         nerChooseTagsDialog->clearCheckboxes();
924         nerChooseTagsDialog->getLocationsCheckbox()->setChecked(true);
925         nerChooseTagsDialog->show();
926     } else {
927         statusBar->showInfo(tr("Initializing NER and predicting..."));
928         QMessageBox::critical(&view, tr("NER"), tr("Memory NER not implemented yet."));
929     }
930 }
doActionFindNerOrganizations()931 void MainWindowPresenter::doActionFindNerOrganizations()
932 {
933     if(orloj->isFacetActiveOutlineManagement()) {
934         nerChooseTagsDialog->clearCheckboxes();
935         nerChooseTagsDialog->getOrganizationsCheckbox()->setChecked(true);
936         nerChooseTagsDialog->show();
937     } else {
938         statusBar->showInfo(tr("Initializing NER and predicting..."));
939         QMessageBox::critical(&view, tr("NER"), tr("Memory NER not implemented yet."));
940     }
941 }
doActionFindNerMisc()942 void MainWindowPresenter::doActionFindNerMisc()
943 {
944     if(orloj->isFacetActiveOutlineManagement()) {
945         nerChooseTagsDialog->clearCheckboxes();
946         nerChooseTagsDialog->getMiscCheckbox()->setChecked(true);
947         nerChooseTagsDialog->show();
948     } else {
949         statusBar->showInfo(tr("Initializing NER and predicting..."));
950         QMessageBox::critical(&view, tr("NER"), tr("Memory NER not implemented yet."));
951     }
952 }
953 
startNerWorkerThread(Mind * m,OrlojPresenter * o,int f,std::vector<NerNamedEntity> * r,QDialog * d)954 NerMainWindowWorkerThread* MainWindowPresenter::startNerWorkerThread(
955         Mind* m,
956         OrlojPresenter* o,
957         int f,
958         std::vector<NerNamedEntity>* r,
959         QDialog* d)
960 {
961     QThread* thread = new QThread;
962     NerMainWindowWorkerThread* worker
963         = new NerMainWindowWorkerThread(thread, m, o, f, r, d);
964 
965     // signals
966     worker->moveToThread(thread);
967     // TODO implement dialog w/ error handling - QObject::connect(worker, SIGNAL(error(QString)), this, SLOT(errorString(QString)));
968     QObject::connect(thread, SIGNAL(started()), worker, SLOT(process()));
969     // open dialog to choose from result(s)
970     QObject::connect(worker, SIGNAL(finished()), this, SLOT(handleChooseNerEntityResult()));
971     // worker's finished signal quits thread ~ thread CANNOT be reused
972     QObject::connect(worker, SIGNAL(finished()), thread, SLOT(quit()));
973     // schedule thread for automatic deletion by Qt - I delete worker myself
974     //QObject::connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
975     QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
976 
977     thread->start();
978 
979     return worker;
980 }
981 
982 // handleFindNerPerson() -> handleChooseNerEntityResult() -> handleFtsNerEntity()
handleFindNerEntities()983 void MainWindowPresenter::handleFindNerEntities()
984 {
985     nerChooseTagsDialog->hide();
986 
987     int entityFilter{};
988     entityFilter =
989           (nerChooseTagsDialog->getPersonsCheckbox()->isChecked()?NerNamedEntityType::PERSON:0) |
990           (nerChooseTagsDialog->getLocationsCheckbox()->isChecked()?NerNamedEntityType::LOCATION:0) |
991           (nerChooseTagsDialog->getOrganizationsCheckbox()->isChecked()?NerNamedEntityType::ORGANIZATION:0) |
992           (nerChooseTagsDialog->getMiscCheckbox()->isChecked()?NerNamedEntityType::MISC:0);
993 
994     MF_DEBUG("Named-entity type filter: " << entityFilter << endl);
995 
996     vector<NerNamedEntity>* result
997         = new vector<NerNamedEntity>{};
998     if(mind->isNerInitilized()) {
999         statusBar->showInfo(tr("Recognizing named entities..."));
1000 
1001         mind->recognizePersons(orloj->getOutlineView()->getCurrentOutline(), entityFilter, *result);
1002 
1003         chooseNerEntityResult(result);
1004     } else {
1005         statusBar->showInfo(tr("Initializing NER and recognizing named entities..."));
1006 
1007         // launch async worker
1008         QDialog* progressDialog
1009             = new QDialog{&view};
1010         nerWorker
1011             = startNerWorkerThread(mind, orloj, entityFilter, result, progressDialog);
1012 
1013         // show PROGRESS dialog - will be closed by worker
1014         QVBoxLayout* mainLayout = new QVBoxLayout{};
1015         QLabel* l = new QLabel{tr(" Initializing (the first run only) NER and predicting... ")};
1016         mainLayout->addWidget(l);
1017         progressDialog->setLayout(mainLayout);
1018         progressDialog->setWindowTitle(tr("Named-entity Recognition"));
1019         //progressDialog->resize(fontMetrics().averageCharWidth()*35, height());
1020         //progressDialog->setModal(true);
1021         progressDialog->update();
1022         progressDialog->activateWindow();
1023         progressDialog->show();
1024         // dialog is deleted by worker thread
1025     }
1026 }
1027 
chooseNerEntityResult(vector<NerNamedEntity> * nerEntities)1028 void MainWindowPresenter::chooseNerEntityResult(vector<NerNamedEntity>* nerEntities)
1029 {
1030     MF_DEBUG("Showing NER results to choose one entity for FTS..." << endl);
1031     statusBar->showInfo(tr("NER predicition finished"));
1032 
1033     if(nerEntities && nerEntities->size()) {
1034         nerResultDialog->show(*nerEntities);
1035     } else {
1036         QMessageBox::information(&view, tr("Named-entity Recognition"), tr("No named entities recognized."));
1037     }
1038 }
1039 
handleChooseNerEntityResult()1040 void MainWindowPresenter::handleChooseNerEntityResult()
1041 {
1042     vector<NerNamedEntity>* nerEntities = nerWorker->getResult();
1043     chooseNerEntityResult(nerEntities);
1044 
1045     // cleanup: thread is deleted by Qt (deleteLater() signal)
1046     delete nerEntities;
1047     delete nerWorker;
1048 }
1049 
handleFtsNerEntity()1050 void MainWindowPresenter::handleFtsNerEntity()
1051 {
1052     if(nerResultDialog->getChoice().size()) {
1053         executeFts(
1054             nerResultDialog->getChoice(),
1055             false,
1056             orloj->getOutlineView()->getCurrentOutline());
1057     }
1058 }
1059 
1060 #endif
1061 
doActionViewRecentNotes()1062 void MainWindowPresenter::doActionViewRecentNotes()
1063 {
1064     vector<Note*> notes{};
1065     mind->getAllNotes(notes, true, true);
1066     orloj->showFacetRecentNotes(notes);
1067 }
1068 
doActionViewDashboard()1069 void MainWindowPresenter::doActionViewDashboard()
1070 {
1071     if(config.getActiveRepository()->getMode()==Repository::RepositoryMode::REPOSITORY) {
1072         orloj->showFacetDashboard();
1073     }
1074 }
1075 
doActionViewOrganizer()1076 void MainWindowPresenter::doActionViewOrganizer()
1077 {
1078     if(config.getActiveRepository()->getMode()==Repository::RepositoryMode::REPOSITORY) {
1079         orloj->showFacetOrganizer(mind->getOutlines());
1080     }
1081 }
1082 
doActionViewKnowledgeGraphNavigator()1083 void MainWindowPresenter::doActionViewKnowledgeGraphNavigator()
1084 {
1085     orloj->showFacetKnowledgeGraphNavigator();
1086 }
1087 
doActionViewHome()1088 bool MainWindowPresenter::doActionViewHome()
1089 {
1090     vector<const Tag*> tagsFilter{};
1091     tagsFilter.push_back(mind->remind().getOntology().findOrCreateTag(Tag::KeyMindForgerHome()));
1092     vector<Outline*> homeOutline{};
1093     mind->findOutlinesByTags(tagsFilter, homeOutline);
1094     if(homeOutline.size()) {
1095         orloj->showFacetOutline(homeOutline.at(0));
1096         return true;
1097     } else {
1098         statusBar->showInfo(tr("Home Notebook is not defined!"));
1099         return false;
1100     }
1101 }
1102 
doActionViewOutlines()1103 void MainWindowPresenter::doActionViewOutlines()
1104 {
1105     if(config.getActiveRepository()->getMode()==Repository::RepositoryMode::REPOSITORY) {
1106         view.getCli()->setBreadcrumbPath("/notebooks");
1107         cli->executeListOutlines();
1108     }
1109 }
1110 
doActionViewTagCloud()1111 void MainWindowPresenter::doActionViewTagCloud()
1112 {
1113     orloj->showFacetTagCloud();
1114 }
1115 
doActionCli()1116 void MainWindowPresenter::doActionCli()
1117 {
1118     view.getCli()->setBreadcrumbPath("/");
1119     view.getCli()->showCli();
1120 }
1121 
doActionViewDistractionFree()1122 void MainWindowPresenter::doActionViewDistractionFree()
1123 {
1124     if(view.statusBar()->isVisible()) {
1125         view.menuBar()->hide();
1126         view.getCli()->hide();
1127         view.statusBar()->hide();
1128     } else {
1129         view.menuBar()->show();
1130         view.getCli()->show();
1131         view.statusBar()->show();
1132     }
1133 }
1134 
doActionViewFullscreen()1135 void MainWindowPresenter::doActionViewFullscreen()
1136 {
1137     if(view.isFullScreen()) {
1138         view.showMaximized();
1139     } else {
1140         view.showFullScreen();
1141     }
1142 }
1143 
doActionNavigatorShuffle()1144 void MainWindowPresenter::doActionNavigatorShuffle()
1145 {
1146     if(orloj->isFacetActive(OrlojPresenterFacets::FACET_NAVIGATOR)) {
1147         orloj->getNavigator()->shuffle();
1148     }
1149 }
1150 
doActionFormatBold()1151 void MainWindowPresenter::doActionFormatBold()
1152 {
1153     if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_NOTE)) {
1154         orloj->getNoteEdit()->getView()->getNoteEditor()->wrapSelectedText("**");
1155     } else if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_OUTLINE_HEADER)) {
1156         orloj->getOutlineHeaderEdit()->getView()->getHeaderEditor()->wrapSelectedText("**");
1157     }
1158 }
1159 
doActionFormatItalic()1160 void MainWindowPresenter::doActionFormatItalic()
1161 {
1162     if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_NOTE)) {
1163         orloj->getNoteEdit()->getView()->getNoteEditor()->wrapSelectedText("_");
1164     } else if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_OUTLINE_HEADER)) {
1165         orloj->getOutlineHeaderEdit()->getView()->getHeaderEditor()->wrapSelectedText("_");
1166     }
1167 }
1168 
doActionFormatCode()1169 void MainWindowPresenter::doActionFormatCode()
1170 {
1171     if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_NOTE)) {
1172         orloj->getNoteEdit()->getView()->getNoteEditor()->wrapSelectedText("`");
1173     } else if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_OUTLINE_HEADER)) {
1174         orloj->getOutlineHeaderEdit()->getView()->getHeaderEditor()->wrapSelectedText("`");
1175     }
1176 }
1177 
doActionFormatMath()1178 void MainWindowPresenter::doActionFormatMath()
1179 {
1180     if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_NOTE)) {
1181         orloj->getNoteEdit()->getView()->getNoteEditor()->wrapSelectedText("$");
1182     } else if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_OUTLINE_HEADER)) {
1183         orloj->getOutlineHeaderEdit()->getView()->getHeaderEditor()->wrapSelectedText("$");
1184     }
1185 }
1186 
doActionFormatMathFrac()1187 void MainWindowPresenter::doActionFormatMathFrac()
1188 {
1189     injectMarkdownText("\\frac{}{}", false, 6);
1190 }
doActionFormatMathSum()1191 void MainWindowPresenter::doActionFormatMathSum()
1192 {
1193     injectMarkdownText("\\sum_{i=0}^n", false, 12);
1194 }
doActionFormatMathInt()1195 void MainWindowPresenter::doActionFormatMathInt()
1196 {
1197     injectMarkdownText("\\int_{x}^{y}", false, 12);
1198 }
doActionFormatMathIiint()1199 void MainWindowPresenter::doActionFormatMathIiint()
1200 {
1201     injectMarkdownText("\\iiint", false, 3);
1202 }
doActionFormatMathAlpha()1203 void MainWindowPresenter::doActionFormatMathAlpha()
1204 {
1205     injectMarkdownText("\\alpha", false, 6);
1206 }
doActionFormatMathBeta()1207 void MainWindowPresenter::doActionFormatMathBeta()
1208 {
1209     injectMarkdownText("\\beta", false, 5);
1210 }
doActionFormatMathDelta()1211 void MainWindowPresenter::doActionFormatMathDelta()
1212 {
1213     injectMarkdownText("\\Delta", false, 6);
1214 }
doActionFormatMathGama()1215 void MainWindowPresenter::doActionFormatMathGama()
1216 {
1217     injectMarkdownText("\\Gama", false, 5);
1218 }
doActionFormatMathText()1219 void MainWindowPresenter::doActionFormatMathText()
1220 {
1221     injectMarkdownText("\\text{}", false, 6);
1222 }
doActionFormatMathBar()1223 void MainWindowPresenter::doActionFormatMathBar()
1224 {
1225     injectMarkdownText("\\bar", false, 4);
1226 }
doActionFormatMathHat()1227 void MainWindowPresenter::doActionFormatMathHat()
1228 {
1229     injectMarkdownText("\\hat", false, 4);
1230 }
doActionFormatMathDot()1231 void MainWindowPresenter::doActionFormatMathDot()
1232 {
1233     injectMarkdownText("\\dot", false, 4);
1234 }
doActionFormatMathOverrightarrow()1235 void MainWindowPresenter::doActionFormatMathOverrightarrow()
1236 {
1237     injectMarkdownText("\\overrightarrow", false, 15);
1238 }
doActionFormatMathCup()1239 void MainWindowPresenter::doActionFormatMathCup()
1240 {
1241     injectMarkdownText("\\cup", false, 4);
1242 }
doActionFormatMathCap()1243 void MainWindowPresenter::doActionFormatMathCap()
1244 {
1245     injectMarkdownText("\\cap", false, 4);
1246 }
doActionFormatMathEmptyset()1247 void MainWindowPresenter::doActionFormatMathEmptyset()
1248 {
1249     injectMarkdownText("\\emptyset", false, 9);
1250 }
doActionFormatMathIn()1251 void MainWindowPresenter::doActionFormatMathIn()
1252 {
1253     injectMarkdownText("\\in", false, 3);
1254 }
doActionFormatMathNotin()1255 void MainWindowPresenter::doActionFormatMathNotin()
1256 {
1257     injectMarkdownText("\\notin", false, 6);
1258 }
doActionFormatMathSqrt()1259 void MainWindowPresenter::doActionFormatMathSqrt()
1260 {
1261     injectMarkdownText("\\sqrt{}", false, 6);
1262 }
1263 
doActionFormatStrikethrough()1264 void MainWindowPresenter::doActionFormatStrikethrough()
1265 {
1266     if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_NOTE)) {
1267         orloj->getNoteEdit()->getView()->getNoteEditor()->wrapSelectedText("~~");
1268     } else if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_OUTLINE_HEADER)) {
1269         orloj->getOutlineHeaderEdit()->getView()->getHeaderEditor()->wrapSelectedText("~~");
1270     }
1271 }
1272 
doActionFormatKeyboard()1273 void MainWindowPresenter::doActionFormatKeyboard()
1274 {
1275     if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_NOTE)) {
1276         orloj->getNoteEdit()->getView()->getNoteEditor()->wrapSelectedText("<kbd>", "</kbd>");
1277     } else if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_OUTLINE_HEADER)) {
1278         orloj->getOutlineHeaderEdit()->getView()->getHeaderEditor()->wrapSelectedText("<kbd>", "</kbd>");
1279     }
1280 }
1281 
handleRowsAndDepth()1282 void MainWindowPresenter::handleRowsAndDepth()
1283 {
1284     QString text{};
1285 
1286     if(rowsAndDepthDialog->getPurpose()==RowsAndDepthDialog::Purpose::BULLETS) {
1287         text += "\n";
1288         for(int r=0; r<rowsAndDepthDialog->getRows(); r++) {
1289             for(int d=0; d<rowsAndDepthDialog->getDepth(); d++) {
1290                 for(int t=0; t<d; t++) {
1291                     text += "    ";
1292                 }
1293                 text += "* ...\n";
1294             }
1295         }
1296     } else if(rowsAndDepthDialog->getPurpose()==RowsAndDepthDialog::Purpose::NUMBERS) {
1297         text += "\n";
1298         for(int r=0; r<rowsAndDepthDialog->getRows(); r++) {
1299             for(int d=0; d<rowsAndDepthDialog->getDepth(); d++) {
1300                 for(int t=0; t<d; t++) {
1301                     text += "    ";
1302                 }
1303                 text += QString::number(r+1);
1304                 text += ". ...\n";
1305             }
1306         }
1307     } else if(rowsAndDepthDialog->getPurpose()==RowsAndDepthDialog::Purpose::TASKS) {
1308         text += "\n";
1309         for(int r=0; r<rowsAndDepthDialog->getRows(); r++) {
1310             for(int d=0; d<rowsAndDepthDialog->getDepth(); d++) {
1311                 for(int t=0; t<d; t++) {
1312                     text += "    ";
1313                 }
1314                 text += QString::number(r+1);
1315                 text += ". [";
1316                 if(d%2) text+="x"; else text+=" ";
1317                 text += "] ...\n";
1318             }
1319         }
1320     } else if(rowsAndDepthDialog->getPurpose()==RowsAndDepthDialog::Purpose::BLOCKQUOTE) {
1321         for(int r=0; r<rowsAndDepthDialog->getRows(); r++) {
1322             text += "\n";
1323             for(int d=0; d<rowsAndDepthDialog->getDepth(); d++) {
1324                 for(int t=0; t<d; t++) {
1325                     text += ">";
1326                 }
1327                 text += "> ...\n";
1328             }
1329         }
1330     }
1331 
1332     if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_NOTE)) {
1333         orloj->getNoteEdit()->getView()->getNoteEditor()->insertMarkdownText(text);
1334     } else if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_OUTLINE_HEADER)) {
1335         orloj->getOutlineHeaderEdit()->getView()->getHeaderEditor()->insertMarkdownText(text);
1336     }
1337 }
1338 
doActionFormatListBullet()1339 void MainWindowPresenter::doActionFormatListBullet()
1340 {
1341     rowsAndDepthDialog->setPurpose(RowsAndDepthDialog::Purpose::BULLETS);
1342     rowsAndDepthDialog->show();
1343 }
1344 
doActionFormatListNumber()1345 void MainWindowPresenter::doActionFormatListNumber()
1346 {
1347     rowsAndDepthDialog->setPurpose(RowsAndDepthDialog::Purpose::NUMBERS);
1348     rowsAndDepthDialog->show();
1349 }
1350 
doActionFormatListTask()1351 void MainWindowPresenter::doActionFormatListTask()
1352 {
1353     rowsAndDepthDialog->setPurpose(RowsAndDepthDialog::Purpose::TASKS);
1354     rowsAndDepthDialog->show();
1355 }
1356 
doActionFormatListTaskItem()1357 void MainWindowPresenter::doActionFormatListTaskItem()
1358 {
1359     QString text{"[ ] "};
1360 
1361     if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_NOTE)) {
1362         orloj->getNoteEdit()->getView()->getNoteEditor()->insertMarkdownText(text, false, text.length());
1363     } else if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_OUTLINE_HEADER)) {
1364         orloj->getOutlineHeaderEdit()->getView()->getHeaderEditor()->insertMarkdownText(text, false, text.length());
1365     }
1366 }
1367 
doActionFormatToc()1368 void MainWindowPresenter::doActionFormatToc()
1369 {
1370     if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_NOTE)
1371             || orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_OUTLINE_HEADER))
1372     {
1373         string* text = mdRepresentation->toc(orloj->getOutlineView()->getCurrentOutline());
1374 
1375         if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_NOTE)) {
1376             orloj->getNoteEdit()->getView()->getNoteEditor()->insertMarkdownText(QString::fromStdString(*text));
1377         } else if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_OUTLINE_HEADER)) {
1378             orloj->getOutlineHeaderEdit()->getView()->getHeaderEditor()->insertMarkdownText(QString::fromStdString(*text));
1379         }
1380 
1381         delete text;
1382     }
1383 }
1384 
1385 // IMPROVE: consolidate methods which just insert a (semi)static string
doActionFormatTimestamp()1386 void MainWindowPresenter::doActionFormatTimestamp()
1387 {
1388     QString text = QString::fromStdString(datetimeToString(datetimeNow()));
1389 
1390     if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_NOTE)) {
1391         orloj->getNoteEdit()->getView()->getNoteEditor()->insertMarkdownText(text, false, text.size());
1392     } else if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_OUTLINE_HEADER)) {
1393         orloj->getOutlineHeaderEdit()->getView()->getHeaderEditor()->insertMarkdownText(text, false, text.size());
1394     }
1395 }
1396 
doActionFormatCodeBlock()1397 void MainWindowPresenter::doActionFormatCodeBlock()
1398 {
1399     // IMPROVE ask for dialect
1400     QString text{
1401         "\n"
1402         "```\n"
1403         "...\n"
1404         "```\n"
1405     };
1406 
1407     if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_NOTE)) {
1408         orloj->getNoteEdit()->getView()->getNoteEditor()->insertMarkdownText(text);
1409     } else if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_OUTLINE_HEADER)) {
1410         orloj->getOutlineHeaderEdit()->getView()->getHeaderEditor()->insertMarkdownText(text);
1411     }
1412 }
1413 
doActionFormatMathBlock()1414 void MainWindowPresenter::doActionFormatMathBlock()
1415 {
1416     QString text{"\n$$\n...\n$$\n"};
1417 
1418     if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_NOTE)) {
1419         orloj->getNoteEdit()->getView()->getNoteEditor()->insertMarkdownText(text);
1420     } else if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_OUTLINE_HEADER)) {
1421         orloj->getOutlineHeaderEdit()->getView()->getHeaderEditor()->insertMarkdownText(text);
1422     }
1423 }
1424 
1425 
injectDiagramBlock(const QString & diagramText)1426 void MainWindowPresenter::injectDiagramBlock(const QString& diagramText)
1427 {
1428     // QString text{"\n```mermaid\n...\n```\n"};
1429     QString text{"\n<div class=\"mermaid\">\n"};
1430     text += diagramText;
1431     text += QString{"\n</div>\n"};
1432 
1433     if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_NOTE)) {
1434         orloj->getNoteEdit()->getView()->getNoteEditor()->insertMarkdownText(text, false, 1);
1435     } else if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_OUTLINE_HEADER)) {
1436         orloj->getOutlineHeaderEdit()->getView()->getHeaderEditor()->insertMarkdownText(text, false, 1);
1437     }
1438 }
1439 
1440 
doActionFormatDiagramBlock()1441 void MainWindowPresenter::doActionFormatDiagramBlock()
1442 {
1443     injectDiagramBlock(QString{"..."});
1444 }
1445 
doActionFormatDiagramPie()1446 void MainWindowPresenter::doActionFormatDiagramPie()
1447 {
1448     injectDiagramBlock(
1449         QString{
1450             "pie title Pets\n"
1451             "    \"Dogs\" : 386\n"
1452             "    \"Cats\" : 85\n"
1453             "    \"Rats\" : 15"
1454         }
1455     );
1456 }
1457 
doActionFormatDiagramFlow()1458 void MainWindowPresenter::doActionFormatDiagramFlow()
1459 {
1460     injectDiagramBlock(
1461         QString{
1462             "graph TD\n"
1463             "a --> b\n"
1464             "a --> c"
1465         }
1466     );
1467 }
1468 
doActionFormatDiagramClass()1469 void MainWindowPresenter::doActionFormatDiagramClass()
1470 {
1471     injectDiagramBlock(
1472         QString{
1473             "classDiagram\n"
1474             "     class Animal\n"
1475             "     Animal : +int age\n"
1476             "     Animal : -String gender\n"
1477             "     Animal: +isMammal()\n"
1478             "     Animal: *mate()"
1479         }
1480     );
1481 }
1482 
doActionFormatDiagramGantt()1483 void MainWindowPresenter::doActionFormatDiagramGantt()
1484 {
1485     injectDiagramBlock(
1486         QString{
1487             "gantt\n"
1488             "        dateFormat  YYYY-MM-DD\n"
1489             "        title GANTT diagram\n"
1490             "        section A section\n"
1491             "        Completed task            :done,    des1, 2014-01-06,2014-01-08\n"
1492             "        Active task               :active,  des2, 2014-01-09, 3d\n"
1493             "        Future task               :         des3, after des2, 5d\n"
1494             "        section Critical tasks\n"
1495             "        Completed task in the critical line :crit, done, 2014-01-06,24h\n"
1496             "        Create tests for parser             :crit, active, 3d\n"
1497             "        Future task in critical line        :crit, 5d\n"
1498             "        Add to mermaid                      :1d"
1499         }
1500     );
1501 }
1502 
doActionFormatDiagramState()1503 void MainWindowPresenter::doActionFormatDiagramState()
1504 {
1505     injectDiagramBlock(QString{"stateDiagram    \ns1"});
1506 }
1507 
doActionFormatDiagramSequence()1508 void MainWindowPresenter::doActionFormatDiagramSequence()
1509 {
1510     injectDiagramBlock(
1511          QString{
1512             "sequenceDiagram\n"
1513             "    participant John\n"
1514             "    participant Alice\n"
1515             "    Alice->>John: Hello John, how are you?\n"
1516             "    John-->>Alice: Great!"
1517          }
1518     );
1519 }
1520 
doActionFormatBlockquote()1521 void MainWindowPresenter::doActionFormatBlockquote()
1522 {
1523     rowsAndDepthDialog->setPurpose(RowsAndDepthDialog::Purpose::BLOCKQUOTE);
1524     rowsAndDepthDialog->show();
1525 }
1526 
doActionFormatTable()1527 void MainWindowPresenter::doActionFormatTable()
1528 {
1529     // IMPROVE ask for number of items using dialog
1530     // IMPROVE left/right alignment options
1531     int count=3;
1532     QString text{"\n . | . | .\n --- | --- | ---\n"};
1533     for(int i=1; i<=count; i++) {
1534         text += " . | . | .\n";
1535     }
1536 
1537     if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_NOTE)) {
1538         orloj->getNoteEdit()->getView()->getNoteEditor()->insertMarkdownText(text);
1539     } else if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_OUTLINE_HEADER)) {
1540         orloj->getOutlineHeaderEdit()->getView()->getHeaderEditor()->insertMarkdownText(text);
1541     }
1542 }
1543 
doActionFormatLinkOrImage(QString link)1544 void MainWindowPresenter::doActionFormatLinkOrImage(QString link)
1545 {
1546     // IMPROVE rebuild model ONLY if dirty i.e. an outline name was changed on save
1547     vector<Outline*> oss{mind->getOutlines()};
1548     mind->remind().sortByName(oss);
1549     vector<Thing*> os{oss.begin(), oss.end()};
1550 
1551     vector<Note*> ns{};
1552     mind->getAllNotes(ns);
1553 
1554     QString selectedText;
1555     if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_NOTE)) {
1556         selectedText = orloj->getNoteEdit()->getView()->getNoteEditor()->getSelectedText();
1557     } else if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_OUTLINE_HEADER)) {
1558         selectedText = orloj->getOutlineHeaderEdit()->getView()->getHeaderEditor()->getSelectedText();
1559     }
1560 
1561     if(config.getActiveRepository()->getMode()==Repository::RepositoryMode::REPOSITORY) {
1562         insertLinkDialog->getCopyCheckBox()->setEnabled(true);
1563     } else {
1564         insertLinkDialog->getCopyCheckBox()->setChecked(false);
1565         insertLinkDialog->getCopyCheckBox()->setEnabled(false);
1566     }
1567 
1568     if(link.size() && (
1569          link.endsWith(".png") ||
1570          link.endsWith(".gif") ||
1571          link.endsWith(".jpg") ||
1572          link.endsWith(".jpeg") ||
1573          link.endsWith(".PNG") ||
1574          link.endsWith(".GIF") ||
1575          link.endsWith(".JPG") ||
1576          link.endsWith(".JPEG")))
1577     {
1578         insertImageDialog->show(
1579             selectedText.size()?selectedText:QString{tr("image")},
1580             link);
1581     } else {
1582         insertLinkDialog->show(
1583             config.getActiveRepository(),
1584             orloj->getOutlineView()->getCurrentOutline(),
1585             os,
1586             ns,
1587             selectedText,
1588             link);
1589     }
1590 }
1591 
doActionFormatLink()1592 void MainWindowPresenter::doActionFormatLink()
1593 {
1594     doActionFormatLinkOrImage(QString{});
1595 }
1596 
injectMarkdownText(const QString & text,bool newline,int offset)1597 void MainWindowPresenter::injectMarkdownText(const QString& text, bool newline, int offset)
1598 {
1599     if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_NOTE)) {
1600         orloj->getNoteEdit()->getView()->getNoteEditor()->insertMarkdownText(text, newline, offset);
1601     } else if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_OUTLINE_HEADER)) {
1602         orloj->getOutlineHeaderEdit()->getView()->getHeaderEditor()->insertMarkdownText(text, newline, offset);
1603     }
1604 }
1605 
copyLinkOrImageToRepository(const string & srcPath,QString & path)1606 void MainWindowPresenter::copyLinkOrImageToRepository(const string& srcPath, QString& path)
1607 {
1608     if(isDirectoryOrFileExists(srcPath.c_str())) {
1609         QString pathPrefix{};
1610         string oPath{};
1611         if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_NOTE)) {
1612             oPath = orloj->getNoteEdit()->getCurrentNote()->getOutlineKey();
1613         } else if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_OUTLINE_HEADER)) {
1614             oPath = orloj->getOutlineHeaderEdit()->getCurrentOutline()->getKey();
1615         }
1616         if(stringEndsWith(oPath, FILE_EXTENSION_MD_MD)) {
1617             pathPrefix = QString::fromStdString(oPath.substr(0, oPath.length()-3));
1618         } else {
1619             pathPrefix = QString::fromStdString(oPath);
1620         }
1621         pathPrefix.append(".");
1622 
1623         string d{}, f{};
1624 #if defined(_WIN32)
1625         QString src{srcPath.c_str()};
1626         pathToDirectoryAndFile(src.replace("/", "\\").toStdString(), d, f);
1627         QString pathSuffix{QString::fromStdString(f)};
1628         path = pathPrefix.replace("/", "\\") + pathSuffix;
1629 #else
1630         pathToDirectoryAndFile(srcPath, d, f);
1631         QString pathSuffix{QString::fromStdString(f)};
1632 
1633         path = pathPrefix + pathSuffix;
1634 #endif
1635 
1636         while(isDirectoryOrFileExists(path.toStdString().c_str())) {
1637             pathSuffix.prepend("_");
1638             path = pathPrefix + pathSuffix;
1639         }
1640 #if defined(_WIN32)
1641         MF_DEBUG("Copying: " << src.toStdString() << " > " << path.toStdString() << endl);
1642         copyFile(src.toStdString(), path.toStdString());
1643 #else
1644         copyFile(srcPath, path.toStdString());
1645 #endif
1646 
1647         d.clear();
1648         f.clear();
1649         pathToDirectoryAndFile(path.toStdString(), d, f);
1650         path = QString::fromStdString(f);
1651 
1652         statusBar->showInfo(tr("File copied to repository path '%1'").arg(path.toStdString().c_str()));
1653     } else {
1654         // fallback: create link, but don't copy
1655         path = insertLinkDialog->getPathText();
1656         statusBar->showInfo(tr("Given path '%1' doesn't exist - target will not be copied, but link will be created").arg(path.toStdString().c_str()));
1657     }
1658 }
1659 
1660 // IMPROVE optimize this function (QString, string, ...)
1661 // IMPROVE deduplicate this method and copy image/attachment code
doActionEditPasteImageData(QImage image)1662 void MainWindowPresenter::doActionEditPasteImageData(QImage image)
1663 {
1664     // save image object as file
1665     string oPath{};
1666     if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_NOTE)) {
1667         oPath = orloj->getNoteEdit()->getCurrentNote()->getOutlineKey();
1668     } else if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_OUTLINE_HEADER)) {
1669         oPath = orloj->getOutlineHeaderEdit()->getCurrentOutline()->getKey();
1670     }
1671     QString pathPrefix{};
1672     if(stringEndsWith(oPath, FILE_EXTENSION_MD_MD)) {
1673         pathPrefix = QString::fromStdString(oPath.substr(0, oPath.length()-3));
1674     } else {
1675         pathPrefix = QString::fromStdString(oPath);
1676     }
1677     pathPrefix.append(".");
1678     QString pathSuffix{"image.png"};
1679     QString path = pathPrefix + pathSuffix;
1680     while(isDirectoryOrFileExists(path.toStdString().c_str())) {
1681         pathSuffix.prepend("_");
1682         path = pathPrefix + pathSuffix;
1683     }
1684 
1685     statusBar->showInfo(tr("Saving pasted image data to file: '%1'").arg(path.toStdString().c_str()));
1686     image.save(path);
1687 
1688     // inject link to file to O
1689     injectImageLinkToEditor(path, QString{"image"});
1690 }
1691 
statusInfoPreviewFlickering()1692 void MainWindowPresenter::statusInfoPreviewFlickering()
1693 {
1694     statusBar->showInfo(QString(tr("HTML Note preview flickering can be eliminated by disabling math and diagrams in Preferences menu")));
1695 }
1696 
1697 /*
1698  * See InsertLinkDialog for link creation hints
1699  */
handleFormatLink()1700 void MainWindowPresenter::handleFormatLink()
1701 {
1702     insertLinkDialog->hide();
1703 
1704     QString path{};
1705     if(insertLinkDialog->isCopyToRepo()) {
1706         copyLinkOrImageToRepository(insertLinkDialog->getPathText().toStdString(), path);
1707     } else {
1708         path = insertLinkDialog->getPathText();
1709     }
1710 
1711     // IMPROVE make this reusable method
1712     QString text{"["};
1713     text += insertLinkDialog->getLinkText();
1714     text += "](";
1715     text += path;
1716     text += ")";
1717 
1718     if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_NOTE)) {
1719         if(orloj->getNoteEdit()->getView()->getNoteEditor()->getSelectedText().size()) {
1720             orloj->getNoteEdit()->getView()->getNoteEditor()->removeSelectedText();
1721         }
1722         orloj->getNoteEdit()->getView()->getNoteEditor()->insertMarkdownText(text, false, 1);
1723     } else if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_OUTLINE_HEADER)) {
1724         if(orloj->getOutlineHeaderEdit()->getView()->getHeaderEditor()->getSelectedText().size()) {
1725             orloj->getOutlineHeaderEdit()->getView()->getHeaderEditor()->removeSelectedText();
1726         }
1727         orloj->getOutlineHeaderEdit()->getView()->getHeaderEditor()->insertMarkdownText(text, false, 1);
1728     }
1729 }
1730 
doActionFormatImage()1731 void MainWindowPresenter::doActionFormatImage()
1732 {
1733     if(config.getActiveRepository()->getMode()==Repository::RepositoryMode::REPOSITORY) {
1734         insertImageDialog->getCopyCheckBox()->setEnabled(true);
1735     } else {
1736         insertImageDialog->getCopyCheckBox()->setChecked(false);
1737         insertImageDialog->getCopyCheckBox()->setEnabled(false);
1738     }
1739 
1740     insertImageDialog->show();
1741 }
1742 
injectImageLinkToEditor(const QString & path,const QString & alternateText)1743 void MainWindowPresenter::injectImageLinkToEditor(
1744         const QString& path,
1745         const QString& alternateText)
1746 {
1747     QString text{"!["};
1748     if(alternateText.size()) {
1749         text += alternateText;
1750     } else {
1751         text += insertImageDialog->getAlternateText();
1752     }
1753     text += "](";
1754 #ifdef _WIN32
1755     // image links are processed by HTML browser > \s must be replaced with /s
1756     // (attachments use \s as the path is used by OS tools)
1757     text +=  QString{path}.replace("\\", "/");
1758 #else
1759     text += path;
1760 #endif
1761     text += ")";
1762 
1763     if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_NOTE)) {
1764         orloj->getNoteEdit()->getView()->getNoteEditor()->insertMarkdownText(text, false, 2);
1765     } else if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_OUTLINE_HEADER)) {
1766         orloj->getOutlineHeaderEdit()->getView()->getHeaderEditor()->insertMarkdownText(text, false);
1767     }
1768 }
1769 
handleFormatImage()1770 void MainWindowPresenter::handleFormatImage()
1771 {
1772     insertImageDialog->hide();
1773 
1774     QString path{};
1775     if(insertImageDialog->isCopyToRepo()) {
1776         copyLinkOrImageToRepository(insertImageDialog->getPathText().toStdString(), path);
1777     } else {
1778         path = insertImageDialog->getPathText();
1779     }
1780 
1781     injectImageLinkToEditor(path, QString{});
1782 }
1783 
doActionFormatHr()1784 void MainWindowPresenter::doActionFormatHr()
1785 {
1786     QString text{"\n---"};
1787 
1788     if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_NOTE)) {
1789         orloj->getNoteEdit()->getView()->getNoteEditor()->insertMarkdownText(text);
1790     } else if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_OUTLINE_HEADER)) {
1791         orloj->getOutlineHeaderEdit()->getView()->getHeaderEditor()->insertMarkdownText(text);
1792     }
1793 }
1794 
doActionOutlineNew()1795 void MainWindowPresenter::doActionOutlineNew()
1796 {
1797     newOutlineDialog->show(mind->remind().getStencils(ResourceType::OUTLINE));
1798 }
1799 
doActionOutlineOrNoteNew()1800 void MainWindowPresenter::doActionOutlineOrNoteNew()
1801 {
1802     if(orloj->isFacetActive(OrlojPresenterFacets::FACET_VIEW_OUTLINE_HEADER)
1803          ||
1804        orloj->isFacetActive(OrlojPresenterFacets::FACET_VIEW_NOTE)
1805          ||
1806        orloj->isFacetActive(OrlojPresenterFacets::FACET_VIEW_OUTLINE))
1807     {
1808         doActionNoteNew();
1809     } else {
1810         doActionOutlineNew();
1811     }
1812 }
1813 
handleOutlineNew()1814 void MainWindowPresenter::handleOutlineNew()
1815 {
1816     string name = newOutlineDialog->getOutlineName().toStdString();
1817 
1818     // preamble
1819     vector<string*>* preamble = nullptr;
1820     if(newOutlineDialog->getPreamble().size()) {
1821         string* preambleText = new string{newOutlineDialog->getPreamble().toStdString()};
1822         preamble = new vector<string*>{};
1823         stringToLines(preambleText, *preamble);
1824         delete preambleText;
1825     }
1826 
1827     mind->outlineNew(
1828         &name,
1829         newOutlineDialog->getOutlineType(),
1830         newOutlineDialog->getImportance(),
1831         newOutlineDialog->getUrgency(),
1832         newOutlineDialog->getProgress(),
1833         &newOutlineDialog->getTags(),
1834         preamble,
1835         newOutlineDialog->getStencil());
1836 
1837     if(orloj->isFacetActive(OrlojPresenterFacets::FACET_LIST_OUTLINES)) {
1838         // IMPROVE PERF add only 1 new outline + sort table (don't load all outlines)
1839         orloj->getOutlinesTable()->refresh(mind->getOutlines());
1840     }
1841     // else Outlines are refreshed on facet change
1842 }
1843 
doActionOutlineEdit()1844 void MainWindowPresenter::doActionOutlineEdit()
1845 {
1846     if(orloj->isFacetActive(OrlojPresenterFacets::FACET_VIEW_OUTLINE)
1847          ||
1848        orloj->isFacetActive(OrlojPresenterFacets::FACET_VIEW_OUTLINE_HEADER)
1849          ||
1850        orloj->isFacetActive(OrlojPresenterFacets::FACET_VIEW_NOTE)
1851          ||
1852        orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_NOTE)
1853     ) {
1854         Outline* o = orloj->getOutlineView()->getCurrentOutline();
1855         if(o) {
1856             orloj->showFacetOutlineHeaderEdit(o);
1857             return;
1858         }
1859     }
1860     QMessageBox::critical(&view, tr("Edit Notebook"), tr("Please open an Notebook to edit."));
1861 }
1862 
handleNoteNew()1863 void MainWindowPresenter::handleNoteNew()
1864 {
1865     int offset
1866         = orloj->getOutlineView()->getOutlineTree()->getCurrentRow();
1867     if(offset == OutlineTreePresenter::NO_ROW) {
1868         offset = NO_PARENT;
1869     } else {
1870         if(newNoteDialog->isPositionBelow()) {
1871             offset++;
1872         }
1873         // else position is ABOVE
1874 
1875     }
1876 
1877     MF_DEBUG("New N: current N offset: " << offset << endl);
1878 
1879     u_int16_t depth;
1880     Note* n = orloj->getOutlineView()->getOutlineTree()->getCurrentNote();
1881     if(n) {
1882         depth = n->getDepth();
1883     } else {
1884         depth = 0;
1885     }
1886 
1887     string name = newNoteDialog->getNoteName().toStdString();
1888     Note* note = mind->noteNew(
1889                 orloj->getOutlineView()->getCurrentOutline()->getKey(),
1890                 // IMPROVE get parent note number from selection (if selected)
1891                 offset,
1892                 &name,
1893                 newNoteDialog->getNoteType(),
1894                 depth,
1895                 &newNoteDialog->getTags(),
1896                 newNoteDialog->getProgress(),
1897                 newNoteDialog->getStencil());
1898     if(note) {
1899         mind->remember(orloj->getOutlineView()->getCurrentOutline()->getKey());
1900 
1901         // insert new N and select it in the tree
1902         orloj->getOutlineView()->insertAndSelect(note);
1903 
1904 
1905         // IMPROVE smarter refresh of outline tree (do less than overall load)
1906         //orloj->showFacetOutline(orloj->getOutlineView()->getCurrentOutline());
1907 
1908         if(newNoteDialog->isOpenInEditor()) {
1909             orloj->showFacetNoteEdit(note);
1910         } else {
1911             orloj->showFacetNoteView(note);
1912         }
1913     } else {
1914         QMessageBox::critical(&view, tr("New Note"), tr("Failed to create new Note!"));
1915     }
1916 }
1917 
doActionOutlineClone()1918 void MainWindowPresenter::doActionOutlineClone()
1919 {
1920     Outline* o = orloj->getOutlineView()->getCurrentOutline();
1921     if(o) {
1922         Outline* clonedOutline = mind->outlineClone(o->getKey());
1923         if(clonedOutline) {
1924             orloj->getOutlineView()->refresh(clonedOutline);
1925             orloj->showFacetOutline(orloj->getOutlineView()->getCurrentOutline());
1926         } else {
1927             QMessageBox::critical(&view, tr("Clone Notebook"), tr("Failed to clone Notebook!"));
1928         }
1929     } else {
1930         QMessageBox::critical(&view, tr("Clone Notebook"), tr("Please open and Notebook to be cloned."));
1931     }
1932 }
1933 
doActionOutlineHome()1934 void MainWindowPresenter::doActionOutlineHome()
1935 {
1936     if(orloj->isFacetActiveOutlineOrNoteView()) {
1937         const Tag* t = mind->remind().getOntology().findOrCreateTag(Tag::KeyMindForgerHome());
1938         Outline* o = orloj->getOutlineView()->getCurrentOutline();
1939         // if O has tag, then toggle (remove) it, else set the tag
1940         if(o->hasTag(t)) {
1941             o->removeTag(t);
1942             mind->remind().remember(o->getKey());
1943             statusBar->showInfo(tr("Home tag toggled/removed - Notebook '%1' is no longer home").arg(o->getName().c_str()));
1944         } else {
1945             if(mind->setOutlineUniqueTag(t, o->getKey())) {
1946                 statusBar->showInfo(tr("Notebook '%1' successfully marked as home").arg(o->getName().c_str()));
1947             }
1948         }
1949     } else {
1950         QMessageBox::critical(&view, tr("Make Notebook home"), tr("Notebook can be marked as home only when viewed."));
1951     }
1952 }
1953 
doActionOutlineForget()1954 void MainWindowPresenter::doActionOutlineForget()
1955 {
1956     if(orloj->isFacetActiveOutlineOrNoteView()) {
1957         QMessageBox::StandardButton choice;
1958         choice = QMessageBox::question(
1959             &view,
1960             tr("Forget Notebook"),
1961             tr("Do you really want to forget '") +
1962             QString::fromStdString(orloj->getOutlineView()->getCurrentOutline()->getName()) +
1963             tr("' Notebook?"));
1964         if (choice == QMessageBox::Yes) {
1965             mind->outlineForget(orloj->getOutlineView()->getCurrentOutline()->getKey());
1966             orloj->slotShowOutlines();
1967         } // else do nothing
1968     } else {
1969         QMessageBox::critical(&view, tr("Forget Notebook"), tr("Notebook can be forgotten only when viewed."));
1970     }
1971 }
1972 
doActionOutlineHtmlExport()1973 void MainWindowPresenter::doActionOutlineHtmlExport()
1974 {
1975     exportOutlineToHtmlDialog->show();
1976 }
1977 
handleOutlineHtmlExport()1978 void MainWindowPresenter::handleOutlineHtmlExport()
1979 {
1980     if(isDirectoryOrFileExists(newFileDialog->getFilePath().toStdString().c_str())) {
1981         QMessageBox::critical(&view, tr("Export Error"), tr("Specified file path already exists!"));
1982     } else {
1983         if(orloj->isFacetActive(OrlojPresenterFacets::FACET_VIEW_OUTLINE)
1984              ||
1985            orloj->isFacetActive(OrlojPresenterFacets::FACET_VIEW_OUTLINE_HEADER)
1986              ||
1987            orloj->isFacetActive(OrlojPresenterFacets::FACET_VIEW_NOTE)
1988              ||
1989            orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_NOTE)
1990         ) {
1991             Outline* o = orloj->getOutlineView()->getCurrentOutline();
1992             if(o) {
1993                 mind->remind().exportToHtml(o, exportOutlineToHtmlDialog->getFilePath().toStdString());
1994                 return;
1995             }
1996         }
1997 
1998         QMessageBox::critical(&view, tr("Export Error"), tr("Unable to find Notebook to export!"));
1999     }
2000 }
2001 
doActionMindCsvExport()2002 void MainWindowPresenter::doActionMindCsvExport()
2003 {
2004     exportMindToCsvDialog->show();
2005 }
2006 
handleMindCsvExport()2007 void MainWindowPresenter::handleMindCsvExport()
2008 {
2009     if(isDirectoryOrFileExists(newFileDialog->getFilePath().toStdString().c_str())) {
2010         QMessageBox::critical(&view, tr("Export Error"), tr("Specified file path already exists!"));
2011     } else {
2012         mind->remind().exportToCsv(exportMindToCsvDialog->getFilePath().toStdString());
2013     }
2014 }
2015 
doActionOutlineTWikiImport()2016 void MainWindowPresenter::doActionOutlineTWikiImport()
2017 {
2018     QString homeDirectory
2019         = QStandardPaths::locate(QStandardPaths::HomeLocation, QString(), QStandardPaths::LocateDirectory);
2020 
2021     QFileDialog importDialog{&view};
2022     importDialog.setWindowTitle(tr("Import TWiki File"));
2023     importDialog.setFileMode(QFileDialog::ExistingFile);
2024     importDialog.setDirectory(homeDirectory);
2025     importDialog.setViewMode(QFileDialog::Detail);
2026 
2027     QStringList directoryNames{};
2028     if(importDialog.exec()) {
2029         directoryNames = importDialog.selectedFiles();
2030         if(directoryNames.size()==1) {
2031             mind->learnOutlineTWiki(directoryNames[0].toStdString());
2032 
2033             // refresh O view
2034             if(config.getActiveRepository()->getMode()==Repository::RepositoryMode::REPOSITORY) {
2035                 orloj->showFacetOutlineList(mind->getOutlines());
2036             } else {
2037                 if(mind->getOutlines().size()>0) {
2038                     orloj->showFacetOutline(*mind->getOutlines().begin());
2039                 }
2040             }
2041             statusBar->showMindStatistics();
2042         } // else too many files
2043     } // else directory closed / nothing choosen
2044 }
2045 
doActionNoteNew()2046 void MainWindowPresenter::doActionNoteNew()
2047 {
2048     if(orloj->isFacetActive(OrlojPresenterFacets::FACET_VIEW_OUTLINE)
2049          ||
2050        orloj->isFacetActive(OrlojPresenterFacets::FACET_VIEW_OUTLINE_HEADER)
2051          ||
2052        orloj->isFacetActive(OrlojPresenterFacets::FACET_VIEW_NOTE))
2053        // IMPROVE if note is edited, show warning that note must be saved
2054     {
2055         newNoteDialog->show(
2056                     QString::fromStdString(orloj->getOutlineView()->getCurrentOutline()->getKey()),
2057                     mind->remind().getStencils(ResourceType::NOTE));
2058     } else {
2059         QMessageBox::critical(&view, tr("New Note"), tr("Open and view a Notebook to create new Note."));
2060     }
2061 }
2062 
doActionOutlineOrNoteEdit()2063 void MainWindowPresenter::doActionOutlineOrNoteEdit()
2064 {
2065     if(orloj->isFacetActive(OrlojPresenterFacets::FACET_VIEW_OUTLINE)
2066          ||
2067        orloj->isFacetActive(OrlojPresenterFacets::FACET_VIEW_NOTE)
2068          ||
2069        orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_NOTE)
2070     ) {
2071         Note* note = orloj->getOutlineView()->getOutlineTree()->getCurrentNote();
2072         if(note) {
2073             orloj->showFacetNoteEdit(note);
2074             return;
2075         }
2076     }
2077 
2078     doActionOutlineEdit();
2079 }
2080 
doActionNoteEdit()2081 void MainWindowPresenter::doActionNoteEdit()
2082 {
2083     if(orloj->isFacetActive(OrlojPresenterFacets::FACET_VIEW_OUTLINE)
2084          ||
2085        orloj->isFacetActive(OrlojPresenterFacets::FACET_VIEW_NOTE)
2086          ||
2087        orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_NOTE)
2088     ) {
2089         Note* note = orloj->getOutlineView()->getOutlineTree()->getCurrentNote();
2090         if(note) {
2091             orloj->showFacetNoteEdit(note);
2092             return;
2093         }
2094     }
2095 
2096 #ifdef __APPLE__
2097     doActionOutlineEdit();
2098 #else
2099     QMessageBox::critical(&view, tr("Edit Note"), tr("Please select a Note to edit in the Notebook."));
2100 #endif
2101 }
2102 
doActionNoteHoist()2103 void MainWindowPresenter::doActionNoteHoist()
2104 {
2105     if(orloj->isFacetActive(OrlojPresenterFacets::FACET_VIEW_OUTLINE)
2106          ||
2107        orloj->isFacetActive(OrlojPresenterFacets::FACET_VIEW_OUTLINE_HEADER)
2108          ||
2109        orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_OUTLINE_HEADER)
2110          ||
2111        orloj->isFacetActive(OrlojPresenterFacets::FACET_VIEW_NOTE)
2112          ||
2113        orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_NOTE))
2114     {
2115         config.setUiHoistedMode(getMainMenu()->getView()->actionViewHoist->isChecked());
2116         orloj->applyFacetHoisting();
2117     }
2118 }
2119 
doActionNoteLeave()2120 void MainWindowPresenter::doActionNoteLeave()
2121 {
2122     orloj->slotShowOutlines();
2123 }
2124 
doActionNoteForget()2125 void MainWindowPresenter::doActionNoteForget()
2126 {
2127     if(orloj->isFacetActive(OrlojPresenterFacets::FACET_VIEW_OUTLINE)
2128          ||
2129        orloj->isFacetActive(OrlojPresenterFacets::FACET_VIEW_NOTE)
2130          ||
2131        orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_NOTE)
2132     ) {
2133         Note* note = orloj->getOutlineView()->getOutlineTree()->getCurrentNote();
2134         if(note) {
2135             QMessageBox msgBox{
2136                 QMessageBox::Question,
2137                 tr("Delete Note"),
2138                 tr("Do you really want to delete note '") +
2139                 QString::fromStdString(note->getName()) +
2140                 tr("' along with its child notes?")};
2141             QPushButton* yes = msgBox.addButton("&Yes", QMessageBox::YesRole);
2142             msgBox.addButton("&No", QMessageBox::NoRole);
2143             msgBox.exec();
2144 
2145             QAbstractButton* choosen = msgBox.clickedButton();
2146             if(yes == choosen) {
2147                 Outline* outline = mind->noteForget(note);
2148                 mind->remember(outline);
2149                 orloj->showFacetOutline(orloj->getOutlineView()->getCurrentOutline());
2150             }
2151             return;
2152         }
2153     }
2154     QMessageBox::critical(&view, tr("Forget Note"), tr("Please select a Note to forget."));
2155 }
2156 
doActionNoteExtract()2157 void MainWindowPresenter::doActionNoteExtract()
2158 {
2159     // TODO distinquish HEADER and NOTE - different places from where to get text
2160     if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_OUTLINE_HEADER)
2161          ||
2162        orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_NOTE)
2163     ) {
2164         // try to get N (might be null if O's header is being edited)
2165         Note* n = orloj->getOutlineView()->getOutlineTree()->getCurrentNote();
2166         QString selectedText;
2167         if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_OUTLINE_HEADER)) {
2168             selectedText = orloj->getOutlineHeaderEdit()->getSelectedText();
2169         } else {
2170             selectedText = orloj->getNoteEdit()->getSelectedText();
2171         }
2172 
2173         if(selectedText.isEmpty()) {
2174             QMessageBox::critical(&view, tr("Extract Note"), tr("Please select a text to extract."));
2175         } else {
2176             int offset = orloj->getOutlineView()->getOutlineTree()->getCurrentRow();
2177             if(offset == OutlineTreePresenter::NO_ROW) {
2178                 offset = NO_PARENT;
2179             } else {
2180                 offset++; // extracted N to be sibling below w/ same depth
2181             }
2182 
2183             static string defaultExtractedNoteName{"Extracted Note"};
2184             Note* extractedNote = mind->noteNew(
2185                         orloj->getOutlineView()->getCurrentOutline()->getKey(),
2186                         offset,
2187                         &defaultExtractedNoteName,
2188                         n?n->getType():mind->remind().getOntology().findOrCreateNoteType(NoteType::KeyNote()),
2189                         n?n->getDepth():0);
2190             if(extractedNote) {
2191                 // parse selected text to description
2192                 std::vector<std::string*> description{};
2193                 string t{selectedText.toStdString()};
2194                 mdRepresentation->description(&t, description);
2195                 extractedNote->setDescription(description);
2196 
2197                 mind->remember(orloj->getOutlineView()->getCurrentOutline()->getKey());
2198                 // IMPROVE smarter refresh of outline tree (do less then overall load)
2199                 orloj->showFacetOutline(orloj->getOutlineView()->getCurrentOutline());
2200                 orloj->showFacetNoteEdit(extractedNote);
2201             } else {
2202                 QMessageBox::critical(&view, tr("Extract Note"), tr("Failed to extract new Note!"));
2203             }
2204         }
2205     } else {
2206         QMessageBox::critical(&view, tr("Extract Note"), tr("Please select a Note, edit it and select a text to extract."));
2207     }
2208 }
2209 
doActionNoteClone()2210 void MainWindowPresenter::doActionNoteClone()
2211 {
2212     Note* n = orloj->getOutlineView()->getOutlineTree()->getCurrentNote();
2213     if(n) {
2214         Note* clonedNote = mind->noteClone(orloj->getOutlineView()->getCurrentOutline()->getKey(), n);
2215         if(clonedNote) {
2216             mind->remind().remember(orloj->getOutlineView()->getCurrentOutline()->getKey());
2217             // IMPROVE smarter refresh of outline tree (do less then overall load)
2218             orloj->showFacetOutline(orloj->getOutlineView()->getCurrentOutline());
2219             // select Note in the tree
2220             QModelIndex idx
2221                 = orloj->getOutlineView()->getOutlineTree()->getView()->model()->index(n->getOutline()->getNoteOffset(clonedNote), 0);
2222             orloj->getOutlineView()->getOutlineTree()->getView()->setCurrentIndex(idx);
2223         } else {
2224             QMessageBox::critical(&view, tr("Clone Note"), tr("Failed to clone Note!"));
2225         }
2226     } else {
2227         QMessageBox::critical(&view, tr("Clone Note"), tr("Please select a Note to be cloned."));
2228     }
2229 }
2230 
doActionNoteFirst()2231 void MainWindowPresenter::doActionNoteFirst()
2232 {
2233     Note* note = orloj->getOutlineView()->getOutlineTree()->getCurrentNote();
2234     if(note) {
2235         // IMPROVE consider patch once in class (cross functions)
2236         Outline::Patch patch{Outline::Patch::Diff::NO,0,0}; // explicit initialization required by older GCC versions
2237         mind->noteFirst(note, &patch);
2238         if(patch.diff != Outline::Patch::Diff::NO) {
2239             mind->remind().remember(note->getOutline());
2240             orloj->getOutlineView()->getOutlineTree()->refresh(note->getOutline(), &patch);
2241             // select Note in the tree
2242             QModelIndex idx
2243                 = orloj->getOutlineView()->getOutlineTree()->getView()->model()->index(patch.start, 0);
2244             orloj->getOutlineView()->getOutlineTree()->getView()->setCurrentIndex(idx);
2245             statusBar->showInfo(QString(tr("Moved Note '%1' to be the first child")).arg(note->getName().c_str()));
2246         }
2247     } else {
2248         QMessageBox::critical(&view, tr("Move Note"), tr("Please select a Note to be moved."));
2249     }
2250 }
2251 
doActionNoteUp()2252 void MainWindowPresenter::doActionNoteUp()
2253 {
2254     Note* note = orloj->getOutlineView()->getOutlineTree()->getCurrentNote();
2255     if(note) {
2256         // IMPROVE consider patch once in class (cross functions)
2257         Outline::Patch patch{Outline::Patch::Diff::NO,0,0}; // explicit initialization required by older GCC versions
2258         mind->noteUp(note, &patch);
2259         if(patch.diff != Outline::Patch::Diff::NO) {
2260             mind->remind().remember(note->getOutline());
2261             orloj->getOutlineView()->getOutlineTree()->refresh(note->getOutline(), &patch);
2262             // select Note in the tree
2263             QModelIndex idx
2264                 = orloj->getOutlineView()->getOutlineTree()->getView()->model()->index(patch.start, 0);
2265             orloj->getOutlineView()->getOutlineTree()->getView()->setCurrentIndex(idx);
2266             statusBar->showInfo(QString(tr("Moved up Note '%1'")).arg(note->getName().c_str()));
2267         }
2268     } else {
2269         QMessageBox::critical(&view, tr("Move Note"), tr("Please select a Note to be moved."));
2270     }
2271 }
2272 
doActionNoteDown()2273 void MainWindowPresenter::doActionNoteDown()
2274 {
2275     Note* note = orloj->getOutlineView()->getOutlineTree()->getCurrentNote();
2276     if(note) {
2277         // IMPROVE consider patch once in class (cross functions)
2278         Outline::Patch patch{Outline::Patch::Diff::NO,0,0}; // explicit initialization required by older GCC versions
2279         mind->noteDown(note, &patch);
2280         if(patch.diff != Outline::Patch::Diff::NO) {
2281             mind->remind().remember(note->getOutline());
2282             orloj->getOutlineView()->getOutlineTree()->refresh(note->getOutline(), &patch);
2283             // select Note in the tree
2284             QModelIndex idx
2285                 = orloj->getOutlineView()->getOutlineTree()->getView()->model()->index(note->getOutline()->getNoteOffset(note), 0);
2286             orloj->getOutlineView()->getOutlineTree()->getView()->setCurrentIndex(idx);
2287             statusBar->showInfo(QString(tr("Moved down Note '%1'").arg(note->getName().c_str())));
2288         }
2289     } else {
2290         QMessageBox::critical(&view, tr("Move Note"), tr("Please select a Note to be moved."));
2291     }
2292 }
2293 
doActionNoteLast()2294 void MainWindowPresenter::doActionNoteLast()
2295 {
2296     Note* note = orloj->getOutlineView()->getOutlineTree()->getCurrentNote();
2297     if(note) {
2298         // IMPROVE consider patch once in class (cross functions)
2299         Outline::Patch patch{Outline::Patch::Diff::NO,0,0}; // explicit initialization required by older GCC versions
2300         mind->noteLast(note, &patch);
2301         if(patch.diff != Outline::Patch::Diff::NO) {
2302             mind->remind().remember(note->getOutline());
2303             orloj->getOutlineView()->getOutlineTree()->refresh(note->getOutline(), &patch);
2304             // select Note in the tree
2305             QModelIndex idx
2306                 = orloj->getOutlineView()->getOutlineTree()->getView()->model()->index(note->getOutline()->getNoteOffset(note), 0);
2307             orloj->getOutlineView()->getOutlineTree()->getView()->setCurrentIndex(idx);
2308             statusBar->showInfo(QString(tr("Moved Note '%1' to be the last child")).arg(note->getName().c_str()));
2309         }
2310     } else {
2311         QMessageBox::critical(&view, tr("Move Note"), tr("Please select a Note to be moved."));
2312     }
2313 }
2314 
doActionOutlineShow()2315 void MainWindowPresenter::doActionOutlineShow()
2316 {
2317     orloj->showFacetOutline(orloj->getOutlineView()->getCurrentOutline());
2318 }
2319 
doActionNotePromote()2320 void MainWindowPresenter::doActionNotePromote()
2321 {
2322     Note* note = orloj->getOutlineView()->getOutlineTree()->getCurrentNote();
2323     if(note) {
2324         // IMPROVE consider patch once in class (cross functions)
2325         Outline::Patch patch{Outline::Patch::Diff::NO,0,0}; // explicit initialization required by older GCC versions
2326         mind->notePromote(note, &patch);
2327         if(patch.diff != Outline::Patch::Diff::NO) {
2328             mind->remind().remember(note->getOutline());
2329             orloj->getOutlineView()->getOutlineTree()->refresh(note->getOutline(), &patch);
2330             statusBar->showInfo(QString(tr("Promoted Note '%1'")).arg(note->getName().c_str()));
2331         }
2332     } else {
2333         QMessageBox::critical(&view, tr("Promote Note"), tr("Please select a Note to be promoted."));
2334     }
2335 }
2336 
doActionNoteDemote()2337 void MainWindowPresenter::doActionNoteDemote()
2338 {
2339     Note* note = orloj->getOutlineView()->getOutlineTree()->getCurrentNote();
2340     if(note) {
2341         // IMPROVE consider patch once in class (cross functions)
2342         Outline::Patch patch{Outline::Patch::Diff::NO,0,0}; // explicit initialization required by older GCC versions
2343         mind->noteDemote(note, &patch);
2344         mind->remind().remember(note->getOutline());
2345         orloj->getOutlineView()->getOutlineTree()->refresh(note->getOutline(), &patch);
2346         if(patch.diff != Outline::Patch::Diff::NO) {
2347             statusBar->showInfo(QString(tr("Demoted Note '%1'")).arg(note->getName().c_str()));
2348         }
2349     } else {
2350         QMessageBox::critical(&view, tr("Demote Note"), tr("Please select a Note to be demoted."));
2351     }
2352 }
2353 
doActionEditFind()2354 void MainWindowPresenter::doActionEditFind()
2355 {
2356     doActionFts();
2357 }
2358 
doActionEditFindAgain()2359 void MainWindowPresenter::doActionEditFindAgain()
2360 {
2361     if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_NOTE)) {
2362         orloj->getNoteEdit()->getView()->getNoteEditor()->findStringAgain();
2363     } else if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_OUTLINE_HEADER)) {
2364         orloj->getOutlineHeaderEdit()->getView()->getHeaderEditor()->findStringAgain();
2365     }
2366 }
2367 
doActionEditWordWrapToggle()2368 void MainWindowPresenter::doActionEditWordWrapToggle()
2369 {
2370     NoteEditorView* editor{};
2371     if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_NOTE)) {
2372         editor = orloj->getNoteEdit()->getView()->getNoteEditor();
2373     } else if(orloj->isFacetActive(OrlojPresenterFacets::FACET_EDIT_OUTLINE_HEADER)) {
2374         editor = orloj->getOutlineHeaderEdit()->getView()->getHeaderEditor();
2375     } else {
2376         return;
2377     }
2378 
2379     if(editor->wordWrapMode() == QTextOption::NoWrap) {
2380         editor->setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
2381     } else {
2382         editor->setWordWrapMode(QTextOption::NoWrap);
2383     }
2384 }
2385 
doActionMindRemember()2386 void MainWindowPresenter::doActionMindRemember()
2387 {
2388     mdConfigRepresentation->save(config);
2389 }
2390 
doActionMindSnapshot()2391 void MainWindowPresenter::doActionMindSnapshot()
2392 {
2393 }
2394 
doActionMindTimeTagScope()2395 void MainWindowPresenter::doActionMindTimeTagScope()
2396 {
2397     TimeScopeAspect& time = mind->getTimeScopeAspect();
2398     scopeDialog->show(
2399         mind->getTagsScopeAspect().getTags(),
2400         time.isEnabled(),
2401         time.getTimeScope().years,
2402         time.getTimeScope().months,
2403         time.getTimeScope().days,
2404         time.getTimeScope().hours,
2405         time.getTimeScope().minutes);
2406 }
2407 
handleMindScope()2408 void MainWindowPresenter::handleMindScope()
2409 {
2410     // time scope
2411     TimeScope& ts=mind->getTimeScopeAspect().getTimeScope();
2412     if(scopeDialog->isTimeScopeSet()) {
2413         ts.years=scopeDialog->getYears();
2414         ts.months=scopeDialog->getMonths();
2415         ts.days=scopeDialog->getDays();
2416         ts.hours=scopeDialog->getHours();
2417         ts.minutes=scopeDialog->getMinutes();
2418 
2419         ts.recalculateRelativeSecs();
2420     } else {
2421         ts.reset();
2422     }
2423     // update components
2424     mind->getTimeScopeAspect().setTimeScope(ts);
2425     config.setTimeScope(mind->getTimeScopeAspect().getTimeScope());
2426 
2427     // tags scope
2428     if(scopeDialog->isTagsScopeSet() && scopeDialog->getTags().size()) {
2429         mind->getTagsScopeAspect().setTags(scopeDialog->getTags());
2430     } else {
2431         mind->getTagsScopeAspect().reset();
2432     }
2433     config.setTagsScope(mind->getTagsScopeAspect().getTags());
2434 
2435     // save configuration
2436     mdConfigRepresentation->save(config);
2437 
2438     // IMPROVE don't change view to Os, but refresh current one
2439     doActionViewOutlines();
2440 }
2441 
doActionMindPreferences()2442 void MainWindowPresenter::doActionMindPreferences()
2443 {
2444     configDialog->show();
2445 }
2446 
handleMindPreferences()2447 void MainWindowPresenter::handleMindPreferences()
2448 {
2449     mdConfigRepresentation->save(config);
2450 
2451     view.getToolBar()->setVisible(config.isUiShowToolbar());
2452     view.getOrloj()->getNoteView()->setZoomFactor(config.getUiHtmlZoomFactor());
2453     view.getOrloj()->getOutlineHeaderView()->setZoomFactor(config.getUiHtmlZoomFactor());
2454 
2455     view.getOrloj()->getNoteView()->getButtonsPanel()->setExpertMode(config.isUiExpertMode());
2456     view.getOrloj()->getNoteView()->getButtonsPanel()->setVisible(!config.isUiExpertMode());
2457     view.getOrloj()->getOutlineHeaderView()->getEditPanel()->setExpertMode(config.isUiExpertMode());
2458     view.getOrloj()->getOutlineHeaderView()->getEditPanel()->setVisible(!config.isUiExpertMode());
2459 
2460     view.getOrloj()->getNoteEdit()->getButtonsPanel()->setVisible(!config.isUiExpertMode());
2461     view.getOrloj()->getOutlineHeaderEdit()->getButtonsPanel()->setVisible(!config.isUiExpertMode());
2462 }
2463 
doActionHelpDocumentation()2464 void MainWindowPresenter::doActionHelpDocumentation()
2465 {
2466     QDesktopServices::openUrl(QUrl{"https://github.com/dvorka/mindforger-repository/blob/master/memory/mindforger/index.md"});
2467 }
2468 
doActionHelpWeb()2469 void MainWindowPresenter::doActionHelpWeb()
2470 {
2471     QDesktopServices::openUrl(QUrl{"http://www.mindforger.com"});
2472 }
2473 
doActionHelpMarkdown()2474 void MainWindowPresenter::doActionHelpMarkdown()
2475 {
2476     QDesktopServices::openUrl(QUrl{"https://guides.github.com/features/mastering-markdown/"});
2477 }
2478 
doActionHelpDiagrams()2479 void MainWindowPresenter::doActionHelpDiagrams()
2480 {
2481     QDesktopServices::openUrl(QUrl{"https://mermaid-js.github.io/mermaid/#/"});
2482 }
2483 
doActionHelpMathLivePreview()2484 void MainWindowPresenter::doActionHelpMathLivePreview()
2485 {
2486     QDesktopServices::openUrl(QUrl{"https://www.mathjax.org/#demo"});
2487 }
2488 
doActionHelpMathQuickReference()2489 void MainWindowPresenter::doActionHelpMathQuickReference()
2490 {
2491     QDesktopServices::openUrl(QUrl{"https://math.meta.stackexchange.com/questions/5020/mathjax-basic-tutorial-and-quick-reference"});
2492 }
2493 
doActionHelpReportBug()2494 void MainWindowPresenter::doActionHelpReportBug()
2495 {
2496     QDesktopServices::openUrl(QUrl{"https://github.com/dvorka/mindforger/issues"});
2497 }
2498 
doActionHelpCheckForUpdates()2499 void MainWindowPresenter::doActionHelpCheckForUpdates()
2500 {
2501     QDesktopServices::openUrl(QUrl{"https://github.com/dvorka/mindforger/releases"});
2502 }
2503 
doActionHelpAboutMindForger()2504 void MainWindowPresenter::doActionHelpAboutMindForger()
2505 {
2506     // IMPROVE move this to view: remove this method and route signal to MainWindowView
2507     QMessageBox::about(
2508         &view,
2509         QString{tr("About MindForger")},
2510         QString{
2511             "<b>MindForger " MINDFORGER_VERSION "</b>"
2512 #ifdef DO_MF_DEBUG
2513             "&nbsp;&nbsp;&nbsp;&nbsp; Qt " QT_VERSION_STR
2514 #endif
2515             "<br>"
2516             "<br>Personal thinking notebook."
2517             "<br>"
2518             "<br>MindForger is licensed under the <a href='https://www.gnu.org/licenses/gpl-2.0.html'>GNU GPLv2</a> or later. "
2519             "See also <a href='https://github.com/dvorka/mindforger/licenses'>licenses</a> directory "
2520             "for 3rd party content licensing."
2521             "<br>"
2522             "<br>MindForger is developed as a free and open source project on <a href='https://github.com/dvorka/mindforger'>github.com/dvorka/mindforger</a>"
2523             "<br>"
2524             "<br>MindForger is built with passion for my personal pleasure."
2525             "<br>"
2526             "<br>Contact me at <a href='mailto:martin.dvorak@mindforger.com'>&lt;martin.dvorak@mindforger.com&gt;</a>"
2527             " or see <a href='https://www.mindforger.com'>www.mindforger.com</a> for more information."
2528             "<br>"
2529             "<br>Copyright (C) 2016-2020 <a href='http://me.mindforger.com'>Martin Dvorak</a> and <a href='https://github.com/dvorka/mindforger/blob/master/CREDITS.md'>contributors</a>."
2530         });
2531 }
2532 
2533 } // m8r namespace
2534