2012-10-05 23:28:47 +02:00
# -*- coding: utf-8 -*-
"""
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
NumberInputPanel . py
- - - - - - - - - - - - - - - - - - - - -
Date : August 2012
Copyright : ( C ) 2012 by Victor Olaya
Email : volayaf at gmail dot com
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* This program is free software ; you can redistribute it and / or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation ; either version 2 of the License , or *
* ( at your option ) any later version . *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
"""
__author__ = ' Victor Olaya '
__date__ = ' August 2012 '
__copyright__ = ' (C) 2012, Victor Olaya '
2013-10-01 20:52:22 +03:00
2012-10-05 23:28:47 +02:00
# This will get replaced with a git SHA1 when you do a git archive
2013-10-01 20:52:22 +03:00
2012-10-05 23:28:47 +02:00
__revision__ = ' $Format: % H$ '
2015-05-18 21:04:20 +03:00
import os
2016-11-11 11:02:42 +10:00
import math
2017-11-10 10:42:25 +10:00
import sip
2018-05-21 14:23:26 +10:00
import warnings
2015-05-18 21:04:20 +03:00
2016-04-29 11:39:26 +02:00
from qgis . PyQt import uic
2018-04-19 17:24:13 +10:00
from qgis . PyQt . QtCore import pyqtSignal , QSize
2018-07-26 14:38:37 +10:00
from qgis . PyQt . QtWidgets import QDialog , QLabel , QComboBox
2015-05-18 21:04:20 +03:00
2018-04-19 17:24:13 +10:00
from qgis . core import ( QgsApplication ,
QgsExpression ,
2017-11-26 17:34:02 +10:00
QgsProperty ,
2018-04-19 17:24:13 +10:00
QgsUnitTypes ,
QgsMapLayer ,
QgsCoordinateReferenceSystem ,
2017-06-13 15:51:40 +10:00
QgsProcessingParameterNumber ,
QgsProcessingOutputNumber ,
2017-12-01 18:19:04 +10:00
QgsProcessingParameterDefinition ,
2017-11-26 17:34:02 +10:00
QgsProcessingModelChildParameterSource ,
QgsProcessingFeatureSourceDefinition ,
QgsProcessingUtils )
2017-03-04 19:41:23 +01:00
from qgis . gui import QgsExpressionBuilderDialog
2017-07-03 21:25:04 +10:00
from processing . tools . dataobjects import createExpressionContext , createContext
2012-09-15 18:25:25 +03:00
2015-05-18 21:04:20 +03:00
pluginPath = os . path . split ( os . path . dirname ( __file__ ) ) [ 0 ]
2018-05-21 14:23:26 +10:00
with warnings . catch_warnings ( ) :
warnings . filterwarnings ( " ignore " , category = DeprecationWarning )
NUMBER_WIDGET , NUMBER_BASE = uic . loadUiType (
os . path . join ( pluginPath , ' ui ' , ' widgetNumberSelector.ui ' ) )
WIDGET , BASE = uic . loadUiType (
os . path . join ( pluginPath , ' ui ' , ' widgetBaseSelector.ui ' ) )
2015-05-18 21:04:20 +03:00
2013-10-01 20:52:22 +03:00
2018-03-12 12:30:50 +10:00
class ModelerNumberInputPanel ( BASE , WIDGET ) :
2017-05-17 07:50:01 +10:00
2016-11-11 11:02:42 +10:00
"""
2018-03-12 12:30:50 +10:00
Number input panel for use inside the modeler - this input panel
2016-11-11 11:02:42 +10:00
is based off the base input panel and includes a text based line input
for entering values . This allows expressions and other non - numeric
values to be set , which are later evalauted to numbers when the model
is run .
"""
2012-09-15 18:25:25 +03:00
2016-01-19 09:00:13 +01:00
hasChanged = pyqtSignal ( )
2016-11-11 11:02:42 +10:00
def __init__ ( self , param , modelParametersDialog ) :
2018-03-12 12:30:50 +10:00
super ( ) . __init__ ( None )
2013-10-09 19:21:11 +03:00
self . setupUi ( self )
2016-09-09 07:02:54 +02:00
self . param = param
2016-09-12 06:17:23 +02:00
self . modelParametersDialog = modelParametersDialog
2017-05-15 19:01:15 +10:00
if param . defaultValue ( ) :
self . setValue ( param . defaultValue ( ) )
2016-09-09 07:02:54 +02:00
self . btnSelect . clicked . connect ( self . showExpressionsBuilder )
self . leText . textChanged . connect ( lambda : self . hasChanged . emit ( ) )
2016-01-19 09:00:13 +01:00
2016-04-08 13:21:29 +02:00
def showExpressionsBuilder ( self ) :
2017-05-17 07:50:01 +10:00
context = createExpressionContext ( )
2017-07-03 21:25:04 +10:00
processing_context = createContext ( )
scope = self . modelParametersDialog . model . createExpressionContextScopeForChildAlgorithm ( self . modelParametersDialog . childId , processing_context )
context . appendScope ( scope )
highlighted = scope . variableNames ( )
context . setHighlightedVariables ( highlighted )
2016-11-11 11:02:42 +10:00
2017-07-03 21:25:04 +10:00
dlg = QgsExpressionBuilderDialog ( None , str ( self . leText . text ( ) ) , self , ' generic ' , context )
2016-11-11 11:02:42 +10:00
2018-02-21 18:44:04 +10:00
dlg . setWindowTitle ( self . tr ( ' Expression Based Input ' ) )
2016-04-08 13:21:29 +02:00
if dlg . exec_ ( ) == QDialog . Accepted :
exp = QgsExpression ( dlg . expressionText ( ) )
if not exp . hasParserError ( ) :
2016-09-09 07:02:54 +02:00
self . setValue ( dlg . expressionText ( ) )
2012-09-15 18:25:25 +03:00
def getValue ( self ) :
2016-11-11 11:02:42 +10:00
value = self . leText . text ( )
2017-06-21 07:40:28 +10:00
for param in self . modelParametersDialog . model . parameterDefinitions ( ) :
2017-06-13 15:51:40 +10:00
if isinstance ( param , QgsProcessingParameterNumber ) :
2017-07-03 21:55:48 +10:00
if " @ " + param . name ( ) == value . strip ( ) :
2017-07-08 20:00:40 +10:00
return QgsProcessingModelChildParameterSource . fromModelParameter ( param . name ( ) )
2017-06-26 16:06:07 +10:00
for alg in list ( self . modelParametersDialog . model . childAlgorithms ( ) . values ( ) ) :
for out in alg . algorithm ( ) . outputDefinitions ( ) :
2017-07-03 21:55:48 +10:00
if isinstance ( out , QgsProcessingOutputNumber ) and " @ %s _ %s " % ( alg . childId ( ) , out . name ( ) ) == value . strip ( ) :
2017-07-08 20:00:40 +10:00
return QgsProcessingModelChildParameterSource . fromChildOutput ( alg . childId ( ) , out . outputName ( ) )
2017-06-26 16:06:07 +10:00
2017-07-03 21:55:48 +10:00
try :
return float ( value . strip ( ) )
except :
2017-07-08 20:00:40 +10:00
return QgsProcessingModelChildParameterSource . fromExpression ( self . leText . text ( ) )
2015-11-18 16:26:55 +11:00
2016-09-07 14:30:20 +02:00
def setValue ( self , value ) :
2017-07-08 20:00:40 +10:00
if isinstance ( value , QgsProcessingModelChildParameterSource ) :
if value . source ( ) == QgsProcessingModelChildParameterSource . ModelParameter :
2017-06-26 16:06:07 +10:00
self . leText . setText ( ' @ ' + value . parameterName ( ) )
2017-07-08 20:00:40 +10:00
elif value . source ( ) == QgsProcessingModelChildParameterSource . ChildOutput :
2017-06-26 16:06:07 +10:00
name = " %s _ %s " % ( value . outputChildId ( ) , value . outputName ( ) )
self . leText . setText ( name )
2017-07-08 20:00:40 +10:00
elif value . source ( ) == QgsProcessingModelChildParameterSource . Expression :
2017-07-03 21:55:48 +10:00
self . leText . setText ( value . expression ( ) )
2017-06-26 16:06:07 +10:00
else :
self . leText . setText ( str ( value . staticValue ( ) ) )
else :
self . leText . setText ( str ( value ) )
2016-11-11 11:02:42 +10:00
class NumberInputPanel ( NUMBER_BASE , NUMBER_WIDGET ) :
2017-05-17 07:50:01 +10:00
2016-11-11 11:02:42 +10:00
"""
2018-03-12 12:30:50 +10:00
Number input panel for use outside the modeler - this input panel
2017-11-10 10:42:25 +10:00
contains a user friendly spin box for entering values .
2016-11-11 11:02:42 +10:00
"""
hasChanged = pyqtSignal ( )
def __init__ ( self , param ) :
super ( NumberInputPanel , self ) . __init__ ( None )
self . setupUi ( self )
2018-07-02 10:12:27 +10:00
self . layer = None
2016-11-11 11:02:42 +10:00
self . spnValue . setExpressionsEnabled ( True )
self . param = param
2017-05-15 19:01:15 +10:00
if self . param . dataType ( ) == QgsProcessingParameterNumber . Integer :
2016-11-11 11:02:42 +10:00
self . spnValue . setDecimals ( 0 )
else :
# Guess reasonable step value
2017-05-15 19:01:15 +10:00
if self . param . maximum ( ) is not None and self . param . minimum ( ) is not None :
2016-11-11 11:02:42 +10:00
try :
2017-05-15 19:01:15 +10:00
self . spnValue . setSingleStep ( self . calculateStep ( float ( self . param . minimum ( ) ) , float ( self . param . maximum ( ) ) ) )
2016-11-11 11:02:42 +10:00
except :
pass
2017-05-15 19:01:15 +10:00
if self . param . maximum ( ) is not None :
self . spnValue . setMaximum ( self . param . maximum ( ) )
2016-11-11 11:02:42 +10:00
else :
self . spnValue . setMaximum ( 999999999 )
2017-05-15 19:01:15 +10:00
if self . param . minimum ( ) is not None :
self . spnValue . setMinimum ( self . param . minimum ( ) )
2016-11-11 11:02:42 +10:00
else :
self . spnValue . setMinimum ( - 999999999 )
2017-12-01 18:19:04 +10:00
self . allowing_null = False
2016-11-11 11:02:42 +10:00
# set default value
2017-12-01 18:19:04 +10:00
if param . flags ( ) & QgsProcessingParameterDefinition . FlagOptional :
self . spnValue . setShowClearButton ( True )
min = self . spnValue . minimum ( ) - 1
self . spnValue . setMinimum ( min )
self . spnValue . setValue ( min )
self . spnValue . setSpecialValueText ( self . tr ( ' Not set ' ) )
self . allowing_null = True
2017-05-15 19:01:15 +10:00
if param . defaultValue ( ) is not None :
self . setValue ( param . defaultValue ( ) )
2017-12-01 18:19:04 +10:00
if not self . allowing_null :
try :
self . spnValue . setClearValue ( float ( param . defaultValue ( ) ) )
except :
pass
2018-07-16 09:36:54 +07:00
elif self . param . minimum ( ) is not None and not self . allowing_null :
2016-11-11 11:02:42 +10:00
try :
2017-05-15 19:01:15 +10:00
self . setValue ( float ( self . param . minimum ( ) ) )
2017-12-01 18:19:04 +10:00
if not self . allowing_null :
self . spnValue . setClearValue ( float ( self . param . minimum ( ) ) )
2016-11-11 11:02:42 +10:00
except :
pass
2017-12-01 18:19:04 +10:00
elif not self . allowing_null :
2016-11-11 11:02:42 +10:00
self . setValue ( 0 )
self . spnValue . setClearValue ( 0 )
2017-11-10 10:42:25 +10:00
# we don't show the expression button outside of modeler
self . layout ( ) . removeWidget ( self . btnSelect )
sip . delete ( self . btnSelect )
self . btnSelect = None
2016-11-11 11:02:42 +10:00
2017-11-26 17:34:02 +10:00
if not self . param . isDynamic ( ) :
# only show data defined button for dynamic properties
self . layout ( ) . removeWidget ( self . btnDataDefined )
sip . delete ( self . btnDataDefined )
self . btnDataDefined = None
else :
self . btnDataDefined . init ( 0 , QgsProperty ( ) , self . param . dynamicPropertyDefinition ( ) )
2017-11-27 08:03:40 +10:00
self . btnDataDefined . registerEnabledWidget ( self . spnValue , False )
2017-11-26 17:34:02 +10:00
2017-11-10 10:42:25 +10:00
self . spnValue . valueChanged . connect ( lambda : self . hasChanged . emit ( ) )
2016-11-11 11:02:42 +10:00
2017-11-26 17:34:02 +10:00
def setDynamicLayer ( self , layer ) :
try :
2018-07-02 10:12:27 +10:00
self . layer = self . getLayerFromValue ( layer )
self . btnDataDefined . setVectorLayer ( self . layer )
2017-11-26 17:34:02 +10:00
except :
pass
2018-04-19 17:24:13 +10:00
def getLayerFromValue ( self , value ) :
context = createContext ( )
if isinstance ( value , QgsProcessingFeatureSourceDefinition ) :
value , ok = value . source . valueAsString ( context . expressionContext ( ) )
if isinstance ( value , str ) :
value = QgsProcessingUtils . mapLayerFromString ( value , context )
2018-07-03 09:03:12 +10:00
if value is None or not isinstance ( value , QgsMapLayer ) :
2018-07-02 10:12:27 +10:00
return None
# need to return layer with ownership - otherwise layer may be deleted when context
# goes out of scope
new_layer = context . takeResultLayer ( value . id ( ) )
# if we got ownership, return that - otherwise just return the layer (which may be owned by the project)
return new_layer if new_layer is not None else value
2018-04-19 17:24:13 +10:00
2016-11-11 11:02:42 +10:00
def getValue ( self ) :
2017-11-26 17:34:02 +10:00
if self . btnDataDefined is not None and self . btnDataDefined . isActive ( ) :
return self . btnDataDefined . toProperty ( )
elif self . allowing_null and self . spnValue . value ( ) == self . spnValue . minimum ( ) :
2017-12-01 18:19:04 +10:00
return None
else :
return self . spnValue . value ( )
2016-11-11 11:02:42 +10:00
def setValue ( self , value ) :
try :
self . spnValue . setValue ( float ( value ) )
except :
return
def calculateStep ( self , minimum , maximum ) :
value_range = maximum - minimum
if value_range < = 1.0 :
step = value_range / 10.0
# round to 1 significant figrue
return round ( step , - int ( math . floor ( math . log10 ( step ) ) ) )
else :
return 1.0
2018-04-19 17:24:13 +10:00
class DistanceInputPanel ( NumberInputPanel ) :
"""
Distance input panel for use outside the modeler - this input panel
contains a label showing the distance unit .
"""
def __init__ ( self , param ) :
super ( ) . __init__ ( param )
self . label = QLabel ( ' ' )
2018-07-26 14:38:37 +10:00
self . units_combo = QComboBox ( )
self . base_units = QgsUnitTypes . DistanceUnknownUnit
for u in ( QgsUnitTypes . DistanceMeters ,
QgsUnitTypes . DistanceKilometers ,
QgsUnitTypes . DistanceFeet ,
QgsUnitTypes . DistanceMiles ,
QgsUnitTypes . DistanceYards ) :
self . units_combo . addItem ( QgsUnitTypes . toString ( u ) , u )
2018-04-19 17:24:13 +10:00
label_margin = self . fontMetrics ( ) . width ( ' X ' )
self . layout ( ) . insertSpacing ( 1 , label_margin / 2 )
self . layout ( ) . insertWidget ( 2 , self . label )
2018-07-26 14:38:37 +10:00
self . layout ( ) . insertWidget ( 3 , self . units_combo )
self . layout ( ) . insertSpacing ( 4 , label_margin / 2 )
2018-04-19 17:24:13 +10:00
self . warning_label = QLabel ( )
icon = QgsApplication . getThemeIcon ( ' mIconWarning.svg ' )
size = max ( 24 , self . spnValue . height ( ) * 0.5 )
self . warning_label . setPixmap ( icon . pixmap ( icon . actualSize ( QSize ( size , size ) ) ) )
self . warning_label . setToolTip ( self . tr ( ' Distance is in geographic degrees. Consider reprojecting to a projected local coordinate system for accurate results. ' ) )
self . layout ( ) . insertWidget ( 4 , self . warning_label )
self . layout ( ) . insertSpacing ( 5 , label_margin )
2018-07-26 14:38:37 +10:00
2018-04-19 17:24:13 +10:00
self . setUnits ( QgsUnitTypes . DistanceUnknownUnit )
def setUnits ( self , units ) :
self . label . setText ( QgsUnitTypes . toString ( units ) )
2018-07-26 14:38:37 +10:00
if QgsUnitTypes . unitType ( units ) != QgsUnitTypes . Standard :
self . units_combo . hide ( )
self . label . show ( )
else :
self . units_combo . setCurrentIndex ( self . units_combo . findData ( units ) )
self . units_combo . show ( )
self . label . hide ( )
2018-04-19 17:24:13 +10:00
self . warning_label . setVisible ( units == QgsUnitTypes . DistanceDegrees )
2018-07-26 14:38:37 +10:00
self . base_units = units
2018-04-19 17:24:13 +10:00
def setUnitParameterValue ( self , value ) :
units = QgsUnitTypes . DistanceUnknownUnit
layer = self . getLayerFromValue ( value )
if isinstance ( layer , QgsMapLayer ) :
units = layer . crs ( ) . mapUnits ( )
elif isinstance ( value , QgsCoordinateReferenceSystem ) :
units = value . mapUnits ( )
2018-04-20 17:14:14 +10:00
elif isinstance ( value , str ) :
crs = QgsCoordinateReferenceSystem ( value )
if crs . isValid ( ) :
units = crs . mapUnits ( )
2018-04-19 17:24:13 +10:00
self . setUnits ( units )
2018-07-26 14:38:37 +10:00
def getValue ( self ) :
val = super ( ) . getValue ( )
if isinstance ( val , float ) and self . units_combo . isVisible ( ) :
display_unit = self . units_combo . currentData ( )
return val * QgsUnitTypes . fromUnitToUnitFactor ( display_unit , self . base_units )
return val
def setValue ( self , value ) :
try :
self . spnValue . setValue ( float ( value ) )
except :
return