WSL/SLF GitLab Repository

PreviewWindow.cc 55.6 KB
Newer Older
1
2
3
4
5
/*****************************************************************************/
/*  Copyright 2019 WSL Institute for Snow and Avalanche Research  SLF-DAVOS  */
/*****************************************************************************/
/* This file is part of INIshell.
   INIshell is free software: you can redistribute it and/or modify
6
   it under the terms of the GNU General Public License as published by
7
8
9
10
11
12
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   INIshell is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
   GNU General Public License for more details.
14

15
16
   You should have received a copy of the GNU General Public License
   along with INIshell.  If not, see <http://www.gnu.org/licenses/>.
17
18
*/

19
#include "PreviewWindow.h"
20
#include "src/gui_elements/Atomic.h"
21
#include "src/main/colors.h"
22
#include "src/main/common.h"
23
#include "src/main/os.h"
24
#include "src/main/dimensions.h"
25
#include "src/main/inishell.h"
26
#include "src/main/settings.h"
27

28
#include <QClipboard>
29
#include <QCoreApplication>
30
31
#include <QFile>
#include <QFileDialog>
32
#include <QFileInfo>
33
34
#include <QFont>
#include <QFontDatabase>
35
#include <QGuiApplication>
36
#include <QKeySequence>
37
#include <QMenuBar>
38
39
#include <QMessageBox>
#include <QStatusBar>
40
#include <QStringList>
Michael Reisecker's avatar
Michael Reisecker committed
41
#include <QTimer>
42
#include <QHostInfo>
43

44
45
#include <vector>

46
47
48
#ifdef DEBUG
	#include <iostream>
#endif //def DEBUG
49

50
51
52
53
54
55
56
57
58
59
60
61
62
/**
 * @class KeyPressFilter
 * @brief Key press event listener for the preview text editors.
 * @param[in] object Object the event stems from (the Text Editor).
 * @param[in] event The type of event.
 * @return True if the event was accepted.
 */
bool EditorKeyPressFilter::eventFilter(QObject *object, QEvent *event)
{
	if (event->type() == QEvent::KeyPress) {
		auto *key_event = static_cast<QKeyEvent *>(event);
		if (keyToSequence(key_event) == QKeySequence::Cut) {
			//cut whole line on empty selection:
63
			auto *current_editor( static_cast<QPlainTextEdit *>(object) );
64
65
			current_editor->moveCursor(QTextCursor::StartOfLine);
			current_editor->moveCursor(QTextCursor::Down, QTextCursor::KeepAnchor);
66
67
			current_editor->cut();
			//our convention: cut the line feed, but don't paste it back
68
			//(convenient shortcut combination to move keys around)
69
70
			QClipboard *clipboard = QGuiApplication::clipboard();
			QString clip_text( clipboard->text() );
71
			clip_text.chop(1); //remove trailing newline
72
73
74
75
76
77
78
79
			clipboard->setText(clip_text);
			event->accept();
			return true;
		}
	}
	return QObject::eventFilter(object, event); //pass to actual event of the object
}

80
81
82
83
84
/**
 * @class SyntaxHighlighter
 * @brief Default constructor for a syntax highlighter used in the INI preview.
 * @param[in] textdoc Text document to handle syntax highlighting for.
 */
85
86
87
88
SyntaxHighlighter::SyntaxHighlighter(QTextDocument *textdoc) : QSyntaxHighlighter(textdoc)
{
	HighlightingRule rule;

89
90
91
92
93
94
95
	/*
	 * The SyntaxHighlighter parses a document for INI file syntax and checks the sections
	 * and keys against the curently loaded XML. Sections and keys that are not present
	 * in the XML are highlighted differently.
	 */

	/* unknown sections */
96
	QTextCharFormat format_section;
97
	format_section.setForeground(colors::getQColor("syntax_unknown_section"));
98
	format_section.setFontWeight(QFont::Bold);
99
	rule.pattern = QRegularExpression(R"(.*\)" + Cst::section_open + R"(.*\)" + Cst::section_close + R"(.*)");
100
101
102
	rule.format = format_section;
	rules_.append(rule);

103
	/* unknown keys */
104
105
	QTextCharFormat format_unknown_key;
	format_unknown_key.setForeground(colors::getQColor("syntax_unknown_key"));
Michael Reisecker's avatar
Michael Reisecker committed
106
107
	rule.pattern = QRegularExpression(R"(^\s*[\w|:|*]+(?=\s*=))"); //TODO: only :: but not :
	//TODO: collect and unify all regex handling (e. g. use the same here as in INIParser)
108
	rule.format = format_unknown_key;
109
110
	rules_.append(rule);

111
	/* INI values */
112
	QTextCharFormat format_value;
113
	format_value.setForeground(colors::getQColor("syntax_value"));
114
115
116
117
	rule.pattern = QRegularExpression(R"((?<=\=).*)");
	rule.format = format_value;
	rules_.append(rule);

118

119
120
121
122
123
124
125
	/* populate highlighter with known sections and keys */
	QTextCharFormat format_known_key;
	format_known_key.setForeground(colors::getQColor("syntax_known_key"));
	QTextCharFormat format_known_section;
	format_known_section.setForeground(colors::getQColor("syntax_known_section"));
	format_known_section.setFontWeight(QFont::Bold);

126
	const QList<Atomic *> panel_list( getMainWindow()->findChildren<Atomic *>() );
127
	for (auto &panel : panel_list) {
Michael Reisecker's avatar
Michael Reisecker committed
128
		if (panel->property("no_ini").toBool()) //e. g. Groups / frames
129
			continue;
130
		QString section, key;
131
		(void) panel->getIniValue(section, key);
132
		key.replace("*", "\\*");
133
		rule.pattern = QRegularExpression("\\" + Cst::section_open + section + "\\" +
134
		    Cst::section_close, QRegularExpression::CaseInsensitiveOption); //TODO: escape only if needed for the set char
135
136
137
138
139
140
141
		rule.format = format_known_section;
		rules_.append(rule);
		rule.pattern = QRegularExpression(R"(^\s*)" + key + R"((=|\s))", QRegularExpression::CaseInsensitiveOption);
		rule.format = format_known_key;
		rules_.append(rule);
	}

142
143
144
145
146
147
148
149
150
	/* comments */
	QTextCharFormat format_block_comment;
	format_block_comment.setForeground(colors::getQColor("syntax_comment"));
	rule.pattern = QRegularExpression(R"(^\s*[#;].*)");
	rule.format = format_block_comment;
	rules_.append(rule);
	rule.pattern = QRegularExpression(R"(([#;](?!$).*))");
	rules_.append(rule);

Mathias Bavay's avatar
Mathias Bavay committed
151
152
153
154
155
156
157
	/* coordinates */
	QTextCharFormat format_coordinate;
	format_coordinate.setForeground(colors::getQColor("coordinate"));
 	rule.pattern = QRegularExpression(R"((latlon|xy)\s*\([-\d\s\.,;]+\))");
	rule.format = format_coordinate;
	rules_.append(rule);
	
158
	/* equals sign */
159
160
161
162
	QTextCharFormat format_equal;
	format_equal.setForeground(Qt::black);
	rule.pattern = QRegularExpression(R"(=)");
	rule.format = format_equal;
163
164
165
	rules_.append(rule);
}

166
167
168
169
/**
 * @brief Send a chunk of text through the syntax highlighter.
 * @param[in] text The text to syntax-highlight.
 */
170
171
172
void SyntaxHighlighter::highlightBlock(const QString &text)
{
	for (const HighlightingRule &rule : qAsConst(rules_)) {
173
		QRegularExpressionMatchIterator mit( rule.pattern.globalMatch(text) );
174
		while (mit.hasNext()) { //run trough regex matches and set the stored formats
175
176
177
178
179
180
			QRegularExpressionMatch match = mit.next();
			setFormat(match.capturedStart(), match.capturedLength(), rule.format);
		}
	}
}

181
182
183
184
185
186
/**
 * @class PreviewWindow
 * @brief Default constructor for the PreviewWindow.
 * @details This constructor creates a tab bar to show multiple INI file versions in.
 * @param [in] parent The PreviewWindow's parent window (the main window).
 */
187
188
PreviewWindow::PreviewWindow(QMainWindow *parent) : QMainWindow(parent),
    preview_ini_(getMainWindow()->getLogger())
189
{
190
	editor_key_filter_ = new EditorKeyPressFilter;
191
	this->setUnifiedTitleAndToolBarOnMac(true);
192
	file_tabs_ = new QTabWidget;
193
	connect(file_tabs_, &QTabWidget::tabCloseRequested, this, &PreviewWindow::closeTab);
194
	file_tabs_->setTabsClosable(true);
195
196
197
	this->setCentralWidget(file_tabs_);
	createMenu();

198
	//size this window (from "outside" via a timer to have it work on macOS; works without for the logger?):
Michael Reisecker's avatar
Michael Reisecker committed
199
	QTimer::singleShot(1, [=]{ dim::setDimensions(this, dim::PREVIEW); });
200
	this->setWindowTitle(tr("Preview") + " ~ " + QCoreApplication::applicationName());
201
	createFindBar(); //do last to keep status bar hidden
202
	statusBar()->hide();
203
204
}

205
206
207
208
209
210
211
212
/**
 * @brief Destructor with minimal cleanup.
 */
PreviewWindow::~PreviewWindow()
{
	delete editor_key_filter_;
}

213
214
215
/**
 * @brief Display the current INI file in a new tab.
 */
216
217
void PreviewWindow::addIniTab()
{
218
	/* get currently set INI values */
219
220
	QString ini_contents;
	QTextStream ss(&ini_contents);
221
	loadIniWithGui(); //extend original file's INIParser with GUI values
222

223
	/* text box for the current INI */
224
	const bool setMonospace = (getSetting("user::preview::mono_font", "value") == "TRUE");
225
	auto *preview_editor( new PreviewEdit(setMonospace) );
226
	preview_editor->installEventFilter(editor_key_filter_);
227
	preview_editor->setStyleSheet("QPlainTextEdit {background-color: " + colors::getQColor("syntax_background").name() + "; color: " + colors::getQColor("syntax_invalid").name() + "}");
228

229
230
	highlighter_ = new SyntaxHighlighter(preview_editor->document());

231
	/* display the current GUI's contents */
232
	preview_ini_.outputIni(ss);
233
	if (ini_contents.isEmpty()) {
234
235
		ini_contents = tr("#Empty INI file\n");
		previewStatus(tr("Open an application and load an INI file to view contents"));
236
237
238
	} else {
		previewStatus(QString());
		this->statusBar()->hide();
239
	}
240
	preview_editor->setPlainText(ini_contents); //without undo history --> can't undo to empty
241
	QFileInfo file_info(getMainWindow()->getIni()->getFilename());
242
	QString file_name(file_info.fileName());
243
244
245
	QString file_path;
	if (file_info.exists()) //avoid warning
		file_path = file_info.absolutePath();
246
	if (file_name.isEmpty()) { //pick file name if no INI is opened yet
247
		file_name = "io_new.ini";
248
249
250
251
252
253
	} else {
		INIParser gui_ini = getMainWindow()->getIniCopy();
		(void) getMainWindow()->getControlPanel()->setIniValuesFromGui(&gui_ini);
		if (getMainWindow()->getIniCopy() != gui_ini)
			file_name += " *"; //asterisk for "not saved yet", unless it's only the info text
	}
254
255
256
257
	if (file_path.isEmpty())
		file_path = QDir::currentPath();
	file_tabs_->addTab(preview_editor, file_name);
	file_tabs_->setTabToolTip(file_tabs_->count() - 1, file_path);
258
	file_tabs_->setCurrentIndex(file_tabs_->count() - 1); //switch to new tab
259
	connect(preview_editor, &QPlainTextEdit::textChanged, this, [=]{ textChanged(file_tabs_->count() - 1); });
260
261
262
263

	onShowWhitespacesMenuClick((getSetting("user::preview::show_ws", "value") == "TRUE"));

} //TODO: tab completion for INI keys
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279

/**
 * @brief Event listener for when the window is being closed.
 * @details This function allows the user to cancel the closing if there are unsaved
 * changes in the opened INI files.
 * @param[in] event The close event.
 */
void PreviewWindow::closeEvent(QCloseEvent *event)
{
	bool has_unsaved_changes = false; //run through tabs and check for asterisks
	for (int ii = 0; ii < file_tabs_->count(); ++ii) {
		if (file_tabs_->tabText(ii).endsWith("*")) {
			has_unsaved_changes = true;
			break;
		}
	}
280
	if (has_unsaved_changes &&
281
	    getSetting("user::inireader::warn_unsaved_ini", "value") == "TRUE") { //at least one tab has unsaved changes
282
283
284
285
286
287
288
		const int cancel = warnOnUnsavedIni();
		if (cancel == QMessageBox::Cancel) {
			event->ignore();
			return;
		}
	}
	event->accept();
289
290
}

291
/**
292
293
 * @brief Event listener for key presses.
 * @details Close the Preview, add tab, or show the logger.
294
295
296
297
 * @param event The key press event that is received.
 */
void PreviewWindow::keyPressEvent(QKeyEvent *event)
{
298
	if (event->key() == Qt::Key_Escape) {
299
		hideFindBar();
300
301
302
303
304
305
	} else if (keyToSequence(event) == QKeySequence::Print) {
		addIniTab();
	} else if (event->modifiers() == Qt::CTRL && event->key() == Qt::Key_L) {
		getMainWindow()->getLogger()->show();
		getMainWindow()->getLogger()->raise();
	}
306
}
307

308
309
310
311
/**
 * @brief Close an INI file's tab.
 * @param[in] index Index of the tab that is being closed.
 */
312
313
void PreviewWindow::closeTab(int index)
{
314
	//Check for unsaved changes. Note that changes that cancel out leaving the INI file
315
	//unaltered will still trigger the warning (unlike the GUI).
316
	if (file_tabs_->tabText(index).endsWith("*") &&
317
	    getSetting("user::inireader::warn_unsaved_ini", "value") == "TRUE") { //not saved yet
318
319
320
321
322
323
		const int cancel = warnOnUnsavedIni();
		if (cancel == QMessageBox::Cancel)
			return;
	}

	file_tabs_->removeTab(index);
324
325
326
327
	if (file_tabs_->count() == 0)
		this->close();
}

328
329
330
/**
 * @brief Create the PreviewWindow's menu.
 */
331
332
void PreviewWindow::createMenu()
{
333
	/* FILE menu */
334
	QMenu *menu_file = this->menuBar()->addMenu(tr("&File"));
335
	auto *file_save = new QAction(getIcon("document-save"), tr("&Save"), menu_file);
336
	file_save->setShortcut( QKeySequence::Save );
337
338
	menu_file->addAction(file_save);
	connect(file_save, &QAction::triggered, this, &PreviewWindow::saveFile);
339
	auto *file_save_as = new QAction(getIcon("document-save-as"), tr("Save &as..."), menu_file);
340
	file_save_as->setShortcut( QKeySequence::SaveAs );
341
342
	menu_file->addAction(file_save_as);
	connect(file_save_as, &QAction::triggered, this, &PreviewWindow::saveFileAs);
343
	menu_file->addSeparator();
344
345
346
347
348
349
	file_save_and_load_ = new QAction(tr("Save and load into GUI"), menu_file);
	menu_file->addAction(file_save_and_load_);
	connect(file_save_and_load_, &QAction::triggered, this, &PreviewWindow::saveFileAndLoadIntoGui);
	file_load_ = new QAction(tr("Load into GUI"), menu_file);
	menu_file->addAction(file_load_);
	connect(file_load_, &QAction::triggered, this, &PreviewWindow::loadIntoGui);
350
351
352
	menu_file->addSeparator();
	auto *file_backup = new QAction(tr("Quicksave backup"), menu_file);
	menu_file->addAction(file_backup);
353
	file_backup->setShortcut(Qt::CTRL + Qt::ALT + Qt::Key_B);
354
	connect(file_backup, &QAction::triggered, this, &PreviewWindow::quickBackup);
355

356
	/* EDIT menu */
357
	QMenu *menu_edit = this->menuBar()->addMenu(tr("&Edit"));
Mathias Bavay's avatar
Mathias Bavay committed
358
	auto *edit_undo = new QAction(getIcon("edit-undo"), tr("Undo"), menu_edit);
359
360
361
	menu_edit->addAction(edit_undo);
	edit_undo->setShortcut(QKeySequence::Undo);
	connect(edit_undo, &QAction::triggered, this, [=]{ getCurrentEditor()->undo(); });
Mathias Bavay's avatar
Mathias Bavay committed
362
	auto *edit_redo = new QAction(getIcon("edit-redo"), tr("Redo"), menu_edit);
363
364
365
366
	menu_edit->addAction(edit_redo);
	edit_redo->setShortcut(QKeySequence::Redo);
	connect(edit_redo, &QAction::triggered, this, [=]{ getCurrentEditor()->redo(); });
	menu_edit->addSeparator();
Mathias Bavay's avatar
Mathias Bavay committed
367
	auto *edit_cut = new QAction(getIcon("edit-cut"), tr("Cut"), menu_edit);
368
369
370
	menu_edit->addAction(edit_cut);
	edit_cut->setShortcut(QKeySequence::Cut);
	connect(edit_cut, &QAction::triggered, this, [=]{ getCurrentEditor()->cut(); });
Mathias Bavay's avatar
Mathias Bavay committed
371
	auto *edit_copy = new QAction(getIcon("edit-copy"), tr("Copy"), menu_edit);
372
373
374
	menu_edit->addAction(edit_copy);
	edit_copy->setShortcut(QKeySequence::Copy);
	connect(edit_copy, &QAction::triggered, this, [=]{ getCurrentEditor()->copy(); });
Mathias Bavay's avatar
Mathias Bavay committed
375
	auto *edit_paste = new QAction(getIcon("edit-paste"), tr("Paste"), menu_edit);
376
377
378
	menu_edit->addAction(edit_paste);
	edit_paste->setShortcut(QKeySequence::Paste);
	connect(edit_paste, &QAction::triggered, this, [=]{ getCurrentEditor()->paste(); });
379
380
	auto *edit_paste_newline = new QAction(tr("Paste to new line"), menu_edit);
	menu_edit->addAction(edit_paste_newline);
381
382
	edit_paste_newline->setShortcut("Alt+" +
	    QKeySequence(QKeySequence::Paste).toString(QKeySequence::NativeText));
383
	connect(edit_paste_newline, &QAction::triggered, this, [=]{ pasteToNewline(); });
Mathias Bavay's avatar
Mathias Bavay committed
384
	auto *edit_select_all = new QAction(getIcon("edit-select-all"), tr("Select all"), menu_edit);
385
386
387
388
389
390
391
392
393
	menu_edit->addAction(edit_select_all);
	edit_select_all->setShortcut(QKeySequence::SelectAll);
	connect(edit_select_all, &QAction::triggered, this, [=]{ getCurrentEditor()->selectAll(); });
	menu_edit->addSeparator();
	auto *edit_find = new QAction(getIcon("edit-find"), tr("&Find text..."), menu_edit);
	menu_edit->addAction(edit_find);
	edit_find->setShortcut(QKeySequence::Find);
	connect(edit_find, &QAction::triggered, this, &PreviewWindow::showFindBar);

394
395
	/* INSERT menu */
	QMenu *menu_insert = this->menuBar()->addMenu(tr("&Insert"));
396
397
398
399
400
401
	auto *edit_insert_header = new QAction(tr("Comment header"), menu_insert);
	menu_insert->addAction(edit_insert_header);
	connect(edit_insert_header, &QAction::triggered, this,
	    [=]{ onInsertMenuClick("insert_header"); });
	menu_insert->addSeparator();
	edit_insert_missing_ = new QAction(tr("Missing keys for GUI"), menu_insert);
402
403
404
	menu_insert->addAction(edit_insert_missing_);
	connect(edit_insert_missing_, &QAction::triggered, this,
	    [=]{ onInsertMenuClick("insert_missing"); });
405
	edit_insert_missing_mandatory_ = new QAction(tr("Mandatory keys for GUI"),
406
407
408
409
410
	     menu_insert);
	menu_insert->addAction(edit_insert_missing_mandatory_);
	connect(edit_insert_missing_mandatory_, &QAction::triggered, this,
	    [=]{ onInsertMenuClick("insert_missing_mandatory"); });

411
	/* TRANSFORM menu */
412
	QMenu *menu_transform = this->menuBar()->addMenu(tr("&Transform"));
413
	/* Whitespaces menu */
414
	auto *transform_whitespaces = new QMenu(tr("Whitespaces"), menu_transform);
415
	transform_whitespaces->setIcon( getIcon("markasblank") );
416
	menu_transform->addMenu(transform_whitespaces);
Mathias Bavay's avatar
Mathias Bavay committed
417
	auto *transform_transform_singlews = new QAction(getIcon("unmarkasblank"), tr("To single spaces"), transform_whitespaces);
418
419
420
	connect(transform_transform_singlews, &QAction::triggered, this,
	    [=]{ onTransformMenuClick("transform_whitespace_singlews"); } );
	transform_whitespaces->addAction(transform_transform_singlews);
421
422
	auto *transform_transform_longestws = new QAction(tr("Adapt to longest keys"),
	    transform_whitespaces);
423
424
425
	connect(transform_transform_longestws, &QAction::triggered, this,
	    [=]{ onTransformMenuClick("transform_whitespace_longestws"); } );
	transform_whitespaces->addAction(transform_transform_longestws);
426
	/* Sort */
427
	auto *transform_sort = new QMenu(tr("Sort"), menu_transform);
Mathias Bavay's avatar
Mathias Bavay committed
428
	transform_sort->setIcon( getIcon("view-sort") );
429
430
431
432
433
434
435
436
437
	menu_transform->addMenu(transform_sort);
	auto *transform_sort_alphabetically = new QAction(tr("Alphabetically"), transform_sort);
	connect(transform_sort_alphabetically, &QAction::triggered, this,
	    [=]{ onTransformMenuClick("transform_sort_alphabetically"); } );
	transform_sort->addAction(transform_sort_alphabetically);
	auto *transform_sort_order = new QAction(tr("In order of INI file"), transform_sort);
	connect(transform_sort_order, &QAction::triggered, this,
	    [=]{ onTransformMenuClick("transform_sort_order"); } );
	transform_sort->addAction(transform_sort_order);
438
	/* Capitalization menu */
439
440
	auto *transform_capitalization = new QMenu(tr("Capitalization"), menu_transform);
	menu_transform->addMenu(transform_capitalization);
441
	auto *transform_capitalization_sections_upper = new QAction(
442
443
444
445
	    tr("Sections to upper case"), transform_capitalization);
	connect(transform_capitalization_sections_upper, &QAction::triggered, this,
	    [=]{ onTransformMenuClick("transform_capitalization_sections_upper"); } );
	transform_capitalization->addAction(transform_capitalization_sections_upper);
446
	auto *transform_capitalization_sections_lower = new QAction(
447
448
449
450
	    tr("Sections to lower case"), transform_capitalization);
	connect(transform_capitalization_sections_lower, &QAction::triggered, this,
	    [=]{ onTransformMenuClick("transform_capitalization_sections_lower"); } );
	transform_capitalization->addAction(transform_capitalization_sections_lower);
451
	auto *transform_capitalization_keys_upper = new QAction(
452
453
454
455
	    tr("Keys to upper case"), transform_capitalization);
	connect(transform_capitalization_keys_upper, &QAction::triggered, this,
	    [=]{ onTransformMenuClick("transform_capitalization_keys_upper"); } );
	transform_capitalization->addAction(transform_capitalization_keys_upper);
456
	auto *transform_capitalization_keys_lower = new QAction(
457
458
459
460
	    tr("Keys to lower case"), transform_capitalization);
	connect(transform_capitalization_keys_lower, &QAction::triggered, this,
	    [=]{ onTransformMenuClick("transform_capitalization_keys_lower"); } );
	transform_capitalization->addAction(transform_capitalization_keys_lower);
461
	auto *transform_capitalization_values_upper = new QAction(
462
463
464
465
	    tr("Values to upper case"), transform_capitalization);
	connect(transform_capitalization_values_upper, &QAction::triggered, this,
	    [=]{ onTransformMenuClick("transform_capitalization_values_upper"); } );
	transform_capitalization->addAction(transform_capitalization_values_upper);
466
	auto *transform_capitalization_values_lower = new QAction(
467
468
469
470
471
472
473
	    tr("Values to lower case"), transform_capitalization);
	connect(transform_capitalization_values_lower, &QAction::triggered, this,
	    [=]{ onTransformMenuClick("transform_capitalization_values_lower"); } );
	transform_capitalization->addAction(transform_capitalization_values_lower);
	transform_capitalization->addSeparator();
	auto *transform_capitalization_upper = new QAction(getIcon("format-text-uppercase"),
	    tr("All to upper case"), transform_capitalization);
474
475
476
	connect(transform_capitalization_upper, &QAction::triggered, this,
	    [=]{ onTransformMenuClick("transform_capitalization_upper"); } );
	transform_capitalization->addAction(transform_capitalization_upper);
477
478
	auto *transform_capitalization_lower = new QAction(getIcon("format-text-lowercase"),
	    tr("All to lower case"), transform_capitalization);
479
480
481
	connect(transform_capitalization_lower, &QAction::triggered, this,
	    [=]{ onTransformMenuClick("transform_capitalization_lower"); } );
	transform_capitalization->addAction(transform_capitalization_lower);
482
	/* Comments menu */
483
	auto *transform_comments = new QMenu(tr("Comments"), menu_transform);
484
	transform_comments->setIcon( getIcon("code-context") );
485
	menu_transform->addMenu(transform_comments);
486
487
488
489
490
491
492
493
494
495
	auto *transform_comment_block = new QAction(tr("Comment selection"));
	transform_comments->addAction(transform_comment_block);
	transform_comment_block->setShortcut(Qt::CTRL + Qt::Key_NumberSign);
	connect(transform_comment_block, &QAction::triggered, this,
	    [=]{ onTransformMenuClick("transform_comment_block"); });
	auto *transform_uncomment_block = new QAction(tr("Uncomment selection"));
	transform_comments->addAction(transform_uncomment_block);
	transform_uncomment_block->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_NumberSign);
	connect(transform_uncomment_block, &QAction::triggered, this,
	    [=]{ onTransformMenuClick("transform_uncomment_block"); });
496
	transform_comments->addSeparator();
497
	auto *transform_comments_content = new QAction(tr("Comment all content"),
498
	    transform_comments);
499
500
501
	connect(transform_comments_content, &QAction::triggered, this,
	    [=]{ onTransformMenuClick("transform_comments_content"); } );
	transform_comments->addAction(transform_comments_content);
502
	auto *transform_comments_duplicate = new QAction(tr("Duplicate all to comment"),
503
	    transform_comments);
504
	transform_comments_duplicate->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_B);
505
506
507
	connect(transform_comments_duplicate, &QAction::triggered, this,
	    [=]{ onTransformMenuClick("transform_comments_duplicate"); } );
	transform_comments->addAction(transform_comments_duplicate);
508
509
510
511
512
	auto *transform_comments_move_values = new QAction(tr("Move next to values"),
	    transform_comments);
	connect(transform_comments_move_values, &QAction::triggered, this,
	    [=]{ onTransformMenuClick("transform_comments_move_value"); } );
	transform_comments->addAction(transform_comments_move_values);
513
	auto *transform_comments_move_end = new QAction(tr("Collect at bottom"),
514
	    transform_comments);
515
516
517
	connect(transform_comments_move_end, &QAction::triggered, this,
	    [=]{ onTransformMenuClick("transform_comments_move_end"); } );
	transform_comments->addAction(transform_comments_move_end);
Mathias Bavay's avatar
Mathias Bavay committed
518
	auto *transform_comments_trim = new QAction(getIcon("edit-clear-all"), tr("Trim"), transform_comments);
519
520
521
522
	connect(transform_comments_trim, &QAction::triggered, this,
	    [=]{ onTransformMenuClick("transform_comments_trim"); } );
	transform_comments->addAction(transform_comments_trim);
	auto *transform_comments_delete = new QAction(tr("Delete all"), transform_comments);
523
524
525
	connect(transform_comments_delete, &QAction::triggered, this,
	    [=]{ onTransformMenuClick("transform_comments_delete"); } );
	transform_comments->addAction(transform_comments_delete);
526
	transform_comments->addSeparator();
527
528
529
530
531
532
533
534
535
	auto *transform_comments_numbersign = new QAction(tr("Switch to #"), transform_comments);
	connect(transform_comments_numbersign, &QAction::triggered, this,
	    [=]{ onTransformMenuClick("transform_comments_numbersign"); } );
	transform_comments->addAction(transform_comments_numbersign);
	auto *transform_comments_semicolon = new QAction(tr("Switch to ;"), transform_comments);
	connect(transform_comments_semicolon, &QAction::triggered, this,
	    [=]{ onTransformMenuClick("transform_comments_semicolon"); } );
	transform_comments->addAction(transform_comments_semicolon);
	/* Reset menu */
536
	auto *transform_reset = new QMenu(tr("Reset"), menu_transform);
Mathias Bavay's avatar
Mathias Bavay committed
537
	transform_reset->setIcon( getIcon("view-refresh") );
538
	menu_transform->addMenu(transform_reset);
539
540
	auto *transform_reset_original = new QAction(tr("To original INI on file system"),
	    transform_reset);
541
542
543
	connect(transform_reset_original, &QAction::triggered, this,
	    [=]{ onTransformMenuClick("transform_reset_original"); } );
	transform_reset->addAction(transform_reset_original);
544
545
	transform_reset_full_ = new QAction(tr("To full INI with GUI keys"), transform_reset);
	connect(transform_reset_full_, &QAction::triggered, this,
546
	    [=]{ onTransformMenuClick("transform_reset_full"); } );
547
	transform_reset->addAction(transform_reset_full_);
548

549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
	/* CONVERT menu */
	QMenu *menu_convert = this->menuBar()->addMenu(tr("&Convert"));
	QMenu *menu_convert_tabs = new QMenu("&Tabs", menu_convert);
	menu_convert->addMenu(menu_convert_tabs);
	auto *convert_long_spaces_to_tabs = new QAction(tr("8 spaces to tabs"), menu_convert_tabs);
	connect (convert_long_spaces_to_tabs, &QAction::triggered, this,
	    [=]{ onConvertMenuClick("convert_long_spaces_to_tabs"); } );
	menu_convert_tabs->addAction(convert_long_spaces_to_tabs);
	auto *convert_short_spaces_to_tabs = new QAction(tr("4 spaces to tabs"), menu_convert_tabs);
	connect (convert_short_spaces_to_tabs, &QAction::triggered, this,
	    [=]{ onConvertMenuClick("convert_short_spaces_to_tabs"); } );
	menu_convert_tabs->addAction(convert_short_spaces_to_tabs);
	auto *convert_tabs_to_long_spaces = new QAction(tr("Tabs to 8 spaces"), menu_convert_tabs);
	connect (convert_tabs_to_long_spaces, &QAction::triggered, this,
	    [=]{ onConvertMenuClick("convert_tabs_to_long_spaces"); } );
	menu_convert_tabs->addAction(convert_tabs_to_long_spaces);
	auto *convert_tabs_to_short_spaces = new QAction(tr("Tabs to 4 spaces"), menu_convert_tabs);
	connect (convert_tabs_to_short_spaces, &QAction::triggered, this,
	    [=]{ onConvertMenuClick("convert_tabs_to_short_spaces"); } );
	menu_convert_tabs->addAction(convert_tabs_to_short_spaces);

570
	/* VIEW menu */
571
	QMenu *menu_view = this->menuBar()->addMenu(tr("&View"));
572
573
574
575
576
577
578
579
	auto *view_hidden_chars = new QAction(tr("Show &whitespaces"), menu_view);
	view_hidden_chars->setCheckable(true);
	if (getSetting("user::preview::show_ws", "value") == "TRUE")
		view_hidden_chars->setChecked(true);
	connect (view_hidden_chars, &QAction::triggered, this,
	    [=]{ onShowWhitespacesMenuClick(view_hidden_chars->isChecked()); } );
	menu_view->addAction(view_hidden_chars);
	menu_view->addSeparator();
580
	auto *view_new_tab = new QAction(getIcon("tab-new"), tr("&New tab"), menu_view);
581
	view_new_tab->setShortcut(QKeySequence::AddTab);
582
583
	connect(view_new_tab, &QAction::triggered, this, [=]{ addIniTab(); });
	menu_view->addAction(view_new_tab);
584
	auto *view_close_tab = new QAction(getIcon("tab-close"), tr("&Close tab"), menu_view);
585
586
587
	view_close_tab->setShortcut(QKeySequence::Close);
	connect(view_close_tab, &QAction::triggered, this, [=]{ closeTab(file_tabs_->currentIndex()); });
	menu_view->addAction(view_close_tab);
588
589

	/* HELP menu */
590
#if !defined Q_OS_MAC
591
592
	auto *menu_help_main = new QMenuBar(this->menuBar());
	QMenu *menu_help = menu_help_main->addMenu(tr("&?"));
593
	menu_help_main->addMenu(menu_help);
594
	auto *help = new QAction(getIcon("help-contents"), tr("&Help"), menu_help);
595
	this->menuBar()->setCornerWidget(menu_help_main); //push help menu to the right
596
	menu_help->addAction(help);
597
598
599
600
601
602
603

#else
	auto *menu_help_main = this->menuBar()->addMenu("&?");
	auto *help = new QAction(getIcon("help-contents"), tr("&Help"), menu_help_main);
	menu_help_main->addAction(help);
#endif
	help->setMenuRole(QAction::ApplicationSpecificRole);
604
605
	connect(help, &QAction::triggered, this,
	    [=]{ getMainWindow()->loadHelp("UI of INIshell", "help-preview"); });
606
607
608
}

/**
609
 * @brief Prepare a panel in the status bar that allows users to search for text in the preview.
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
 */
void PreviewWindow::createFindBar()
{
	find_text_ = new QLineEdit(this);
	connect(find_text_, &QLineEdit::textChanged, this, &PreviewWindow::onFindTextChanged);
	close_find_bar_ = new QToolButton(this);
	close_find_bar_->setIcon(getIcon("window-close"));
	close_find_bar_->setAutoRaise(true);
	connect(close_find_bar_, &QToolButton::clicked, this, [&]{ hideFindBar(); });
	this->statusBar()->addWidget(find_text_, 1);
	this->statusBar()->addWidget(close_find_bar_);
	hideFindBar();
}

/**
 * @brief Show the find bar.
 */
void PreviewWindow::showFindBar()
{
	previewStatus(QString( ));
	statusBar()->show();
	find_text_->show();
	close_find_bar_->show();
	find_text_->setFocus();
	find_text_->selectAll();
}

/**
 * @brief Hide the find bar.
 */
void PreviewWindow::hideFindBar()

{
	find_text_->hide();
	close_find_bar_->hide();
	statusBar()->hide();
646
647
}

648
649
650
651
652
653
654
655
656
657
658
659
660
661
/**
 * @brief Delegated event listener for when the preview text changes.
 * @details A lambda calls this function with the text box index to add an asterisk
 * to the title ("not saved").
 * @param[in] index Index of the tab the text changed in.
 */
void PreviewWindow::textChanged(const int &index)
{
	const QString file_name(file_tabs_->tabText(index));
	if (!file_name.endsWith("*"))
		file_tabs_->setTabText(index, file_name + " *");

}

662
663
664
665
666
667
/**
 * @brief Combine the original file's INIParser with GUI values and
 * set the local copy to access both.
 */
void PreviewWindow::loadIniWithGui()
{
668
	const QString current_app( getMainWindow()->getCurrentApplication() );
669
	preview_ini_ = getMainWindow()->getIniCopy();
670
671
	preview_ini_.clear(true); //only keep unknown keys (which are transported from input to output)

672
673
	getMainWindow()->getControlPanel()->setIniValuesFromGui(&preview_ini_);
	file_save_and_load_->setText("Save and load into " + getMainWindow()->getCurrentApplication());
674
	file_load_->setText(tr("Load into ") + current_app);
675
676
677
	edit_insert_missing_->setText(tr("Missing keys for ") + current_app);
	edit_insert_missing_mandatory_->setText(tr("Mandatory keys for ") + current_app);
	transform_reset_full_->setText(tr("To full INI with %1 keys").arg(current_app));
678
679
}

680
681
682
683
684
685
686
687
/**
 * @brief Write an opened INI file to the file system.
 * @param[in] file_name The path to save to.
 */
void PreviewWindow::writeIniToFile(const QString &file_name)
{
	QFile outfile(file_name);
	if (!outfile.open(QIODevice::WriteOnly)) {
688
		previewStatus(tr("Could not open %1").arg(QDir::toNativeSeparators(file_name)));
689
690
691
		return;
	}
	QTextStream ss(&outfile);
692
	const auto text_box( getCurrentEditor() );
693
694
695
696
	if (text_box)
		ss << text_box->toPlainText();
	outfile.close();

697
	const QFileInfo finfo(file_name); //switch the displayed name to new file (without asterisk)
698
	const QString shown_name(finfo.fileName());
699
	file_tabs_->setTabText(file_tabs_->currentIndex(), shown_name);
700
	previewStatus(tr("Saved to ") + file_name);
701
702

	setSetting("auto::history::last_preview_write", "path", finfo.absoluteDir().path());
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
}

/**
 * @brief Ask a user if they want to cancel an action.
 * @details This function is called before closing INI tabs with unsaved changes.
 * @return Button the user has clicked.
 */
int PreviewWindow::warnOnUnsavedIni()
{
	QMessageBox msgNotSaved;
	msgNotSaved.setWindowTitle("Warning ~ " + QCoreApplication::applicationName());
	msgNotSaved.setText(tr("<b>INI file not saved yet.</b>"));
	msgNotSaved.setInformativeText(tr("Your INI file(s) may contain unsaved changes."));
	msgNotSaved.setIcon(QMessageBox::Warning);
	msgNotSaved.setStandardButtons(QMessageBox::Cancel | QMessageBox::Discard);
	msgNotSaved.setDefaultButton(QMessageBox::Cancel);
	int clicked = msgNotSaved.exec();
	return clicked;
}

723
724
725
726
727
/**
 * @brief Show a temporary status message in the preview window. This text is volatile.
 * @param[in] text Text to show in the status.
 */
void PreviewWindow::previewStatus(const QString &text)
728
{ //temporary message - erased for e. g. on menu hover
729
730
	statusBar()->showMessage(text);
	statusBar()->show(); //might be hidden from closing the find bar
731
	statusBar()->setToolTip(text);
732
733
}

734
/**
735
736
 * @brief Helper function to get the current tab's text editor.
 * @return The text editor currently in view.
737
 */
738
PreviewEdit * PreviewWindow::getCurrentEditor()
739
{
740
#ifdef DEBUG
741
742
	if (!qobject_cast<PreviewEdit *>(file_tabs_->currentWidget()))
		qDebug() << "PreviewWindow::getCurrentEditor() should have casted to a PreviewEdit but did not";
743
#endif //def DEBUG
744
	return qobject_cast<PreviewEdit *>(file_tabs_->currentWidget());
745
746
747
748
749
750
751
}

/**
 * @brief Helper function to retrieve the currently displayed file's name.
 * @return The current INI file's path.
 */
QString PreviewWindow::getCurrentFilename() const
752
{ //TODO: Store the filename properly without asterisk shuffling
753
	QString shown_name( file_tabs_->tabText(file_tabs_->currentIndex()) );
754
	if (shown_name.endsWith("*")) //remove "unsaved" asterisk for the file name
755
		shown_name.chop(2);
756
757
758
	return file_tabs_->tabToolTip(file_tabs_->currentIndex()) + "/" + shown_name;
}

759
760
761
762
763
764
765
/**
 * @brief Set an editor's text programmatically and add it to the undo history.
 * @details This is a small hack and inserts the text by selecting all and pasting;
 * otherwise this change is not remembered in the document's history.
 * @param[in] editor Editor to set text for.
 * @param[in] text Text to set.
 */
766
void PreviewWindow::setTextWithHistory(QPlainTextEdit *editor, const QString &text)
767
768
769
770
771
772
773
{
	QTextDocument *doc( editor->document() );
	QTextCursor cursor( doc );
	cursor.select(QTextCursor::Document);
	cursor.insertText(text);
}

774
775
776
777
/**
 * @brief Insert missing keys.
 * @details This function looks through the keys in the GUI and adds the
 * ones that are missing.
778
 * @param[in] mode Insert mode (fill missing, fill missing mandatory).
779
 */
780
void PreviewWindow::insertText(const insert_text &mode)
781
{
782
	if (mode == HEADER) {
783
		static const QString marker("############################################################");
784
785
		const QString year( QDate::currentDate().toString("yyyy") );
		const QString date( QDate::currentDate().toString("yyyy-MM-dd") );
786
		const QString username( os::getLogName() );
787
		const QString user_domain( QHostInfo::localDomainName() );
788
789
790
791
792
793
794
795
		QString copyright("# Copyright ");
		if (!username.isEmpty()) copyright.append(username);
		if (!user_domain.isEmpty()) {
			if (!username.isEmpty()) copyright.append(" - ");
			copyright.append(user_domain);
		}
		if (!username.isEmpty() || !user_domain.isEmpty()) copyright.append(", ");
		copyright.append(year);
796
		for (int ii=copyright.size()+1; ii<marker.size(); ii++)
797
			copyright.append(" ");
Michael Reisecker's avatar
Michael Reisecker committed
798

799
		QString header;
800
		header += marker + "\n";
801
		header += copyright + "#\n";
802
		header += marker + "\n";
803
804
		header += "#" + QCoreApplication::applicationName() + " " + APP_VERSION_STR;
		header += " for " + getMainWindow()->getCurrentApplication() + "\n";
805
806
807
808
809
		header += "#" + date + "\n\n";
		preview_ini_.parseText(getCurrentEditor()->toPlainText().prepend(header));
		return;
	}

810
811
812
813
814
	INIParser gui_ini(getMainWindow()->getLogger());
	getMainWindow()->getControlPanel()->setIniValuesFromGui(&gui_ini);
	int counter = 0;
	for (auto &sec : *gui_ini.getSections()) {
		for (auto &keyval : sec.getKeyValueList()) {
Michael Reisecker's avatar
Michael Reisecker committed
815
			if (!preview_ini_.hasKeyValue(keyval.first)) {
816
817
818
819
820
821
822
823
824
825
826
827
828
				if ((mode == MISSING) ||
				    (mode == MISSING_MANDATORY && keyval.second.isMandatory())) {
					QString value( keyval.second.getValue() );
					preview_ini_.set(sec.getName(), keyval.first,
					    value.isEmpty()? "MISSING" : value);
					counter++;
				}
			}
		}
	}
	previewStatus(tr("Inserted %1 keys").arg(counter));
}

829
830
831
832
833
834
835
836
837
838
839
/**
 * @brief Perform whitespaces related transformations on the local INIParser copy.
 * @param[in] mode Type of transformation.
 */
void PreviewWindow::transformWhitespaces(const transform_whitespaces &mode)
{
	switch (mode) {
	case SINGLE_WS:
		for (auto &sec : *preview_ini_.getSections()) {
			for (auto &key : sec.getKeyValueList())
				sec.getKeyValue(key.first)->setKeyValWhitespaces(
840
				    std::vector<QString>( {"", " ", " ", " "} )); //(0)key(1)=(2)value(3)#comment
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
		}
		break;
	case LONGEST_WS:
		for (auto &sec : *preview_ini_.getSections()) {
			int max_key_length = 0;
			for (auto &key : sec.getKeyValueList()) {
				if (!key.second.getValue().isNull() && key.first.length() > max_key_length)
					max_key_length = key.first.length();
			}
			for (auto &key : sec.getKeyValueList()) {
				const int nr_ws = max_key_length - key.first.length() + 1;
				sec.getKeyValue(key.first)->setKeyValWhitespaces(
				    std::vector<QString>( {"", QString(" ").repeated(nr_ws), " ", " "} ) );
			}
		}
	} //switch
}

/**
 * @brief Perform capitalization related transformations on the local INIParser copy.
 * @param[in] mode Type of transformation.
 */
void PreviewWindow::transformCapitalization(const transform_capitalization &mode)
{
865
866
867
868
869
870
871
	const bool lower = (mode == LOWER_CASE || mode == SECTIONS_LOWER ||
	    mode == KEYS_LOWER || mode == VALUES_LOWER);
	const bool value = (mode == VALUES_UPPER || mode == VALUES_LOWER);
	const bool all = (mode == UPPER_CASE || mode == LOWER_CASE);
	const bool section = (mode == SECTIONS_UPPER || mode == SECTIONS_LOWER);

	for (auto &sec : *preview_ini_.getSections()) {
872
		if (section || all)
873
			sec.setName(lower? sec.getName().toLower() : sec.getName().toUpper());
874
		if (!section || all) {
875
876
			for (auto &key : sec.getKeyValueList()) {
				auto *keyvalue = sec.getKeyValue(key.first);
877
878
879
				if (value || all) {
					keyvalue->setValue(lower? keyvalue->getValue().toLower() :
					    keyvalue->getValue().toUpper());
880
881
				}
				if (!value || all) {
882
883
884
885
886
887
					keyvalue->setKey(lower? keyvalue->getKey().toLower() :
					    keyvalue->getKey().toUpper());
				}
			} //endfor key
		} //endif section
	} //endfor sec
888
889
890
891
892
}

/**
 * @brief Perform transformations concerning INI comments on the local INIParser copy.
 * @param[in] mode Type of transformation.
893
 * @return True if at least one comment prefix was removed.
894
 */
895
bool PreviewWindow::transformComments(const transform_comments &mode)
896
{
897
	bool removed_comment = false;
898
899
900
	if (mode == BLOCK_COMMENT || mode == BLOCK_UNCOMMENT) { //block (un)comment
		int first_line, last_line;
		getSelectionMargins(first_line, last_line); //span of selected lines
901
		QStringList lines( getCurrentEditor()->toPlainText().split("\n") );
902
903
904
905
906
907
908
909
910
911
912
		for (int ii = first_line - 1; ii < last_line; ++ii) {
			if (mode == BLOCK_COMMENT) { //add comment prefix
				lines[ii] = "#" + lines[ii];
			} else { //remove comment prefix
				if (lines.at(ii).trimmed().startsWith("#") ||
				    lines.at(ii).trimmed().startsWith(";")) {
					//find first "#" or ";" and delete:
					int prefix_pos = lines.at(ii).indexOf("#");
					if (prefix_pos == -1)
						prefix_pos = lines.at(ii).indexOf(";");
					lines[ii].remove(prefix_pos, 1);
913
					removed_comment = true;
914
915
916
917
				}
			}
		}
		setTextWithHistory(getCurrentEditor(), lines.join("\n"));
918
919
920
921
922
923
924
925
926
927
928
929
930
931
	} else if (mode == ALL_CONTENT) { //put whole content in comment
		QStringList all_lines( getCurrentEditor()->toPlainText().split("\n") );
		for (auto &line : all_lines)
			line = "#" + line;
		const QString loaded_filename( preview_ini_.getFilename() );
		preview_ini_.clear();
		preview_ini_.setFilename(loaded_filename);
		preview_ini_.setBlockCommentAtEnd(all_lines.join("\n"));
	} else if (mode == DUPLICATE) { //copy content to comment
		QStringList all_lines( getCurrentEditor()->toPlainText().split("\n") );
		for (auto &line : all_lines)
			line = "#" + line;
		preview_ini_.setBlockCommentAtEnd(preview_ini_.getBlockCommentAtEnd() + "\n" +
		    all_lines.join("\n"));
932
933
934
935
936
937
938
939
940
941
942
	} else if (mode == MOVE_TO_VALUES) { //remove spaces before comments
		for (auto &sec : *preview_ini_.getSections()) {
			std::vector<QString> ws_sec(sec.getKeyValWhiteSpaces()); //(0)[SECTION](1)#comment
			ws_sec.at(1) = " ";
			sec.setKeyValWhitespaces(ws_sec);
			for (auto &key : sec.getKeyValueList()) {
				std::vector<QString> ws_key(sec.getKeyValue(key.first)->getKeyValWhiteSpaces());
				ws_key.at(3) = " ";
				sec.getKeyValue(key.first)->setKeyValWhitespaces(ws_key);
			}
		}
943
	} else if (mode == MOVE_TO_END) { //collect all comments at end of file
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
		QString comment;
		for (auto &sec : *preview_ini_.getSections()) {
			comment += carryIfContent(sec.getBlockComment());
			comment += carryIfContent(sec.getInlineComment());
			sec.setBlockComment(QString());
			sec.setInlineComment(QString());
			for (auto &key : sec.getKeyValueList()) {
				auto *keyvalue = sec.getKeyValue(key.first);
				comment += carryIfContent(keyvalue->getBlockComment());
				comment += carryIfContent(keyvalue->getInlineComment());
				keyvalue->setBlockComment(QString());
				keyvalue->setInlineComment(QString());
			}
		} //endfor sec
		preview_ini_.setBlockCommentAtEnd(preview_ini_.getBlockCommentAtEnd()
		    + "\n" + comment);
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
	} else if (mode == TRIM) {
		preview_ini_.setBlockCommentAtEnd(trimComment(preview_ini_.getBlockCommentAtEnd()));
		for (auto &sec : *preview_ini_.getSections()) {
			sec.setBlockComment(trimComment(sec.getBlockComment()));
			sec.setInlineComment(trimComment(sec.getInlineComment()));
			for (auto &key : sec.getKeyValueList()) {
				auto *keyvalue = sec.getKeyValue(key.first);
				keyvalue->setBlockComment(trimComment(keyvalue->getBlockComment()));
				keyvalue->setInlineComment(trimComment(keyvalue->getInlineComment()));
			}
		}
	} else if (mode == DELETE) { //delete all comments
		preview_ini_.setBlockCommentAtEnd(QString());
		for (auto &sec : *preview_ini_.getSections()) {
			sec.setBlockComment(QString());
			sec.setInlineComment(QString());
			for (auto &key : sec.getKeyValueList()) {
				auto *keyvalue = sec.getKeyValue(key.first);
				keyvalue->setBlockComment(QString());
				keyvalue->setInlineComment(QString());
			}
		}
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
	} else if (mode == CONVERT_NUMBERSIGN || mode == CONVERT_SEMICOLON) {
		const bool hash = (mode == CONVERT_NUMBERSIGN);
		for (auto &sec : *preview_ini_.getSections()) {
			if (!sec.getBlockComment().isEmpty())
				sec.setBlockComment(convertPrefix(sec.getBlockComment(), hash));
			if (!sec.getInlineComment().isEmpty())
				sec.setInlineComment(convertPrefix(sec.getInlineComment(), hash));
			for (auto &key : sec.getKeyValueList()) {
				auto *keyvalue = sec.getKeyValue(key.first);
				if (!keyvalue->getBlockComment().isEmpty())
					keyvalue->setBlockComment(convertPrefix(
					    keyvalue->getBlockComment(), hash));
				if (!keyvalue->getInlineComment().isEmpty())
					keyvalue->setInlineComment(convertPrefix(
					    keyvalue->getInlineComment(), hash));
			}
			if (!preview_ini_.getBlockCommentAtEnd().isEmpty())
				preview_ini_.setBlockCommentAtEnd(convertPrefix(
				    preview_ini_.getBlockCommentAtEnd(), hash));
For faster browsing, not all history is shown. View entire blame