diff --git a/felt/core/layer_exporter.py b/felt/core/layer_exporter.py index 272243f..3207013 100644 --- a/felt/core/layer_exporter.py +++ b/felt/core/layer_exporter.py @@ -267,7 +267,8 @@ def generate_file_name(self, suffix: str) -> str: def export_layer_for_felt( self, layer: QgsMapLayer, - feedback: Optional[QgsFeedback] = None + feedback: Optional[QgsFeedback] = None, + upload_raster_as_styled: bool = True ) -> ZippedExportResult: """ Exports a layer into a format acceptable for Felt @@ -276,7 +277,9 @@ def export_layer_for_felt( if isinstance(layer, QgsVectorLayer): res = self.export_vector_layer(layer, feedback) elif isinstance(layer, QgsRasterLayer): - res = self.export_raster_layer(layer, feedback) + res = self.export_raster_layer( + layer, feedback, + upload_raster_as_styled) else: assert False @@ -504,17 +507,17 @@ def run_raster_writer(self, def export_raster_layer( self, layer: QgsRasterLayer, - feedback: Optional[QgsFeedback] = None) -> LayerExportDetails: + feedback: Optional[QgsFeedback] = None, + upload_raster_as_styled: bool = True) -> LayerExportDetails: """ Exports a raster layer into a format acceptable for Felt """ - raw_dest_file = self.generate_file_name('.tif') - styled_dest_file = raw_dest_file.replace('.tif', '_styled.tif') + dest_file = self.generate_file_name('.tif') layer_export_result, error_message = self.run_raster_writer( layer, - file_name=styled_dest_file, - use_style=True, + file_name=dest_file, + use_style=upload_raster_as_styled, feedback=feedback) if error_message: @@ -526,31 +529,9 @@ def export_raster_layer( ) raise LayerPackagingException(error_message) - filenames = [styled_dest_file] - if layer_export_result != LayerExportResult.Canceled: - # also write raw raster - - layer_export_result, error_message = self.run_raster_writer( - layer, - file_name=raw_dest_file, - use_style=False, - feedback=feedback) - - if error_message: - Logger.instance().log_error_json( - { - 'type': Logger.PACKAGING_RASTER, - 'error': 'Error packaging layer: {}'.format( - error_message) - } - ) - raise LayerPackagingException(error_message) - - filenames.append(raw_dest_file) - return LayerExportDetails( - representative_filename=raw_dest_file, - filenames=filenames, + representative_filename=dest_file, + filenames=[dest_file], result=layer_export_result, error_message=error_message, qgis_style_xml=self._get_original_style_xml(layer) diff --git a/felt/core/map_uploader.py b/felt/core/map_uploader.py index f55fb12..a1f944a 100644 --- a/felt/core/map_uploader.py +++ b/felt/core/map_uploader.py @@ -42,7 +42,8 @@ QgsTask, QgsFeedback, QgsBlockingNetworkRequest, - QgsReferencedRectangle + QgsReferencedRectangle, + QgsSettings ) from qgis.utils import iface @@ -250,6 +251,10 @@ def run(self): self.feedback = QgsFeedback() + upload_raster_as_styled = QgsSettings().value( + "felt/upload_raster_as_styled", True, bool, QgsSettings.Plugins + ) + multi_step_feedback = MultiStepFeedback( total_steps, self.feedback ) @@ -342,7 +347,8 @@ def run(self): try: result = exporter.export_layer_for_felt( layer, - multi_step_feedback + multi_step_feedback, + upload_raster_as_styled=upload_raster_as_styled ) except LayerPackagingException as e: layer.moveToThread(None) diff --git a/felt/gui/create_map_dialog.py b/felt/gui/create_map_dialog.py index d7a1a01..9533d65 100644 --- a/felt/gui/create_map_dialog.py +++ b/felt/gui/create_map_dialog.py @@ -21,23 +21,30 @@ from qgis.PyQt import uic from qgis.PyQt.QtCore import ( Qt, - QUrl + QUrl, + QSize ) from qgis.PyQt.QtGui import ( QDesktopServices, QFontMetrics, - QColor + QColor, + QPalette ) from qgis.PyQt.QtWidgets import ( QWidget, QDialog, QDialogButtonBox, - QVBoxLayout + QVBoxLayout, + QLabel, + QMenu, + QAction, + QToolButton ) from qgis.core import ( QgsMapLayer, QgsApplication, - QgsProject + QgsProject, + QgsSettings ) from qgis.gui import QgsGui @@ -47,12 +54,12 @@ PRIVACY_POLICY_URL, TOS_URL ) -from .workspaces_combo import WorkspacesComboBox from .felt_dialog_header import FeltDialogHeader from .gui_utils import ( GuiUtils, FELT_STYLESHEET ) +from .workspaces_combo import WorkspacesComboBox from ..core import ( MapUploaderTask, Map @@ -92,10 +99,33 @@ def __init__(self, # pylint: disable=too-many-statements vl = QVBoxLayout() vl.setContentsMargins(0, 0, 0, 0) - vl.addWidget(FeltDialogHeader()) - self.widget_logo.setStyleSheet('background: solid #3d521e;') + header = FeltDialogHeader() + vl.addWidget(header) self.widget_logo.setLayout(vl) + self.header_label = QLabel( + """""" + """Privacy """ + """Terms """ + """Contact us
""" + ) + self.header_label.setMouseTracking(True) + self.header_label.linkActivated.connect(self._link_activated) + self.header_label.setText( + GuiUtils.set_link_color(self.header_label.text(), + color='rgba(255,255,255,.7)') + ) + + header_label_vl = QVBoxLayout() + header_label_vl.setContentsMargins(0, 0, 0, 0) + header_label_vl.addStretch() + header_label_vl.addWidget(self.header_label) + + header_label_widget = QWidget() + header_label_widget.setLayout(header_label_vl) + + header.push_widget(header_label_widget) + self.setWindowTitle(self.tr('Add to Felt')) self.footer_label.setMinimumWidth( @@ -124,6 +154,73 @@ def __init__(self, # pylint: disable=too-many-statements GuiUtils.set_link_color(self.footer_label.text()) ) + self.setting_menu = QMenu(self) + palette = self.setting_menu.palette() + palette.setColor(QPalette.Active, QPalette.Base, QColor(255, 255, 255)) + palette.setColor(QPalette.Active, QPalette.Text, QColor(0, 0, 0)) + palette.setColor(QPalette.Active, QPalette.Highlight, + QColor('#3d521e')) + palette.setColor(QPalette.Active, QPalette.HighlightedText, + QColor(255, 255, 255)) + self.setting_menu.setPalette(palette) + + self.upload_raster_as_styled_action = QAction( + self.tr('Upload Raster Layers as Styled Images'), + self.setting_menu) + self.upload_raster_as_styled_action.setCheckable(True) + self.upload_raster_as_styled_action.setChecked( + QgsSettings().value( + "felt/upload_raster_as_styled", True, bool, QgsSettings.Plugins + ) + ) + + def upload_raster_as_styled_toggled(): + """ + Called when upload raster as style action is toggled + """ + QgsSettings().setValue( + "felt/upload_raster_as_styled", + self.upload_raster_as_styled_action.isChecked(), + QgsSettings.Plugins + ) + + self.upload_raster_as_styled_action.toggled.connect( + upload_raster_as_styled_toggled) + self.setting_menu.addAction(self.upload_raster_as_styled_action) + + self.setting_menu.addSeparator() + self.logout_action = QAction(self.tr('Log Out'), self.setting_menu) + self.setting_menu.addAction(self.logout_action) + self.logout_action.triggered.connect(self._logout) + + palette = self.setting_button.palette() + palette.setColor(QPalette.Active, QPalette.Button, QColor('#ececec')) + self.setting_button.setPalette(palette) + + self.setting_button.setMenu(self.setting_menu) + self.setting_button.setIcon(GuiUtils.get_icon('setting_icon.svg')) + self.setting_button.setPopupMode(QToolButton.InstantPopup) + self.setting_button.setStyleSheet( + """QToolButton::menu-indicator { image: none }""" + ) + self.setting_button.setFixedHeight( + self.button_box.button(QDialogButtonBox.Cancel).height() + ) + self.setting_button.setFixedWidth( + int(self.setting_button.size().height() * 1.8) + ) + self.setting_button.setIconSize( + QSize(int(self.setting_button.size().width() * 0.6), + int(self.setting_button.size().height() * 0.6) + )) + # setting the setting button to a fixed height doesn't always + # guarantee that the height exactly matches the Close/Add buttons. + # So let's play it safe and force them to match always: + for b in (QDialogButtonBox.Cancel, QDialogButtonBox.Ok): + self.button_box.button(b).setFixedHeight( + self.setting_button.size().height() + ) + # pylint: disable=import-outside-toplevel from .recent_maps_list_view import RecentMapsWidget # pylint: enable=import-outside-toplevel @@ -163,17 +260,9 @@ def __init__(self, # pylint: disable=too-many-statements self.progress_bar.setValue(0) if AUTHORIZATION_MANAGER.user: - self.label_user.setText( - GuiUtils.set_link_color( - self.tr( - '{} ({})' - ).format(AUTHORIZATION_MANAGER.user.name, - AUTHORIZATION_MANAGER.user.email) + - ' ' + self.tr('Log out') + '', - wrap_color=False - ) + self.footer_label.setText( + AUTHORIZATION_MANAGER.user.email ) - self.label_user.linkActivated.connect(self._link_activated) self.started = False self._validate() @@ -224,6 +313,13 @@ def _cancel(self): else: self.reject() + def _logout(self): + """ + Triggers a logout + """ + AUTHORIZATION_MANAGER.deauthorize() + self.close() + def _link_activated(self, link: str): """ Called when a hyperlink is clicked in dialog labels @@ -233,8 +329,7 @@ def _link_activated(self, link: str): elif link == 'terms_of_use': url = QUrl(TOS_URL) elif link == 'logout': - AUTHORIZATION_MANAGER.deauthorize() - self.close() + self._logout() return else: url = QUrl(link) diff --git a/felt/gui/felt_dialog_header.py b/felt/gui/felt_dialog_header.py index 9761986..b288be9 100644 --- a/felt/gui/felt_dialog_header.py +++ b/felt/gui/felt_dialog_header.py @@ -27,7 +27,8 @@ from qgis.PyQt.QtWidgets import ( QWidget, QVBoxLayout, - QSizePolicy + QSizePolicy, + QHBoxLayout ) from .gui_utils import ( @@ -53,22 +54,36 @@ def __init__(self, parent: Optional[QWidget] = None): QSizePolicy.Fixed ) - self.setStyleSheet('background: solid #3d521e;') svg_logo_widget = QSvgWidget() fixed_size = QSize(self.LOGO_WIDTH_PIXELS, self.LOGO_HEIGHT_PIXELS) svg_logo_widget.setFixedSize(fixed_size) svg_logo_widget.load(GuiUtils.get_icon_svg('felt_logo_white.svg')) svg_logo_widget.setStyleSheet('background: transparent;') + svg_logo_container = QVBoxLayout() + svg_logo_container.setContentsMargins(0, 0, 0, 4) + svg_logo_container.addWidget(svg_logo_widget) vl = QVBoxLayout() - vl.setContentsMargins(12, 0, 0, 19) + vl.setContentsMargins(12, 0, 12, 15) vl.addStretch(1) - vl.addWidget(svg_logo_widget) + + self.header_layout = QHBoxLayout() + self.header_layout.setContentsMargins(0, 0, 0, 0) + self.header_layout.addLayout(svg_logo_container) + self.header_layout.addStretch() + + vl.addLayout(self.header_layout) self.setLayout(vl) # QWidget interface # pylint: disable=missing-function-docstring + def push_widget(self, widget: QWidget): + """ + Pushes a new widget into the right section of the header + """ + self.header_layout.addWidget(widget) + def sizeHint(self): return QSize(0, self.FIXED_HEIGHT_PIXELS) diff --git a/felt/gui/gui_utils.py b/felt/gui/gui_utils.py index 4ace385..97b46d0 100644 --- a/felt/gui/gui_utils.py +++ b/felt/gui/gui_utils.py @@ -16,7 +16,10 @@ import math import os import re -from typing import Optional +from typing import ( + Optional, + Union +) from qgis.PyQt.QtCore import Qt from qgis.PyQt.QtGui import ( @@ -76,12 +79,15 @@ class GuiUtils: @staticmethod def set_link_color(html: str, wrap_color=True, - color: Optional[QColor] = None) -> str: + color: Optional[Union[QColor, str]] = None) -> str: """ Adds style tags to links in a HTML string for the standard link color """ if color: - color_string = color.name() + if isinstance(color, str): + color_string = color + else: + color_string = color.name() else: color_string = 'rgba(0,0,0,.3)' res = re.sub(r'(', diff --git a/felt/icons/setting_icon.svg b/felt/icons/setting_icon.svg new file mode 100644 index 0000000..a100399 --- /dev/null +++ b/felt/icons/setting_icon.svg @@ -0,0 +1,91 @@ + + diff --git a/felt/test/test_layer_exporter.py b/felt/test/test_layer_exporter.py index 02d581e..6e56675 100644 --- a/felt/test/test_layer_exporter.py +++ b/felt/test/test_layer_exporter.py @@ -217,9 +217,9 @@ def test_gml_conversion(self): self.assertEqual(out_layer.featureCount(), layer.featureCount()) self.assertEqual(out_layer.wkbType(), QgsWkbTypes.MultiPolygon) - def test_raster_conversion(self): + def test_raster_conversion_raw(self): """ - Test raster layer conversion + Test raw raster layer conversion """ file = str(TEST_DATA_PATH / 'dem.tif') layer = QgsRasterLayer(file, 'test') @@ -228,15 +228,14 @@ def test_raster_conversion(self): exporter = LayerExporter( QgsCoordinateTransformContext() ) - result = exporter.export_layer_for_felt(layer) + result = exporter.export_layer_for_felt(layer, + upload_raster_as_styled=False) self.assertEqual(result.result, LayerExportResult.Success) self.assertTrue(result.filename) self.assertEqual(result.filename[-4:], '.zip') with zipfile.ZipFile(result.filename) as z: tif_files = [f for f in z.namelist() if f.endswith('tif')] - self.assertEqual(len(tif_files), 2) - - styled_tif = [f for f in tif_files if '_styled' in f][0] + self.assertEqual(len(tif_files), 1) self.assertEqual( result.qgis_style_xml[:58], @@ -244,20 +243,14 @@ def test_raster_conversion(self): ) out_layer = QgsRasterLayer( - '/vsizip/{}/{}'.format(result.filename, styled_tif), + '/vsizip/{}/{}'.format(result.filename, tif_files[0]), 'test') self.assertTrue(out_layer.isValid()) self.assertEqual(out_layer.width(), 373) self.assertEqual(out_layer.height(), 350) - self.assertEqual(out_layer.bandCount(), 4) + self.assertEqual(out_layer.bandCount(), 1) self.assertEqual(out_layer.dataProvider().dataType(1), - Qgis.DataType.Byte) - self.assertEqual(out_layer.dataProvider().dataType(2), - Qgis.DataType.Byte) - self.assertEqual(out_layer.dataProvider().dataType(3), - Qgis.DataType.Byte) - self.assertEqual(out_layer.dataProvider().dataType(4), - Qgis.DataType.Byte) + Qgis.DataType.Float32) self.assertEqual(out_layer.crs(), QgsCoordinateReferenceSystem('EPSG:4326')) self.assertAlmostEqual(out_layer.extent().xMinimum(), @@ -269,16 +262,46 @@ def test_raster_conversion(self): self.assertAlmostEqual(out_layer.extent().yMaximum(), 45.8117014376, 3) - raw_tif = [f for f in tif_files if '_styled' not in f][0] + def test_raster_conversion_styled(self): + """ + Test raster layer conversion + """ + file = str(TEST_DATA_PATH / 'dem.tif') + layer = QgsRasterLayer(file, 'test') + self.assertTrue(layer.isValid()) + + exporter = LayerExporter( + QgsCoordinateTransformContext() + ) + result = exporter.export_layer_for_felt(layer, + upload_raster_as_styled=True) + self.assertEqual(result.result, LayerExportResult.Success) + self.assertTrue(result.filename) + self.assertEqual(result.filename[-4:], '.zip') + with zipfile.ZipFile(result.filename) as z: + tif_files = [f for f in z.namelist() if f.endswith('tif')] + self.assertEqual(len(tif_files), 1) + + self.assertEqual( + result.qgis_style_xml[:58], + "" + ) + out_layer = QgsRasterLayer( - '/vsizip/{}/{}'.format(result.filename, raw_tif), + '/vsizip/{}/{}'.format(result.filename, tif_files[0]), 'test') self.assertTrue(out_layer.isValid()) self.assertEqual(out_layer.width(), 373) self.assertEqual(out_layer.height(), 350) - self.assertEqual(out_layer.bandCount(), 1) + self.assertEqual(out_layer.bandCount(), 4) self.assertEqual(out_layer.dataProvider().dataType(1), - Qgis.DataType.Float32) + Qgis.DataType.Byte) + self.assertEqual(out_layer.dataProvider().dataType(2), + Qgis.DataType.Byte) + self.assertEqual(out_layer.dataProvider().dataType(3), + Qgis.DataType.Byte) + self.assertEqual(out_layer.dataProvider().dataType(4), + Qgis.DataType.Byte) self.assertEqual(out_layer.crs(), QgsCoordinateReferenceSystem('EPSG:4326')) self.assertAlmostEqual(out_layer.extent().xMinimum(), diff --git a/felt/ui/create_map.ui b/felt/ui/create_map.ui index 893fb8d..c30bf42 100644 --- a/felt/ui/create_map.ui +++ b/felt/ui/create_map.ui @@ -42,7 +42,7 @@