1 /*
2 * Copyright 2012, 2013 Thomas Schöps
3 * Copyright 2014-2019 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 "symbol_render_widget.h"
23
24 #include <QApplication>
25 #include <QBuffer>
26 #include <QClipboard>
27 #include <QDrag>
28 #include <QInputDialog>
29 #include <QMenu>
30 #include <QMessageBox>
31 #include <QMimeData>
32 #include <QPainter>
33 #include <QPaintEvent>
34 #include <QResizeEvent>
35 #include <QScopedValueRollback>
36
37 #include "settings.h"
38 #include "core/map.h"
39 #include "core/objects/object.h"
40 #include "core/symbols/area_symbol.h"
41 #include "core/symbols/combined_symbol.h"
42 #include "core/symbols/line_symbol.h"
43 #include "core/symbols/point_symbol.h"
44 #include "core/symbols/symbol.h"
45 #include "core/symbols/symbol_icon_decorator.h"
46 #include "core/symbols/text_symbol.h"
47 #include "gui/symbols/symbol_setting_dialog.h"
48 #include "gui/widgets/symbol_tooltip.h"
49 #include "util/backports.h" // IWYU pragma: keep
50 #include "util/overriding_shortcut.h"
51
52
53 namespace OpenOrienteering {
54
55 namespace MimeType {
56
57 /// The index of a symbol during drag-and-drop
OpenOrienteeringSymbolIndex()58 const QString OpenOrienteeringSymbolIndex()
59 {
60 return QStringLiteral("openorienteering/symbol_index");
61 }
62
63 /// Symbol definitions
OpenOrienteeringSymbols()64 const QString OpenOrienteeringSymbols()
65 {
66 return QStringLiteral("openorienteering/symbols");
67 }
68
69
70 } // namespace MimeType
71
72
73
74 //### SymbolRenderWidget ###
75
SymbolRenderWidget(Map * map,bool mobile_mode,QWidget * parent)76 SymbolRenderWidget::SymbolRenderWidget(Map* map, bool mobile_mode, QWidget* parent)
77 : QWidget(parent)
78 , map(map)
79 , mobile_mode(mobile_mode)
80 , selection_locked(false)
81 , dragging(false)
82 , current_symbol_index(-1)
83 , hover_symbol_index(-1)
84 , last_drop_pos(-1)
85 , last_drop_row(-1)
86 , icon_size(Settings::getInstance().getSymbolWidgetIconSizePx())
87 , icons_per_row(6)
88 , num_rows(5)
89 , preferred_size(icons_per_row * icon_size, num_rows * icon_size)
90 , hidden_symbol_decoration(new HiddenSymbolDecorator(icon_size))
91 , protected_symbol_decoration(new ProtectedSymbolDecorator(icon_size))
92 {
93 setBackgroundRole(QPalette::Base);
94 setMouseTracking(true);
95 setFocusPolicy(Qt::ClickFocus);
96 setAcceptDrops(true);
97
98 QShortcut* description_shortcut = new OverridingShortcut(
99 QKeySequence(tr("F1", "Shortcut for displaying the symbol's description")),
100 this
101 );
102 tooltip = new SymbolToolTip(this, description_shortcut);
103 // TODO: Use a placeholder in the literal and pass the actual shortcut's string representation.
104 setStatusTip(tr("For symbols with description, press F1 while the tooltip is visible to show it"));
105
106 context_menu = new QMenu(this);
107
108 QMenu* new_menu = new QMenu(tr("New symbol"), context_menu);
109 new_menu->setIcon(QIcon(QStringLiteral(":/images/plus.png")));
110 /*QAction* new_point_action =*/ new_menu->addAction(tr("Point"), this, SLOT(newPointSymbol()));
111 /*QAction* new_line_action =*/ new_menu->addAction(tr("Line"), this, SLOT(newLineSymbol()));
112 /*QAction* new_area_action =*/ new_menu->addAction(tr("Area"), this, SLOT(newAreaSymbol()));
113 /*QAction* new_text_action =*/ new_menu->addAction(tr("Text"), this, SLOT(newTextSymbol()));
114 /*QAction* new_combined_action =*/ new_menu->addAction(tr("Combined"), this, SLOT(newCombinedSymbol()));
115 context_menu->addMenu(new_menu);
116
117 edit_action = context_menu->addAction(tr("Edit"), this, SLOT(editSymbol()));
118 duplicate_action = context_menu->addAction(QIcon(QStringLiteral(":/images/tool-duplicate.png")), tr("Duplicate"), this, SLOT(duplicateSymbol()));
119 delete_action = context_menu->addAction(QIcon(QStringLiteral(":/images/minus.png")), tr("Delete"), this, SLOT(deleteSymbols()));
120 scale_action = context_menu->addAction(QIcon(QStringLiteral(":/images/tool-scale.png")), tr("Scale..."), this, SLOT(scaleSymbol()));
121 context_menu->addSeparator();
122 copy_action = context_menu->addAction(QIcon(QStringLiteral(":/images/copy.png")), tr("Copy"), this, SLOT(copySymbols()));
123 paste_action = context_menu->addAction(QIcon(QStringLiteral(":/images/paste.png")), tr("Paste"), this, SLOT(pasteSymbols()));
124 context_menu->addSeparator();
125 switch_symbol_action = context_menu->addAction(QIcon(QStringLiteral(":/images/tool-switch-symbol.png")), tr("Switch symbol of selected objects"), this, SIGNAL(switchSymbolClicked()));
126 fill_border_action = context_menu->addAction(QIcon(QStringLiteral(":/images/tool-fill-border.png")), tr("Fill / Create border for selected objects"), this, SIGNAL(fillBorderClicked()));
127 // text will be filled in by updateContextMenuState()
128 select_objects_action = context_menu->addAction(QIcon(QStringLiteral(":/images/tool-edit.png")), {}, this, SLOT(selectObjectsExclusively()));
129 select_objects_additionally_action = context_menu->addAction(QIcon(QStringLiteral(":/images/tool-edit.png")), {}, this, SLOT(selectObjectsAdditionally()));
130 deselect_objects_action = context_menu->addAction(QIcon(QStringLiteral(":/images/tool-edit.png")), {}, this, SLOT(deselectObjects()));
131 context_menu->addSeparator();
132 hide_action = context_menu->addAction({}, this, SLOT(setSelectedSymbolVisibility(bool)));
133 hide_action->setCheckable(true);
134 protect_action = context_menu->addAction({}, this, SLOT(setSelectedSymbolProtection(bool)));
135 protect_action->setCheckable(true);
136 context_menu->addSeparator();
137
138 show_custom_icons = context_menu->addAction(tr("Show custom icons"), this, SLOT(setCustomIconsVisible(bool)));
139 show_custom_icons->setCheckable(true);
140
141 QMenu* select_menu = new QMenu(tr("Select symbols"), context_menu);
142 select_menu->addAction(tr("Select all"), this, SLOT(selectAll()));
143 select_menu->addAction(tr("Select unused"), this, SLOT(selectUnused()));
144 select_menu->addSeparator();
145 select_menu->addAction(tr("Invert selection"), this, SLOT(invertSelection()));
146 context_menu->addMenu(select_menu);
147
148 QMenu* sort_menu = new QMenu(tr("Sort symbols"), context_menu);
149 sort_menu->addAction(tr("Sort by number"), this, SLOT(sortByNumber()));
150 sort_menu->addAction(tr("Sort by primary color"), this, SLOT(sortByColor()));
151 sort_menu->addAction(tr("Sort by primary color priority"), this, SLOT(sortByColorPriority()));
152 sort_manual_action = sort_menu->addAction(tr("Enable drag and drop"));
153 sort_manual_action->setCheckable(true);
154 context_menu->addMenu(sort_menu);
155
156 connect(map, &Map::colorDeleted, this, QOverload<>::of(&QWidget::update));
157 connect(map, &Map::symbolAdded, this, &SymbolRenderWidget::updateAll);
158 connect(map, &Map::symbolDeleted, this, &SymbolRenderWidget::symbolDeleted);
159 connect(map, &Map::symbolChanged, this, &SymbolRenderWidget::symbolChanged);
160 connect(map, &Map::symbolIconChanged, this, &SymbolRenderWidget::updateSingleIcon);
161 connect(map, &Map::symbolIconZoomChanged, this, &SymbolRenderWidget::updateAll);
162 connect(&Settings::getInstance(), &Settings::settingsChanged, this, &SymbolRenderWidget::settingsChanged);
163 }
164
~SymbolRenderWidget()165 SymbolRenderWidget::~SymbolRenderWidget()
166 {
167 ; // nothing
168 }
169
symbolDeleted(int pos,const Symbol * old_symbol)170 void SymbolRenderWidget::symbolDeleted(int pos, const Symbol *old_symbol)
171 {
172 Q_UNUSED(old_symbol);
173
174 std::set<int>::iterator it = selected_symbols.find(pos);
175 if (it != selected_symbols.end())
176 {
177 // Remove pos
178 std::set<int>::iterator new_it = it;
179 ++new_it;
180 selected_symbols.erase(it);
181 // Renumber successors
182 for (it = new_it; it != selected_symbols.end(); it = ++new_it)
183 {
184 new_it = selected_symbols.insert(*it - 1).first;
185 selected_symbols.erase(it);
186 }
187 emitGuarded_selectedSymbolsChanged();
188 }
189
190 adjustLayout();
191 update();
192 }
193
symbolChanged(int pos,const Symbol * new_symbol,const Symbol * old_symbol)194 void SymbolRenderWidget::symbolChanged(int pos, const Symbol* new_symbol, const Symbol* old_symbol)
195 {
196 Q_UNUSED(new_symbol);
197 Q_UNUSED(old_symbol);
198
199 updateSingleIcon(pos);
200
201 if (isSymbolSelected(pos))
202 emitGuarded_selectedSymbolsChanged();
203 }
204
updateAll()205 void SymbolRenderWidget::updateAll()
206 {
207 adjustLayout();
208 update();
209 }
210
updateSingleIcon(int i)211 void SymbolRenderWidget::updateSingleIcon(int i)
212 {
213 if (i >= 0)
214 {
215 QPoint pos = iconPosition(i);
216 update(pos.x(), pos.y(), icon_size, icon_size);
217 }
218 }
219
settingsChanged()220 void SymbolRenderWidget::settingsChanged()
221 {
222 const auto new_size = Settings::getInstance().getSymbolWidgetIconSizePx();
223 if (icon_size != new_size)
224 {
225 for (int i = 0; i < map->getNumSymbols(); ++i)
226 {
227 auto symbol = map->getSymbol(i);
228 if (symbol->getIcon(map).width() != new_size)
229 symbol->resetIcon();
230 }
231 updateAll();
232 }
233 }
234
235
236
sizeHint() const237 QSize SymbolRenderWidget::sizeHint() const
238 {
239 return preferred_size;
240 }
241
adjustLayout()242 void SymbolRenderWidget::adjustLayout()
243 {
244 auto old_icon_size = icon_size;
245 icon_size = Settings::getInstance().getSymbolWidgetIconSizePx() + 1;
246 // Allow symbol widget to be that much wider than the viewport
247 int overflow = icon_size / 3;
248 icons_per_row = qMax(1, (width() + overflow) / icon_size);
249 num_rows = qMax(1, (map->getNumSymbols() + icons_per_row -1) / icons_per_row);
250 setFixedHeight(num_rows * icon_size);
251
252 if (old_icon_size != icon_size)
253 {
254 hidden_symbol_decoration.reset(new HiddenSymbolDecorator(icon_size));
255 protected_symbol_decoration.reset(new ProtectedSymbolDecorator(icon_size));
256 }
257 }
258
emitGuarded_selectedSymbolsChanged()259 void SymbolRenderWidget::emitGuarded_selectedSymbolsChanged()
260 {
261 QScopedValueRollback<bool> save(selection_locked);
262 selection_locked = true;
263 emit selectedSymbolsChanged();
264 }
265
singleSelectedSymbol() const266 const Symbol* SymbolRenderWidget::singleSelectedSymbol() const
267 {
268 if (selected_symbols.size() != 1)
269 return nullptr;
270 return static_cast<const Map*>(map)->getSymbol(*(selected_symbols.begin()));
271 }
272
singleSelectedSymbol()273 Symbol* SymbolRenderWidget::singleSelectedSymbol()
274 {
275 if (selected_symbols.size() != 1)
276 return nullptr;
277 return map->getSymbol(*(selected_symbols.begin()));
278 }
279
isSymbolSelected(const Symbol * symbol) const280 bool SymbolRenderWidget::isSymbolSelected(const Symbol* symbol) const
281 {
282 return isSymbolSelected(map->findSymbolIndex(symbol));
283 }
284
isSymbolSelected(int i) const285 bool SymbolRenderWidget::isSymbolSelected(int i) const
286 {
287 return selected_symbols.find(i) != selected_symbols.end();
288 }
289
selectSingleSymbol(const Symbol * symbol)290 void SymbolRenderWidget::selectSingleSymbol(const Symbol *symbol)
291 {
292 int index = map->findSymbolIndex(symbol);
293 if (index >= 0)
294 selectSingleSymbol(index);
295 }
296
selectSingleSymbol(int i)297 void SymbolRenderWidget::selectSingleSymbol(int i)
298 {
299 if (selection_locked)
300 return;
301
302 if (selected_symbols.size() == 1 && *selected_symbols.begin() == i)
303 return; // Symbol already selected as only selection
304
305 updateSelectedIcons();
306 selected_symbols.clear();
307 if (i >= 0)
308 {
309 selected_symbols.insert(i);
310 updateSingleIcon(i);
311 }
312 current_symbol_index = i;
313
314 emitGuarded_selectedSymbolsChanged();
315 }
316
hover(const QPoint & pos)317 void SymbolRenderWidget::hover(const QPoint& pos)
318 {
319 int i = symbolIndexAt(pos);
320
321 if (i != hover_symbol_index)
322 {
323 updateSingleIcon(hover_symbol_index);
324 hover_symbol_index = i;
325 updateSingleIcon(hover_symbol_index);
326
327 if (hover_symbol_index >= 0)
328 {
329 Symbol* symbol = map->getSymbol(hover_symbol_index);
330 if (tooltip->getSymbol() != symbol)
331 {
332 const QRect icon_rect(mapToGlobal(iconPosition(hover_symbol_index)), QSize(icon_size, icon_size));
333 tooltip->scheduleShow(symbol, map, icon_rect, mobile_mode);
334 }
335 }
336 else
337 {
338 tooltip->reset();
339 }
340 }
341 }
342
iconPosition(int i) const343 QPoint SymbolRenderWidget::iconPosition(int i) const
344 {
345 return QPoint((i % icons_per_row) * icon_size, (i / icons_per_row) * icon_size);
346 }
347
symbolIndexAt(const QPoint & pos) const348 int SymbolRenderWidget::symbolIndexAt(const QPoint& pos) const
349 {
350 int i = -1;
351
352 int icon_in_row = pos.x() / icon_size;
353 if (icon_in_row < icons_per_row)
354 {
355 i = icon_in_row + icons_per_row * (pos.y() / icon_size);
356 if (i >= map->getNumSymbols())
357 i = -1;
358 }
359
360 return i;
361 }
362
updateSelectedIcons()363 void SymbolRenderWidget::updateSelectedIcons()
364 {
365 for (auto symbol_index : selected_symbols)
366 updateSingleIcon(symbol_index);
367 }
368
dropPosition(const QPoint & pos,int & row,int & pos_in_row)369 bool SymbolRenderWidget::dropPosition(const QPoint& pos, int& row, int& pos_in_row)
370 {
371 row = pos.y() / icon_size;
372 if (row >= num_rows)
373 {
374 row = -1;
375 pos_in_row = -1;
376 return false;
377 }
378
379 pos_in_row = (pos.x() + icon_size / 2) / icon_size;
380 if (pos_in_row > icons_per_row)
381 {
382 pos_in_row = icons_per_row;
383 }
384
385 int global_pos = row * icons_per_row + pos_in_row;
386 if (global_pos > map->getNumSymbols())
387 {
388 row = -1;
389 pos_in_row = -1;
390 return false;
391 }
392
393 return true;
394 }
395
dropIndicatorRect(int row,int pos_in_row)396 QRect SymbolRenderWidget::dropIndicatorRect(int row, int pos_in_row)
397 {
398 const int rect_width = 3;
399 return QRect(pos_in_row * icon_size - rect_width / 2 - 1, row * icon_size - 1, rect_width, icon_size + 2);
400 }
401
drawIcon(QPainter & painter,int i) const402 void SymbolRenderWidget::drawIcon(QPainter &painter, int i) const
403 {
404 painter.save();
405
406 Symbol* symbol = map->getSymbol(i);
407 painter.drawImage(0, 0, symbol->getIcon(map));
408
409 if (isSymbolSelected(i) || i == current_symbol_index)
410 {
411 painter.setOpacity(0.5);
412 QPen pen(Qt::white);
413 pen.setWidth(2);
414 pen.setJoinStyle(Qt::MiterJoin);
415 painter.setPen(pen);
416 painter.drawRect(QRectF(1.5f, 1.5f, icon_size - 5, icon_size - 5));
417
418 painter.setOpacity(1.0);
419 }
420
421 if (symbol->isProtected())
422 protected_symbol_decoration->draw(painter);
423
424 if (symbol->isHidden())
425 hidden_symbol_decoration->draw(painter);
426
427 if (isSymbolSelected(i))
428 {
429 QPen pen(qRgb(12, 0, 255));
430 pen.setWidth(2);
431 pen.setJoinStyle(Qt::MiterJoin);
432 painter.setPen(pen);
433 painter.drawRect(QRectF(0.5f, 0.5f, icon_size - 3, icon_size - 3));
434
435 if (i == hover_symbol_index)
436 {
437 QPen pen(qRgb(255, 150, 0));
438 pen.setWidth(2);
439 pen.setDashPattern(QVector<qreal>() << 1 << 2);
440 pen.setJoinStyle(Qt::MiterJoin);
441 painter.setPen(pen);
442 painter.drawRect(QRectF(0.5f, 0.5f, icon_size - 3, icon_size - 3));
443 }
444 else if (i == current_symbol_index)
445 {
446 QPen pen(Qt::white);
447 pen.setDashPattern(QVector<qreal>() << 2 << 2);
448 painter.setPen(pen);
449 painter.drawRect(1, 1, icon_size - 4, icon_size - 4);
450 }
451 }
452 else if (i == hover_symbol_index)
453 {
454 QPen pen(qRgb(255, 150, 0));
455 pen.setWidth(2);
456 pen.setJoinStyle(Qt::MiterJoin);
457 painter.setPen(pen);
458 painter.drawRect(QRectF(0.5f, 0.5f, icon_size - 3, icon_size - 3));
459 }
460
461 painter.setPen(Qt::gray);
462 painter.drawLine(QPoint(0, icon_size - 1), QPoint(icon_size - 1, icon_size - 1));
463 painter.drawLine(QPoint(icon_size - 1, 0), QPoint(icon_size - 1, icon_size - 2));
464
465 painter.restore();
466 }
467
paintEvent(QPaintEvent * event)468 void SymbolRenderWidget::paintEvent(QPaintEvent* event)
469 {
470 QRect event_rect = event->rect().adjusted(-icon_size, -icon_size, 0, 0);
471
472 QPainter painter(this);
473 painter.setPen(Qt::gray);
474
475 int x = 0;
476 int y = 0;
477 for (int i = 0; i < map->getNumSymbols(); ++i)
478 {
479 if (event_rect.contains(x,y))
480 {
481 painter.save();
482 painter.translate(x, y);
483 drawIcon(painter, i);
484 painter.restore();
485 }
486
487 x += icon_size;
488 if (x >= icons_per_row * icon_size)
489 {
490 x = 0;
491 y += icon_size;
492 }
493 }
494
495 // Drop indicator?
496 if (last_drop_pos >= 0 && last_drop_row >= 0)
497 {
498 QRect drop_rect = dropIndicatorRect(last_drop_row, last_drop_pos);
499 painter.setPen(qRgb(255, 150, 0));
500 painter.setBrush(Qt::NoBrush);
501 painter.drawRect(drop_rect.left(), drop_rect.top(), drop_rect.width() - 1, drop_rect.height() - 1);
502 }
503
504 painter.end();
505 }
506
resizeEvent(QResizeEvent * event)507 void SymbolRenderWidget::resizeEvent(QResizeEvent* event)
508 {
509 if (width() != event->oldSize().width())
510 {
511 adjustLayout();
512 }
513 }
514
mouseMoveEvent(QMouseEvent * event)515 void SymbolRenderWidget::mouseMoveEvent(QMouseEvent* event)
516 {
517 if (mobile_mode)
518 {
519 if (event->buttons() & Qt::LeftButton)
520 {
521 hover(event->pos());
522
523 if ((event->pos() - last_click_pos).manhattanLength() < Settings::getInstance().getStartDragDistancePx())
524 return;
525 dragging = sort_manual_action->isChecked();
526 }
527 }
528 else
529 {
530 if (event->buttons() & Qt::LeftButton
531 && current_symbol_index >= 0
532 && sort_manual_action->isChecked())
533 {
534 if ((event->pos() - last_click_pos).manhattanLength() < Settings::getInstance().getStartDragDistancePx())
535 return;
536
537 tooltip->hide();
538
539 QDrag* drag = new QDrag(this);
540 QMimeData* mime_data = new QMimeData();
541
542 QByteArray data;
543 data.append((const char*)¤t_symbol_index, sizeof(int));
544 mime_data->setData(MimeType::OpenOrienteeringSymbolIndex(), data);
545 drag->setMimeData(mime_data);
546
547 drag->exec(Qt::MoveAction);
548 }
549 else if (event->button() == Qt::NoButton)
550 {
551 hover(event->pos());
552 }
553 }
554 event->accept();
555 }
556
mousePressEvent(QMouseEvent * event)557 void SymbolRenderWidget::mousePressEvent(QMouseEvent* event)
558 {
559 dragging = false;
560
561 if (mobile_mode)
562 {
563 tooltip->hide();
564
565 last_click_pos = event->pos();
566 hover(event->pos());
567 return;
568 }
569
570 updateSingleIcon(current_symbol_index);
571 int old_symbol_index = current_symbol_index;
572 current_symbol_index = symbolIndexAt(event->pos());
573 updateSingleIcon(current_symbol_index);
574
575 if (event->button() == Qt::LeftButton || event->button() == Qt::RightButton)
576 {
577 if (event->modifiers() & Qt::ControlModifier)
578 {
579 if (current_symbol_index >= 0)
580 {
581 if (!isSymbolSelected(current_symbol_index))
582 selected_symbols.insert(current_symbol_index);
583 else
584 selected_symbols.erase(current_symbol_index);
585 emitGuarded_selectedSymbolsChanged();
586 }
587 }
588 else if (event->modifiers() & Qt::ShiftModifier)
589 {
590 if (current_symbol_index >= 0)
591 {
592 bool insert = !isSymbolSelected(current_symbol_index);
593 int i = (old_symbol_index >= 0) ? old_symbol_index : current_symbol_index;
594 while (true)
595 {
596 if (insert)
597 selected_symbols.insert(i);
598 else
599 selected_symbols.erase(i);
600 updateSingleIcon(i);
601
602 if (current_symbol_index > i)
603 ++i;
604 else if (current_symbol_index < i)
605 --i;
606 else
607 break;
608 }
609 emitGuarded_selectedSymbolsChanged();
610 }
611 }
612 else
613 {
614 if (event->button() == Qt::LeftButton ||
615 (event->button() == Qt::RightButton &&
616 current_symbol_index >= 0 && !isSymbolSelected(current_symbol_index)))
617 selectSingleSymbol(current_symbol_index);
618 }
619 }
620
621 else if (event->button() == Qt::LeftButton && current_symbol_index >= 0 && !(event->modifiers() & Qt::ShiftModifier))
622 {
623 last_click_pos = event->pos();
624 last_drop_pos = -1;
625 last_drop_row = -1;
626 }
627 }
628
mouseReleaseEvent(QMouseEvent * event)629 void SymbolRenderWidget::mouseReleaseEvent(QMouseEvent* event)
630 {
631 if (mobile_mode)
632 {
633 if (dragging)
634 {
635 dragging = false;
636 return;
637 }
638
639 hover(event->pos());
640 if (tooltip->isVisible())
641 return;
642
643 updateSingleIcon(current_symbol_index);
644 current_symbol_index = symbolIndexAt(event->pos());
645 updateSingleIcon(current_symbol_index);
646
647 if (current_symbol_index >= 0 && !isSymbolSelected(current_symbol_index))
648 selectSingleSymbol(current_symbol_index);
649 else
650 emitGuarded_selectedSymbolsChanged(); // HACK, will close the symbol selection screen
651 }
652 }
653
mouseDoubleClickEvent(QMouseEvent * event)654 void SymbolRenderWidget::mouseDoubleClickEvent(QMouseEvent* event)
655 {
656 if (mobile_mode)
657 return;
658
659 int i = symbolIndexAt(event->pos());
660 if (i < 0)
661 return;
662
663 updateSingleIcon(current_symbol_index);
664 current_symbol_index = i;
665 updateSingleIcon(current_symbol_index);
666 editSymbol();
667 }
668
leaveEvent(QEvent * event)669 void SymbolRenderWidget::leaveEvent(QEvent* event)
670 {
671 Q_UNUSED(event);
672 updateSingleIcon(hover_symbol_index);
673 hover_symbol_index = -1;
674
675 tooltip->reset();
676 }
677
dragEnterEvent(QDragEnterEvent * event)678 void SymbolRenderWidget::dragEnterEvent(QDragEnterEvent* event)
679 {
680 if (event->mimeData()->hasFormat(MimeType::OpenOrienteeringSymbolIndex()))
681 event->acceptProposedAction();
682 }
683
dragMoveEvent(QDragMoveEvent * event)684 void SymbolRenderWidget::dragMoveEvent(QDragMoveEvent* event)
685 {
686 if (event->mimeData()->hasFormat(MimeType::OpenOrienteeringSymbolIndex()))
687 {
688 int row, pos_in_row;
689 if (!dropPosition(event->pos(), row, pos_in_row))
690 {
691 if (last_drop_pos >= 0 && last_drop_row >= 0)
692 {
693 update(dropIndicatorRect(last_drop_row, last_drop_pos));
694 last_drop_pos = -1;
695 last_drop_row = -1;
696 }
697 return;
698 }
699
700 if (row != last_drop_row || pos_in_row != last_drop_pos)
701 {
702 if (last_drop_row >= 0 && last_drop_pos >= 0)
703 update(dropIndicatorRect(last_drop_row, last_drop_pos));
704
705 last_drop_row = row;
706 last_drop_pos = pos_in_row;
707
708 if (last_drop_row >= 0 && last_drop_pos >= 0)
709 update(dropIndicatorRect(last_drop_row, last_drop_pos));
710 }
711
712 event->acceptProposedAction();
713 }
714 }
715
dropEvent(QDropEvent * event)716 void SymbolRenderWidget::dropEvent(QDropEvent* event)
717 {
718 last_drop_row = -1;
719 last_drop_pos = -1;
720
721 if (event->source() != this || current_symbol_index < 0)
722 return;
723
724 if (event->proposedAction() == Qt::MoveAction)
725 {
726 int row, pos_in_row;
727 if (!dropPosition(event->pos(), row, pos_in_row))
728 return;
729
730 int pos = row * icons_per_row + pos_in_row;
731 if (pos == current_symbol_index)
732 return;
733
734 event->acceptProposedAction();
735
736 // save selection
737 std::set<Symbol *> sel;
738 for (auto symbol_index : selected_symbols) {
739 sel.insert(map->getSymbol(symbol_index));
740 }
741
742 map->moveSymbol(current_symbol_index, pos);
743 update();
744
745
746 if (pos > current_symbol_index)
747 --pos;
748 current_symbol_index = pos;
749 selectSingleSymbol(pos);
750 }
751 }
752
selectObjectsExclusively()753 void SymbolRenderWidget::selectObjectsExclusively()
754 {
755 emit selectObjectsClicked(true);
756 }
757
selectObjectsAdditionally()758 void SymbolRenderWidget::selectObjectsAdditionally()
759 {
760 emit selectObjectsClicked(false);
761 }
762
deselectObjects()763 void SymbolRenderWidget::deselectObjects()
764 {
765 emit deselectObjectsClicked();
766 }
767
newPointSymbol()768 void SymbolRenderWidget::newPointSymbol()
769 {
770 newSymbol(new PointSymbol());
771 }
772
newLineSymbol()773 void SymbolRenderWidget::newLineSymbol()
774 {
775 newSymbol(new LineSymbol());
776 }
777
newAreaSymbol()778 void SymbolRenderWidget::newAreaSymbol()
779 {
780 newSymbol(new AreaSymbol());
781 }
782
newTextSymbol()783 void SymbolRenderWidget::newTextSymbol()
784 {
785 newSymbol(new TextSymbol());
786 }
787
newCombinedSymbol()788 void SymbolRenderWidget::newCombinedSymbol()
789 {
790 newSymbol(new CombinedSymbol());
791 }
792
editSymbol()793 void SymbolRenderWidget::editSymbol()
794 {
795 Q_ASSERT(current_symbol_index >= 0);
796
797 Symbol* symbol = map->getSymbol(current_symbol_index);
798 SymbolSettingDialog dialog(symbol, map, this);
799 dialog.setWindowModality(Qt::WindowModal);
800 if (dialog.exec() == QDialog::Accepted)
801 {
802 map->setSymbol(dialog.getNewSymbol().release(), current_symbol_index);
803 }
804 }
805
scaleSymbol()806 void SymbolRenderWidget::scaleSymbol()
807 {
808 Q_ASSERT(!selected_symbols.empty());
809
810 bool ok;
811 double percent = QInputDialog::getDouble(this, tr("Scale symbols"), tr("Scale to percentage:"), 100, 0, 999999, 6, &ok);
812 if (!ok || percent == 100)
813 return;
814
815 for (auto symbol_index : selected_symbols)
816 {
817 auto symbol = map->getSymbol(symbol_index);
818 symbol->scale(percent / 100.0);
819 updateSingleIcon(current_symbol_index);
820 map->changeSymbolForAllObjects(symbol, symbol); // update the objects
821 }
822
823 map->setSymbolsDirty();
824 }
825
deleteSymbols()826 void SymbolRenderWidget::deleteSymbols()
827 {
828 // Collect selected symbols
829 std::vector<const Symbol*> saved_selection;
830 saved_selection.reserve(selected_symbols.size());
831
832 bool need_confirmation = true;
833 for (auto symbol_index : selected_symbols)
834 {
835 auto* symbol = map->getSymbol(symbol_index);
836 if (need_confirmation && map->existsObjectWithSymbol(symbol))
837 {
838 auto answer = QMessageBox::warning(
839 this,
840 tr("Confirmation"),
841 tr("The map contains objects with the symbol \"%1\". "
842 "Deleting it will delete those objects and clear the undo history! "
843 "Do you really want to do that?")
844 .arg(symbol->getName()),
845 QMessageBox::Yes | QMessageBox::YesToAll | QMessageBox::No | QMessageBox::Cancel);
846 switch (answer)
847 {
848 case QMessageBox::Cancel:
849 return;
850 case QMessageBox::YesToAll:
851 need_confirmation = false;
852 break;
853 case QMessageBox::Yes:
854 break;
855 case QMessageBox::No:
856 continue;
857 default:
858 Q_UNREACHABLE();
859 }
860 }
861 saved_selection.push_back(symbol);
862 }
863
864 // Delete collected symbols
865 for (auto* symbol : saved_selection)
866 map->deleteSymbol(map->findSymbolIndex(symbol));
867
868 if (selected_symbols.empty()) {
869 if (map->getFirstSelectedObject())
870 selectSingleSymbol(map->getFirstSelectedObject()->getSymbol());
871 else
872 selectSingleSymbol(-1);
873 }
874 }
875
duplicateSymbol()876 void SymbolRenderWidget::duplicateSymbol()
877 {
878 Q_ASSERT(current_symbol_index >= 0);
879
880 map->addSymbol(duplicate(*map->getSymbol(current_symbol_index)).release(), current_symbol_index + 1);
881 selectSingleSymbol(current_symbol_index + 1);
882 }
883
copySymbols()884 void SymbolRenderWidget::copySymbols()
885 {
886 // Create map containing all colors and the selected symbols.
887 // Copying all colors improves preservation of relative order during paste.
888 Map copy_map;
889 copy_map.setScaleDenominator(map->getScaleDenominator());
890 copy_map.importMap(*map, Map::ColorImport);
891
892 std::vector<bool> selection(map->getNumSymbols(), false);
893 for (auto symbol_index : selected_symbols)
894 selection[symbol_index] = true;
895
896 copy_map.importMap(*map, Map::MinimalSymbolImport, &selection);
897
898 // Save map to memory
899 QBuffer buffer;
900 if (!copy_map.exportToIODevice(buffer))
901 {
902 QMessageBox::warning(nullptr, tr("Error"), tr("An internal error occurred, sorry!"));
903 return;
904 }
905
906 // Put buffer into clipboard
907 QMimeData* mime_data = new QMimeData();
908 mime_data->setData(MimeType::OpenOrienteeringSymbols(), buffer.data());
909 QApplication::clipboard()->setMimeData(mime_data);
910 }
911
pasteSymbols()912 void SymbolRenderWidget::pasteSymbols()
913 {
914 if (!QApplication::clipboard()->mimeData()->hasFormat(MimeType::OpenOrienteeringSymbols()))
915 {
916 QMessageBox::warning(nullptr, tr("Error"), tr("There are no symbols in clipboard which could be pasted!"));
917 return;
918 }
919
920 // Get buffer from clipboard
921 QByteArray byte_array = QApplication::clipboard()->mimeData()->data(MimeType::OpenOrienteeringSymbols());
922 QBuffer buffer(&byte_array);
923
924 // Create map from buffer
925 Map paste_map;
926 if (!paste_map.importFromIODevice(buffer))
927 {
928 QMessageBox::warning(nullptr, tr("Error"), tr("An internal error occurred, sorry!"));
929 return;
930 }
931
932 if (paste_map.getScaleDenominator() != map->getScaleDenominator())
933 {
934 /// \todo Adjust message to this particular action, and remove Map context.
935 /// See also MapEditor::importMap.
936 int answer = QMessageBox::question(
937 window(),
938 Map::tr("Question"),
939 Map::tr("The scale of the imported data is 1:%1 "
940 "which is different from this map's scale of 1:%2.\n\n"
941 "Rescale the imported data?")
942 .arg(QLocale().toString(paste_map.getScaleDenominator()),
943 QLocale().toString(map->getScaleDenominator())),
944 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes);
945 if (answer == QMessageBox::Cancel)
946 return;
947
948 paste_map.changeScale(map->getScaleDenominator(), 1.0, {0, 0}, answer == QMessageBox::Yes, false, false, false);
949 }
950
951 // Ensure that a change in selection is detected
952 selectSingleSymbol(-1);
953
954 // Import pasted map
955 map->importMap(paste_map, Map::MinimalSymbolImport, nullptr, current_symbol_index, false);
956
957 selectSingleSymbol(current_symbol_index);
958 }
959
setSelectedSymbolVisibility(bool checked)960 void SymbolRenderWidget::setSelectedSymbolVisibility(bool checked)
961 {
962 bool selection_changed = false;
963 for (auto symbol_index : selected_symbols)
964 {
965 auto symbol = map->getSymbol(symbol_index);
966 if (symbol->isHidden() != checked)
967 {
968 symbol->setHidden(checked);
969 updateSingleIcon(symbol_index);
970 if (checked)
971 selection_changed |= map->removeSymbolFromSelection(symbol, false);
972 }
973 }
974 if (selection_changed)
975 map->emitSelectionChanged();
976 map->updateAllMapWidgets();
977 map->setSymbolsDirty();
978 emitGuarded_selectedSymbolsChanged();
979 }
980
setSelectedSymbolProtection(bool checked)981 void SymbolRenderWidget::setSelectedSymbolProtection(bool checked)
982 {
983 bool selection_changed = false;
984 for (auto symbol_index : selected_symbols)
985 {
986 auto symbol = map->getSymbol(symbol_index);
987 if (symbol->isProtected() != checked)
988 {
989 symbol->setProtected(checked);
990 updateSingleIcon(symbol_index);
991 if (checked)
992 selection_changed |= map->removeSymbolFromSelection(symbol, false);
993 }
994 }
995 if (selection_changed)
996 map->emitSelectionChanged();
997 map->setSymbolsDirty();
998 emitGuarded_selectedSymbolsChanged();
999 }
1000
selectAll()1001 void SymbolRenderWidget::selectAll()
1002 {
1003 for (int i = 0; i < map->getNumSymbols(); ++i)
1004 selected_symbols.insert(i);
1005 emitGuarded_selectedSymbolsChanged();
1006 update();
1007 }
1008
selectUnused()1009 void SymbolRenderWidget::selectUnused()
1010 {
1011 std::vector<bool> symbols_in_use;
1012 map->determineSymbolsInUse(symbols_in_use);
1013
1014 updateSelectedIcons();
1015 selected_symbols.clear();
1016 for (size_t i = 0, end = symbols_in_use.size(); i < end; ++i)
1017 {
1018 if (!symbols_in_use[i])
1019 {
1020 selected_symbols.insert(i);
1021 updateSingleIcon(i);
1022 }
1023 }
1024 emitGuarded_selectedSymbolsChanged();
1025 }
1026
invertSelection()1027 void SymbolRenderWidget::invertSelection()
1028 {
1029 std::set<int> new_set;
1030 for (int i = 0; i < map->getNumSymbols(); ++i)
1031 {
1032 if (!isSymbolSelected(i))
1033 new_set.insert(i);
1034 }
1035 swap(selected_symbols, new_set);
1036 emitGuarded_selectedSymbolsChanged();
1037 update();
1038 }
1039
sortByNumber()1040 void SymbolRenderWidget::sortByNumber()
1041 {
1042 sort(Symbol::lessByNumber);
1043 }
1044
sortByColor()1045 void SymbolRenderWidget::sortByColor()
1046 {
1047 sort(Symbol::lessByColor(map));
1048 }
1049
sortByColorPriority()1050 void SymbolRenderWidget::sortByColorPriority()
1051 {
1052 sort(Symbol::lessByColorPriority);
1053 }
1054
setCustomIconsVisible(bool checked)1055 void SymbolRenderWidget::setCustomIconsVisible(bool checked)
1056 {
1057 for (int i = 0; i < map->getNumSymbols(); ++i)
1058 {
1059 auto symbol = map->getSymbol(i);
1060 if (!symbol->getCustomIcon().isNull())
1061 symbol->resetIcon();
1062 }
1063 Settings::getInstance().setSetting(Settings::SymbolWidget_ShowCustomIcons, checked);
1064 }
1065
showContextMenu(const QPoint & global_pos)1066 void SymbolRenderWidget::showContextMenu(const QPoint& global_pos)
1067 {
1068 updateContextMenuState();
1069 context_menu->popup(global_pos);
1070 }
1071
contextMenuEvent(QContextMenuEvent * event)1072 void SymbolRenderWidget::contextMenuEvent(QContextMenuEvent* event)
1073 {
1074 showContextMenu(event->globalPos());
1075 event->accept();
1076 }
1077
updateContextMenuState()1078 void SymbolRenderWidget::updateContextMenuState()
1079 {
1080 bool have_selection = selectedSymbolsCount() > 0;
1081 bool single_selection = selectedSymbolsCount() == 1 && current_symbol_index >= 0;
1082 const Symbol* single_symbol = singleSelectedSymbol();
1083 bool all_symbols_hidden = have_selection;
1084 bool all_symbols_protected = have_selection;
1085 for (auto symbol_index : selected_symbols)
1086 {
1087 if (!map->getSymbol(symbol_index)->isHidden())
1088 all_symbols_hidden = false;
1089 if (!map->getSymbol(symbol_index)->isProtected())
1090 all_symbols_protected = false;
1091 if (!all_symbols_hidden && !all_symbols_protected)
1092 break;
1093 }
1094
1095 bool single_symbol_compatible;
1096 bool single_symbol_different;
1097 map->getSelectionToSymbolCompatibility(single_symbol, single_symbol_compatible, single_symbol_different);
1098
1099 edit_action->setEnabled(single_selection);
1100 scale_action->setEnabled(have_selection);
1101 copy_action->setEnabled(have_selection);
1102 paste_action->setEnabled(QApplication::clipboard()->mimeData()->hasFormat(MimeType::OpenOrienteeringSymbols()));
1103 switch_symbol_action->setEnabled(single_symbol_compatible && single_symbol_different);
1104 fill_border_action->setEnabled(single_symbol_compatible && single_symbol_different);
1105 hide_action->setEnabled(have_selection);
1106 hide_action->setChecked(all_symbols_hidden);
1107 protect_action->setEnabled(have_selection);
1108 protect_action->setChecked(all_symbols_protected);
1109 duplicate_action->setEnabled(single_selection);
1110 delete_action->setEnabled(have_selection);
1111
1112 if (single_selection)
1113 {
1114 select_objects_action->setText(tr("Select all objects with this symbol"));
1115 select_objects_additionally_action->setText(tr("Add all objects with this symbol to selection"));
1116 deselect_objects_action->setText(tr("Remove all objects with this symbol from selection"));
1117 hide_action->setText(tr("Hide objects with this symbol"));
1118 protect_action->setText(tr("Protect objects with this symbol"));
1119 }
1120 else
1121 {
1122 select_objects_action->setText(tr("Select all objects with selected symbols"));
1123 select_objects_additionally_action->setText(tr("Add all objects with selected symbols to selection"));
1124 deselect_objects_action->setText(tr("Remove all objects with selected symbols from selection"));
1125 hide_action->setText(tr("Hide objects with selected symbols"));
1126 protect_action->setText(tr("Protect objects with selected symbols"));
1127 }
1128 select_objects_action->setEnabled(have_selection);
1129 select_objects_additionally_action->setEnabled(have_selection);
1130 deselect_objects_action->setEnabled(have_selection);
1131
1132 show_custom_icons->setChecked(Settings::getInstance().getSetting(Settings::SymbolWidget_ShowCustomIcons).toBool());
1133 }
1134
newSymbol(Symbol * prototype)1135 bool SymbolRenderWidget::newSymbol(Symbol* prototype)
1136 {
1137 SymbolSettingDialog dialog(prototype, map, this);
1138 dialog.setWindowModality(Qt::WindowModal);
1139 delete prototype;
1140 if (dialog.exec() == QDialog::Rejected)
1141 return false;
1142
1143 int pos = (current_symbol_index >= 0) ? current_symbol_index : map->getNumSymbols();
1144 map->addSymbol(dialog.getNewSymbol().release(), pos);
1145 // Ensure that a change in selection is detected
1146 selectSingleSymbol(-1);
1147 selectSingleSymbol(pos);
1148 return true;
1149 }
1150
1151 template<typename T>
sort(T compare)1152 void SymbolRenderWidget::sort(T compare)
1153 {
1154 // save selection
1155 std::set<const Symbol*> saved_selection;
1156 for (auto symbol_index : selected_symbols)
1157 {
1158 saved_selection.insert(map->getSymbol(symbol_index));
1159 }
1160
1161 map->sortSymbols(compare);
1162
1163 // restore selection
1164 selected_symbols.clear();
1165 for (int i = 0; i < map->getNumSymbols(); ++i)
1166 {
1167 if (saved_selection.find(map->getSymbol(i)) != saved_selection.end())
1168 selected_symbols.insert(i);
1169 }
1170
1171 update();
1172 }
1173
1174
1175 } // namespace OpenOrienteering
1176