Source code for svir.dialogs.weight_data_dialog

# -*- coding: utf-8 -*-
# /***************************************************************************
# Irmt
#                                 A QGIS plugin
# OpenQuake Integrated Risk Modelling Toolkit
#                              -------------------
#        begin                : 2013-10-24
#        copyright            : (C) 2014 by GEM Foundation
#        email                : devops@openquake.org
# ***************************************************************************/
#
# OpenQuake is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# OpenQuake is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with OpenQuake.  If not, see <http://www.gnu.org/licenses/>.

from copy import deepcopy
import json

from qgis.PyQt.QtCore import (
    Qt, QUrl, QSettings, pyqtProperty, pyqtSignal, pyqtSlot)
from qgis.PyQt.QtWebKit import QWebSettings
from qgis.PyQt.QtWidgets import QDialog, QDialogButtonBox
from qgis.PyQt.QtPrintSupport import QPrinter

from svir.utilities.shared import (DEFAULT_OPERATOR,
                                   OPERATORS_DICT,
                                   NUMERIC_FIELD_TYPES,
                                   NODE_TYPES,
                                   DEBUG)
from svir.utilities.utils import (get_field_names,
                                  confirmation_on_close,
                                  ask_for_destination_full_path_name,
                                  log_msg,
                                  get_ui_class,
                                  )

FORM_CLASS = get_ui_class('ui_weight_data.ui')


if DEBUG:
    # turn on developer tools in webkit so we can get at the
    # javascript console for debugging (it causes segfaults in tests, so it has
    # to be kept disabled while it is not used for debugging).
    QWebSettings.globalSettings().setAttribute(
        QWebSettings.DeveloperExtrasEnabled, True)


[docs]class WeightDataDialog(QDialog, FORM_CLASS): """ Modal dialog allowing to select weights in a d3.js visualization """ # QVariantMap is to map a JSON to dict see: # http://pyqt.sourceforge.net/Docs/PyQt4/incompatibilities.html#pyqt4-v4-7-4 # this is for javascript to emit when it changes the json json_updated = pyqtSignal(['QVariantMap'], name='json_updated') # Python classes should connect to json_cleaned json_cleaned = pyqtSignal(['QVariantMap'], name='json_cleaned') def __init__(self, iface, project_definition): self.iface = iface QDialog.__init__(self) # Set up the user interface from Designer. self.setupUi(self) self.ok_button = self.buttonBox.button(QDialogButtonBox.Ok) self.added_attrs_ids = set() self.discarded_feats = set() self.any_changes_made = False # keep track of changes made to the tree while on-the-fly calculations # are off (the tree has changed, but indices haven't been recalculated) self.modified_project_definition = None self.active_layer_numeric_fields = [] self.active_layer_numeric_fields_aliases = [] self.update_active_layer_numeric_fields() # keep track of the fact that the user has explicitly selected a field # to base the style on. Note that also the selection of the empty item # means that the user explicitly wants the system to use the default # behavior. self.style_by_field_selected = False self.project_definition = deepcopy(project_definition) try: proj_title = self.project_definition['title'] except KeyError: proj_title = 'Untitled' dialog_title = ( 'Set weights and operators for project: "%s"' % proj_title) self.setWindowTitle(dialog_title) self.web_view = self.web_view self.web_view.page().mainFrame().setScrollBarPolicy( Qt.Vertical, Qt.ScrollBarAlwaysOff) self.web_view.page().mainFrame().setScrollBarPolicy( Qt.Horizontal, Qt.ScrollBarAlwaysOff) self.web_view.load(QUrl('qrc:/plugins/irmt/weight_data.html')) self.printer = QPrinter(QPrinter.HighResolution) self.printer.setPageSize(QPrinter.A4) self.printer.setOutputFormat(QPrinter.PdfFormat) self.printer.setPrintRange(QPrinter.AllPages) self.printer.setOrientation(QPrinter.Portrait) self.printer.setDocName(proj_title) self.printer.setCreator( 'GEM Integrated Risk Modelling Toolkit QGIS Plugin') self.frame = self.web_view.page().mainFrame() self.frame.javaScriptWindowObjectCleared.connect(self.setup_js) self.web_view.loadFinished.connect(self.show_tree) self.json_updated.connect(self.handle_json_updated) self.populate_style_by_field_cbx() if not DEBUG: self.web_view.setContextMenuPolicy(Qt.NoContextMenu) self.web_view.settings().setAttribute( QWebSettings.JavascriptEnabled, True)
[docs] def closeEvent(self, event): confirmation_on_close(self, event)
[docs] def reject(self): confirmation_on_close(self)
[docs] def update_project_definition(self, project_definition): self.project_definition = deepcopy(project_definition) self.populate_style_by_field_cbx()
[docs] def update_active_layer_numeric_fields(self): self.active_layer_numeric_fields = [ field.name() for field in self.iface.activeLayer().fields() if field.typeName() in NUMERIC_FIELD_TYPES] self.active_layer_numeric_fields_aliases = [ self.iface.activeLayer().attributeAlias(field_idx) for field_idx, field in enumerate( self.iface.activeLayer().fields()) if field.typeName() in NUMERIC_FIELD_TYPES]
[docs] def populate_style_by_field_cbx(self): self.update_active_layer_numeric_fields() fields_in_proj_def = get_field_names(self.project_definition) fields_for_styling = [ field for field in self.active_layer_numeric_fields if field in fields_in_proj_def] # block signals to avoid performing the onchange actions while adding # items programmatically self.style_by_field_cbx.blockSignals(True) self.style_by_field_cbx.clear() self.style_by_field_cbx.addItem('') self.style_by_field_cbx.addItems(fields_for_styling) if 'style_by_field' in self.project_definition: idx = self.style_by_field_cbx.findText( self.project_definition['style_by_field']) self.style_by_field_cbx.setCurrentIndex(idx) # reactivate the signals, so the user's changes will trigger something self.style_by_field_cbx.blockSignals(False)
[docs] def setup_js(self): # pass a reference (called qt_page) of self to the JS world # to expose a member of self to js you need to declare it as property # see for example self.json_str() if DEBUG: log_msg("######################### for weight_data_debug.html") self.print_self_for_debug() log_msg("######################### end for weight_data_debug.html") self.frame.addToJavaScriptWindowObject('qt_page', self)
[docs] def show_tree(self): # start the tree self.frame.evaluateJavaScript('init_tree()')
[docs] def handle_json_updated(self, data): self.any_changes_made = True if DEBUG: import pprint ppdata = pprint.pformat(data, indent=4) log_msg('in handle_json_updated, data=\n%s' % ppdata) if self.on_the_fly_ckb.isChecked(): self.project_definition = self.clean_json([data]) self._manage_style_by_field() self.json_cleaned.emit(self.project_definition) # nothing has changed since the last recalculation self.modified_project_definition = None else: # keep track of the current status of the project definition # as it is in the d3 tree, so it can be used when OK is pressed self.modified_project_definition = self.clean_json([data])
def _manage_style_by_field(self): if self.style_by_field_selected: if self.style_by_field_cbx.currentText(): self.project_definition['style_by_field'] = \ self.style_by_field_cbx.currentText() elif 'style_by_field' in self.project_definition: # if the empty item is selected, clean the project definition del self.project_definition['style_by_field']
[docs] def clean_json(self, data): # this method takes a list of dictionaries and removes some unneeded # keys. It recurses into the children elements if data == []: return data ignore_keys = ['depth', 'x', 'y', 'id', 'x0', 'y0', 'parent'] for element in data: for key in ignore_keys: element.pop(key, None) if 'children' in element: self.clean_json(element['children']) # return the main element return data[0]
@pyqtSlot()
[docs] def on_print_btn_clicked(self): dest_full_path_name = ask_for_destination_full_path_name( self, filter='Pdf files (*.pdf)') if not dest_full_path_name: return self.printer.setOutputFileName(dest_full_path_name) try: self.web_view.print_(self.printer) except Exception: msg = 'It was impossible to create the pdf' log_msg(msg, level='C', message_bar=self.iface.messageBar()) else: msg = ('Project definition printed as pdf and saved to: %s' % dest_full_path_name) log_msg(msg, level='S', message_bar=self.iface.messageBar())
@pyqtSlot(str)
[docs] def on_style_by_field_cbx_currentIndexChanged(self): self.style_by_field_selected = True self._manage_style_by_field() self.json_updated.emit(self.project_definition)
@pyqtProperty(str) def json_str(self): # This method gets exposed to JS thanks to @pyqtProperty(str) return json.dumps(self.project_definition, sort_keys=False, indent=2, separators=(',', ': ')) @pyqtProperty(str) def DEFAULT_OPERATOR(self): return DEFAULT_OPERATOR @pyqtProperty(bool) def DEV_MODE(self): developer_mode = QSettings().value( '/irmt/developer_mode', True, type=bool) return developer_mode @pyqtProperty(str) def OPERATORS(self): return ';'.join(list(OPERATORS_DICT.values())) @pyqtProperty(str) def ACTIVE_LAYER_NUMERIC_FIELDS(self): return ';'.join(self.active_layer_numeric_fields) @pyqtProperty(str) def ACTIVE_LAYER_NUMERIC_FIELDS_ALIASES(self): return ';'.join(self.active_layer_numeric_fields_aliases) @pyqtProperty(str) def NODE_TYPES(self): return ';'.join(["%s:%s" % (k, v) for k, v in NODE_TYPES.items()])
[docs] def print_self_for_debug(self): msg = """ var qt_page = { ACTIVE_LAYER_NUMERIC_FIELDS: "%s", ACTIVE_LAYER_NUMERIC_FIELDS_ALIASES: "%s", DEFAULT_OPERATOR: "%s", NODE_TYPES: "%s", OPERATORS: "%s", json_str: '%s', json_updated: function (updated_json_str) { console.log("json_updated signal emitted with this JSON:"); console.log(updated_json_str) }, DEV_MODE: %s };""" % (self.ACTIVE_LAYER_NUMERIC_FIELDS, self.ACTIVE_LAYER_NUMERIC_FIELDS_ALIASES, self.DEFAULT_OPERATOR, self.NODE_TYPES, self.OPERATORS, self.json_str.replace('\n', ''), str(self.DEV_MODE).lower()) log_msg(msg)