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