class Slider(InputType): ''' slider input, displays a slider and a number input next to it, both connected to each other ''' def __init__(self, minimum: int = 0, maximum: int = 100000000, step: int = 1, width: int = 300, lockable: bool = False, locked: bool = False, **kwargs): ''' Parameters ---------- width : int, optional width of slider in pixels, defaults to 300 pixels minimum : int, optional minimum value that the user can set maximum : int, optional maximum value that the user can set step : int, optional the tick intervall of the slider and single step of the number input, defaults to 1 lockable : bool, optional the slider and number input can be locked by a checkbox that will be displayed next to them if True, defaults to not lockable locked : bool, optional initial lock-state of inputs, only applied if lockable is True, defaults to inputs being not locked ''' super().__init__(**kwargs) self.minimum = minimum self.maximum = maximum self.lockable = lockable self.step = step self.slider = QSlider(Qt.Horizontal) self.slider.setMinimum(minimum) self.slider.setMaximum(maximum) self.slider.setTickInterval(step) self.slider.setFixedWidth(width) self.spinbox = QSpinBox() self.spinbox.setMinimum(minimum) self.spinbox.setMaximum(maximum) self.spinbox.setSingleStep(step) self.registerFocusEvent(self.spinbox) self.registerFocusEvent(self.slider) if lockable: self.lock_button = QPushButton() self.lock_button.setCheckable(True) self.lock_button.setChecked(locked) self.lock_button.setSizePolicy( QSizePolicy.Fixed, QSizePolicy.Fixed) def toggle_icon(emit=True): is_locked = self.lock_button.isChecked() fn = '20190619_iconset_mob_lock_locked_02.png' if is_locked \ else '20190619_iconset_mob_lock_unlocked_03.png' self.slider.setEnabled(not is_locked) self.spinbox.setEnabled(not is_locked) icon_path = os.path.join(settings.IMAGE_PATH, 'iconset_mob', fn) icon = QIcon(icon_path) self.lock_button.setIcon(icon) self.locked.emit(is_locked) toggle_icon(emit=False) self.lock_button.clicked.connect(lambda: toggle_icon(emit=True)) self.slider.valueChanged.connect( lambda: self.set_value(self.slider.value())) self.spinbox.valueChanged.connect( lambda: self.set_value(self.spinbox.value())) self.slider.valueChanged.connect( lambda: self.changed.emit(self.get_value())) self.spinbox.valueChanged.connect( lambda: self.changed.emit(self.get_value()) ) def set_value(self, value: int): ''' set a number to both the slider and the number input Parameters ---------- checked : int check-state ''' for element in [self.slider, self.spinbox]: # avoid infinite recursion element.blockSignals(True) element.setValue(value or 0) element.blockSignals(False) @property def is_locked(self) -> bool: ''' Returns ------- bool current lock-state of slider and number input ''' if not self.lockable: return False return self.lock_button.isChecked() def draw(self, layout: QLayout, unit: str = ''): ''' add slider, the connected number and the lock (if lockable) input to the layout Parameters ---------- layout : QLayout layout to add the inputs to unit : str, optional the unit shown after the value, defaults to no unit ''' l = QHBoxLayout() l.addWidget(self.slider) l.addWidget(self.spinbox) if unit: l.addWidget(QLabel(unit)) if self.lockable: l.addWidget(self.lock_button) layout.addLayout(l) def get_value(self) -> int: ''' get the currently set number Returns ------- int currently set number ''' return self.slider.value()
class SpinBox(InputType): ''' spinbox integer number input ''' InputClass = QSpinBox def __init__(self, minimum=0, maximum=100000000, step=1, lockable=False, locked=False, reversed_lock=False, **kwargs): ''' Parameters ---------- minimum : int, optional minimum value that the user can set maximum : int, optional maximum value that the user can set step : int, optional the single step of the number input when changing values, defaults to 1 lockable : bool, optional the number input can be locked by a checkbox that will be displayed next to it if True, defaults to not lockable locked : bool, optional initial lock-state of input, only applied if lockable is True, defaults to input being not locked reversed_lock : bool, optional reverses the locking logic, if True checking the lock will enable the inputs instead of disabling them, defaults to normal lock behaviour (disabling inputs when setting lock-state to True) ''' super().__init__(**kwargs) self.minimum = minimum self.maximum = maximum self.input = self.InputClass() self.input.setMinimum(minimum) self.input.setMaximum(maximum) self.input.setSingleStep(step) self.input.valueChanged.connect(self.changed.emit) self.registerFocusEvent(self.input) self.lockable = lockable # ToDo: almost the same as in Slider, outsource into common function if lockable: self.lock_button = QPushButton() self.lock_button.setCheckable(True) self.lock_button.setChecked(locked) self.lock_button.setSizePolicy( QSizePolicy.Fixed, QSizePolicy.Fixed) def toggle_icon(emit=True): is_locked = self.lock_button.isChecked() fn = '20190619_iconset_mob_lock_locked_02.png' if is_locked \ else '20190619_iconset_mob_lock_unlocked_03.png' self.input.setEnabled(is_locked if reversed_lock else not is_locked) icon_path = os.path.join(settings.IMAGE_PATH, 'iconset_mob', fn) icon = QIcon(icon_path) self.lock_button.setIcon(icon) self.locked.emit(is_locked) toggle_icon(emit=False) self.lock_button.clicked.connect(lambda: toggle_icon(emit=True)) def set_value(self, value: int): ''' set the value of the input Parameters ---------- value : int value to set ''' self.input.setValue(value or 0) def get_value(self) -> int: ''' get the current value of the input Returns ------- value : int current value of input ''' return self.input.value() @property def is_locked(self) -> bool: ''' Returns ------- bool current lock-state of number input ''' if not self.lockable: return False return self.lock_button.isChecked() def draw(self, layout: QLayout, unit: str = ''): ''' add number input and the lock (if lockable) to the layout Parameters ---------- layout : QLayout layout to add the inputs to unit : str, optional the unit shown after the value, defaults to no unit ''' l = QHBoxLayout() l.addWidget(self.input) if unit: l.addWidget(QLabel(unit)) if self.lockable: l.addWidget(self.lock_button) layout.addLayout(l)
class RiverMetricsDockWidget(QDockWidget, FORM_CLASS): closingPlugin = pyqtSignal() def __init__(self, parent=None): """Constructor.""" super(RiverMetricsDockWidget, self).__init__(parent) # Set up the user interface from Designer. # After setupUI you can access any designer object by doing # self.<objectname>, and you can use autoconnect slots - see # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html # #widgets-and-dialogs-with-auto-connect self.setupUi(self) # fill the layer combobox with vector layers self.vectorCombo.setFilters(QgsMapLayerProxyModel.LineLayer) self.validate.clicked.connect(self.validateLayer) self.graph.clicked.connect(self.graph_data) self.clear_graph.clicked.connect(self.do_clear_graph) #self.vectorCombo.currentIndexChanged.connect(self.setup_gui) #global variables self.validate_test = None self.graphicState = False self.canvas = None self.figure = None self.breaks = [] # breaks of river axes self.line = None # line geometry self.breakButton = QPushButton('Add breaks') self.breakButton.setCheckable(True) self.Xcsv = None self.Ycsv = None self.breakButton.clicked.connect(self.addBreaks) self.browseBtn.clicked.connect(self.writeFile) self.filecsvtemp = tempfile.NamedTemporaryFile(suffix='.csv') self.filecsvpath = os.path.splitext(str( self.filecsvtemp.name))[0] + '.csv' self.lineOutput.setText(self.filecsvpath) #create container plot self.setup_gui() def closeEvent(self, event): self.closingPlugin.emit() event.accept() def message(self, text, col): self.validator.setText(text) self.validator.setStyleSheet('background-color: ' + col) def setup_gui(self): #PLOT container # a figure instance to plot on self.figure = plt.figure() # this is the Canvas Widget that displays the `figure` # it takes the `figure` instance as a parameter to __init__ self.canvas = FigureCanvas(self.figure) # this is the Navigation widget # it takes the Canvas widget and a parent self.toolbar = NavigationToolbar(self.canvas, self) # create a new empty QVboxLayout self.layout = QVBoxLayout() self.layout.addWidget(self.toolbar) self.layout.addWidget(self.canvas) self.layout.addWidget(self.breakButton) # set the Qframe layout self.frame_for_plot.setLayout(self.layout) # si alla fine def validateLayer(self): self.validator.clear() self.validator.setStyleSheet('background-color: None') vlayer = self.vectorCombo.currentLayer() if not vlayer.isValid(): self.message(vlayer.name() + ' is not valid', 'red') self.validate_test = False elif vlayer.geometryType() != QgsWkbTypes.LineGeometry: self.message('your vector layer is not a Line', 'red') self.validate_test = False else: self.validate_test = True self.message(vlayer.name() + ' is valid', 'white') if vlayer.featureCount() > 1: self.message('You have more then one feature on your vector layer', 'yellow') self.validate_test = False else: for feat in vlayer.getFeatures(): the_geom = feat.geometry() len = the_geom.length() self.validate_test = True if self.stepSpin.value() >= the_geom.length(): self.message( 'Use a smaller value for step less than ' + str(round(len / 1000, 2)) + ' km', 'yellow') self.validate_test = False #TODO -- add validate CRS layer to be not a geographic crslayer = vlayer.crs().toProj4() if 'proj=longlat' in crslayer: self.validate_test = False self.message('The layer crs is not projected', 'yellow') if crslayer == '': self.validate_test = True self.message('warning:the crs seems missing', 'yellow') def clearLayout(self, layout): ''' clear layout function :return: ''' while layout.count(): child = layout.takeAt(0) if child.widget(): child.widget().deleteLater() def graph_data(self): step = self.stepSpin.value() shif = self.shiftSpin.value() if self.validate_test == None: self.message('You have to validate your layers first', 'yellow') if self.validate_test == True: vlayer = self.vectorCombo.currentLayer() for feat in vlayer.getFeatures(): the_geom = feat.geometry() x, y = sinuosity(the_geom, step, shif) self.line = the_geom self.figure.clear() # create an axis ax = self.figure.add_subplot(111) ax.plot(x, y, 'bo', x, y, 'k') self.Xcsv = x self.Ycsv = y #def addVline(): #if breakButton.isChecked(): # figure.canvas.mpl_connect('button_press_event', OnClick) #TODO: debug #self.canvas.show() self.canvas.draw() #set variable graphState to true to remember graph is plotted self.graphicState = True self.writecsv() #self.graph.hide() def addBreaks(self): def OnClick(event): ax = self.figure.add_subplot(111) ax.axvline(event.xdata, linewidth=4, color='r') self.canvas.draw() self.breaks.append(float(event.xdata)) if self.graphicState is True: if self.breakButton.isChecked(): self.breakButton.setText('stop-break') self.cid = self.figure.canvas.mpl_connect( 'button_press_event', OnClick) #TODO: create memory layer splitted with breaks else: self.breakButton.setText('Add Break') self.figure.canvas.mpl_disconnect(self.cid) self.final() #print self.breaks # ll1 = createMemLayer(self.line, self.breaks) # QgsMapLayerRegistry.instance().addMapLayers([ll1]) else: self.message('You have to graph your data first', 'yellow') def final(self): vlayer = self.vectorCombo.currentLayer() for feat in vlayer.getFeatures(): the_geom = feat.geometry() #TODO: remove the line for debigging #self.message(str(vlayer.name())+'|'+str(the_geom.length())+'|'+str(self.breaks), 'red') ll1 = createMemLayer(the_geom, self.breaks) QgsProject.instance().addMapLayer(ll1) def writeFile(self): fileName, __ = QFileDialog.getSaveFileName( self, 'Save CSV file', "", "CSV (*.csv);;All files (*)") fileName = os.path.splitext(str(fileName))[0] + '.csv' self.lineOutput.setText(fileName) def writecsv(self): filecsv = open(self.lineOutput.text(), 'w') filecsv.write('length,sinuosity\n') for row in range(len(self.Xcsv)): filecsv.write( str(round(self.Xcsv[row], 4)) + ',' + str(round(self.Ycsv[row], 4)) + '\n') filecsv.close() def do_clear_graph(self): self.figure.clear() self.canvas.draw()