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 " 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'><martin.dvorak@mindforger.com></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