1 /*
2  *    Copyright 2012, 2013 Thomas Schöps
3  *    Copyright 2012-2020 Kai Pastor
4  *
5  *    This file is part of OpenOrienteering.
6  *
7  *    OpenOrienteering is free software: you can redistribute it and/or modify
8  *    it under the terms of the GNU General Public License as published by
9  *    the Free Software Foundation, either version 3 of the License, or
10  *    (at your option) any later version.
11  *
12  *    OpenOrienteering is distributed in the hope that it will be useful,
13  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *    GNU General Public License for more details.
16  *
17  *    You should have received a copy of the GNU General Public License
18  *    along with OpenOrienteering.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 
22 #include "template_list_widget.h"
23 
24 #include <vector>
25 
26 #include <Qt>
27 #include <QtGlobal>
28 #include <QAbstractButton>
29 #include <QAbstractItemView>
30 #include <QAbstractSlider>
31 #include <QAction>
32 #include <QBoxLayout>
33 #include <QBrush>
34 #include <QByteArray>
35 #include <QCheckBox>
36 #include <QColor>
37 #include <QCoreApplication>
38 #include <QDialog>
39 #include <QDir>
40 #include <QEvent>
41 #include <QEventLoop>
42 #include <QFileInfo>
43 #include <QFlags>
44 #include <QHBoxLayout>
45 #include <QHeaderView>
46 #include <QIcon>
47 #include <QInputDialog>
48 #include <QItemSelectionModel>
49 #include <QKeyEvent>
50 #include <QLabel>
51 #include <QLatin1Char>
52 #include <QLatin1String>
53 #include <QList>
54 #include <QLocale>
55 #include <QMenu>
56 #include <QMessageBox>
57 #include <QPainter>
58 #include <QPalette>
59 #include <QPixmap>
60 #include <QRect>
61 #include <QRectF>
62 #include <QScroller>
63 #include <QSettings>
64 #include <QSignalBlocker>
65 #include <QSize>
66 #include <QSlider>
67 #include <QStringList>
68 #include <QStyle>
69 #include <QStyleOption>
70 #include <QStyleOptionButton>
71 #include <QStyleOptionViewItem>
72 #include <QTableWidget>
73 #include <QTableWidgetItem>
74 #include <QToolButton>
75 #include <QToolTip>
76 #include <QVBoxLayout>
77 #include <QVariant>
78 
79 #ifdef WITH_COVE
80 #include <app/coverunner.h>
81 #endif /* WITH_COVE */
82 
83 #include "settings.h"
84 #include "core/georeferencing.h"
85 #include "core/map.h"
86 #include "core/map_coord.h"
87 #include "fileformats/file_format_registry.h"
88 #include "fileformats/file_import_export.h"
89 #include "gui/file_dialog.h"
90 #include "gui/main_window.h"
91 #include "gui/util_gui.h"
92 #include "gui/map/map_editor.h"
93 #include "gui/map/map_editor_activity.h"
94 #include "gui/map/map_widget.h"
95 #include "gui/widgets/segmented_button_layout.h"
96 #include "templates/template.h"
97 #include "templates/template_adjust.h"
98 #include "templates/template_image.h"
99 #include "templates/template_map.h"
100 #include "templates/template_tool_move.h"
101 #include "util/item_delegates.h"
102 
103 
104 namespace OpenOrienteering {
105 
106 // ### TemplateListWidget ###
107 
108 // Template grouping implementation is incomplete
109 #define NO_TEMPLATE_GROUP_SUPPORT
110 
TemplateListWidget(Map * map,MapView * main_view,MapEditorController * controller,QWidget * parent)111 TemplateListWidget::TemplateListWidget(Map* map, MapView* main_view, MapEditorController* controller, QWidget* parent)
112 : QWidget(parent)
113 , map(map)
114 , main_view(main_view)
115 , controller(controller)
116 , mobile_mode(controller->isInMobileMode())
117 , name_column(3)
118 {
119 	Q_ASSERT(main_view);
120 	Q_ASSERT(controller);
121 
122 	setWhatsThis(Util::makeWhatThis("templates.html#setup"));
123 
124 	QStyleOption style_option(QStyleOption::Version, QStyleOption::SO_DockWidget);
125 
126 	// Wrap the checkbox in a widget and layout to force a margin.
127 	auto top_bar_widget = new QWidget();
128 	auto top_bar_layout = new QHBoxLayout(top_bar_widget);
129 
130 	// Reuse the translation from MapEditorController action.
131 	all_hidden_check = new QCheckBox(::OpenOrienteering::MapEditorController::tr("Hide all templates"));
132 	top_bar_layout->addWidget(all_hidden_check);
133 
134 	if (mobile_mode)
135 	{
136 		auto close_action = new QAction(QIcon(QString::fromLatin1(":/images/close.png")), ::OpenOrienteering::MainWindow::tr("Close"), this);
137 		connect(close_action, &QAction::triggered, this, &TemplateListWidget::closeClicked );
138 
139 		auto close_button = new QToolButton();
140 		close_button->setDefaultAction(close_action);
141 		close_button->setAutoRaise(true);
142 
143 		top_bar_layout->addWidget(close_button);
144 	}
145 
146 	top_bar_layout->setContentsMargins(
147 	            style()->pixelMetric(QStyle::PM_LayoutLeftMargin, &style_option) / 2,
148 	            style()->pixelMetric(QStyle::PM_LayoutTopMargin, &style_option) / 2,
149 	            style()->pixelMetric(QStyle::PM_LayoutRightMargin, &style_option) / 2,
150 	            0 // Covered by the main layout's spacing.
151 	);
152 
153 	// Template table
154 	template_table = new QTableWidget(map->getNumTemplates() + 1, 4);
155 	QScroller::grabGesture(template_table->viewport(), QScroller::TouchGesture);
156 	template_table->installEventFilter(this);
157 	template_table->setEditTriggers(QAbstractItemView::AllEditTriggers);
158 	template_table->setSelectionBehavior(QAbstractItemView::SelectRows);
159 	template_table->setSelectionMode(QAbstractItemView::SingleSelection);
160 	template_table->verticalHeader()->setVisible(false);
161 #ifdef NO_TEMPLATE_GROUP_SUPPORT
162 	// Template grouping is not yet implemented.
163 	template_table->hideColumn(2);
164 #endif
165 
166 	auto header_view = template_table->horizontalHeader();
167 	if (mobile_mode)
168 	{
169 		header_view->setVisible(false);
170 		template_table->setShowGrid(false);
171 		template_table->hideColumn(3);
172 		name_column = 0;
173 	}
174 	else
175 	{
176 		template_table->setHorizontalHeaderLabels(QStringList() << QString{} << tr("Opacity") << tr("Group") << tr("Filename"));
177 		template_table->horizontalHeaderItem(0)->setData(Qt::ToolTipRole, tr("Show"));
178 
179 		header_view->setSectionResizeMode(0, QHeaderView::Fixed);
180 
181 		QStyleOptionButton option;
182 		auto geometry = style()->subElementRect(QStyle::SE_ItemViewItemCheckIndicator, &option, nullptr);
183 		template_table->setColumnWidth(0, geometry.width() * 14 / 10);
184 
185 		auto header_check_size = geometry.size();
186 		if (header_check_size.isValid())
187 		{
188 			QCheckBox header_check;
189 			header_check.setChecked(true);
190 			header_check.setEnabled(false);
191 			QPixmap pixmap(header_check_size);
192 			pixmap.fill(Qt::transparent);
193 			QPainter painter(&pixmap);
194 			QStyleOptionViewItem option;
195 			option.rect = { {0, 0}, geometry.size() };
196 			style()->drawPrimitive(QStyle::PE_IndicatorViewItemCheck, &option, &painter, nullptr);
197 			painter.end();
198 			template_table->horizontalHeaderItem(0)->setData(Qt::DecorationRole, pixmap);
199 		}
200 	}
201 
202 	auto percentage_delegate = new PercentageDelegate(this, 5);
203 	template_table->setItemDelegateForColumn(1, percentage_delegate);
204 
205 	for (int i = 1; i < 3; ++i)
206 		header_view->setSectionResizeMode(i, QHeaderView::ResizeToContents);
207 	header_view->setSectionResizeMode(name_column, QHeaderView::Stretch);
208 	header_view->setSectionsClickable(false);
209 
210 	for (int i = 0; i < map->getNumTemplates() + 1; ++i)
211 		addRowItems(i);
212 
213 	all_templates_layout = new QVBoxLayout();
214 	all_templates_layout->setMargin(0);
215 	all_templates_layout->addWidget(top_bar_widget);
216 	all_templates_layout->addWidget(template_table, 1);
217 
218 	auto new_button_menu = new QMenu(this);
219 	if (!mobile_mode)
220 	{
221 		new_button_menu->addAction(QIcon(QString::fromLatin1(":/images/open.png")), tr("Open..."), this, SLOT(openTemplate()));
222 		new_button_menu->addAction(controller->getAction("reopentemplate"));
223 	}
224 	duplicate_action = new_button_menu->addAction(QIcon(QString::fromLatin1(":/images/tool-duplicate.png")), tr("Duplicate"), this, SLOT(duplicateTemplate()));
225 #if 0
226 	current_action = new_button_menu->addAction(tr("Sketch"));
227 	current_action->setDisabled(true);
228 	current_action = new_button_menu->addAction(tr("GPS"));
229 	current_action->setDisabled(true);
230 #endif
231 
232 	auto new_button = newToolButton(QIcon(QString::fromLatin1(":/images/plus.png")), tr("Add template..."));
233 	new_button->setPopupMode(QToolButton::InstantPopup);
234 	new_button->setMenu(new_button_menu);
235 
236 	delete_button = newToolButton(QIcon(QString::fromLatin1(":/images/minus.png")), tr("Remove"));
237 
238 	auto add_remove_layout = new SegmentedButtonLayout();
239 	add_remove_layout->addWidget(new_button);
240 	add_remove_layout->addWidget(delete_button);
241 
242 	move_up_button = newToolButton(QIcon(QString::fromLatin1(":/images/arrow-up.png")), tr("Move Up"));
243 	move_up_button->setAutoRepeat(true);
244 	move_down_button = newToolButton(QIcon(QString::fromLatin1(":/images/arrow-down.png")), tr("Move Down"));
245 	move_down_button->setAutoRepeat(true);
246 
247 	auto up_down_layout = new SegmentedButtonLayout();
248 	up_down_layout->addWidget(move_up_button);
249 	up_down_layout->addWidget(move_down_button);
250 
251 	move_by_hand_action = new QAction(QIcon(QString::fromLatin1(":/images/move.png")), tr("Move by hand"), this);
252 	move_by_hand_action->setCheckable(true);
253 	move_by_hand_button = newToolButton(move_by_hand_action->icon(), move_by_hand_action->text());
254 	move_by_hand_button->setDefaultAction(move_by_hand_action);
255 	move_by_hand_button->setVisible(!mobile_mode);
256 	adjust_button = newToolButton(QIcon(QString::fromLatin1(":/images/georeferencing.png")), tr("Adjust..."));
257 	adjust_button->setCheckable(true);
258 	adjust_button->setVisible(!mobile_mode);
259 
260 	auto edit_menu = new QMenu(this);
261 	georef_action = edit_menu->addAction(tr("Georeferenced"), this, SLOT(changeGeorefClicked()));
262 	georef_action->setCheckable(true);
263 	position_action = edit_menu->addAction(tr("Positioning..."));
264 	position_action->setCheckable(true);
265 	edit_menu->addSeparator();
266 	import_action =  edit_menu->addAction(tr("Import and remove"), this, SLOT(importClicked()));
267 
268 #if WITH_COVE
269 	vectorize_action = edit_menu->addAction(tr("Vectorize lines"), this, SLOT(vectorizeClicked()));
270 #else
271 	vectorize_action = nullptr;
272 #endif /* WITH_COVE */
273 
274 	edit_button = newToolButton(QIcon(QString::fromLatin1(":/images/settings.png")),
275 	                            ::OpenOrienteering::MapEditorController::tr("&Edit").remove(QLatin1Char('&')));
276 	edit_button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
277 	edit_button->setPopupMode(QToolButton::InstantPopup);
278 	edit_button->setMenu(edit_menu);
279 	edit_button->setVisible(!mobile_mode);
280 
281 	// The buttons row layout
282 	auto list_buttons_layout = new QHBoxLayout();
283 	list_buttons_layout->setContentsMargins(0,0,0,0);
284 	list_buttons_layout->addLayout(add_remove_layout);
285 	list_buttons_layout->addLayout(up_down_layout);
286 	list_buttons_layout->addWidget(adjust_button);
287 	list_buttons_layout->addWidget(move_by_hand_button);
288 	list_buttons_layout->addWidget(edit_button);
289 
290 	list_buttons_group = new QWidget();
291 	list_buttons_group->setLayout(list_buttons_layout);
292 
293 	auto all_buttons_layout = new QHBoxLayout();
294 	all_buttons_layout->setContentsMargins(
295 		style()->pixelMetric(QStyle::PM_LayoutLeftMargin, &style_option) / 2,
296 		0, // Covered by the main layout's spacing.
297 		style()->pixelMetric(QStyle::PM_LayoutRightMargin, &style_option) / 2,
298 		style()->pixelMetric(QStyle::PM_LayoutBottomMargin, &style_option) / 2
299 	);
300 	all_buttons_layout->addWidget(list_buttons_group);
301 	all_buttons_layout->addWidget(new QLabel(QString::fromLatin1("   ")), 1);
302 
303 	if (!mobile_mode)
304 	{
305 		auto help_button = newToolButton(QIcon(QString::fromLatin1(":/images/help.png")), tr("Help"));
306 		help_button->setAutoRaise(true);
307 		all_buttons_layout->addWidget(help_button);
308 		connect(help_button, &QAbstractButton::clicked, this, &TemplateListWidget::showHelp);
309 	}
310 
311 	all_templates_layout->addLayout(all_buttons_layout);
312 
313 	setLayout(all_templates_layout);
314 
315 	//group_button = new QPushButton(QIcon(QString::fromLatin1(":/images/group.png")), tr("(Un)group"));
316 	/*more_button = new QToolButton();
317 	more_button->setText(tr("More..."));
318 	more_button->setPopupMode(QToolButton::InstantPopup);
319 	more_button->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed));
320 	QMenu* more_button_menu = new QMenu(more_button);
321 	more_button_menu->addAction(QIcon(QString::fromLatin1(":/images/window-new.png")), tr("Numeric transformation window"));
322 	more_button_menu->addAction(tr("Set transparent color..."));
323 	more_button_menu->addAction(tr("Trace lines..."));
324 	more_button->setMenu(more_button_menu);*/
325 
326 	updateButtons();
327 
328 	setAllTemplatesHidden(main_view->areAllTemplatesHidden());
329 
330 	// Connections
331 	connect(all_hidden_check, &QAbstractButton::toggled, controller, &MapEditorController::hideAllTemplates);
332 
333 	connect(template_table, &QTableWidget::cellChanged, this, &TemplateListWidget::cellChange);
334 	connect(template_table->selectionModel(), &QItemSelectionModel::selectionChanged, this, &TemplateListWidget::updateButtons);
335 	connect(template_table, &QTableWidget::cellClicked, this, &TemplateListWidget::cellClicked, Qt::QueuedConnection);
336 	connect(template_table, &QTableWidget::cellDoubleClicked, this, &TemplateListWidget::cellDoubleClicked, Qt::QueuedConnection);
337 
338 	connect(delete_button, &QAbstractButton::clicked, this, &TemplateListWidget::deleteTemplate);
339 	connect(move_up_button, &QAbstractButton::clicked, this, &TemplateListWidget::moveTemplateUp);
340 	connect(move_down_button, &QAbstractButton::clicked, this, &TemplateListWidget::moveTemplateDown);
341 
342 	connect(move_by_hand_action, &QAction::triggered, this, &TemplateListWidget::moveByHandClicked);
343 	connect(adjust_button, &QAbstractButton::clicked, this, &TemplateListWidget::adjustClicked);
344 	connect(position_action, &QAction::triggered, this, &TemplateListWidget::positionClicked);
345 
346 	//connect(group_button, SIGNAL(clicked(bool)), this, SLOT(groupClicked()));
347 	//connect(more_button_menu, SIGNAL(triggered(QAction*)), this, SLOT(moreActionClicked(QAction*)));
348 
349 	connect(main_view, &MapView::visibilityChanged, this, &TemplateListWidget::updateVisibility);
350 	connect(controller, &MapEditorController::templatePositionDockWidgetClosed, this, &TemplateListWidget::templatePositionDockWidgetClosed);
351 }
352 
353 TemplateListWidget::~TemplateListWidget() = default;
354 
355 
356 
newToolButton(const QIcon & icon,const QString & text)357 QToolButton* TemplateListWidget::newToolButton(const QIcon& icon, const QString& text)
358 {
359 	auto button = new QToolButton();
360 	button->setToolButtonStyle(Qt::ToolButtonIconOnly);
361 	button->setToolTip(text);
362 	button->setIcon(icon);
363 	button->setText(text);
364 	button->setWhatsThis(Util::makeWhatThis("templates.html#setup"));
365 	return button;
366 }
367 
368 // slot
setAllTemplatesHidden(bool value)369 void TemplateListWidget::setAllTemplatesHidden(bool value)
370 {
371 	all_hidden_check->setChecked(value);
372 
373 	bool enabled = !value;
374 	template_table->setEnabled(enabled);
375 	list_buttons_group->setEnabled(enabled);
376 	updateButtons();
377 }
378 
addTemplateAt(Template * new_template,int pos)379 void TemplateListWidget::addTemplateAt(Template* new_template, int pos)
380 {
381 	/*int row;
382 	if (pos >= 0)
383 		row = template_table->rowCount() - 1 - ((pos >= map->getFirstFrontTemplate()) ? (pos + 1) : pos);
384 	else
385 		row = template_table->rowCount() - 1 - map->getFirstFrontTemplate();*/
386 
387 	if (pos < map->getFirstFrontTemplate())
388 		map->setFirstFrontTemplate(map->getFirstFrontTemplate() + 1);
389 	if (pos < 0)
390 		pos = map->getFirstFrontTemplate() - 1;
391 
392 	map->addTemplate(new_template, pos);
393 	map->setTemplateAreaDirty(pos);
394 
395 	map->setTemplatesDirty();
396 }
397 
showOpenTemplateDialog(QWidget * dialog_parent,MapEditorController * controller)398 std::unique_ptr<Template> TemplateListWidget::showOpenTemplateDialog(QWidget* dialog_parent, MapEditorController* controller)
399 {
400 	QSettings settings;
401 	QString template_directory = settings.value(QString::fromLatin1("templateFileDirectory"), QDir::homePath()).toString();
402 
403 	QString pattern;
404 	for (const auto& extension : Template::supportedExtensions())
405 	{
406 		pattern.append(QLatin1String(" *."));
407 		pattern.append(QLatin1String(extension));
408 	}
409 	pattern.remove(0, 1);
410 	QString path = FileDialog::getOpenFileName(dialog_parent,
411 	                                           tr("Open image, GPS track or DXF file"),
412 	                                           template_directory,
413 	                                           QString::fromLatin1("%1 (%2);;%3 (*.*)").arg(
414 	                                               tr("Template files"), pattern, tr("All files")));
415 	auto canonical_path = QFileInfo(path).canonicalFilePath();
416 	if (!canonical_path.isEmpty())
417 	{
418 		path = canonical_path;
419 		settings.setValue(QString::fromLatin1("templateFileDirectory"), QFileInfo(path).canonicalPath());
420 	}
421 	else if (path.isEmpty())
422 	{
423 		return {};
424 	}
425 
426 	bool center_in_view = true;
427 	QString error;
428 	auto new_temp = Template::templateForFile(path, controller->getMap());
429 	if (!new_temp)
430 	{
431 		error = tr("File format not recognized.");
432 	}
433 	else if (!new_temp->preLoadConfiguration(dialog_parent))
434 	{
435 		// For now, an empty error string means the step was canceled by the user.
436 		error = new_temp->errorString();
437 		new_temp.reset();
438 	}
439 	else if (!new_temp->loadTemplateFile(true))
440 	{
441 		error = new_temp->errorString();
442 		/// \todo Review the default error message. Don't use question mark.
443 		if (error.isEmpty())
444 			error = tr("Failed to load template. Does the file exist and is it valid?");
445 		new_temp.reset();
446 	}
447 	else if (!new_temp->postLoadConfiguration(dialog_parent, center_in_view))
448 	{
449 		// For now, an empty error string means the step was canceled by the user.
450 		error = new_temp->errorString();
451 		new_temp.reset();
452 	}
453 	// If the template is not georeferenced, position it at the viewport midpoint
454 	else if (!new_temp->isTemplateGeoreferenced() && center_in_view)
455 	{
456 		auto main_view = controller->getMainWidget()->getMapView();
457 		auto view_pos = main_view->center();
458 		auto offset = MapCoord { new_temp->calculateTemplateBoundingBox().center() };
459 		new_temp->setTemplatePosition(view_pos - offset);
460 	}
461 
462 	if (!new_temp && !error.isEmpty())
463 	{
464 		auto const error_template = tr("Cannot open template\n%1:\n%2");
465 		QMessageBox::warning(dialog_parent, tr("Error"), error_template.arg(path, error));
466 	}
467 
468 	return new_temp;
469 }
470 
eventFilter(QObject * watched,QEvent * event)471 bool TemplateListWidget::eventFilter(QObject* watched, QEvent* event)
472 {
473 	if (watched == template_table)
474 	{
475 		switch (event->type())
476 		{
477 		case QEvent::KeyPress:
478 			if (static_cast<QKeyEvent*>(event)->key() == Qt::Key_Space)
479 			{
480 				int row = template_table->currentRow();
481 				if (row >= 0 && template_table->item(row, 1)->flags().testFlag(Qt::ItemIsEnabled))
482 				{
483 					bool is_checked = template_table->item(row, 0)->checkState() != Qt::Unchecked;
484 					template_table->item(row, 0)->setCheckState(is_checked ? Qt::Unchecked : Qt::Checked);
485 				}
486 				return true;
487 			}
488 			break;
489 
490 		case QEvent::KeyRelease:
491 			if (static_cast<QKeyEvent*>(event)->key() == Qt::Key_Space)
492 				return true;
493 			break;
494 
495 #ifdef Q_OS_ANDROID
496 		case QEvent::Show:
497 			{
498 				auto map_row = rowFromPos(map->getFirstFrontTemplate()) + 1;
499 				template_table->resizeRowToContents(map_row);
500 				template_table->verticalHeader()->setDefaultSectionSize(template_table->verticalHeader()->sectionSize(map_row));
501 			}
502 			break;
503 #endif
504 
505 		default:
506 			; //nothing
507 		}
508 	}
509 
510 	return false;
511 }
512 
newTemplate(QAction * action)513 void TemplateListWidget::newTemplate(QAction* action)
514 {
515 	if (action->text() == tr("Sketch"))
516 	{
517 		// TODO
518 	}
519 	else if (action->text() == tr("GPS"))
520 	{
521 		// TODO
522 	}
523 }
524 
openTemplate()525 void TemplateListWidget::openTemplate()
526 {
527 	auto new_template = showOpenTemplateDialog(window(), controller);
528 	if (new_template)
529 	{
530 		int pos = -1;
531 		int row = template_table->currentRow();
532 		if (row >= 0)
533 			pos = posFromRow(row);
534 
535 		addTemplateAt(new_template.release(), pos);
536 	}
537 }
538 
deleteTemplate()539 void TemplateListWidget::deleteTemplate()
540 {
541 	int pos = posFromRow(template_table->currentRow());
542 	Q_ASSERT(pos >= 0);
543 
544 	map->setTemplateAreaDirty(pos);
545 
546 	if (Settings::getInstance().getSettingCached(Settings::Templates_KeepSettingsOfClosed).toBool())
547 		map->closeTemplate(pos);
548 	else
549 		map->deleteTemplate(pos);
550 
551 	{
552 		QSignalBlocker block(template_table);
553 		template_table->removeRow(template_table->currentRow());
554 	}
555 
556 	if (pos < map->getFirstFrontTemplate())
557 		map->setFirstFrontTemplate(map->getFirstFrontTemplate() - 1);
558 
559 	map->setTemplatesDirty();
560 
561 	// Do a change of selection to trigger a button update
562 	int current_row = template_table->currentRow();
563 	template_table->clearSelection();
564 	template_table->selectRow(current_row);
565 }
566 
duplicateTemplate()567 void TemplateListWidget::duplicateTemplate()
568 {
569 	int row = template_table->currentRow();
570 	Q_ASSERT(row >= 0);
571 	int pos = posFromRow(row);
572 	Q_ASSERT(pos >= 0);
573 
574 	const auto* prototype = map->getTemplate(pos);
575 	const auto visibility = main_view->getTemplateVisibility(prototype);
576 
577 	auto new_template = prototype->duplicate();
578 	addTemplateAt(new_template, pos);
579 	main_view->setTemplateVisibility(new_template, visibility);
580 	updateRow(row+1);
581 }
582 
moveTemplateUp()583 void TemplateListWidget::moveTemplateUp()
584 {
585 	int row = template_table->currentRow();
586 	Q_ASSERT(row >= 1);
587 	if (!(row >= 1)) return; // in release mode
588 
589 	int cur_pos = posFromRow(row);
590 	int above_pos = posFromRow(row - 1);
591 	map->setTemplateAreaDirty(cur_pos);
592 	map->setTemplateAreaDirty(above_pos);
593 
594 	if (cur_pos < 0)
595 	{
596 		// Moving the map layer up
597 		map->setFirstFrontTemplate(map->getFirstFrontTemplate() + 1);
598 	}
599 	else if (above_pos < 0)
600 	{
601 		// Moving something above the map layer
602 		map->setFirstFrontTemplate(map->getFirstFrontTemplate() - 1);
603 	}
604 	else
605 	{
606 		// Exchanging two templates
607 		auto above_template = map->getTemplate(above_pos);
608 		auto cur_template = map->getTemplate(cur_pos);
609 		map->setTemplate(cur_template, above_pos);
610 		map->setTemplate(above_template, cur_pos);
611 	}
612 
613 	map->setTemplateAreaDirty(cur_pos);
614 	map->setTemplateAreaDirty(above_pos);
615 	updateRow(row - 1);
616 	updateRow(row);
617 
618 	{
619 		QSignalBlocker block(template_table);
620 		template_table->setCurrentCell(row - 1, template_table->currentColumn());
621 	}
622 	//updateButtons();
623 	map->setTemplatesDirty();
624 }
625 
moveTemplateDown()626 void TemplateListWidget::moveTemplateDown()
627 {
628 	int row = template_table->currentRow();
629 	Q_ASSERT(row >= 0);
630 	if (!(row >= 0)) return; // in release mode
631 	Q_ASSERT(row < template_table->rowCount() - 1);
632 	if (!(row < template_table->rowCount() - 1)) return; // in release mode
633 
634 	int cur_pos = posFromRow(row);
635 	int below_pos = posFromRow(row + 1);
636 	map->setTemplateAreaDirty(cur_pos);
637 	map->setTemplateAreaDirty(below_pos);
638 
639 	if (cur_pos < 0)
640 	{
641 		// Moving the map layer down
642 		map->setFirstFrontTemplate(map->getFirstFrontTemplate() - 1);
643 	}
644 	else if (below_pos < 0)
645 	{
646 		// Moving something below the map layer
647 		map->setFirstFrontTemplate(map->getFirstFrontTemplate() + 1);
648 	}
649 	else
650 	{
651 		// Exchanging two templates
652 		auto below_template = map->getTemplate(below_pos);
653 		auto cur_template = map->getTemplate(cur_pos);
654 		map->setTemplate(cur_template, below_pos);
655 		map->setTemplate(below_template, cur_pos);
656 	}
657 
658 	map->setTemplateAreaDirty(cur_pos);
659 	map->setTemplateAreaDirty(below_pos);
660 	updateRow(row + 1);
661 	updateRow(row);
662 
663 	{
664 		QSignalBlocker block(template_table);
665 		template_table->setCurrentCell(row + 1, template_table->currentColumn());
666 	}
667 	updateButtons();
668 	map->setTemplatesDirty();
669 }
670 
showHelp()671 void TemplateListWidget::showHelp()
672 {
673 	Util::showHelp(controller->getWindow(), "templates.html", "setup");
674 }
675 
cellChange(int row,int column)676 void TemplateListWidget::cellChange(int row, int column)
677 {
678 	int pos = posFromRow(row);
679 
680 	Template* temp = nullptr;
681 	auto state = Template::Loaded;
682 	auto visibility = main_view->getMapVisibility();
683 	if (pos >= 0)
684 	{
685 		// Template row, not map row
686 		temp = map->getTemplate(pos);
687 		state = temp->getTemplateState();
688 		visibility = main_view->getTemplateVisibility(temp);
689 	}
690 
691 	auto updateVisibility = [this](Template* temp, TemplateVisibility vis)
692 	{
693 		if (temp)
694 			main_view->setTemplateVisibility(temp, vis);
695 		else
696 			main_view->setMapVisibility(vis);
697 	};
698 
699 	if (state != Template::Invalid)
700 	{
701 		auto setAreaDirty = [this, pos]()
702 		{
703 			if (pos >= 0)
704 			{
705 				map->setTemplateAreaDirty(pos);
706 			}
707 			else
708 			{
709 				//QRectF map_bounds = map->calculateExtent(true, false, nullptr);
710 				//map->setObjectAreaDirty(map_bounds);
711 				main_view->updateAllMapWidgets();  // Map change - doesn't need to update the map cache
712 			}
713 		};
714 
715 		switch (column)
716 		{
717 		case 0:  // Visibility checkbox
718 			{
719 				bool visible = template_table->item(row, column)->checkState() == Qt::Checked;
720 				if (visibility.visible != visible)
721 				{
722 					if (!visible)
723 					{
724 						setAreaDirty();
725 						visibility.visible = false;
726 						updateVisibility(temp, visibility);
727 					}
728 					else
729 					{
730 						if (state != Template::Loaded)
731 						{
732 							// Ensure feedback before slow loading/drawing
733 							QSignalBlocker block(template_table);
734 							template_table->item(row, 0)->setCheckState(Qt::PartiallyChecked);
735 							auto item_rect = template_table->visualItemRect(template_table->item(row, 1));
736 							QToolTip::showText(template_table->mapToGlobal(item_rect.bottomLeft()),
737 							                   qApp->translate("OpenOrienteering::MainWindow", "Opening %1").arg(temp->getTemplateFilename()) );
738 							QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 100 /* ms */);
739 						}
740 						visibility.visible = true;
741 						updateVisibility(temp, visibility);
742 						setAreaDirty();
743 						if (state != Template::Loaded)
744 						{
745 							QToolTip::hideText();
746 							if (temp->getTemplateState() != Template::Loaded)
747 							{
748 								QMessageBox::warning(this,
749 								                     qApp->translate("OpenOrienteering::MainWindow", "Error"),
750 								                     qApp->translate("OpenOrienteering::Importer", "Failed to load template '%1', reason: %2")
751 								                     .arg(temp->getTemplateFilename(), temp->errorString()) );
752 							}
753 						}
754 					}
755 					updateRow(row);
756 					updateButtons();
757 				}
758 			}
759 			break;
760 
761 		case 1:  // Opacity spinbox or slider
762 			{
763 				auto const opacity = template_table->item(row, column)->data(Qt::DisplayRole).toReal();
764 				if (!qFuzzyCompare(1.0+opacity, 1.0+visibility.opacity))
765 				{
766 					visibility.opacity = qBound(0.0, opacity, 1.0);
767 					updateVisibility(temp, visibility);
768 					setAreaDirty();
769 					template_table->item(row, 1)->setData(Qt::DecorationRole, QColor::fromCmykF(0.0, 0.0, 0.0, visibility.opacity));
770 				}
771 			}
772 			break;
773 
774 #ifndef NO_TEMPLATE_GROUP_SUPPORT
775 		case 2:
776 			{
777 				QString text = template_table->item(row, column)->text().trimmed();
778 				bool ok = true;
779 				int ivalue = text.toInt(&ok);
780 
781 				if (text.isEmpty())
782 				{
783 					temp->setTemplateGroup(-1);
784 				}
785 				else if (!ok)
786 				{
787 					QMessageBox::warning(window(), tr("Error"), tr("Please enter a valid integer number to set a group or leave the field empty to ungroup the template!"));
788 					template_table->item(row, column)->setText(QString::number(temp->getTemplateGroup()));
789 				}
790 				else
791 					temp->setTemplateGroup(ivalue);
792 			}
793 #endif
794 		default:
795 			; // nothing
796 		}
797 	}
798 }
799 
updateButtons()800 void TemplateListWidget::updateButtons()
801 {
802 	bool map_row_selected = false;  // does the selection contain the map row?
803 	bool first_row_selected = false;
804 	bool last_row_selected = false;
805 	int num_rows_selected = 0;
806 	int visited_row = -1;
807 	for (auto&& item : template_table->selectedItems())
808 	{
809 		const int row = item->row();
810 		if (row == visited_row)
811 			continue;
812 
813 		visited_row = row;
814 		++num_rows_selected;
815 
816 		if (posFromRow(row) < 0)
817 			map_row_selected = true;
818 
819 		if (row == 0)
820 			first_row_selected = true;
821 		if (row == template_table->rowCount() - 1)
822 			last_row_selected = true;
823 	}
824 	bool single_row_selected = (num_rows_selected == 1);
825 
826 	auto single_template_selected = single_row_selected && !map_row_selected;
827 	duplicate_action->setEnabled(single_template_selected);
828 	delete_button->setEnabled(single_template_selected);	/// \todo Make it possible to delete multiple templates at once
829 	move_up_button->setEnabled(single_row_selected && !first_row_selected);
830 	move_down_button->setEnabled(single_row_selected && !last_row_selected);
831 
832 	if (!mobile_mode)
833 	{
834 		// Update and show other buttons
835 
836 		bool is_georeferenced = false;
837 		bool edit_enabled   = false;
838 		bool georef_enabled = false;
839 		bool custom_enabled = false;
840 		bool import_enabled = false;
841 		bool vectorize_enabled  = false;
842 		if (single_template_selected)
843 		{
844 			auto temp = map->getTemplate(posFromRow(visited_row));
845 			is_georeferenced = temp->isTemplateGeoreferenced();
846 			if (template_table->item(visited_row, 0)->checkState() == Qt::Checked)
847 			{
848 				edit_enabled   = true;
849 				georef_enabled = temp->canChangeTemplateGeoreferenced();
850 				custom_enabled = !is_georeferenced;
851 				import_enabled = bool(qobject_cast<TemplateMap*>(getCurrentTemplate()));
852 				vectorize_enabled = qobject_cast<TemplateImage*>(getCurrentTemplate())
853 									&& getCurrentTemplate()->getTemplateState() == Template::Loaded;
854 			}
855 		}
856 		else if (single_row_selected)
857 		{
858 			Q_ASSERT(map_row_selected);
859 			is_georeferenced = map->getGeoreferencing().isValid() && !map->getGeoreferencing().isLocal();
860 		}
861 
862 		edit_button->setEnabled(edit_enabled);
863 		georef_action->setChecked(is_georeferenced);
864 		georef_action->setEnabled(georef_enabled);
865 		move_by_hand_button->setEnabled(custom_enabled);
866 		adjust_button->setEnabled(custom_enabled);
867 		position_action->setEnabled(custom_enabled);
868 		import_action->setEnabled(import_enabled);
869 		if (vectorize_action)
870 			vectorize_action->setEnabled(vectorize_enabled);
871 	}
872 }
873 
cellClicked(int row,int column)874 void TemplateListWidget::cellClicked(int row, int column)
875 {
876 	auto pos = posFromRow(qMax(0, row));
877 
878 	switch (column)
879 	{
880 	case 1:
881 		if (mobile_mode
882 		    && row >= 0
883 		    && template_table->item(row, 0)->checkState() == Qt::Checked)
884 		{
885 			showOpacitySlider(row);
886 		}
887 		break;
888 
889 	case 3:
890 		if (!mobile_mode
891 		    && row >= 0 && pos >= 0
892 		    && map->getTemplate(pos)->getTemplateState() == Template::Invalid)
893 		{
894 			changeTemplateFile(pos);
895 		}
896 		break;
897 
898 	default:
899 		break;
900 	}
901 }
902 
cellDoubleClicked(int row,int column)903 void TemplateListWidget::cellDoubleClicked(int row, int column)
904 {
905 	auto pos = posFromRow(qMax(0, row));
906 
907 	switch (column)
908 	{
909 	default:
910 		if (! (row >= 0 && pos >= 0
911 		       && map->getTemplate(pos)->getTemplateState() == Template::Invalid))
912 			break;
913 		// Invalid template:
914 		Q_FALLTHROUGH();
915 	case 3:
916 		if (!mobile_mode
917 		    && row >= 0 && pos >= 0)
918 		{
919 			changeTemplateFile(pos);
920 		}
921 	}
922 }
923 
moveByHandClicked(bool checked)924 void TemplateListWidget::moveByHandClicked(bool checked)
925 {
926 	auto temp = getCurrentTemplate();
927 	Q_ASSERT(temp);
928 	controller->setTool(checked ? new TemplateMoveTool(temp, controller, move_by_hand_action) : nullptr);
929 }
930 
adjustClicked(bool checked)931 void TemplateListWidget::adjustClicked(bool checked)
932 {
933 	if (checked)
934 	{
935 		auto temp = getCurrentTemplate();
936 		Q_ASSERT(temp);
937 		auto activity = new TemplateAdjustActivity(temp, controller);
938 		controller->setEditorActivity(activity);
939 		connect(activity->getDockWidget(), &TemplateAdjustDockWidget::closed, this, &TemplateListWidget::adjustWindowClosed);
940 	}
941 	else
942 	{
943 		controller->setEditorActivity(nullptr);	// TODO: default activity?!
944 	}
945 }
946 
adjustWindowClosed()947 void TemplateListWidget::adjustWindowClosed()
948 {
949 	auto current_template = getCurrentTemplate();
950 	if (!current_template)
951 		return;
952 
953 	if (controller->getEditorActivity() && controller->getEditorActivity()->getActivityObject() == current_template)
954 		adjust_button->setChecked(false);
955 }
956 
957 #ifndef NO_TEMPLATE_GROUP_SUPPORT
groupClicked()958 void TemplateListWidget::groupClicked()
959 {
960 	// TODO
961 }
962 #endif
963 
positionClicked(bool checked)964 void TemplateListWidget::positionClicked(bool checked)
965 {
966 	Q_UNUSED(checked);
967 
968 	auto temp = getCurrentTemplate();
969 	if (!temp)
970 		return;
971 
972 	if (controller->existsTemplatePositionDockWidget(temp))
973 		controller->removeTemplatePositionDockWidget(temp);
974 	else
975 		controller->addTemplatePositionDockWidget(temp);
976 }
977 
importClicked()978 void TemplateListWidget::importClicked()
979 {
980 	auto prototype = qobject_cast<const TemplateMap*>(getCurrentTemplate());
981 	if (!prototype)
982 		return;
983 
984 	TemplateTransform transform;
985 	if (!prototype->isTemplateGeoreferenced())
986 		prototype->getTransform(transform);
987 
988 	Map template_map;
989 	bool ok = true;
990 	if (qstrcmp(prototype->getTemplateType(), "OgrTemplate") == 0)
991 	{
992 		template_map.importMap(*prototype->templateMap(), Map::MinimalObjectImport);
993 		if (!prototype->isTemplateGeoreferenced())
994 		{
995 			template_map.applyOnAllObjects(transform.makeObjectTransform());
996 			template_map.setGeoreferencing(map->getGeoreferencing());
997 		}
998 		auto template_scale = (transform.template_scale_x + transform.template_scale_y) / 2;
999 		template_scale *= double(prototype->templateMap()->getScaleDenominator()) / map->getScaleDenominator();
1000 		if (!qFuzzyCompare(template_scale, 1))
1001 		{
1002 			template_map.scaleAllSymbols(template_scale);
1003 		}
1004 	}
1005 	else if (qstrcmp(prototype->getTemplateType(), "TemplateMap") == 0)
1006 	{
1007 		auto importer = FileFormats.makeImporter(prototype->getTemplatePath(), template_map, nullptr);
1008 		if (!importer)
1009 		{
1010 			QMessageBox::warning(this, tr("Error"), tr("Cannot load map file, aborting."));
1011 			return;
1012 		}
1013 		if (!importer->doImport())
1014 		{
1015 			QMessageBox::warning(this, tr("Error"), importer->warnings().back());
1016 			return;
1017 		}
1018 		if (!importer->warnings().empty())
1019 		{
1020 			MainWindow::showMessageBox(this, tr("Warning"), tr("The map import generated warnings."), importer->warnings());
1021 		}
1022 
1023 		if (!prototype->isTemplateGeoreferenced())
1024 			template_map.applyOnAllObjects(transform.makeObjectTransform());
1025 
1026 		auto nominal_scale = double(template_map.getScaleDenominator()) / map->getScaleDenominator();
1027 		auto current_scale = 0.5 * (transform.template_scale_x + transform.template_scale_y);
1028 		auto scale = 1.0;
1029 		QStringList scale_options;
1030 		if (qAbs(nominal_scale - 1.0) > 0.009)
1031 			scale_options.append(tr("Scale by nominal map scale ratio (%1 %)").arg(locale().toString(nominal_scale * 100.0, 'f', 1)));
1032 		if (qAbs(current_scale - 1.0) > 0.009 && qAbs(current_scale - nominal_scale) > 0.009)
1033 			scale_options.append(tr("Scale by current template scaling (%1 %)").arg(locale().toString(current_scale * 100.0, 'f', 1)));
1034 		if (!scale_options.isEmpty())
1035 		{
1036 			scale_options.prepend(tr("Don't scale"));
1037 			QString option = QInputDialog::getItem( window(),
1038 			  tr("Template import"),
1039 			  tr("How shall the symbols of the imported template map be scaled?"),
1040 			  scale_options, 0, false, &ok );
1041 			if (option.isEmpty())
1042 				return;
1043 			else if (option == scale_options[0])
1044 				Q_ASSERT(scale == 1.0);
1045 			else if (option == scale_options[1])
1046 				scale = nominal_scale;
1047 			else // This option may have been omitted.
1048 				scale = current_scale;
1049 		}
1050 
1051 		if (ok && scale != 1.0)
1052 				template_map.scaleAllSymbols(scale);
1053 	}
1054 	else
1055 	{
1056 		QMessageBox::warning(this, tr("Error"), tr("Cannot load map file, aborting."));
1057 		return;
1058 	}
1059 
1060 	map->importMap(template_map, Map::MinimalObjectImport);
1061 	deleteTemplate();
1062 
1063 	if (main_view->isOverprintingSimulationEnabled()
1064 	    && !template_map.hasSpotColors())
1065 	{
1066 		auto answer = QMessageBox::question(
1067 		                  window(),
1068 		                  tr("Template import"),
1069 		                  tr("The template will be invisible in the overprinting simulation. "
1070 		                     "Switch to normal view?"),
1071 		                  QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No),
1072 		                  QMessageBox::Yes );
1073 		if (answer == QMessageBox::Yes)
1074 		{
1075 			if (auto action = controller->getAction("overprintsimulation"))
1076 				action->trigger();
1077 		}
1078 	}
1079 
1080 
1081 	auto map_visibility = main_view->getMapVisibility();
1082 	if (!map_visibility.visible)
1083 	{
1084 		map_visibility.visible = true;
1085 		updateRow(map->getNumTemplates() - map->getFirstFrontTemplate());
1086 	}
1087 }
1088 
changeGeorefClicked()1089 void TemplateListWidget::changeGeorefClicked()
1090 {
1091 	auto* templ = getCurrentTemplate();
1092 	if (templ && templ->canChangeTemplateGeoreferenced())
1093 	{
1094 		auto new_value = !templ->isTemplateGeoreferenced();
1095 		if (new_value)
1096 		{
1097 			// Properly tear down positioning activities
1098 			if (move_by_hand_action->isChecked())
1099 				move_by_hand_action->trigger();
1100 			if (adjust_button->isChecked())
1101 				adjust_button->click();
1102 			if (position_action->isChecked())
1103 				position_action->trigger();
1104 		}
1105 		if (!templ->trySetTemplateGeoreferenced(new_value, this))
1106 		{
1107 			QMessageBox::warning(this, tr("Error"), tr("Cannot change the georeferencing state."));
1108 			georef_action->setChecked(templ->isTemplateGeoreferenced());
1109 		}
1110 		updateButtons();
1111 	}
1112 }
1113 
vectorizeClicked()1114 void TemplateListWidget::vectorizeClicked()
1115 {
1116 #ifdef WITH_COVE
1117 	cove::CoveRunner cr;
1118 	auto templ = qobject_cast<TemplateImage*>(getCurrentTemplate());
1119 	cr.run(controller->getWindow(), map, templ);
1120 #endif /* WITH_COVE */
1121 }
1122 
moreActionClicked(QAction * action)1123 void TemplateListWidget::moreActionClicked(QAction* action)
1124 {
1125 	Q_UNUSED(action);
1126 	// TODO
1127 }
1128 
templateAdded(int pos,const Template * temp)1129 void TemplateListWidget::templateAdded(int pos, const Template* temp)
1130 {
1131 	Q_UNUSED(temp);
1132 	int row = rowFromPos(pos);
1133 	template_table->insertRow(row);
1134 	addRowItems(row);
1135 	template_table->setCurrentCell(row, 0);
1136 }
1137 
templatePositionDockWidgetClosed(Template * temp)1138 void TemplateListWidget::templatePositionDockWidgetClosed(Template* temp)
1139 {
1140 	auto current_temp = getCurrentTemplate();
1141 	if (current_temp == temp)
1142 		position_action->setChecked(false);
1143 }
1144 
updateVisibility(MapView::VisibilityFeature feature,bool active,const Template * temp)1145 void TemplateListWidget::updateVisibility(MapView::VisibilityFeature feature, bool active, const Template* temp)
1146 {
1147 	switch (feature)
1148 	{
1149 	case MapView::AllTemplatesHidden:
1150 		setAllTemplatesHidden(active);
1151 		break;
1152 
1153 	case MapView::MapVisible:
1154 		updateRow(map->getFirstFrontTemplate());
1155 		break;
1156 
1157 	case MapView::TemplateVisible:
1158 		if (map->getNumTemplates() == template_table->rowCount()+1)
1159 		{
1160 			auto row = map->findTemplateIndex(temp);
1161 			if (row >= 0)
1162 				updateRow(posFromRow(row));
1163 			break;
1164 		}
1165 		Q_FALLTHROUGH();
1166 	case MapView::MultipleFeatures:
1167 		updateAll();
1168 		break;
1169 
1170 	default:
1171 		;  // nothing
1172 	}
1173 }
1174 
updateAll()1175 void TemplateListWidget::updateAll()
1176 {
1177 	auto templates_hidden = main_view->areAllTemplatesHidden();
1178 	template_table->setEnabled(!templates_hidden); // Color scheme depends on state
1179 
1180 	auto old_size = template_table->rowCount();
1181 	template_table->setRowCount(map->getNumTemplates() + 1);
1182 	for (auto i = 0; i < old_size; ++i)
1183 		updateRow(i);
1184 	for (auto i = old_size; i < template_table->rowCount(); ++i)
1185 		addRowItems(i);
1186 
1187 	setAllTemplatesHidden(templates_hidden);  // implicit updateButtons
1188 }
1189 
addRowItems(int row)1190 void TemplateListWidget::addRowItems(int row)
1191 {
1192 	QSignalBlocker block(template_table);
1193 
1194 	for (int i = 0; i < 4; ++i)
1195 	{
1196 		auto item = new QTableWidgetItem();
1197 		template_table->setItem(row, i, item);
1198 	}
1199 	template_table->item(row, 0)->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled /* | Qt::ItemIsSelectable*/);
1200 	template_table->item(row, 1)->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
1201 	template_table->item(row, 2)->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
1202 
1203 	updateRow(row);
1204 }
1205 
updateRow(int row)1206 void TemplateListWidget::updateRow(int row)
1207 {
1208 	int pos = posFromRow(row);
1209 	//int group = -1;
1210 	QString name;
1211 	QString path;
1212 	bool valid = true;
1213 
1214 	TemplateVisibility vis;
1215 
1216 	QPalette::ColorGroup color_group = template_table->isEnabled() ? QPalette::Active : QPalette::Disabled;
1217 #ifdef Q_OS_ANDROID
1218 	auto background_color = QPalette().color(color_group, QPalette::Background);
1219 #else
1220 	auto background_color = QPalette().color(color_group, QPalette::Base);
1221 #endif
1222 
1223 	if (pos >= 0)
1224 	{
1225 		auto temp = map->getTemplate(pos);
1226 		//group = temp->getTemplateGroup();
1227 		name = temp->getTemplateFilename();
1228 		path = temp->getTemplatePath();
1229 		valid = temp->getTemplateState() != Template::Invalid;
1230 		/// @todo Get visibility values from the MapView of the active MapWidget (instead of always main_view)
1231 		vis = main_view->getTemplateVisibility(temp);
1232 	}
1233 	else
1234 	{
1235 		name = tr("- Map -");
1236 		vis = main_view->getMapVisibility();
1237 #ifdef Q_OS_ANDROID
1238 		auto r = (128 + 5 * background_color.red()) / 6;
1239 		auto g = (128 + 5 * background_color.green()) / 6;
1240 		auto b = (128 + 5 * background_color.blue()) / 6;
1241 		background_color = QColor(r, g, b);
1242 #else
1243 		background_color = QPalette().color(color_group, QPalette::AlternateBase);
1244 #endif
1245 	}
1246 
1247 	// Cheep defaults, mostly for !vis.visible
1248 	auto check_state    = Qt::Unchecked;
1249 	auto opacity_color  = QColor{ Qt::transparent };
1250 	auto text_color     = QColor::fromRgb(255, 51, 51);
1251 	auto decoration     = QVariant{ };
1252 	auto checkable      = Qt::ItemIsUserCheckable;
1253 	auto editable       = Qt::NoItemFlags;
1254 	//auto group_editable = Qt::NoItemFlags;
1255 
1256 	if (valid)
1257 	{
1258 		if (vis.visible)
1259 		{
1260 			check_state   = Qt::Checked;
1261 			opacity_color = QColor::fromCmykF(0.0, 0.0, 0.0, vis.opacity);
1262 			text_color    = QPalette().color(color_group, QPalette::Foreground);
1263 			if (!mobile_mode)
1264 			{
1265 				editable = Qt::ItemIsEditable;
1266 				if (pos >= 0)
1267 				{
1268 					//group_editable = Qt::ItemIsEditable;
1269 				}
1270 			}
1271 		}
1272 		else
1273 		{
1274 			text_color = QPalette().color(QPalette::Disabled, QPalette::Foreground);
1275 		}
1276 		decoration = QVariant{ opacity_color };
1277 	}
1278 	else
1279 	{
1280 		if (vis.visible)
1281 		{
1282 			check_state = Qt::PartiallyChecked;
1283 			text_color = text_color.darker();
1284 		}
1285 		decoration = QIcon::fromTheme(QLatin1String("image-missing"), QIcon{QLatin1String(":/images/close.png")});
1286 		checkable  = Qt::NoItemFlags;
1287 		editable   = Qt::NoItemFlags;
1288 	}
1289 
1290 	auto foreground = QBrush(text_color);
1291 	auto background = QBrush(background_color);
1292 
1293 	QSignalBlocker block(template_table);
1294 	{
1295 		auto item0 = template_table->item(row, 0);
1296 		item0->setBackground(background);
1297 		item0->setCheckState(check_state);
1298 		item0->setFlags(checkable | Qt::ItemIsEnabled);
1299 #ifdef Q_OS_ANDROID
1300 		// Some combinations not working well in Android style
1301 		if (!valid)
1302 		{
1303 		//	item0->setData(Qt::CheckStateRole, {});
1304 		//	item0->setFlags(enabled);
1305 		}
1306 #endif
1307 	}
1308 	{
1309 		auto item1 = template_table->item(row, 1);
1310 		item1->setBackground(background);
1311 		item1->setForeground(foreground);
1312 		item1->setData(Qt::DisplayRole, vis.opacity);
1313 		item1->setData(Qt::DecorationRole, decoration);
1314 		item1->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | editable);
1315 	}
1316 #ifndef NO_TEMPLATE_GROUP_SUPPORT
1317 	{
1318 		auto item2 = template_table->item(row, 2);
1319 		item->setBackground(background);
1320 		item->setForeground(foreground);
1321 		item->setText((group < 0) ? "" : QString::number(group));
1322 		item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | groupable);
1323 	}
1324 #endif
1325 	{
1326 		auto name_item = template_table->item(row, name_column);
1327 		name_item->setBackground(background);
1328 		name_item->setForeground(foreground);
1329 		name_item->setText(name);
1330 		name_item->setData(Qt::ToolTipRole, path);
1331 		name_item->setData(Qt::DecorationRole, {});
1332 		auto prev_checkable = name_item->flags() & Qt::ItemIsUserCheckable;
1333 		name_item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | prev_checkable);
1334 	}
1335 }
1336 
posFromRow(int row)1337 int TemplateListWidget::posFromRow(int row)
1338 {
1339 	int pos = template_table->rowCount() - 1 - row;
1340 
1341 	if (pos == map->getFirstFrontTemplate())
1342 		pos = -1; // the map row
1343 	else if (pos > map->getFirstFrontTemplate())
1344 		pos = pos - 1; // before the map row
1345 
1346 	return pos;
1347 }
1348 
rowFromPos(int pos)1349 int TemplateListWidget::rowFromPos(int pos)
1350 {
1351 	Q_ASSERT(pos >= 0);
1352 	return map->getNumTemplates() - 1 - ((pos >= map->getFirstFrontTemplate()) ? pos : (pos - 1));
1353 }
1354 
getCurrentTemplate()1355 Template* TemplateListWidget::getCurrentTemplate()
1356 {
1357 	int current_row = template_table->currentRow();
1358 	if (current_row < 0)
1359 		return nullptr;
1360 	int pos = posFromRow(current_row);
1361 	if (pos < 0)
1362 		return nullptr;
1363 	return map->getTemplate(pos);
1364 }
1365 
changeTemplateFile(int pos)1366 void TemplateListWidget::changeTemplateFile(int pos)
1367 {
1368 	auto temp = map->getTemplate(pos);
1369 	Q_ASSERT(temp);
1370 	temp->execSwitchTemplateFileDialog(this);
1371 	updateRow(rowFromPos(pos));
1372 	updateButtons();
1373 	temp->setTemplateAreaDirty();
1374 	map->setTemplatesDirty();
1375 }
1376 
showOpacitySlider(int row)1377 void TemplateListWidget::showOpacitySlider(int row)
1378 {
1379 	auto geometry = template_table->visualItemRect(template_table->item(row, name_column));
1380 	geometry.translate(0, geometry.height());
1381 
1382 	QDialog dialog(this);
1383 	dialog.move(template_table->viewport()->mapToGlobal(geometry.topLeft()));
1384 
1385 	auto slider = new QSlider();
1386 	slider->setOrientation(Qt::Horizontal);
1387 	slider->setRange(0, 20);
1388 	slider->setMinimumWidth(geometry.width());
1389 
1390 	auto opacity_item = template_table->item(row, 1);
1391 	slider->setValue(qRound(opacity_item->data(Qt::DisplayRole).toFloat() * 20));
1392 	connect(slider, &QSlider::valueChanged, [opacity_item](int value) {
1393 		opacity_item->setData(Qt::DisplayRole, 0.05f * value);
1394 	} );
1395 
1396 	auto close_button = new QToolButton();
1397 	close_button->setIcon(style()->standardIcon(QStyle::SP_DialogApplyButton, nullptr, this));
1398 	close_button->setAutoRaise(true);
1399 	connect(close_button, &QToolButton::clicked, &dialog, &QDialog::accept);
1400 
1401 	auto layout = new QHBoxLayout(&dialog);
1402 	layout->addWidget(slider);
1403 	layout->addWidget(close_button);
1404 
1405 	dialog.exec();
1406 }
1407 
1408 
1409 }  // namespace OpenOrienteering
1410