1 /*
2  main.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 <iostream>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #ifndef _WIN32
23 #  include <getopt.h>
24 #else
25 #  include "../../deps/getopt/getopt.h"
26 #endif // _WIN32
27 #include <QtWidgets>
28 
29 #include "../../lib/src/version.h"
30 #include "../../lib/src/representations/markdown/markdown_configuration_representation.h"
31 
32 #include "gear/qutils.h"
33 #include "i18nl10n.h"
34 #include "main_window_view.h"
35 #include "main_window_presenter.h"
36 #include "look_n_feel.h"
37 
38 using namespace std;
39 
40 /**
41  * @brief MindForger command line interface.
42  */
43 
44 /* MindForger command line interface description.
45  *
46  * GUI:
47  *
48  * $ mindforger
49  *   ... lookup repository as follows
50  *     1. configured in ~/.mindforger,
51  *     2. specified by environment variable MINDFORGER_REPOSITORY,
52  *     3. check existence of MindForger repository in default location i.e. ~/mindforger-repository
53  *     4. create new MindForger repository in default location i.e. ~/mindforger-repository
54  * $ mindforger ~/my-mf-repository
55  *   ... MindForger repository
56  * $ mindforger ~/books/marathon-training
57  *   ... directory structure w/ Markdowns
58  * $ mindforger ~/my-mf-repository/memory/plans.md
59  *   ... Markdown-based MindForger DSL file
60  * $ mindforger ~/books/marathon-training/07-lsd.md
61  *   ... Markdown file
62  *
63  *
64  *
65  * Options proposal:
66  *
67  * $ mindforger --theme dark
68  *   -t
69  * $ mindforger --config forget=25%
70  * $ mindforger --config editor-show-syntax-highlighting=true
71  *   -c
72  * $ mindforger --generate-toc my.md
73  *   -T
74  * $ mindforger --strip-metadata my.md
75  *   -S
76  * $ mindforger --shell
77  *   -s
78  * $ mindforger --version
79  *   -V
80  * $ mindforger --help
81  *   -h
82  *
83  *
84  *
85  * Terminal CLI commands proposal:
86  *
87  * $ mindforger --command LIST outlines
88  * $ mindforger -C LIST outlines
89  *
90  * $ mindforger -C "LIST outlines"
91  * $ mindforger -C "FIND outline 'abc'"
92  * $ mindforger -C "FIND outline 'a''c'"
93  * $ mindforger -C "VIEW outline 'abc'"
94  * $ mindforger -C "EDIT outline 'abc'"
95  * $ mindforger -C "LIST associations OF outline 'abc'"
96  *
97  * $ mindforger -C "LIST notes"
98  * $ mindforger -C "FIND note 'abc'"
99  * $ mindforger -C "VIEW note 'abc'.'efg'"
100  * $ mindforger -C "EDIT note 'abc'.'efg'"
101  * $ mindforger -C "LIST notes OF outline 'abc'"
102  * $ mindforger -C "LIST associations OF note 'abc'.'efg'"
103  *
104  * $ mindforger -C "FTS 'expr'"
105  * $ mindforger -C "FTS 'expr' SCOPE outline 'abc'"
106  * $ mindforger -C "FTS 'expr' SCOPE note 'abc'.'efg'"
107  */
main(int argc,char * argv[])108 int main(int argc, char* argv[])
109 {
110     // check whether running in GUI (and not in text console tty)
111 #if not defined(__APPLE__) && not defined(_WIN32)
112     char* term = getenv(m8r::ENV_VAR_DISPLAY);
113     if(!term || !strlen(term)) {
114         cerr << endl
115              << QCoreApplication::translate(
116                     "main",
117                     "MindForger CANNOT be run from text console - set DISPLAY environment variable or run MindForger from GUI.").toUtf8().constData()
118              << endl;
119         exit(1);
120     }
121     // default terminal macOS environment: TERM=xterm-256color DISPLAY=
122 #endif
123 
124     // stupid & ugly reused code as macOS requires to pass --disable-web-security parameter to QApplication
125     // so that it allows loading of images by QWebEngine
126 #if defined(__APPLE__) || defined(_WIN32)
127     char ARG_DISABLE_WEB_SECURITY[] = "--disable-web-security";
128     int newArgc = argc + 1 + 1;
129     char** newArgv = new char*[static_cast<size_t>(newArgc)];
130     // IMPROVE new version of Chrome/QWebEngine may require --user-data-dir
131     // --disable-web-security must go first, other parameters next
132     newArgv[0] = argv[0];
133     newArgv[1] = ARG_DISABLE_WEB_SECURITY;
134     for(int i=2; i<newArgc-1; i++) {
135         newArgv[i] = argv[i-1];
136     }
137     newArgv[newArgc-1] = nullptr;
138 
139 #ifdef DO_MF_DEBUG
140     MF_DEBUG("argv: " << newArgc << endl);
141     for(int i=0; i<newArgc; i++) {
142         if(newArgv[i] == nullptr) {
143             MF_DEBUG("  " << i << " NULL" << endl);
144             break;
145         }
146         MF_DEBUG("  " << i << " " << newArgv[i] << endl);
147     }
148 #endif
149 
150     QApplication mindforgerApplication(newArgc, newArgv);
151 #else
152     QApplication mindforgerApplication(argc, argv);
153 #endif
154 
155 #ifdef MF_DEBUG_QRC
156     QDirIterator it(":", QDirIterator::Subdirectories);
157     while (it.hasNext()) {
158         MF_DEBUG(it.next() << endl);
159     }
160 #endif
161     QApplication::setApplicationName("MindForger");
162     QApplication::setApplicationVersion(MINDFORGER_VERSION);
163     mindforgerApplication.setWindowIcon(QIcon(":/icons/mindforger-icon.png"));
164 
165     std::string useRepository{};
166     QString themeOptionValue{};
167     QString configurationFilePath{};
168     if(argc > 1) {
169         QCommandLineParser parser;
170         // process command line as parameters/options are present
171         parser.setApplicationDescription("Thinking notebook.");
172         parser.addPositionalArgument("[<directory>|<file>]", QCoreApplication::translate("main", "MindForger repository or directory/file with Markdown(s) to open"));
173         QCommandLineOption themeOption(QStringList() << "t" << "theme",
174                 QCoreApplication::translate("main", "Use 'dark', 'light' or other GUI <theme>."),
175                 QCoreApplication::translate("main", "theme"));
176         parser.addOption(themeOption);
177         QCommandLineOption configPathOption(QStringList() << "c" << "config-file-path",
178                 QCoreApplication::translate("main", "Load configuration from given <file>."),
179                 QCoreApplication::translate("main", "file"));
180         parser.addOption(configPathOption);
181 #if defined(__APPLE__) || defined(_WIN32)
182         QCommandLineOption macosDisableSecurityOption(QStringList() << "S" << "disable-web-security",
183                 QCoreApplication::translate("main", "Disable WebEngine security to allow loading of images on macOS."));
184         parser.addOption(macosDisableSecurityOption);
185 #endif
186         QCommandLineOption versionOption=parser.addVersionOption();
187         QCommandLineOption helpOption=parser.addHelpOption();
188         // process the actual command line arguments given by the user
189         parser.process(mindforgerApplication);
190 
191         if(parser.isSet(helpOption) || parser.isSet(versionOption)) {
192             return 0;
193         }
194 
195         QStringList arguments = parser.positionalArguments();
196 
197         if(arguments.size()==1) {
198             useRepository.assign(arguments[0].toStdString());
199         } else if(arguments.size()>1) {
200             // TODO i18n
201             cerr << "Error: Too many arguments (" << dec << arguments.size() << ") - at most one directory or file can be specified" << endl;
202             return 1;
203         }
204 
205         if(parser.isSet(themeOption)) {
206             themeOptionValue = parser.value(themeOption);
207         }
208 
209         if(parser.isSet(configPathOption)) {
210             configurationFilePath = parser.value(configPathOption);
211         }
212     }
213     // else there are no parameters and options > simply load GUI
214 
215     // load configuration
216     m8r::MarkdownConfigurationRepresentation mdConfigRepresentation{};
217     m8r::Configuration& config = m8r::Configuration::getInstance();
218     if(configurationFilePath.size()) {
219         config.setConfigFilePath(configurationFilePath.toStdString());
220     }
221     if(!mdConfigRepresentation.load(config)) {
222         mdConfigRepresentation.save(m8r::File{config.getConfigFilePath()});
223     }
224 
225     // l10n is initalized after conf so that it can be configured by MF
226     m8r::l10n(mindforgerApplication);
227 
228     m8r::initRandomizer();
229 
230     if(!useRepository.empty()) {
231         m8r::Repository* r = m8r::RepositoryIndexer::getRepositoryForPath(useRepository);
232         if(r) {
233             config.setActiveRepository(config.addRepository(r));
234         } else {
235             if(config.createEmptyMarkdownFile(useRepository)) {
236                 r = m8r::RepositoryIndexer::getRepositoryForPath(useRepository);
237                 config.setActiveRepository(config.addRepository(r));
238             } else {
239                 cerr << QCoreApplication::translate("main", "Error: Unable to find given repository/file to open - open MindForger without parameters and create it from menu Mind/New: '").toUtf8().constData()
240                      << useRepository
241                      << "'"
242                      << endl;
243                 exit(1);
244             }
245         }
246     } else {
247         config.findOrCreateDefaultRepository();
248     }
249 
250     // choose L&F
251     m8r::LookAndFeels& lookAndFeels = m8r::LookAndFeels::getInstance();
252     lookAndFeels.init(&mindforgerApplication);
253     lookAndFeels.setFontPointSize(config.getUiFontPointSize());
254     if(!themeOptionValue.isEmpty()) {
255         if(lookAndFeels.isThemeNameValid(themeOptionValue)) {
256             lookAndFeels.setTheme(themeOptionValue);
257         } else {
258             cerr << QCoreApplication::translate("main", "Ignoring unknown GUI theme: '").toUtf8().constData()
259                  << themeOptionValue.toUtf8().constData()
260                  << "'\n";
261             lookAndFeels.setTheme(QString::fromStdString(config.getUiThemeName()));
262         }
263     } else {
264         lookAndFeels.setTheme(QString::fromStdString(config.getUiThemeName()));
265     }
266 
267     // initialize and start UI
268     m8r::MainWindowView mainWindowView(lookAndFeels);
269     m8r::MainWindowPresenter mainWindowPresenter(mainWindowView);
270     mainWindowView.showMaximized();
271     mindforgerApplication.font().setPointSize(config.getUiFontPointSize());
272     mainWindowPresenter.showInitialView();
273 
274     // run application
275     return mindforgerApplication.exec();
276 }
277