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