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 PipeSectionDialog(QDialog): def __init__(self, parent, iface, params, pipe_ft): QDialog.__init__(self, parent) main_lay = QVBoxLayout(self) self.parent = parent self.params = params self.pipe_ft = pipe_ft self.setMinimumWidth(600) self.setMinimumHeight(400) self.setWindowTitle('Pipe section editor') # TODO: softcode self.setWindowModality(QtCore.Qt.ApplicationModal) curr_dir = os.path.dirname(os.path.abspath(__file__)) self.fra_toolbar = QFrame(self) fra_toolbar_lay = QHBoxLayout(self.fra_toolbar) self.btn_zoom = QPushButton('Zoom') self.btn_zoom.clicked.connect(self.btn_zoom_clicked) set_up_button(self.btn_zoom, os.path.join(curr_dir, 'i_zoom.png'), True, 13, 13, 'Zoom') # TODO: softcode self.btn_pan = QPushButton('Pan') self.btn_pan.clicked.connect(self.btn_pan_clicked) set_up_button(self.btn_pan, os.path.join(curr_dir, 'i_pan.png'), True, 15, 15, 'Pan') # TODO: softcode self.btn_home = QPushButton('Full extent') self.btn_home.clicked.connect(self.btn_home_clicked) self.btn_back = QPushButton('Back') self.btn_back.clicked.connect(self.btn_back_clicked) set_up_button(self.btn_back, os.path.join(curr_dir, 'i_back.png'), False, 7, 13, 'Back') # TODO: softcode self.btn_forth = QPushButton('Forth') self.btn_forth.clicked.connect(self.btn_forth_clicked) set_up_button(self.btn_forth, os.path.join(curr_dir, 'i_forth.png'), False, 7, 13, 'Forward') # TODO: softcode self.btn_edit = QPushButton('Edit') self.btn_edit.clicked.connect(self.btn_edit_clicked) fra_toolbar_lay.addWidget(self.btn_zoom) fra_toolbar_lay.addWidget(self.btn_pan) fra_toolbar_lay.addWidget(self.btn_home) fra_toolbar_lay.addWidget(self.btn_back) fra_toolbar_lay.addWidget(self.btn_forth) fra_toolbar_lay.addWidget(self.btn_edit) # Graph canvas self.fra_graph = QFrame(self) self.static_canvas = SectionCanvas(iface, params, self) # Toolbar self.toolbar = NavigationToolbar2QT(self.static_canvas, self) self.toolbar.hide() # OK/Cancel buttons self.fra_buttons = QFrame(self) fra_buttons_lay = QHBoxLayout(self.fra_buttons) self.btn_Cancel = QPushButton('Cancel') self.btn_Ok = QPushButton('OK') fra_buttons_lay.addWidget(self.btn_Ok) fra_buttons_lay.addWidget(self.btn_Cancel) main_lay.addWidget(self.fra_toolbar) main_lay.addWidget(self.static_canvas) main_lay.addWidget(self.fra_buttons) self.setup() self.initialize() def setup(self): # Buttons self.btn_Cancel.clicked.connect(self.btn_cancel_clicked) self.btn_Ok.clicked.connect(self.btn_ok_clicked) def initialize(self): dem_xz = self.find_raster_distz() pipe_xz = self.find_line_distz() self.static_canvas.draw_pipe_section(dem_xz, pipe_xz) def btn_home_clicked(self): self.toolbar.home() def btn_zoom_clicked(self): self.toolbar.zoom() self.btn_pan.setChecked(False) def btn_pan_clicked(self): self.toolbar.pan() self.btn_zoom.setChecked(False) def btn_back_clicked(self): self.toolbar.back() def btn_forth_clicked(self): self.toolbar.forward() def btn_edit_clicked(self): # Deactivate tools if self.toolbar._active == "PAN": self.toolbar.pan() elif self.toolbar._active == "ZOOM": self.toolbar.zoom() def btn_cancel_clicked(self): self.setVisible(False) def btn_ok_clicked(self): new_zs = self.static_canvas.pipe_line.get_ydata() pipe_geom_v2 = self.pipe_ft.geometry().get() for p in range(pipe_geom_v2.vertexCount(0, 0)): vertex_id = QgsVertexId(0, 0, p, QgsVertexId.SegmentVertex) vertex = pipe_geom_v2.vertexAt(vertex_id) new_pos_pt = QgsPoint(vertex.x(), vertex.y()) new_pos_pt.addZValue(new_zs[p]) LinkHandler.move_link_vertex(self.params, self.params.pipes_vlay, self.pipe_ft, new_pos_pt, p) # Update delta z for nodes (start_node_ft, end_node_ft) = NetworkUtils.find_start_end_nodes( self.params, self.pipe_ft.geometry()) start_node_elev = start_node_ft.attribute(Junction.field_name_elev) # end_node_deltaz = end_node_ft.attribute(Junction.field_name_delta_z) end_node_elev = end_node_ft.attribute(Junction.field_name_elev) start_node_new_deltaz = new_zs[0] - start_node_elev end_node_new_deltaz = new_zs[-1] - end_node_elev start_node_ft.setAttribute( start_node_ft.fieldNameIndex(Junction.field_name_delta_z), start_node_new_deltaz) end_node_ft.setAttribute( end_node_ft.fieldNameIndex(Junction.field_name_delta_z), end_node_new_deltaz) # Update start node elevation attribute start_node_lay = NetworkUtils.find_node_layer(self.params, start_node_ft.geometry()) vector_utils.update_attribute(start_node_lay, start_node_ft, Junction.field_name_delta_z, float(start_node_new_deltaz)) # Update end node elevation attribute end_node_lay = NetworkUtils.find_node_layer(self.params, end_node_ft.geometry()) vector_utils.update_attribute(end_node_lay, end_node_ft, Junction.field_name_delta_z, float(end_node_new_deltaz)) # Update pipe length # Calculate 3D length pipe_geom_2 = self.pipe_ft.geometry() if self.params.dem_rlay is not None: length_3d = LinkHandler.calc_3d_length(self.params, pipe_geom_2) else: length_3d = pipe_geom_2.length() vector_utils.update_attribute(self.params.pipes_vlay, self.pipe_ft, Pipe.field_name_length, length_3d) self.setVisible(False) def find_line_distz(self): # Get start and end nodes zs and deltazs from table pipe_geom = self.pipe_ft.geometry() (start_node_ft, end_node_ft) = NetworkUtils.find_start_end_nodes( self.params, self.pipe_ft.geometry()) start_node_z = start_node_ft.attribute(Junction.field_name_elev) if start_node_z == NULL: start_node_z = 0 start_node_deltaz = start_node_ft.attribute( Junction.field_name_delta_z) end_node_z = end_node_ft.attribute(Junction.field_name_elev) if end_node_z == NULL: end_node_z = 0 end_node_deltaz = end_node_ft.attribute(Junction.field_name_delta_z) total_dist = 0 dist_z = OrderedDict() pipe_geom_v2 = pipe_geom.get() dist_z[0] = start_node_z + start_node_deltaz # Interpolate deltaZs for remaining vertices for p in range(1, pipe_geom_v2.vertexCount(0, 0)): vertex = pipe_geom_v2.vertexAt( QgsVertexId(0, 0, p, QgsVertexId.SegmentVertex)) vertex_prev = pipe_geom_v2.vertexAt( QgsVertexId(0, 0, p - 1, QgsVertexId.SegmentVertex)) total_dist += math.sqrt((vertex.x() - vertex_prev.x())**2 + (vertex.y() - vertex_prev.y())**2) # Interpolate delta z for vertex using distance from nodes and delta z of nodes z = (total_dist / self.pipe_ft.geometry().length() * (end_node_z - start_node_z)) + start_node_z # z = raster_utils.read_layer_val_from_coord(self.params.dem_rlay, QgsPoint(vertex.x(), vertex.y())) delta_z = ( total_dist / self.pipe_ft.geometry().length() * (end_node_deltaz - start_node_deltaz)) + start_node_deltaz dist_z[total_dist] = z + delta_z return dist_z def find_line_distz3D(self): pipe_geom_v2 = self.pipe_ft.geometry().get() # Find start and end nodes (needed to know delta z) (start_node_ft, end_node_ft) = NetworkUtils.find_start_end_nodes( self.params, self.pipe_ft.geometry()) start_node_deltaz = start_node_ft.attribute( Junction.field_name_delta_z) end_node_deltaz = end_node_ft.attribute(Junction.field_name_delta_z) total_dist = 0 dist_z = OrderedDict() dist_z[0] = pipe_geom_v2.vertexAt( QgsVertexId(0, 0, 0, QgsVertexId.SegmentVertex)).z() #+ start_node_deltaz for p in range(1, pipe_geom_v2.vertexCount(0, 0)): vertex = pipe_geom_v2.vertexAt( QgsVertexId(0, 0, p, QgsVertexId.SegmentVertex)) vertex_prev = pipe_geom_v2.vertexAt( QgsVertexId(0, 0, p - 1, QgsVertexId.SegmentVertex)) total_dist += math.sqrt((vertex.x() - vertex_prev.x())**2 + (vertex.y() - vertex_prev.y())**2) # Interpolate delta z for vertex using distance from nodes and delta z of nodes delta_z = ( total_dist / self.pipe_ft.geometry().length() * (end_node_deltaz - start_node_deltaz)) + start_node_deltaz dist_z[total_dist] = vertex.z() #+ delta_z return dist_z def find_raster_distz(self): dem_extent = self.params.dem_rlay.extent() ul_coord = QgsPoint(dem_extent.xMinimum(), dem_extent.yMaximum()) x_cell_size = self.params.dem_rlay.rasterUnitsPerPixelX() y_cell_size = -self.params.dem_rlay.rasterUnitsPerPixelY() points = [] pipe_pts = self.pipe_ft.geometry().asPolyline() for p in range(1, len(pipe_pts)): start_col_row = raster_utils.get_col_row(pipe_pts[p - 1], ul_coord, x_cell_size, y_cell_size) end_col_row = raster_utils.get_col_row(pipe_pts[p], ul_coord, x_cell_size, y_cell_size) points.extend( bresenham.get_line((start_col_row.x, start_col_row.y), (end_col_row.x, end_col_row.y))) total_dist = 0 dist_z = OrderedDict() dist_z[total_dist] = raster_utils.read_layer_val_from_coord( self.params.dem_rlay, raster_utils.get_coords(points[0][0], points[0][1], ul_coord, x_cell_size, y_cell_size)) for p in range(1, len(points)): total_dist += math.sqrt(( (points[p][0] - points[p - 1][0]) * x_cell_size)**2 + ( (points[p][1] - points[p - 1][1]) * y_cell_size)**2) dist_z[total_dist] = raster_utils.read_layer_val_from_coord( self.params.dem_rlay, raster_utils.get_coords(points[p][0], points[p][1], ul_coord, x_cell_size, y_cell_size)) return dist_z
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()
def initialize_widgets(self): """Dynamically set up widgets based on detected files.""" self.widgets_per_file.clear() files_widgets = [ self.widget_general, self.widget_terrain_model, self.widget_simple_infiltration, self.widget_groundwater, self.widget_interflow, ] files_info_collection = [ self.general_files, self.terrain_model_files, self.simple_infiltration_files, self.groundwater_files, self.interflow_files, ] for widget in files_widgets: widget.hide() current_main_layout_row = 1 for widget, files_info in zip(files_widgets, files_info_collection): widget_layout = widget.layout() for field_name, name in files_info.items(): try: file_state = self.detected_files[field_name] except KeyError: continue status = file_state["status"] widget.show() name_label = QLabel(name) name_label.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) widget_layout.addWidget(name_label, current_main_layout_row, 0) status_label = QLabel(status.value) status_label.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) widget_layout.addWidget(status_label, current_main_layout_row, 1) empty_label = QLabel() widget_layout.addWidget(empty_label, current_main_layout_row, 2) no_action_pb_name = "Ignore" if status == UploadFileStatus.DELETED_LOCALLY: action_pb_name = "Delete online" else: action_pb_name = "Upload" # Add valid reference widgets all_actions_widget = QWidget() actions_sublayout = QGridLayout() all_actions_widget.setLayout(actions_sublayout) valid_ref_widget = QWidget() valid_ref_sublayout = QGridLayout() valid_ref_widget.setLayout(valid_ref_sublayout) no_action_pb = QPushButton(no_action_pb_name) no_action_pb.setCheckable(True) no_action_pb.setAutoExclusive(True) no_action_pb.clicked.connect( partial(self.toggle_action, field_name, False)) action_pb = QPushButton(action_pb_name) action_pb.setCheckable(True) action_pb.setAutoExclusive(True) action_pb.setChecked(True) action_pb.clicked.connect( partial(self.toggle_action, field_name, True)) valid_ref_sublayout.addWidget(no_action_pb, 0, 0) valid_ref_sublayout.addWidget(action_pb, 0, 1) # Add invalid reference widgets invalid_ref_widget = QWidget() invalid_ref_sublayout = QGridLayout() invalid_ref_widget.setLayout(invalid_ref_sublayout) filepath_sublayout = QGridLayout() filepath_line_edit = QLineEdit() filepath_line_edit.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) browse_pb = QPushButton("...") browse_pb.clicked.connect( partial(self.browse_for_raster, field_name)) filepath_sublayout.addWidget(filepath_line_edit, 0, 0) filepath_sublayout.addWidget(browse_pb, 0, 1) invalid_ref_sublayout.addLayout(filepath_sublayout, 0, 0) update_ref_pb = QPushButton("Update reference") update_ref_pb.clicked.connect( partial(self.update_raster_reference, field_name)) invalid_ref_sublayout.addWidget(update_ref_pb, 0, 1) actions_sublayout.addWidget(valid_ref_widget, 0, 0) actions_sublayout.addWidget(invalid_ref_widget, 0, 1) # Add all actions widget into the main widget layout widget_layout.addWidget(all_actions_widget, current_main_layout_row, 2) # Hide some of the widgets based on files states if status == UploadFileStatus.NO_CHANGES_DETECTED: all_actions_widget.hide() elif status == UploadFileStatus.INVALID_REFERENCE: valid_ref_widget.hide() else: invalid_ref_widget.hide() self.widgets_per_file[field_name] = ( name_label, status_label, valid_ref_widget, action_pb, invalid_ref_widget, filepath_line_edit, ) current_main_layout_row += 1