1 /*
2  *    Copyright 2012, 2013 Thomas Schöps
3  *    Copyright 2012-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 #ifdef QT_PRINTSUPPORT_LIB
23 
24 #include "print_widget.h"
25 
26 #include <limits>
27 #include <memory>
28 // IWYU pragma: no_include <type_traits>
29 
30 #include <Qt>
31 #include <QtGlobal>
32 #include <QAbstractButton> // IWYU pragma: keep
33 #include <QButtonGroup>
34 #include <QCheckBox>
35 #include <QColor>
36 #include <QComboBox>
37 #include <QDialog>
38 #include <QDialogButtonBox>
39 #include <QDoubleSpinBox>
40 #include <QFile>
41 #include <QFileInfo>
42 #include <QFormLayout>
43 #include <QHBoxLayout>
44 #include <QIcon>
45 #include <QImage>
46 #include <QLabel>
47 #include <QLatin1Char>
48 #include <QLatin1String>
49 #include <QLayout>
50 #include <QLineEdit>
51 #include <QMargins>
52 #include <QMessageBox>
53 #include <QPageSize>
54 #include <QPainter>
55 #include <QPointF>
56 #include <QPrinter>
57 #include <QPrinterInfo>
58 #include <QPrintPreviewDialog>
59 #include <QPushButton>
60 #include <QRadioButton>
61 #include <QRectF>
62 #include <QRegExp>
63 #include <QRegExpValidator>
64 #include <QScrollArea>
65 #include <QScrollBar>
66 #include <QSignalBlocker>
67 #include <QSizeF>
68 #include <QSpacerItem>
69 #include <QSpinBox>
70 #include <QStringRef>
71 #include <QStyle>
72 #include <QStyleOption>
73 #include <QToolButton>
74 #include <QTransform>
75 #include <QVBoxLayout>
76 #include <QVariant>
77 #include <QXmlStreamReader>
78 #include <QXmlStreamWriter>
79 
80 #include <printer_properties.h>
81 
82 #include "core/georeferencing.h"
83 #include "core/map.h"
84 #include "core/map_coord.h"
85 #include "core/map_printer.h"
86 #include "core/map_view.h"
87 #include "gui/file_dialog.h"
88 #include "gui/main_window.h"
89 #include "gui/print_progress_dialog.h"
90 #include "gui/print_tool.h"
91 #include "gui/util_gui.h"
92 #include "gui/map/map_editor.h"
93 #include "gui/map/map_widget.h"
94 #include "templates/template.h" // IWYU pragma: keep
95 #include "templates/world_file.h"
96 #include "util/backports.h"  // IWYU pragma: keep
97 #include "util/scoped_signals_blocker.h"
98 
99 
100 namespace OpenOrienteering {
101 
102 namespace {
103 
createPrintModeButton(const QIcon & icon,const QString & label,QWidget * parent=nullptr)104 	QToolButton* createPrintModeButton(const QIcon& icon, const QString& label, QWidget* parent = nullptr)
105 	{
106 		static const QSize icon_size(48,48);
107 		auto button = new QToolButton(parent);
108 		button->setAutoRaise(true);
109 		button->setCheckable(true);
110 		button->setIconSize(icon_size);
111 		button->setIcon(icon);
112 		button->setText(label);
113 		button->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
114 		return button;
115 	}
116 
117 
118 }  // namespace
119 
120 
121 //### PrintWidget ###
122 
PrintWidget(Map * map,MainWindow * main_window,MapView * main_view,MapEditorController * editor,QWidget * parent)123 PrintWidget::PrintWidget(Map* map, MainWindow* main_window, MapView* main_view, MapEditorController* editor, QWidget* parent)
124 : QWidget     { parent }
125 , task        { UNDEFINED_TASK }
126 , map         { map }
127 , map_printer { new MapPrinter(*map, main_view) }
128 , main_window { main_window }
129 , main_view   { main_view }
130 , editor      { editor }
131 , print_tool  { nullptr }
132 , active      { false }
133 {
134 	Q_ASSERT(main_window);
135 
136 	layout = new QFormLayout();
137 
138 	target_combo = new QComboBox();
139 	target_combo->setMinimumWidth(1); // Not zero, but not as long as the items
140 	layout->addRow(Util::Headline::create(tr("Printer:")), target_combo);
141 
142 	if (PlatformPrinterProperties::dialogSupported())
143 	{
144 		printer_properties_button = new QToolButton();
145 		printer_properties_button->setText(tr("Properties"));
146 		layout->addRow(nullptr, printer_properties_button);
147 	}
148 	else
149 	{
150 		printer_properties_button = nullptr;
151 	}
152 
153 	paper_size_combo = new QComboBox();
154 	layout->addRow(tr("Page format:"), paper_size_combo);
155 
156 	auto page_size_widget = new QWidget();
157 	auto page_size_layout = new QHBoxLayout();
158 	page_size_widget->setLayout(page_size_layout);
159 	page_size_layout->setMargin(0);
160 	page_width_edit = Util::SpinBox::create(1, 0.1, 1000.0, tr("mm"), 1.0);
161 	page_width_edit->setEnabled(false);
162 	page_size_layout->addWidget(page_width_edit, 1);
163 	page_size_layout->addWidget(new QLabel(QString::fromLatin1("x")), 0);
164 	page_height_edit = Util::SpinBox::create(1, 0.1, 1000.0, tr("mm"), 1.0);
165 	page_height_edit->setEnabled(false);
166 	page_size_layout->addWidget(page_height_edit, 1);
167 	layout->addRow({}, page_size_widget);
168 
169 	page_orientation_widget = new QWidget();
170 	auto page_orientation_layout = new QHBoxLayout();
171 	page_orientation_layout->setContentsMargins(QMargins());
172 	page_orientation_widget->setLayout(page_orientation_layout);
173 	auto portrait_button = new QRadioButton(tr("Portrait"));
174 	page_orientation_layout->addWidget(portrait_button);
175 	auto landscape_button = new QRadioButton(tr("Landscape"));
176 	page_orientation_layout->addWidget(landscape_button);
177 	page_orientation_group = new QButtonGroup(this);
178 	page_orientation_group->addButton(portrait_button, QPrinter::Portrait);
179 	page_orientation_group->addButton(landscape_button, QPrinter::Landscape);
180 	layout->addRow(tr("Page orientation:"), page_orientation_widget);
181 
182 	copies_edit = Util::SpinBox::create(1, 99999);
183 	layout->addRow(tr("Copies:"), copies_edit);
184 
185 	layout->addItem(Util::SpacerItem::create(this));
186 
187 	policy_combo = new QComboBox();
188 	policy_combo->addItem(tr("Single page"), SinglePage);
189 	policy_combo->addItem(tr("Custom area"), CustomArea);
190 	layout->addRow(Util::Headline::create(tr("Map area:")), policy_combo); // or print/export area
191 
192 	center_check = new QCheckBox(tr("Center print area"));
193 	layout->addRow(center_check);
194 
195 	left_edit = Util::SpinBox::create(2, -999999.9, 999999.9, tr("mm"), 1.0);
196 	layout->addRow(tr("Left:"), left_edit);
197 
198 	top_edit = Util::SpinBox::create(2, -999999.9, 999999.9, tr("mm"), 1.0);
199 	layout->addRow(tr("Top:"), top_edit);
200 
201 	width_edit = Util::SpinBox::create(2, -999999.9, 999999.9, tr("mm"), 1.0);
202 	layout->addRow(tr("Width:"), width_edit);
203 
204 	height_edit = Util::SpinBox::create(2, -999999.9, 999999.9, tr("mm"), 1.0);
205 	layout->addRow(tr("Height:"), height_edit);
206 
207 	overlap_edit = Util::SpinBox::create(2, -999999.9, 999999.9, tr("mm"), 1.0);
208 	layout->addRow(tr("Page overlap:"), overlap_edit);
209 
210 	layout->addItem(Util::SpacerItem::create(this));
211 
212 	layout->addRow(Util::Headline::create(tr("Options")));
213 
214 	auto mode_widget = new QWidget();
215 	auto mode_layout = new QHBoxLayout();
216 	mode_widget->setLayout(mode_layout);
217 	mode_layout->setMargin(0);
218 
219 	vector_mode_button = createPrintModeButton(QIcon(QString::fromLatin1(":/images/print-mode-vector.png")), tr("Vector\ngraphics"));
220 	raster_mode_button = createPrintModeButton(QIcon(QString::fromLatin1(":/images/print-mode-raster.png")), tr("Raster\ngraphics"));
221 	separations_mode_button = createPrintModeButton(QIcon(QString::fromLatin1(":/images/print-mode-separations.png")), tr("Color\nseparations"));
222 	vector_mode_button->setChecked(true);
223 
224 	auto mode_button_group = new QButtonGroup(this);
225 	mode_button_group->addButton(vector_mode_button);
226 	mode_button_group->addButton(raster_mode_button);
227 	mode_button_group->addButton(separations_mode_button);
228 
229 	mode_layout->addWidget(vector_mode_button);
230 	mode_layout->addWidget(raster_mode_button);
231 	mode_layout->addWidget(separations_mode_button);
232 	mode_layout->addStretch(1);
233 
234 	layout->addRow(tr("Mode:"), mode_widget);
235 
236 	color_mode_combo = new QComboBox();
237 	color_mode_combo->setEditable(false);
238 	color_mode_combo->addItem(tr("Default"), QVariant());
239 	color_mode_combo->addItem(tr("Device CMYK"), QVariant(true));
240 	layout->addRow(tr("Color mode:"), color_mode_combo);
241 
242 	dpi_combo = new QComboBox();
243 	dpi_combo->setEditable(true);
244 	dpi_combo->setValidator(new QRegExpValidator(QRegExp(QLatin1String("^[1-9]\\d{0,4}$|^[1-9]\\d{0,4} ")+tr("dpi")+QLatin1Char('$')), dpi_combo));
245 	// TODO: Implement spinbox-style " dpi" suffix
246 	layout->addRow(tr("Resolution:"), dpi_combo);
247 
248 	different_scale_check = new QCheckBox(tr("Print in different scale:"));
249 	// Limit the difference between nominal and printing scale in order to limit the number of page breaks.
250 	int min_scale = qMax(1, int(map->getScaleDenominator() / 10000) * 100);
251 	different_scale_edit = Util::SpinBox::create(min_scale, std::numeric_limits<int>::max(), {}, 500);
252 	different_scale_edit->setPrefix(QString::fromLatin1("1 : "));
253 	different_scale_edit->setEnabled(false);
254 	int different_scale_height = qMax(
255 	  different_scale_edit->minimumSizeHint().height(),
256 	  different_scale_check->minimumSizeHint().height() );
257 	different_scale_check->setMinimumHeight(different_scale_height);
258 	different_scale_edit->setMinimumHeight(different_scale_height);
259 	layout->addRow(different_scale_check, different_scale_edit);
260 
261 	// this must be created before its value is used to determine the default setting of page_orientation_combo
262 	show_templates_check = new QCheckBox(tr("Show templates"));
263 	auto templates_warning_layout = new QHBoxLayout();
264 	QIcon warning_icon = style()->standardIcon(QStyle::SP_MessageBoxWarning);
265 	templates_warning_icon = new QLabel();
266 	int pixmap_size = qBound(8, style()->pixelMetric(QStyle::PM_IndicatorHeight), 32);
267 	templates_warning_icon->setPixmap(warning_icon.pixmap(QSize(pixmap_size, pixmap_size)));
268 	templates_warning_layout->addWidget(templates_warning_icon);
269 	templates_warning_text = new QLabel(tr("Template appearance may differ."));
270 	templates_warning_layout->addWidget(templates_warning_text, 1);
271 	layout->addRow(show_templates_check, templates_warning_layout);
272 
273 	show_grid_check = new QCheckBox(tr("Show grid"));
274 	layout->addRow(show_grid_check);
275 
276 	overprinting_check = new QCheckBox(tr("Simulate overprinting"));
277 	layout->addRow(overprinting_check);
278 
279 	world_file_check = new QCheckBox(tr("Save world file"));
280 	layout->addRow(world_file_check);
281 	world_file_check->hide();
282 
283 	scrolling_content = new QWidget();
284 	scrolling_content->setLayout(layout);
285 
286 	auto outer_layout = new QVBoxLayout();
287 	outer_layout->setContentsMargins(QMargins());
288 
289 	scroll_area = new QScrollArea();
290 	scroll_area->setWidget(scrolling_content);
291 	scroll_area->setWidgetResizable(true);
292 	scroll_area->setMinimumWidth((scrolling_content->sizeHint() + scroll_area->verticalScrollBar()->sizeHint()).width());
293 	outer_layout->addWidget(scroll_area);
294 
295 	button_box = new QDialogButtonBox();
296 	QStyleOption style_option(QStyleOption::Version, QStyleOption::SO_DockWidget);
297 	button_box->layout()->setContentsMargins(
298 	    style()->pixelMetric(QStyle::PM_LayoutLeftMargin, &style_option),
299 	    style()->pixelMetric(QStyle::PM_LayoutTopMargin, &style_option),
300 	    style()->pixelMetric(QStyle::PM_LayoutRightMargin, &style_option),
301 	    style()->pixelMetric(QStyle::PM_LayoutBottomMargin, &style_option)
302 	);
303 	preview_button = new QPushButton(tr("Preview..."));
304 	button_box->addButton(preview_button, QDialogButtonBox::ActionRole);
305 	print_button = new QPushButton(tr("Print"));
306 	button_box->addButton(print_button, QDialogButtonBox::ActionRole);
307 	// Use a distinct export button.
308 	// Changing the text at runtime causes distortions on Mac OS X.
309 	export_button = new QPushButton(tr("Export..."));
310 	export_button->hide();
311 	button_box->addButton(export_button, QDialogButtonBox::ActionRole);
312 	auto close_button = button_box->addButton(QDialogButtonBox::Close);
313 	outer_layout->addWidget(button_box);
314 
315 	setLayout(outer_layout);
316 
317 	connect(target_combo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &PrintWidget::targetChanged);
318 	if (printer_properties_button)
319 		connect(printer_properties_button, &QAbstractButton::clicked, this, &PrintWidget::propertiesClicked, Qt::QueuedConnection);
320 	connect(paper_size_combo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &PrintWidget::paperSizeChanged);
321 	connect(page_width_edit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &PrintWidget::paperDimensionsChanged);
322 	connect(page_orientation_group, QOverload<int>::of(&QButtonGroup::buttonClicked), this, &PrintWidget::pageOrientationChanged);
323 	connect(page_height_edit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &PrintWidget::paperDimensionsChanged);
324 
325 	connect(top_edit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &PrintWidget::printAreaMoved);
326 	connect(left_edit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &PrintWidget::printAreaMoved);
327 	connect(width_edit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &PrintWidget::printAreaResized);
328 	connect(height_edit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &PrintWidget::printAreaResized);
329 	connect(overlap_edit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &PrintWidget::overlapEdited);
330 
331 	connect(mode_button_group, QOverload<QAbstractButton*>::of(&QButtonGroup::buttonClicked), this, &PrintWidget::printModeChanged);
332 	connect(dpi_combo->lineEdit(), &QLineEdit::textEdited, this, &PrintWidget::resolutionEdited);
333 	connect(dpi_combo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &PrintWidget::resolutionEdited);
334 	connect(different_scale_check, &QAbstractButton::clicked, this, &PrintWidget::differentScaleClicked);
335 	connect(different_scale_edit, QOverload<int>::of(&QSpinBox::valueChanged), this, &PrintWidget::differentScaleEdited);
336 	connect(show_templates_check, &QAbstractButton::clicked, this, &PrintWidget::showTemplatesClicked);
337 	connect(show_grid_check, &QAbstractButton::clicked, this, &PrintWidget::showGridClicked);
338 	connect(overprinting_check, &QAbstractButton::clicked, this, &PrintWidget::overprintingClicked);
339 	connect(color_mode_combo, &QComboBox::currentTextChanged, this, &PrintWidget::colorModeChanged);
340 
341 	connect(preview_button, &QAbstractButton::clicked, this, &PrintWidget::previewClicked);
342 	connect(print_button, &QAbstractButton::clicked, this, &PrintWidget::printClicked);
343 	connect(export_button, &QAbstractButton::clicked, this, &PrintWidget::printClicked);
344 	connect(close_button, &QAbstractButton::clicked, this, &PrintWidget::closeClicked);
345 
346 	policy = map_printer->config().single_page_print_area ? SinglePage : CustomArea;
347 	policy_combo->setCurrentIndex(policy_combo->findData(policy));
348 	connect(policy_combo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &PrintWidget::printAreaPolicyChanged);
349 
350 	center_check->setChecked(map_printer->config().center_print_area);
351 	connect(center_check, &QAbstractButton::clicked, this, &PrintWidget::applyCenterPolicy);
352 
353 	setPageFormat(map_printer->getPageFormat());
354 	connect(map_printer, &MapPrinter::pageFormatChanged, this, &PrintWidget::setPageFormat);
355 
356 	connect(map_printer, &MapPrinter::optionsChanged, this, &PrintWidget::setOptions);
357 	spotColorPresenceChanged(map->hasSpotColors());
358 	connect(map, &Map::spotColorPresenceChanged, this, &PrintWidget::spotColorPresenceChanged);
359 
360 	setPrintArea(map_printer->getPrintArea());
361 	connect(map_printer, &MapPrinter::printAreaChanged, this, &PrintWidget::setPrintArea);
362 
363 	connect(map_printer, &MapPrinter::targetChanged, this, &PrintWidget::setTarget);
364 
365 	connect(this, &PrintWidget::finished, this, &PrintWidget::savePrinterConfig);
366 }
367 
~PrintWidget()368 PrintWidget::~PrintWidget()
369 {
370 	delete map_printer;
371 }
372 
sizeHint() const373 QSize PrintWidget::sizeHint() const
374 {
375 	QSize size = QWidget::sizeHint();
376 	size.setHeight(scrolling_content->sizeHint().height() +
377 	               2 * scroll_area->frameWidth() +
378 	               button_box->sizeHint().height() +
379 	               layout->horizontalSpacing() );
380 	return size;
381 }
382 
383 // slot
setTask(PrintWidget::TaskFlags type)384 void PrintWidget::setTask(PrintWidget::TaskFlags type)
385 {
386 	if (task != type)
387 	{
388 		task = type;
389 		bool is_print_task = type==PRINT_TASK;
390 		bool is_multipage  = type.testFlag(MULTIPAGE_FLAG);
391 		layout->labelForField(target_combo)->setVisible(is_print_task);
392 		target_combo->setVisible(is_print_task);
393 		layout->labelForField(copies_edit)->setVisible(is_multipage);
394 		copies_edit->setVisible(is_multipage);
395 		policy_combo->setVisible(is_multipage);
396 		updateTargets();
397 		switch (type)
398 		{
399 			case PRINT_TASK:
400 				// Reset values which are typically modified for exporting
401 				if (policy == SinglePage && !map->printerConfig().single_page_print_area)
402 				{
403 					policy = CustomArea;
404 					policy_combo->setCurrentIndex(policy_combo->findData(policy));
405 				}
406 				if (map_printer->getPageFormat().page_size == QPageSize::Custom)
407 				{
408 					map_printer->setPageSize(map->printerConfig().page_format.page_size);
409 				}
410 				// TODO: Set target to most recently used printer
411 				emit taskChanged(tr("Print"));
412 				break;
413 
414 			case EXPORT_PDF_TASK:
415 				map_printer->setTarget(MapPrinter::pdfTarget());
416 				if (active)
417 					setOptions(map_printer->getOptions());
418 				emit taskChanged(tr("PDF export"));
419 				break;
420 
421 			case EXPORT_IMAGE_TASK:
422 				map_printer->setTarget(MapPrinter::imageTarget());
423 				if (active)
424 					setOptions(map_printer->getOptions());
425 				policy = SinglePage;
426 				if (policy_combo->itemData(policy_combo->currentIndex()) != policy)
427 				{
428 					map_printer->setCustomPageSize(map_printer->getPrintAreaPaperSize());
429 					policy_combo->setCurrentIndex(policy_combo->findData(policy));
430 				}
431 				emit taskChanged(tr("Image export"));
432 				break;
433 
434 			default:
435 				emit taskChanged(QString{});
436 		}
437 	}
438 }
439 
440 // slot
savePrinterConfig() const441 void PrintWidget::savePrinterConfig() const
442 {
443 	MapPrinterConfig printer_config(map_printer->config());
444 	printer_config.center_print_area = center_check->isChecked();
445 	if (task.testFlag(MULTIPAGE_FLAG))
446 	{
447 		printer_config.single_page_print_area = policy == SinglePage;
448 	}
449 	if (task.testFlag(EXPORT_IMAGE_TASK))
450 	{
451 		// Don't override the printer page format from the custom image format.
452 		printer_config.page_format = map->printerConfig().page_format;
453 	}
454 	map->setPrinterConfig(printer_config);
455 }
456 
457 // slot
setActive(bool active)458 void PrintWidget::setActive(bool active)
459 {
460 	if (this->active != active)
461 	{
462 		this->active = active;
463 
464 		if (active)
465 		{
466 			// Save the current state of the map view.
467 			saved_view_state.clear();
468 			QXmlStreamWriter writer(&saved_view_state);
469 			main_view->save(writer, QLatin1String("saved_view"), false);
470 
471 			editor->setViewOptionsEnabled(false);
472 
473 			// Printers may have been added or removed.
474 			updateTargets();
475 
476 			// Update the map view from the current options
477 			setOptions(map_printer->getOptions());
478 			connect(main_view, &MapView::visibilityChanged, this, &PrintWidget::onVisibilityChanged);
479 
480 			// Set reasonable zoom.
481 			bool zoom_to_map = true;
482 			if (zoom_to_map)
483 			{
484 				// Ensure the visibility of the whole map.
485 				auto const map_extent = map->calculateExtent(true, !main_view->areAllTemplatesHidden(), main_view);
486 				editor->getMainWidget()->ensureVisibilityOfRect(map_extent, MapWidget::ContinuousZoom);
487 			}
488 			else
489 			{
490 				// Ensure the visibility of the print area.
491 				auto const print_area = map_printer->getPrintArea();
492 				editor->getMainWidget()->ensureVisibilityOfRect(print_area, MapWidget::ContinuousZoom);
493 			}
494 
495 			// Activate PrintTool.
496 			if (!print_tool)
497 			{
498 				print_tool = new PrintTool(editor, map_printer);
499 			}
500 			editor->setOverrideTool(print_tool);
501 			editor->setEditingInProgress(true);
502 		}
503 		else
504 		{
505 			disconnect(main_view, &MapView::visibilityChanged, this, &PrintWidget::onVisibilityChanged);
506 
507 			editor->setEditingInProgress(false);
508 			editor->setOverrideTool(nullptr);
509 			print_tool = nullptr;
510 
511 			// Restore view
512 			QXmlStreamReader reader(saved_view_state);
513 			reader.readNextStartElement();
514 			main_view->load(reader);
515 
516 			editor->setViewOptionsEnabled(true);
517 		}
518 	}
519 }
520 
521 
522 
updateTargets()523 void PrintWidget::updateTargets()
524 {
525 	QVariant current_target = target_combo->itemData(target_combo->currentIndex());
526 	const auto* saved_printer = map_printer->getTarget();
527 	const QString saved_printer_name = saved_printer ? saved_printer->printerName() : QString{};
528 	int saved_target_index = -1;
529 	int default_printer_index = -1;
530 
531 	const QSignalBlocker block(target_combo);
532 	target_combo->clear();
533 
534 	if (task == PRINT_TASK)
535 	{
536 		// Exporters
537 		target_combo->addItem(tr("Save to PDF"), QVariant(int(PdfExporter)));
538 		target_combo->insertSeparator(target_combo->count());
539 		target_combo->setCurrentIndex(0);
540 
541 		// Printers
542 		auto default_printer_name = QPrinterInfo::defaultPrinterName();
543 		printers = QPrinterInfo::availablePrinterNames();
544 		for (int i = 0; i < printers.size(); ++i)
545 		{
546 			const QString& name = printers[i];
547 			if (name == saved_printer_name)
548 				saved_target_index = target_combo->count();
549 			if (name == default_printer_name)
550 				default_printer_index = target_combo->count();
551 			target_combo->addItem(name, i);
552 		}
553 	}
554 
555 	// Restore selected target if possible and exit on success
556 	if (current_target.isValid())
557 	{
558 		int index = target_combo->findData(current_target);
559 		if (index >= 0)
560 		{
561 			target_combo->setCurrentIndex(index);
562 			return;
563 		}
564 	}
565 
566 	if (saved_target_index >= 0)
567 		// Restore saved target if possible
568 		target_combo->setCurrentIndex(saved_target_index);
569 	else if (default_printer_index >= 0)
570 		// Set default printer as current target
571 		target_combo->setCurrentIndex(default_printer_index);
572 
573 	// Explicitly invoke signal handler
574 	targetChanged(target_combo->currentIndex());
575 }
576 
577 // slot
setTarget(const QPrinterInfo * target)578 void PrintWidget::setTarget(const QPrinterInfo* target)
579 {
580 	int target_index = printers.size()-1;
581 	if (target == MapPrinter::pdfTarget())
582 	{
583 		target_index = PdfExporter;
584 	}
585 	else if (target == MapPrinter::imageTarget())
586 	{
587 		target_index = ImageExporter;
588 	}
589 	else
590 	{
591 		for (; target_index >= 0; target_index--)
592 		{
593 			if (target && printers[target_index] == target->printerName())
594 				break;
595 			if (!target)
596 				break;
597 		}
598 	}
599 	target_combo->setCurrentIndex(target_combo->findData(QVariant(target_index)));
600 
601 	updatePaperSizes(target);
602 	updateResolutions(target);
603 
604 	bool supports_pages = (target != MapPrinter::imageTarget());
605 	bool supports_copies = (supports_pages && target && QPrinter(*target).supportsMultipleCopies());
606 	copies_edit->setEnabled(supports_copies);
607 	layout->labelForField(copies_edit)->setEnabled(supports_copies);
608 
609 	bool is_printer = map_printer->isPrinter();
610 	print_button->setVisible(is_printer);
611 	print_button->setDefault(is_printer);
612 	export_button->setVisible(!is_printer);
613 	export_button->setDefault(!is_printer);
614 	if (printer_properties_button)
615 		printer_properties_button->setEnabled(is_printer);
616 
617 	bool is_image_target = target == MapPrinter::imageTarget();
618 	vector_mode_button->setEnabled(!is_image_target);
619 	separations_mode_button->setEnabled(!is_image_target && map->hasSpotColors());
620 	if (is_image_target)
621 	{
622 		raster_mode_button->setChecked(true);
623 		printModeChanged(raster_mode_button);
624 	}
625 
626 	world_file_check->setVisible(is_image_target);
627 	// If MapCoord (0,0) maps to projected (0,0), then there is probably
628 	// no point in writing a world file.
629 	world_file_check->setChecked(!map->getGeoreferencing().toProjectedCoords(MapCoordF{}).isNull());
630 
631 	updateColorMode();
632 }
633 
634 // slot
targetChanged(int index) const635 void PrintWidget::targetChanged(int index) const
636 {
637 	if (index < 0)
638 		return;
639 
640 	int target_index = target_combo->itemData(index).toInt();
641 	Q_ASSERT(target_index >= -2);
642 	Q_ASSERT(target_index < printers.size());
643 
644 	if (target_index == PdfExporter)
645 		map_printer->setTarget(MapPrinter::pdfTarget());
646 	else if (target_index == ImageExporter)
647 		map_printer->setTarget(MapPrinter::imageTarget());
648 	else
649 	{
650 		auto info = QPrinterInfo::printerInfo(printers[target_index]);
651 		map_printer->setTarget(&info);
652 	}
653 }
654 
655 // slot
propertiesClicked()656 void PrintWidget::propertiesClicked()
657 {
658 	if (map_printer && map_printer->isPrinter())
659 	{
660 		std::shared_ptr<void> buffer; // must not be destroyed before printer.
661 		auto printer = map_printer->makePrinter();
662 		Q_ASSERT(printer->outputFormat() == QPrinter::NativeFormat);
663 		if (PlatformPrinterProperties::execDialog(printer.get(), buffer, this) == QDialog::Accepted)
664 			map_printer->takePrinterSettings(printer.get());
665 	}
666 }
667 
updatePaperSizes(const QPrinterInfo * target) const668 void PrintWidget::updatePaperSizes(const QPrinterInfo* target) const
669 {
670 	QString prev_paper_size_name = paper_size_combo->currentText();
671 	bool have_custom_size = false;
672 
673 	const QSignalBlocker block(paper_size_combo);
674 
675 	paper_size_combo->clear();
676 	QList<QPageSize> size_list;
677 	if (target)
678 		size_list = target->supportedPageSizes();
679 	if (size_list.isEmpty())
680 		size_list = defaultPageSizes();
681 
682 	for (auto const & size : qAsConst(size_list))
683 	{
684 		if (size.id() == QPageSize::Custom)
685 			have_custom_size = true; // add it once after all other entries
686 		else
687 			paper_size_combo->addItem(size.name(), size.id());
688 	}
689 
690 	if (have_custom_size)
691 		paper_size_combo->addItem(QPageSize(QPageSize::Custom).name(), QPageSize::Custom);
692 
693 	int paper_size_index = paper_size_combo->findData(map_printer->getPageFormat().page_size);
694 	if (!prev_paper_size_name.isEmpty())
695 	{
696 		paper_size_index = paper_size_combo->findText(prev_paper_size_name);
697 	}
698 	paper_size_combo->setCurrentIndex(qMax(0, paper_size_index));
699 	paperSizeChanged(paper_size_combo->currentIndex());
700 }
701 
702 // slot
setPageFormat(const MapPrinterPageFormat & format)703 void PrintWidget::setPageFormat(const MapPrinterPageFormat& format)
704 {
705 	ScopedMultiSignalsBlocker block(
706 	            paper_size_combo, page_orientation_group,
707 	            page_width_edit, page_height_edit,
708 	            overlap_edit
709 	);
710 	paper_size_combo->setCurrentIndex(paper_size_combo->findData(format.page_size));
711 	page_orientation_group->button(format.orientation)->setChecked(true);
712 	page_width_edit->setValue(format.paper_dimensions.width());
713 	page_width_edit->setEnabled(format.page_size == QPageSize::Custom);
714 	page_height_edit->setValue(format.paper_dimensions.height());
715 	page_height_edit->setEnabled(format.page_size == QPageSize::Custom);
716 	// We only have a single overlap edit field, but MapPrinter supports
717 	// distinct horizontal and vertical overlap. Choose the minimum.
718 	overlap_edit->setValue(qMin(format.h_overlap, format.v_overlap));
719 	applyPrintAreaPolicy();
720 }
721 
722 // slot
paperSizeChanged(int index) const723 void PrintWidget::paperSizeChanged(int index) const
724 {
725 	if (index >= 0)
726 	{
727 		auto paper_size = QPageSize::PageSizeId(paper_size_combo->itemData(index).toInt());
728 		map_printer->setPageSize(paper_size);
729 	}
730 }
731 
732 // slot
paperDimensionsChanged() const733 void PrintWidget::paperDimensionsChanged() const
734 {
735 	const QSizeF dimensions(page_width_edit->value(), page_height_edit->value());
736 	map_printer->setCustomPageSize(dimensions);
737 }
738 
739 // slot
pageOrientationChanged(int id) const740 void PrintWidget::pageOrientationChanged(int id) const
741 {
742 	if (id == QPrinter::Portrait || id == QPrinter::Landscape)
743 	{
744 		map_printer->setPageOrientation((id == QPrinter::Portrait) ? MapPrinterPageFormat::Portrait : MapPrinterPageFormat::Landscape);
745 	}
746 }
747 
748 
749 // slot
printAreaPolicyChanged(int index)750 void PrintWidget::printAreaPolicyChanged(int index)
751 {
752 	policy = PrintAreaPolicy(policy_combo->itemData(index).toInt());
753 	applyPrintAreaPolicy();
754 }
755 
756 
757 // slot
applyPrintAreaPolicy() const758 void PrintWidget::applyPrintAreaPolicy() const
759 {
760 	if (policy == SinglePage)
761 	{
762 		setOverlapEditEnabled(false);
763 		auto print_area = map_printer->getPrintArea();
764 		const auto center = print_area.center();
765 		print_area.setSize(map_printer->getPageRectPrintAreaSize());
766 		if (center_check->isChecked())
767 			centerOnMap(print_area);
768 		else
769 			print_area.moveCenter(center);
770 		map_printer->setPrintArea(print_area);
771 	}
772 	else
773 	{
774 		setOverlapEditEnabled(true);
775 	}
776 }
777 
778 // slot
applyCenterPolicy() const779 void PrintWidget::applyCenterPolicy() const
780 {
781 	if (center_check->isChecked())
782 	{
783 		auto print_area = map_printer->getPrintArea();
784 		centerOnMap(print_area);
785 		map_printer->setPrintArea(print_area);
786 	}
787 }
788 
centerOnMap(QRectF & area) const789 void PrintWidget::centerOnMap(QRectF& area) const
790 {
791 	auto map_extent = map->calculateExtent(false, show_templates_check->isChecked(), main_view);
792 	area.moveLeft(map_extent.center().x() - area.width() / 2);
793 	area.moveTop(map_extent.center().y() - area.height() / 2);
794 }
795 
796 
797 
798 // slot
setPrintArea(const QRectF & area)799 void PrintWidget::setPrintArea(const QRectF& area)
800 {
801 	ScopedMultiSignalsBlocker block(
802 	            left_edit, top_edit,
803 	            width_edit, height_edit
804 	);
805 
806 	left_edit->setValue(area.left());
807 	top_edit->setValue(-area.top()); // Flip sign!
808 	width_edit->setValue(area.width());
809 	height_edit->setValue(area.height());
810 
811 	if (center_check->isChecked())
812 	{
813 		auto centered_area = map_printer->getPrintArea();
814 		centerOnMap(centered_area);
815 		if ( qAbs(centered_area.left() - area.left()) > 0.005 ||
816 			 qAbs(centered_area.top()  - area.top())  > 0.005 )
817 		{
818 			// No longer centered.
819 			center_check->setChecked(false);
820 		}
821 	}
822 
823 	if (policy == SinglePage)
824 	{
825 		if (map_printer->getPageFormat().page_size == QPageSize::Custom || !task.testFlag(MULTIPAGE_FLAG))
826 		{
827 			// Update custom paper size from print area size
828 			QSizeF area_dimensions = area.size() * map_printer->getScaleAdjustment();
829 			if (map_printer->getPageFormat().page_rect.size() != area_dimensions)
830 			{
831 				// Don't force a custom paper size unless necessary
832 				map_printer->setCustomPageSize(area_dimensions);
833 			}
834 		}
835 		else
836 		{
837 			QSizeF page_dimensions = map_printer->getPageRectPrintAreaSize();
838 			if ( qAbs(area.width()  - page_dimensions.width())  > 0.005 ||
839 				 qAbs(area.height() - page_dimensions.height()) > 0.005 )
840 			{
841 				// No longer single page.
842 				block << policy_combo;
843 				policy = CustomArea;
844 				policy_combo->setCurrentIndex(policy_combo->findData(policy));
845 				center_check->setChecked(false);
846 				setOverlapEditEnabled(true);
847 			}
848 		}
849 	}
850 }
851 
852 // slot
printAreaMoved()853 void PrintWidget::printAreaMoved()
854 {
855 	auto area = map_printer->getPrintArea();
856 	area.moveLeft(left_edit->value());
857 	area.moveTop(-top_edit->value()); // Flip sign!
858 	map_printer->setPrintArea(area);
859 }
860 
861 // slot
printAreaResized()862 void PrintWidget::printAreaResized()
863 {
864 	auto area = map_printer->getPrintArea();
865 	area.setWidth(width_edit->value());
866 	area.setHeight(height_edit->value());
867 	map_printer->setPrintArea(area);
868 }
869 
870 // slot
overlapEdited(double overlap)871 void PrintWidget::overlapEdited(double overlap)
872 {
873 	map_printer->setOverlap(overlap, overlap);
874 }
875 
setOverlapEditEnabled(bool state) const876 void PrintWidget::setOverlapEditEnabled(bool state) const
877 {
878 	overlap_edit->setEnabled(state);
879 	layout->labelForField(overlap_edit)->setEnabled(state);
880 }
881 
882 
883 // slot
setOptions(const MapPrinterOptions & options)884 void PrintWidget::setOptions(const MapPrinterOptions& options)
885 {
886 	using namespace Util::TristateCheckbox;
887 
888 	ScopedMultiSignalsBlocker block(
889 	            dpi_combo->lineEdit(),
890 	            show_templates_check,
891 	            show_grid_check,
892 	            overprinting_check,
893 	            color_mode_combo,
894 	            vector_mode_button,
895 	            raster_mode_button,
896 	            separations_mode_button,
897 	            different_scale_check,
898 	            different_scale_edit
899 	);
900 
901 	switch (options.mode)
902 	{
903 	case MapPrinterOptions::Vector:
904 		vector_mode_button->setChecked(true);
905 		setEnabledAndChecked(show_templates_check, options.show_templates);
906 		setEnabledAndChecked(show_grid_check,      options.show_grid);
907 		setDisabledAndChecked(overprinting_check,  options.simulate_overprinting);
908 		main_view->setAllTemplatesHidden(!options.show_templates);
909 		main_view->setGridVisible(options.show_grid);
910 		main_view->setOverprintingSimulationEnabled(false);
911 		break;
912 	case MapPrinterOptions::Raster:
913 		raster_mode_button->setChecked(true);
914 		setEnabledAndChecked(show_templates_check, options.show_templates);
915 		setEnabledAndChecked(show_grid_check,      options.show_grid);
916 		setEnabledAndChecked(overprinting_check,   options.simulate_overprinting);
917 		main_view->setAllTemplatesHidden(!options.show_templates);
918 		main_view->setGridVisible(options.show_grid);
919 		main_view->setOverprintingSimulationEnabled(options.simulate_overprinting);
920 		break;
921 	case MapPrinterOptions::Separations:
922 		separations_mode_button->setChecked(true);
923 		setDisabledAndChecked(show_templates_check, options.show_templates);
924 		setDisabledAndChecked(show_grid_check,      options.show_grid);
925 		setDisabledAndChecked(overprinting_check,   options.simulate_overprinting);
926 		main_view->setAllTemplatesHidden(true);
927 		main_view->setGridVisible(false);
928 		main_view->setOverprintingSimulationEnabled(true);
929 		break;
930 	}
931 
932 	switch (options.color_mode)
933 	{
934 	case MapPrinterOptions::DefaultColorMode:
935 		color_mode_combo->setCurrentIndex(0);
936 		break;
937 	case MapPrinterOptions::DeviceCmyk:
938 		color_mode_combo->setCurrentIndex(1);
939 		break;
940 	}
941 
942 	checkTemplateConfiguration();
943 	updateColorMode();
944 
945 	static QString dpi_template(QLatin1String("%1 ") + tr("dpi"));
946 	dpi_combo->setEditText(dpi_template.arg(options.resolution));
947 
948 	if (options.scale != map->getScaleDenominator())
949 	{
950 		different_scale_check->setChecked(true);
951 		different_scale_edit->setEnabled(true);
952 	}
953 
954 	auto scale = int(options.scale);
955 	different_scale_edit->setValue(scale);
956 	differentScaleEdited(scale);
957 
958 	if (options.mode != MapPrinterOptions::Raster
959 	    && map_printer->engineWillRasterize())
960 	{
961 		QMessageBox::warning(this, tr("Error"),
962 		                     tr("The map contains transparent elements"
963 		                        " which require the raster mode."));
964 		map_printer->setMode(MapPrinterOptions::Raster);
965 	}
966 }
967 
onVisibilityChanged()968 void PrintWidget::onVisibilityChanged()
969 {
970 	map_printer->setPrintTemplates(!main_view->areAllTemplatesHidden());
971 	map_printer->setPrintGrid(main_view->isGridVisible());
972 	map_printer->setSimulateOverprinting(main_view->isOverprintingSimulationEnabled());
973 }
974 
updateResolutions(const QPrinterInfo * target) const975 void PrintWidget::updateResolutions(const QPrinterInfo* target) const
976 {
977 	static const QList<int> default_resolutions(QList<int>() << 150 << 300 << 600 << 1200);
978 
979 	// Numeric resolution list
980 	QList<int> supported_resolutions;
981 	if (target)
982 	{
983 		QPrinter pr(*target, QPrinter::HighResolution);
984 		supported_resolutions = pr.supportedResolutions();
985 		if (supported_resolutions.size() == 1 && supported_resolutions[0] == 72)
986 		{
987 			// X11/CUPS
988 			supported_resolutions.clear();
989 		}
990 	}
991 	if (supported_resolutions.isEmpty())
992 		supported_resolutions = default_resolutions;
993 
994 	// Resolution list item with unit "dpi"
995 	static QString dpi_template(QLatin1String("%1 ") + tr("dpi"));
996 	QStringList resolutions;
997 	resolutions.reserve(supported_resolutions.size());
998 	for (auto resolution : qAsConst(supported_resolutions))
999 		resolutions << dpi_template.arg(resolution);
1000 
1001 	QString dpi_text = dpi_combo->currentText();
1002 	{
1003 		const QSignalBlocker block(dpi_combo);
1004 		dpi_combo->clear();
1005 		dpi_combo->addItems(resolutions);
1006 	}
1007 	dpi_combo->lineEdit()->setText(dpi_text.isEmpty() ? dpi_template.arg(600) : dpi_text);
1008 }
1009 
updateColorMode()1010 void PrintWidget::updateColorMode()
1011 {
1012 	bool enable = map_printer->getTarget() == MapPrinter::pdfTarget()
1013 	              && !raster_mode_button->isChecked();
1014 	color_mode_combo->setEnabled(enable);
1015 	layout->labelForField(color_mode_combo)->setEnabled(enable);
1016 	if (!enable)
1017 		color_mode_combo->setCurrentIndex(0);
1018 }
1019 
1020 // slot
resolutionEdited()1021 void PrintWidget::resolutionEdited()
1022 {
1023 	auto resolution_text = dpi_combo->currentText();
1024 	auto index_of_space = resolution_text.indexOf(QLatin1Char(' '));
1025 	auto dpi_value = resolution_text.leftRef(index_of_space).toInt();
1026 	if (dpi_value > 0)
1027 	{
1028 		auto pos = dpi_combo->lineEdit()->cursorPosition();
1029 		map_printer->setResolution(dpi_value);
1030 		dpi_combo->lineEdit()->setCursorPosition(pos);
1031 	}
1032 }
1033 
1034 // slot
differentScaleClicked(bool checked)1035 void PrintWidget::differentScaleClicked(bool checked)
1036 {
1037 	if (!checked)
1038 		different_scale_edit->setValue(int(map->getScaleDenominator()));
1039 
1040 	different_scale_edit->setEnabled(checked);
1041 }
1042 
1043 // slot
differentScaleEdited(int value)1044 void PrintWidget::differentScaleEdited(int value)
1045 {
1046 	map_printer->setScale(static_cast<unsigned int>(value));
1047 	applyPrintAreaPolicy();
1048 
1049 	if (different_scale_edit->value() < 500)
1050 	{
1051 		different_scale_edit->setSingleStep(500 - different_scale_edit->value());
1052 	}
1053 	else
1054 	{
1055 		different_scale_edit->setSingleStep(500);
1056 	}
1057 }
1058 
1059 // slot
spotColorPresenceChanged(bool has_spot_colors)1060 void PrintWidget::spotColorPresenceChanged(bool has_spot_colors)
1061 {
1062 	separations_mode_button->setEnabled(has_spot_colors);
1063 	if (!has_spot_colors && separations_mode_button->isChecked())
1064 	{
1065 		map_printer->setMode(MapPrinterOptions::Vector);
1066 	}
1067 }
1068 
1069 // slot
printModeChanged(QAbstractButton * button)1070 void PrintWidget::printModeChanged(QAbstractButton* button)
1071 {
1072 	if (button == vector_mode_button)
1073 	{
1074 		map_printer->setMode(MapPrinterOptions::Vector);
1075 	}
1076 	else if (button == raster_mode_button)
1077 	{
1078 		map_printer->setMode(MapPrinterOptions::Raster);
1079 	}
1080 	else
1081 	{
1082 		map_printer->setMode(MapPrinterOptions::Separations);
1083 	}
1084 }
1085 
1086 // slot
showTemplatesClicked(bool checked)1087 void PrintWidget::showTemplatesClicked(bool checked)
1088 {
1089 	map_printer->setPrintTemplates(checked);
1090 	checkTemplateConfiguration();
1091 }
1092 
checkTemplateConfiguration()1093 void PrintWidget::checkTemplateConfiguration()
1094 {
1095 	bool visibility = map_printer->engineMayRasterize() && show_templates_check->isChecked();
1096 	templates_warning_icon->setVisible(visibility);
1097 	templates_warning_text->setVisible(visibility);
1098 }
1099 
1100 // slot
showGridClicked(bool checked)1101 void PrintWidget::showGridClicked(bool checked)
1102 {
1103 	map_printer->setPrintGrid(checked);
1104 }
1105 
1106 // slot
overprintingClicked(bool checked)1107 void PrintWidget::overprintingClicked(bool checked)
1108 {
1109 	map_printer->setSimulateOverprinting(checked);
1110 }
1111 
colorModeChanged()1112 void PrintWidget::colorModeChanged()
1113 {
1114 	if (color_mode_combo->currentData().toBool())
1115 		map_printer->setColorMode(MapPrinterOptions::DeviceCmyk);
1116 	else
1117 		map_printer->setColorMode(MapPrinterOptions::DefaultColorMode);
1118 }
1119 
1120 // slot
previewClicked()1121 void PrintWidget::previewClicked()
1122 {
1123 #if defined(Q_OS_ANDROID)
1124 	// Qt for Android has no QPrintPreviewDialog
1125 	QMessageBox::warning(this, tr("Error"), tr("Not supported on Android."));
1126 #else
1127 	if (checkForEmptyMap())
1128 		return;
1129 
1130 	auto printer = map_printer->makePrinter();
1131 	if (!printer)
1132 	{
1133 		QMessageBox::warning(this, tr("Error"), tr("Failed to prepare the preview."));
1134 		return;
1135 	}
1136 
1137 	printer->setCreator(main_window->appName());
1138 	printer->setDocName(QFileInfo(main_window->currentPath()).baseName());
1139 
1140 	QPrintPreviewDialog preview(printer.get(), editor->getWindow());
1141 	preview.setWindowModality(Qt::ApplicationModal); // Required for OSX, cf. QTBUG-40112
1142 
1143 	PrintProgressDialog progress(map_printer, editor->getWindow());
1144 	progress.setWindowTitle(tr("Print Preview Progress"));
1145 	connect(&preview, &QPrintPreviewDialog::paintRequested, &progress, &PrintProgressDialog::paintRequested);
1146 	// Doesn't work as expected, on OSX at least.
1147 	//connect(&progress, &QProgressDialog::canceled, &preview, &QPrintPreviewDialog::reject);
1148 
1149 	preview.exec();
1150 #endif
1151 }
1152 
1153 // slot
printClicked()1154 void PrintWidget::printClicked()
1155 {
1156 	if (checkForEmptyMap())
1157 		return;
1158 
1159 	if (map->isAreaHatchingEnabled() || map->isBaselineViewEnabled())
1160 	{
1161 		if (QMessageBox::question(this, tr("Warning"),
1162 		                          tr("A non-standard view mode is activated. "
1163 		                             "Are you sure to print / export the map like this?"),
1164 		                          QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel)
1165 			return;
1166 	}
1167 
1168 	if (map_printer->getTarget() == MapPrinter::imageTarget())
1169 		exportToImage();
1170 	else if (map_printer->getTarget() == MapPrinter::pdfTarget())
1171 		exportToPdf();
1172 	else
1173 		print();
1174 }
1175 
exportToImage()1176 void PrintWidget::exportToImage()
1177 {
1178 	static const QString filter_template(QString::fromLatin1("%1 (%2)"));
1179 	QStringList filters = { filter_template.arg(tr("PNG"), QString::fromLatin1("*.png")),
1180 	                        filter_template.arg(tr("BMP"), QString::fromLatin1("*.bmp")),
1181 	                        filter_template.arg(tr("TIFF"), QString::fromLatin1("*.tif *.tiff")),
1182 	                        filter_template.arg(tr("JPEG"), QString::fromLatin1("*.jpg *.jpeg")),
1183 	                        tr("All files (*.*)") };
1184 	QString path = FileDialog::getSaveFileName(this, tr("Export map ..."), {}, filters.join(QString::fromLatin1(";;")));
1185 	if (path.isEmpty())
1186 		return;
1187 
1188 	if (!path.endsWith(QLatin1String(".png"), Qt::CaseInsensitive)
1189 	    && !path.endsWith(QLatin1String(".bmp"), Qt::CaseInsensitive)
1190 	    && !path.endsWith(QLatin1String(".tif"), Qt::CaseInsensitive) && !path.endsWith(QLatin1String(".tiff"), Qt::CaseInsensitive)
1191 	    && !path.endsWith(QLatin1String(".jpg"), Qt::CaseInsensitive) && !path.endsWith(QLatin1String(".jpeg"), Qt::CaseInsensitive) )
1192 	{
1193 		path.append(QString::fromLatin1(".png"));
1194 	}
1195 
1196 	qreal pixel_per_mm = map_printer->getOptions().resolution / 25.4;
1197 	int print_width = qRound(map_printer->getPrintAreaPaperSize().width() * pixel_per_mm);
1198 	int print_height = qRound(map_printer->getPrintAreaPaperSize().height() * pixel_per_mm);
1199 	QImage image(print_width, print_height, QImage::Format_ARGB32_Premultiplied);
1200 	if (image.isNull())
1201 	{
1202 		QMessageBox::warning(this, tr("Error"), tr("Failed to prepare the image. Not enough memory."));
1203 		return;
1204 	}
1205 
1206 	int dots_per_meter = qRound(pixel_per_mm * 1000);
1207 	image.setDotsPerMeterX(dots_per_meter);
1208 	image.setDotsPerMeterY(dots_per_meter);
1209 
1210 	image.fill(QColor(Qt::white));
1211 
1212 #if 0  // Pointless unless drawPage drives the event loop and sends progress
1213 	PrintProgressDialog progress(map_printer, main_window);
1214 	progress.setWindowTitle(tr("Export map ..."));
1215 #endif
1216 
1217 	// Export the map
1218 	QPainter p(&image);
1219 	map_printer->drawPage(&p, map_printer->getPrintArea(), &image);
1220 	p.end();
1221 	if (!image.save(path))
1222 	{
1223 		QMessageBox::warning(this, tr("Error"), tr("Failed to save the image. Does the path exist? Do you have sufficient rights?"));
1224 	}
1225 	else
1226 	{
1227 		main_window->showStatusBarMessage(tr("Exported successfully to %1").arg(path), 4000);
1228 		if (world_file_check->isChecked())
1229 			exportWorldFile(path);  /// \todo Handle errors
1230 		emit finished(0);
1231 	}
1232 }
1233 
exportWorldFile(const QString & path) const1234 void PrintWidget::exportWorldFile(const QString& path) const
1235 {
1236 	const auto& georef = map->getGeoreferencing();
1237 	const auto& mm_to_world = georef.mapToProjected();
1238 	qreal pixel_per_mm = (map_printer->getOptions().resolution / 25.4) * map_printer->getScaleAdjustment();
1239 	const auto xscale = mm_to_world.m11() / pixel_per_mm;
1240 	const auto yscale = mm_to_world.m22() / pixel_per_mm;
1241 	const auto xskew  = mm_to_world.m12() / pixel_per_mm;
1242 	const auto yskew  = mm_to_world.m21() / pixel_per_mm;
1243 	const auto top_left = georef.toProjectedCoords(MapCoord{map_printer->getPrintArea().topLeft()});
1244 	const QTransform pixel_to_world(xscale, yskew, 0, xskew, yscale, 0, top_left.x(), top_left.y());
1245 	const WorldFile world_file(pixel_to_world);
1246 	world_file.save(WorldFile::pathForImage(path));
1247 }
1248 
exportToPdf()1249 void PrintWidget::exportToPdf()
1250 {
1251 	auto printer = map_printer->makePrinter();
1252 	if (!printer)
1253 	{
1254 		QMessageBox::warning(this, tr("Error"), tr("Failed to prepare the PDF export."));
1255 		return;
1256 	}
1257 
1258 	printer->setOutputFormat(QPrinter::PdfFormat);
1259 	printer->setNumCopies(copies_edit->value());
1260 	printer->setCreator(main_window->appName());
1261 	printer->setDocName(QFileInfo(main_window->currentPath()).baseName());
1262 
1263 	static const QString filter_template(QString::fromLatin1("%1 (%2)"));
1264 	QStringList filters = { filter_template.arg(tr("PDF"), QString::fromLatin1("*.pdf")),
1265 	                        tr("All files (*.*)") };
1266 	QString path = FileDialog::getSaveFileName(this, tr("Export map ..."), {}, filters.join(QString::fromLatin1(";;")));
1267 	if (path.isEmpty())
1268 	{
1269 		return;
1270 	}
1271 	else if (!path.endsWith(QLatin1String(".pdf"), Qt::CaseInsensitive))
1272 	{
1273 		path.append(QLatin1String(".pdf"));
1274 	}
1275 	printer->setOutputFileName(path);
1276 
1277 	PrintProgressDialog progress(map_printer, main_window);
1278 	progress.setWindowTitle(tr("Export map ..."));
1279 
1280 	// Export the map
1281 	if (!map_printer->printMap(printer.get()))
1282 	{
1283 		QFile(path).remove();
1284 		QMessageBox::warning(this, tr("Error"), tr("Failed to finish the PDF export."));
1285 	}
1286 	else if (!progress.wasCanceled())
1287 	{
1288 		main_window->showStatusBarMessage(tr("Exported successfully to %1").arg(path), 4000);
1289 		emit finished(0);
1290 	}
1291 	else
1292 	{
1293 		QFile(path).remove();
1294 		main_window->showStatusBarMessage(tr("Canceled."), 4000);
1295 	}
1296 }
1297 
print()1298 void PrintWidget::print()
1299 {
1300 	auto printer = map_printer->makePrinter();
1301 	if (!printer)
1302 	{
1303 		QMessageBox::warning(this, tr("Error"), tr("Failed to prepare the printing."));
1304 		return;
1305 	}
1306 
1307 	printer->setNumCopies(copies_edit->value());
1308 	printer->setCreator(main_window->appName());
1309 	printer->setDocName(QFileInfo(main_window->currentPath()).baseName());
1310 
1311 	PrintProgressDialog progress(map_printer, main_window);
1312 	progress.setWindowTitle(tr("Printing Progress"));
1313 
1314 	// Print the map
1315 	if (!map_printer->printMap(printer.get()))
1316 	{
1317 		QMessageBox::warning(main_window, tr("Error"), tr("An error occurred during printing."));
1318 	}
1319 	else if (!progress.wasCanceled())
1320 	{
1321 		main_window->showStatusBarMessage(tr("Successfully created print job"), 4000);
1322 		emit finished(0);
1323 	}
1324 	else if (printer->abort())
1325 	{
1326 		main_window->showStatusBarMessage(tr("Canceled."), 4000);
1327 	}
1328 	else
1329 	{
1330 		QMessageBox::warning(main_window, tr("Error"), tr("The print job could not be stopped."));
1331 	}
1332 }
1333 
defaultPageSizes() const1334 QList<QPageSize> PrintWidget::defaultPageSizes() const
1335 {
1336 	// TODO: Learn from user's past choices, present reduced list unless asked for more.
1337 	static QList<QPageSize> default_paper_sizes(QList<QPageSize>()
1338 	  << QPageSize{ QPageSize::A4 }
1339 	  << QPageSize{ QPageSize::Letter }
1340 	  << QPageSize{ QPageSize::Legal }
1341 	  << QPageSize{ QPageSize::Executive }
1342 	  << QPageSize{ QPageSize::A0 }
1343 	  << QPageSize{ QPageSize::A1 }
1344 	  << QPageSize{ QPageSize::A2 }
1345 	  << QPageSize{ QPageSize::A3 }
1346 	  << QPageSize{ QPageSize::A5 }
1347 	  << QPageSize{ QPageSize::A6 }
1348 	  << QPageSize{ QPageSize::A7 }
1349 	  << QPageSize{ QPageSize::A8 }
1350 	  << QPageSize{ QPageSize::A9 }
1351 	  << QPageSize{ QPageSize::B0 }
1352 	  << QPageSize{ QPageSize::B1 }
1353 	  << QPageSize{ QPageSize::B10 }
1354 	  << QPageSize{ QPageSize::B2 }
1355 	  << QPageSize{ QPageSize::B3 }
1356 	  << QPageSize{ QPageSize::B4 }
1357 	  << QPageSize{ QPageSize::B5 }
1358 	  << QPageSize{ QPageSize::B6 }
1359 	  << QPageSize{ QPageSize::B7 }
1360 	  << QPageSize{ QPageSize::B8 }
1361 	  << QPageSize{ QPageSize::B9 }
1362 	  << QPageSize{ QPageSize::C5E }
1363 	  << QPageSize{ QPageSize::Comm10E }
1364 	  << QPageSize{ QPageSize::DLE }
1365 	  << QPageSize{ QPageSize::Folio }
1366 	  << QPageSize{ QPageSize::Ledger }
1367 	  << QPageSize{ QPageSize::Tabloid }
1368 	  << QPageSize{ QPageSize::Custom }
1369 	);
1370 	return default_paper_sizes;
1371 }
1372 
1373 
checkForEmptyMap()1374 bool PrintWidget::checkForEmptyMap()
1375 {
1376 	if (map_printer->isOutputEmpty())
1377 	{
1378 		QMessageBox::warning(this, tr("Error"), tr("The map area is empty. Output canceled."));
1379 		return true;
1380 	}
1381 	return false;
1382 }
1383 
1384 
1385 }  // namespace OpenOrienteering
1386 
1387 #endif  // QT_PRINTSUPPORT_LIB
1388