
from typing import List, Optional, Union

from pathlib import Path
import logging
import re
import random
import colorsys

logger = logging.getLogger(__name__)

from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import matplotlib.pyplot as plt

from qgis.PyQt.QtCore import (
    Qt,
    QFileInfo,
)

from qgis.PyQt.QtGui import (
    QColor,
)

from qgis.PyQt.QtWidgets import (
    QAbstractItemView,
    QButtonGroup,
    QCheckBox,
    QComboBox,
    QDialog,
    QDialogButtonBox,
    QDoubleSpinBox,
    QFileDialog,
    QFormLayout,
    QGridLayout,
    QGroupBox,
    QHBoxLayout,
    QLabel,
    QLineEdit,
    QPushButton,
    QRadioButton,
    QSpacerItem,
    QSpinBox,
    QTextEdit,
    QTreeWidget,
    QTreeWidgetItem,
    QVBoxLayout,
    QWidget,
)

from qgis.gui import (
    QgsColorButton,
    QgsOpacityWidget,
    QgsRubberBand,
)

from qgis.core import (
    QgsWkbTypes,
    QgsPointXY,
    QgsVectorLayer,
    QgsProject,
)

from .structures import *
from .constants import UIConstants

from ...apps.qt.colors import *
from ...apps.qt.tools import *
from ...apps.qt.messages import *
from ...apps.qt.filesystem import *

from ...apps.qgis.messages import *
from ...apps.qgis.project import *
from ...apps.qgis.canvas import *
from ...apps.qgis.symbology import *

from ...utils.geoprofiler.export import *

from geogst.core.geology.profiles.geoprofiles import *
from geogst.plots.parameters import *
from geogst.core.geometries.shapes.lines import *


class CreateNewGeopackageDialog(QDialog):
    """
    Dialog to define a new GeoPackage and the first layer to create inside it.
    """

    def __init__(self, plugin_name, parent=None):
        super().__init__(parent)

        self.plugin_name = plugin_name

        self.setWindowTitle("Create new GeoPackage")
        self.setModal(True)
        self.resize(620, 0)

        self._build_ui()
        self._connect_signals()
        self._update_create_button_state()

    def _build_ui(self):
        main_layout = QVBoxLayout()
        main_layout.setContentsMargins(12, 12, 12, 12)
        main_layout.setSpacing(10)

        intro_label = QLabel(
            "Create a new GeoPackage file and define the first 3D line layer "
            "that will be used for geological profile storage."
        )
        intro_label.setWordWrap(True)
        main_layout.addWidget(intro_label)

        form_layout = QFormLayout()
        form_layout.setContentsMargins(0, 0, 0, 0)
        form_layout.setSpacing(8)
        form_layout.setLabelAlignment(
            form_layout.labelAlignment()
        )

        # GeoPackage path row
        self.geopackage_path_lineedit = QLineEdit()
        self.geopackage_path_lineedit.setPlaceholderText(
            "Choose output GeoPackage path (*.gpkg)"
        )
        self.geopackage_path_lineedit.setToolTip(
            "Path of the GeoPackage file to create."
        )

        self.browse_button = QPushButton("Browse…")
        self.browse_button.setToolTip(
            "Choose where the new GeoPackage will be saved."
        )

        path_row_widget = QWidget()
        path_row_layout = QHBoxLayout()
        path_row_layout.setContentsMargins(0, 0, 0, 0)
        path_row_layout.setSpacing(6)
        path_row_layout.addWidget(self.geopackage_path_lineedit, 1)
        path_row_layout.addWidget(self.browse_button, 0)
        path_row_widget.setLayout(path_row_layout)

        form_layout.addRow("GeoPackage file", path_row_widget)

        # Layer name row
        self.layer_name_lineedit = QLineEdit()
        self.layer_name_lineedit.setPlaceholderText(
            "Enter the name of the new 3D line layer"
        )
        self.layer_name_lineedit.setToolTip(
            "Name of the layer to create inside the GeoPackage."
        )
        form_layout.addRow("Layer name", self.layer_name_lineedit)

        # Optional project loading
        self.load_into_project_checkbox = QCheckBox(
            "Load created layer into current project"
        )
        self.load_into_project_checkbox.setChecked(True)
        self.load_into_project_checkbox.setToolTip(
            "If checked, the newly created layer will be added to the current QGIS project."
        )
        form_layout.addRow("", self.load_into_project_checkbox)

        main_layout.addLayout(form_layout)

        self.info_label = QLabel(
            "Geometry type: LineString25D"
        )
        self.info_label.setWordWrap(True)
        main_layout.addWidget(self.info_label)

        self.button_box = QDialogButtonBox(
            QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
        )
        self.create_button = self.button_box.button(QDialogButtonBox.StandardButton.Ok)
        self.create_button.setText("Create")
        self.create_button.setDefault(True)

        main_layout.addWidget(self.button_box)

        self.setLayout(main_layout)

    def _connect_signals(self):
        self.browse_button.clicked.connect(self.define_outpath)
        self.button_box.accepted.connect(self._validate_and_accept)
        self.button_box.rejected.connect(self.reject)

        self.geopackage_path_lineedit.textChanged.connect(
            self._on_geopackage_path_changed
        )
        self.geopackage_path_lineedit.textChanged.connect(
            self._update_create_button_state
        )
        self.layer_name_lineedit.textChanged.connect(
            self._update_create_button_state
        )

    def define_outpath(self):
        outfile_path = new_file_path(
            self,
            "Create new GeoPackage",
            "",
            "GeoPackages (*.gpkg)"
        )

        if not outfile_path:
            return

        if not outfile_path.lower().endswith(".gpkg"):
            outfile_path = f"{outfile_path}.gpkg"

        self.geopackage_path_lineedit.setText(outfile_path)

    def _on_geopackage_path_changed(self, text):
        """
        Automatically suggest the layer name from the selected file name,
        but only if the layer name field is still empty.
        """
        if self.layer_name_lineedit.text().strip():
            return

        cleaned_text = text.strip()
        if not cleaned_text:
            return

        suggested_name = Path(cleaned_text).stem.strip()
        if suggested_name:
            self.layer_name_lineedit.setText(suggested_name)

    def _update_create_button_state(self):
        geopackage_path = self.geopackage_path_lineedit.text().strip()
        layer_name = self.layer_name_lineedit.text().strip()

        self.create_button.setEnabled(bool(geopackage_path) and bool(layer_name))

    def _validate_and_accept(self):
        geopackage_path = self.geopackage_path_lineedit.text().strip()
        layer_name = self.layer_name_lineedit.text().strip()

        if not geopackage_path:
            warn_qgis(
                self.plugin_name,
                "Please define the output GeoPackage path."
            )
            self.geopackage_path_lineedit.setFocus()
            return

        if not geopackage_path.lower().endswith(".gpkg"):
            warn_qgis(
                self.plugin_name,
                "The output file must have .gpkg extension."
            )
            self.geopackage_path_lineedit.setFocus()
            return

        if not layer_name:
            warn_qgis(
                self.plugin_name,
                "Please define the layer name."
            )
            self.layer_name_lineedit.setFocus()
            return

        self.accept()

    @property
    def geopackage_path(self):
        return self.geopackage_path_lineedit.text().strip()

    @property
    def layer_name(self):
        return self.layer_name_lineedit.text().strip()

    @property
    def load_into_project(self):
        return self.load_into_project_checkbox.isChecked()


def _get_cat_id(arrlist) -> str:
    """
    Prova in ordine:
    - .id (tipico di CategorizedArrays nel tuo arrays.py)
    - ._id
    - parsing dal repr: 'rec_id=XYZ'
    """
    for attr in ("id", "_id"):
        if hasattr(arrlist, attr):
            v = getattr(arrlist, attr)
            if v is not None:
                return str(v).strip()

    m = re.search(r"rec_id=([^\n\r\)]+)", repr(arrlist))
    return m.group(1).strip() if m else ""

def make_polygon_colors(polygons_intersections, seed: int | None = None) -> dict[str, QColor]:
    """
    polygons_intersections: es. [[[ArrayList(...), ArrayList(...), ...]]]
    Ritorna: { "PL": QColor(...), "Qt": QColor(...), ... }
    """
    if seed is not None:
        random.seed(seed)

    cats = []
    def walk(x):
        if x is None:
            return
        if isinstance(x, (list, tuple)):
            for e in x:
                walk(e)
        else:
            # x dovrebbe essere ArrayList/CategorizedArrays
            cat = _get_cat_id(x)
            if cat:
                cats.append(cat)

    walk(polygons_intersections)

    uniq = sorted(set(cats))
    if not uniq:
        return {}

    # colori “ben separati”: hue diverso per categoria, sat/val random in range buono
    colors = {}
    n = len(uniq)
    for i, cat in enumerate(uniq):
        # Hue distribuito uniformemente
        h = (i / max(1, n)) % 1.0
        # not security/cryptography-relevant: only used to vary plot colors
        s = random.uniform(UIConstants.RANDOM_SATURATION_MIN, UIConstants.RANDOM_SATURATION_MAX)  # nosec B311
        v = random.uniform(UIConstants.RANDOM_VALUE_MIN, UIConstants.RANDOM_VALUE_MAX)  # nosec B311

        r, g, b = colorsys.hsv_to_rgb(h, s, v)
        colors[cat] = (r, g, b)

    return colors


class FigureWindow(QDialog):
    """
    Shows a figure, with the controls the caller asks for.

    Besides saving, two optional controls are available, so that the same window
    serves both the stacked plot of every profile and a single exploded one:

    - an "Explode" chooser, when the caller supplies the profile labels and a
      handler, to open one profile on its own;
    - a "Graphical parameters" button, when the caller supplies a handler that
      restyles and returns a new figure, which then replaces the one on show.
    """

    def __init__(self,
                 figure: Figure,
                 explode_labels: Optional[List[str]] = None,
                 on_explode=None,
                 on_restyle=None,
                 parent=None,
                 ):
        """
        :param figure: the figure to show.
        :param explode_labels: one label per profile, in profile order, to fill
            the "Explode" chooser with. Omit for a window with no chooser.
        :param on_explode: called with the chosen profile's index when the user
            asks to explode it.
        :param on_restyle: called with no arguments when the user asks to change
            this figure's graphical parameters; returns the new figure to show,
            or None to leave the current one alone.
        """

        super().__init__(parent)

        self.figure = figure
        self._on_explode = on_explode
        self._on_restyle = on_restyle

        layout = QVBoxLayout()

        controls_layout = QHBoxLayout()

        self.export_button = QPushButton("Save figure")
        self.export_button.clicked.connect(self.save_figure)
        controls_layout.addWidget(self.export_button)

        if on_restyle is not None:
            self.restyle_button = QPushButton("Graphical parameters")
            self.restyle_button.clicked.connect(self.restyle_figure)
            controls_layout.addWidget(self.restyle_button)

        if explode_labels and on_explode is not None:

            controls_layout.addStretch()
            controls_layout.addWidget(QLabel("Explode"))

            self.explode_combobox = QComboBox()
            self.explode_combobox.addItems(explode_labels)
            controls_layout.addWidget(self.explode_combobox)

            self.explode_button = QPushButton("Explode")
            self.explode_button.clicked.connect(self.explode_chosen_profile)
            controls_layout.addWidget(self.explode_button)

        layout.addLayout(controls_layout)

        self.canvas = FigureCanvas(figure)
        layout.addWidget(self.canvas)

        self.setLayout(layout)

        self.setWindowFlags(
            Qt.WindowType.Window | Qt.WindowType.WindowMinimizeButtonHint | Qt.WindowType.WindowMaximizeButtonHint | Qt.WindowType.WindowCloseButtonHint)

        self.setSizeGripEnabled(True)

        self.resize(UIConstants.DEFAULT_WINDOW_WIDTH, UIConstants.DEFAULT_WINDOW_HEIGHT)
        self.setMinimumSize(UIConstants.MIN_WINDOW_WIDTH, UIConstants.MIN_WINDOW_HEIGHT)
        self.setMaximumSize(UIConstants.MAX_WINDOW_SIZE, UIConstants.MAX_WINDOW_SIZE)

        self.show()

    def explode_chosen_profile(self):
        """Hands the chosen profile's index to the caller's explode handler."""

        self._on_explode(self.explode_combobox.currentIndex())

    def restyle_figure(self):
        """Asks the caller for a restyled figure and shows it in place."""

        new_figure = self._on_restyle()

        if new_figure is not None:
            self.set_figure(new_figure)

    def set_figure(self, figure: Figure):
        """
        Replaces the figure on show, dropping the canvas that held the previous
        one: a canvas is bound to its figure, so it cannot be reused.
        """

        self.layout().removeWidget(self.canvas)
        self.canvas.setParent(None)
        self.canvas.deleteLater()

        self.figure = figure
        self.canvas = FigureCanvas(figure)
        self.layout().addWidget(self.canvas)

    def export_as_svg(self):
        """Combina tutte le figure e le esporta come un unico file SVG."""
        # Apri un QFileDialog per scegliere il file di output
        file_path, _ = QFileDialog.getSaveFileName(
            self,
            "Esporta come SVG",
            "",
            "SVG Files (*.svg);;All Files (*)"
        )

        if file_path:
            # Calcola le proporzioni relative di ogni figura
            total_height = sum(canvas.size().height() for canvas in self.fig_canvases)
            figure_ratios = [canvas.size().height() / total_height for canvas in self.fig_canvases]

            # Crea una nuova figura combinata con dimensioni proporzionali
            combined_figure = plt.figure(figsize=(8, 8))  # Larghezza fissa, altezza regolata
            current_height = 0

            for i, (canvas, ratio) in enumerate(zip(self.figure, figure_ratios)):
                ax_combined = combined_figure.add_axes(
                    [UIConstants.FIGURE_AXES_LEFT, current_height, UIConstants.FIGURE_AXES_WIDTH, ratio]
                )
                original_figure = canvas.figure
                for ax in original_figure.axes:
                    for line in ax.get_lines():
                        ax_combined.plot(
                            line.get_xdata(),
                            line.get_ydata(),
                            label=line.get_label()
                        )
                ax_combined.set_title(f"Figura combinata {i + 1}")
                ax_combined.legend()
                current_height += ratio  # Aggiorna la posizione verticale

            # Salva la figura combinata come SVG
            combined_figure.savefig(file_path, format="svg")

    def save_figure(self):
        """Salva il canvas della figura in un file."""
        # Apri un QFileDialog per scegliere il file
        file_path, _ = QFileDialog.getSaveFileName(
            self,
            "Salva Figura",
            "",
            "PDF Files (*.pdf);;SVG Files (*.svg);;TIFF Files (*.tif *.tiff);;JPEG Files (*.jpg *.jpeg);;PNG Files (*.png);;All Files (*)"
        )

        if file_path:  # Se un file è stato selezionato
            # Salva la figura nel formato selezionato
            try:
                self.figure.savefig(file_path)
            except Exception as e:
                warn_qgis("qgSurf", f"Unable to save figure to {file_path}: {e}")


class GraphsWindow(QDialog):

    def __init__(self,
        figs: List[Figure]):

        super().__init__()

        self.figs = figs

        # Layout per contenere il canvas
        layout = QVBoxLayout()

        # Aggiungi un pulsante per esportare in SVG
        self.export_button = QPushButton("Export as SVG")
        self.export_button.clicked.connect(self.export_as_svg)
        layout.addWidget(self.export_button)

        # Aggiunta del canvas al layout

        self.fig_canvases = []
        for fig in figs:
            canvas = FigureCanvas(fig)
            layout.addWidget(canvas)
            self.fig_canvases.append(canvas)

        self.setLayout(layout)

        # Permetti il resize
        #self.setWindowFlags(self.windowFlags() | Qt.WindowMaximizeButtonHint)
        self.setWindowFlags(Qt.WindowType.Window | Qt.WindowType.WindowMinimizeButtonHint | Qt.WindowType.WindowMaximizeButtonHint | Qt.WindowType.WindowCloseButtonHint)

        self.setSizeGripEnabled(True)

        # Imposta dimensioni minime o iniziali
        self.resize(UIConstants.DEFAULT_WINDOW_WIDTH, UIConstants.DEFAULT_WINDOW_HEIGHT)
        self.setMinimumSize(UIConstants.MIN_WINDOW_WIDTH, UIConstants.MIN_WINDOW_HEIGHT)
        self.setMaximumSize(UIConstants.MAX_WINDOW_SIZE, UIConstants.MAX_WINDOW_SIZE)

        self.show()

    def export_as_svg(self):
        """Combina tutte le figure e le esporta come un unico file SVG."""
        # Apri un QFileDialog per scegliere il file di output
        file_path, _ = QFileDialog.getSaveFileName(
            self,
            "Esporta come SVG",
            "",
            "SVG Files (*.svg);;All Files (*)"
        )

        if file_path:
            # Calcola le proporzioni relative di ogni figura
            total_height = sum(canvas.size().height() for canvas in self.fig_canvases)
            figure_ratios = [canvas.size().height() / total_height for canvas in self.fig_canvases]

            # Crea una nuova figura combinata con dimensioni proporzionali
            combined_figure = plt.figure(figsize=(8, 8))  # Larghezza fissa, altezza regolata
            current_height = 0

            for i, (canvas, ratio) in enumerate(zip(self.figs, figure_ratios)):
                ax_combined = combined_figure.add_axes(
                    [UIConstants.FIGURE_AXES_LEFT, current_height, UIConstants.FIGURE_AXES_WIDTH, ratio]
                )
                original_figure = canvas.figure
                for ax in original_figure.axes:
                    for line in ax.get_lines():
                        ax_combined.plot(
                            line.get_xdata(),
                            line.get_ydata(),
                            label=line.get_label()
                        )
                ax_combined.set_title(f"Figura combinata {i + 1}")
                ax_combined.legend()
                current_height += ratio  # Aggiorna la posizione verticale

            # Salva la figura combinata come SVG
            combined_figure.savefig(file_path, format="svg")

    def save_figure(self):
        """Salva il canvas della figura in un file."""
        # Apri un QFileDialog per scegliere il file
        file_path, _ = QFileDialog.getSaveFileName(
            self,
            "Salva Figura",
            "",
            "PDF Files (*.pdf);;SVG Files (*.svg);;TIFF Files (*.tif *.tiff);;JPEG Files (*.jpg *.jpeg);;PNG Files (*.png);;All Files (*)"
        )

        if file_path:  # Se un file è stato selezionato
            # Salva la figura nel formato selezionato
            try:
                self.figure.savefig(file_path)
            except Exception as e:
                warn_qgis("qgSurf", f"Unable to save figure to {file_path}: {e}")


class SourceDEMSelectionDialog(QDialog):

    def __init__(self,
                 plugin_name,
                 raster_layers,
                 parent=None
                 ):

        super(SourceDEMSelectionDialog, self).__init__(parent)

        self.plugin_name = plugin_name

        self.singleband_raster_layers_in_project = raster_layers

        self.listDEMs_treeWidget = QTreeWidget()
        self.listDEMs_treeWidget.setColumnCount(1)
        self.listDEMs_treeWidget.headerItem().setText(0, "Name")
        self.listDEMs_treeWidget.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
        self.listDEMs_treeWidget.setDragEnabled(False)
        self.listDEMs_treeWidget.setDragDropMode(QAbstractItemView.DragDropMode.NoDragDrop)
        self.listDEMs_treeWidget.setAlternatingRowColors(True)
        self.listDEMs_treeWidget.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
        self.listDEMs_treeWidget.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
        self.listDEMs_treeWidget.setTextElideMode(Qt.TextElideMode.ElideLeft)

        self.populate_raster_layer_treewidget()

        self.listDEMs_treeWidget.resizeColumnToContents(0)

        okButton = QPushButton("&OK")
        cancelButton = QPushButton("Cancel")

        buttonLayout = QHBoxLayout()
        buttonLayout.addStretch()
        buttonLayout.addWidget(okButton)
        buttonLayout.addWidget(cancelButton)

        layout = QGridLayout()

        layout.addWidget(
            self.listDEMs_treeWidget,
            0, 0, 1, 3)
        layout.addLayout(
            buttonLayout,
            1, 0, 1, 3)

        self.setLayout(layout)

        okButton.clicked.connect(self.accept)
        cancelButton.clicked.connect(self.reject)

        self.setWindowTitle("Define elevation source")

    def populate_raster_layer_treewidget(self):

        self.listDEMs_treeWidget.clear()

        for raster_layer in self.singleband_raster_layers_in_project:
            tree_item = QTreeWidgetItem(self.listDEMs_treeWidget)
            tree_item.setText(0, raster_layer.name())


class DefineSourceLineLayerDialog(QDialog):

    def __init__(self,
                 plugin_name,
                 current_line_layers,
                 parent=None
                 ):

        super(DefineSourceLineLayerDialog, self).__init__(parent)

        self.plugin_name = plugin_name
        self.current_line_layers = current_line_layers

        layout = QGridLayout()

        layout.addWidget(
            QLabel(self.tr("Input line layer:")),
            0, 0, 1, 1)

        self.LineLayers_comboBox = QComboBox()
        layout.addWidget(
            self.LineLayers_comboBox,
            0, 1, 1, 3)
        self.refresh_input_profile_layer_combobox()

        # Ok/Cancel choices

        okButton = QPushButton("&OK")
        cancelButton = QPushButton("Cancel")

        buttonLayout = QHBoxLayout()
        buttonLayout.addStretch()
        buttonLayout.addWidget(okButton)
        buttonLayout.addWidget(cancelButton)

        layout.addLayout(
            buttonLayout,
            1, 0, 1, 3)

        self.setLayout(layout)

        okButton.clicked.connect(self.accept)
        cancelButton.clicked.connect(self.reject)

        self.setWindowTitle("Define source line layer")

    def refresh_input_profile_layer_combobox(self):

        self.LineLayers_comboBox.clear()

        for layer in self.current_line_layers:
            self.LineLayers_comboBox.addItem(layer.name())

        shape_qgis_ndx = self.LineLayers_comboBox.currentIndex()
        self.line_shape = self.current_line_layers[shape_qgis_ndx]

    def refresh_order_field_combobox(self):

        self.Trace2D_order_field_comboBox.clear()
        self.Trace2D_order_field_comboBox.addItem('--optional--')

        shape_qgis_ndx = self.LineLayers_comboBox.currentIndex()
        self.line_shape = self.current_line_layers[shape_qgis_ndx]

        line_layer_field_list = self.line_shape.dataProvider().fields().toList()
        for field in line_layer_field_list:
            self.Trace2D_order_field_comboBox.addItem(field.name())

    def refresh_name_field_combobox(self):

        self.Trace2D_name_field_comboBox.clear()
        self.Trace2D_name_field_comboBox.addItem('--optional--')

        shape_qgis_ndx = self.LineLayers_comboBox.currentIndex()
        self.line_shape = self.current_line_layers[shape_qgis_ndx]

        line_layer_field_list = self.line_shape.dataProvider().fields().toList()
        for field in line_layer_field_list:
            self.Trace2D_name_field_comboBox.addItem(field.name())

    def refresh_label_field_combobox(self):

        self.Trace2D_label_field_comboBox.clear()
        self.Trace2D_label_field_comboBox.addItem('--optional--')

        shape_qgis_ndx = self.LineLayers_comboBox.currentIndex()
        self.line_shape = self.current_line_layers[shape_qgis_ndx]

        line_layer_field_list = self.line_shape.dataProvider().fields().toList()
        for field in line_layer_field_list:
            self.Trace2D_label_field_comboBox.addItem(field.name())


class GeologicalDataIntersectLinesDialog(QDialog):
    """
    Intersects the profiles with a line layer: defines input parameters.
    """

    def __init__(self,
                 plugin_name,
                 current_line_layers,
                 parent=None
                 ):

        super(GeologicalDataIntersectLinesDialog, self).__init__(parent)

        self.plugin_name = plugin_name
        self.current_line_layers = current_line_layers

        # GUI

        layout = QGridLayout()

        layout.addWidget(
            QLabel(self.tr("Input line layer:")),
            0, 0, 1, 1)

        self.LineLayers_comboBox = QComboBox()
        layout.addWidget(
            self.LineLayers_comboBox,
            0, 1, 1, 3)
        self.refresh_input_profile_layer_combobox()

        layout.addWidget(QLabel("Id field"), 1, 0, 1, 1)
        self.inters_input_id_fld_line_comboBox = QComboBox()
        layout.addWidget(
            self.inters_input_id_fld_line_comboBox,
            1, 1, 1, 3
        )

        self.refresh_id_field_combobox()
        self.LineLayers_comboBox.currentIndexChanged.connect(self.refresh_id_field_combobox)

        # Ok/Cancel choices

        okButton = QPushButton("&OK")
        cancelButton = QPushButton("Cancel")

        buttonLayout = QHBoxLayout()
        buttonLayout.addStretch()
        buttonLayout.addWidget(okButton)
        buttonLayout.addWidget(cancelButton)

        layout.addLayout(
            buttonLayout,
            3, 0, 1, 3)

        self.setLayout(layout)

        okButton.clicked.connect(self.accept)
        cancelButton.clicked.connect(self.reject)

        self.setWindowTitle("Intersection line layer")

    def refresh_input_profile_layer_combobox(self):

        self.LineLayers_comboBox.clear()

        for layer in self.current_line_layers:
            self.LineLayers_comboBox.addItem(layer.name())

        shape_qgis_ndx = self.LineLayers_comboBox.currentIndex()
        self.line_shape = self.current_line_layers[shape_qgis_ndx]

    def refresh_id_field_combobox(self):

        self.inters_input_id_fld_line_comboBox.clear()
        #self.inters_input_id_fld_line_comboBox.addItem('--optional--')

        shape_qgis_ndx = self.LineLayers_comboBox.currentIndex()
        self.line_shape = self.current_line_layers[shape_qgis_ndx]

        line_layer_field_list = self.line_shape.dataProvider().fields().toList()
        for field in line_layer_field_list:
            self.inters_input_id_fld_line_comboBox.addItem(field.name())


class GeologicalDataIntersectPolygonsDialog(QDialog):
    """
    Intersects the profiles with a polygon layer:
    defines input parameters.
    """

    def __init__(self,
                 plugin_name,
                 current_polygon_layers,
                 parent=None
                 ):

        super(GeologicalDataIntersectPolygonsDialog, self).__init__(parent)

        self.plugin_name = plugin_name
        self.current_polygon_layers = current_polygon_layers

        # GUI

        layout = QGridLayout()

        layout.addWidget(
            QLabel(self.tr("Input polygon layer:")),
            0, 0, 1, 1)

        self.PolygonLayers_comboBox = QComboBox()
        layout.addWidget(
            self.PolygonLayers_comboBox,
            0, 1, 1, 3)
        self.refresh_input_profile_layer_combobox()

        self.refresh_id_field_combobox()
        self.PolygonLayers_comboBox.currentIndexChanged.connect(self.refresh_id_field_combobox)

        # Ok/Cancel choices

        okButton = QPushButton("&OK")
        cancelButton = QPushButton("Cancel")

        buttonLayout = QHBoxLayout()
        buttonLayout.addStretch()
        buttonLayout.addWidget(okButton)
        buttonLayout.addWidget(cancelButton)

        layout.addLayout(
            buttonLayout,
            3, 0, 1, 3)

        self.setLayout(layout)

        okButton.clicked.connect(self.accept)
        cancelButton.clicked.connect(self.reject)

        self.setWindowTitle("Intersection polygon layer")

    def refresh_input_profile_layer_combobox(self):

        self.PolygonLayers_comboBox.clear()

        for layer in self.current_polygon_layers:
            self.PolygonLayers_comboBox.addItem(layer.name())

        shape_qgis_ndx = self.PolygonLayers_comboBox.currentIndex()
        self.pol_shape = self.current_polygon_layers[shape_qgis_ndx]

    def refresh_id_field_combobox(self):

        shape_qgis_ndx = self.PolygonLayers_comboBox.currentIndex()
        self.pol_shape = self.current_polygon_layers[shape_qgis_ndx]


class BaseDataLoadPointListDialog(QDialog):

    def __init__(self, plugin_name, parent=None):

        super(BaseDataLoadPointListDialog, self).__init__(parent)

        self.plugin_name = plugin_name
        layout = QGridLayout()

        layout.addWidget(
            QLabel(self.tr("Point list, with at least two points.")),
            0, 0, 1, 1)
        layout.addWidget(
            QLabel(self.tr("Each point is defined by a comma-separated, x-y coordinate pair (same CRS as project), one for each row")), 1, 0,
            1, 1)
        layout.addWidget(
            QLabel(self.tr("Example:\n549242.7, 242942.2\n578370.3, 322634.5")),
            2, 0, 1, 1)

        self.point_list_qtextedit = QTextEdit()
        layout.addWidget(
            self.point_list_qtextedit,
            3, 0, 1, 1)

        okButton = QPushButton("&OK")
        cancelButton = QPushButton("Cancel")

        buttonLayout = QHBoxLayout()
        buttonLayout.addStretch()
        buttonLayout.addWidget(okButton)
        buttonLayout.addWidget(cancelButton)

        layout.addLayout(
            buttonLayout,
            4, 0, 1, 3)

        self.setLayout(layout)

        okButton.clicked.connect(self.accept)
        cancelButton.clicked.connect(self.reject)

        self.setWindowTitle("Point list")


class StylesElevationsParamsDialog(QDialog):

    def __init__(self,
                 plugin_name,
                 current_graphical_params,
                 parent=None
                 ):

        super(StylesElevationsParamsDialog, self).__init__(parent)

        self.plugin_name = plugin_name
        self.current_graphical_params = current_graphical_params

        self.qtwdElevationLayers = QTreeWidget()
        self.qtwdElevationLayers.setColumnCount(2)
        self.qtwdElevationLayers.headerItem().setText(0, "Type")
        self.qtwdElevationLayers.headerItem().setText(1, "Color")
        self.qtwdElevationLayers.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
        self.qtwdElevationLayers.setDragEnabled(False)
        self.qtwdElevationLayers.setDragDropMode(QAbstractItemView.DragDropMode.NoDragDrop)
        self.qtwdElevationLayers.setAlternatingRowColors(True)
        self.qtwdElevationLayers.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
        self.qtwdElevationLayers.setTextElideMode(Qt.TextElideMode.ElideLeft)

        self.populate_elevation_layer_treewidget()

        self.qtwdElevationLayers.resizeColumnToContents(0)
        self.qtwdElevationLayers.resizeColumnToContents(1)

        okButton = QPushButton("&OK")
        cancelButton = QPushButton("Cancel")

        buttonLayout = QHBoxLayout()
        buttonLayout.addStretch()
        buttonLayout.addWidget(okButton)
        buttonLayout.addWidget(cancelButton)

        layout = QGridLayout()

        layout.addWidget(
            self.qtwdElevationLayers,
            0, 0, 1, 3)
        layout.addLayout(
            buttonLayout,
            1, 0, 1, 3)

        self.setLayout(layout)

        okButton.clicked.connect(self.accept)
        cancelButton.clicked.connect(self.reject)

        self.setWindowTitle("Elevation style")

    def populate_elevation_layer_treewidget(self):

        self.qtwdElevationLayers.clear()

        for ndx, layer_name in enumerate(["Elevation"]):
            tree_item = QTreeWidgetItem(self.qtwdElevationLayers)
            tree_item.setText(0, layer_name)
            color_button = QgsColorButton()
            qcolor = create_qcolor(self.current_graphical_params.elevations.color)
            color_button.setColor(qcolor)
            self.qtwdElevationLayers.setItemWidget(tree_item, 1, color_button)


class DepthOptionsWidget(QWidget):
    """
    The "values are depths" controls shared by the point and focal mechanism
    projection dialogs.

    A layer may store the vertical position of its points as a depth measured
    downwards from a datum (the usual form for hypocentres) rather than as an
    elevation. These controls say so, from which datum, and in which unit; the
    tool converts to the elevation the profiles are drawn in.

    They apply only where the vertical value is read from the layer itself (a
    field, or the point geometry): a DEM always yields an elevation, so the
    caller disables the whole widget when the DEM option is selected.
    """

    def __init__(self,
                 default_is_depth: bool = False,
                 parent=None
                 ):

        super(DepthOptionsWidget, self).__init__(parent)

        layout = QGridLayout()
        layout.setContentsMargins(20, 0, 0, 0)  # indent under the owning radio button

        self.is_depth_qcheckbox = QCheckBox("Values are depths, not elevations")
        self.is_depth_qcheckbox.setChecked(default_is_depth)
        layout.addWidget(self.is_depth_qcheckbox, 0, 0, 1, 2)

        self.reference_qlabel = QLabel("Depth measured from")
        layout.addWidget(self.reference_qlabel, 1, 0, 1, 1)

        self.reference_combobox = QComboBox()
        self.reference_combobox.insertItems(0, list(depth_references.keys()))
        layout.addWidget(self.reference_combobox, 1, 1, 1, 1)

        self.unit_qlabel = QLabel("Depth unit")
        layout.addWidget(self.unit_qlabel, 2, 0, 1, 1)

        self.unit_combobox = QComboBox()
        self.unit_combobox.insertItems(0, list(depth_units.keys()))
        layout.addWidget(self.unit_combobox, 2, 1, 1, 1)

        self.setLayout(layout)

        self.is_depth_qcheckbox.toggled.connect(self._refresh_depth_controls)
        self._refresh_depth_controls(self.is_depth_qcheckbox.isChecked())

    def _refresh_depth_controls(self, is_depth: bool):
        """Datum and unit only mean something while the values are read as depths."""

        for widget in (
            self.reference_qlabel,
            self.reference_combobox,
            self.unit_qlabel,
            self.unit_combobox,
        ):
            widget.setEnabled(is_depth)

    def z_is_depth(self) -> bool:

        return self.is_depth_qcheckbox.isChecked()

    def depth_reference(self) -> DepthReference:

        return depth_references[self.reference_combobox.currentText()]

    def depth_unit_factor(self) -> float:
        """Length of one depth unit, in the metres the profiles are built in."""

        return depth_units[self.unit_combobox.currentText()]


class GeologicalDataProjectPointsDialog(QDialog):

    def __init__(self,
                 plugin_name,
                 current_point_layers: List,
                 parent=None
                 ):

        super(GeologicalDataProjectPointsDialog, self).__init__(parent)

        self.plugin_name = plugin_name
        self.current_point_layers = current_point_layers

        # gui definition

        main_layout = QGridLayout()

        # input point geological layer

        main_layout.addWidget(QLabel("Layer "),
                                       0, 0, 1, 1)

        self.point_layer_combobox = QComboBox()

        main_layout.addWidget(self.point_layer_combobox, 0, 1, 1, 1)

        # Creazione del gruppo di RadioButton per rendere le opzioni mutuamente esclusive
        self.radio_group = QButtonGroup()

        # Opzione 1: RadioButton per attributi Z

        self.radio_attributi_z = QRadioButton("Point attributes have z field")
        self.radio_group.addButton(self.radio_attributi_z)
        main_layout.addWidget(self.radio_attributi_z, 1, 0, 1, 1)

        self.radio_attributi_z.setChecked(True)

        self.z_fld_comboBox = QComboBox()
        main_layout.addWidget(self.z_fld_comboBox, 1, 1, 1, 1)

        # Opzione 2: RadioButton per punti 3D
        self.radio_punti_3d = QRadioButton("Points are already 3D")
        self.radio_group.addButton(self.radio_punti_3d)
        main_layout.addWidget(self.radio_punti_3d, 2, 0, 1, 3)

        # depth handling for whichever of the two options is selected: both read
        # the vertical value from the layer, so both can be depths

        self.depth_options = DepthOptionsWidget(default_is_depth=False)
        main_layout.addWidget(self.depth_options, 3, 0, 1, 2)

        main_layout.addWidget(QLabel("DEM for depths below topography"), 4, 0, 1, 1)

        self.dem_combobox = QComboBox()
        self.current_raster_layers = get_dict_of_monoband_raster_layers()
        self.dem_combobox.addItems(self.current_raster_layers.keys())
        main_layout.addWidget(self.dem_combobox, 4, 1, 1, 1)

        main_layout.addWidget(QLabel("Max point distance from profile trace"), 5, 0, 1, 1)
        self.max_profile_distance_qdoublespinbox = QDoubleSpinBox()
        from .constants import ProfileConstants
        self.max_profile_distance_qdoublespinbox.setMaximum(ProfileConstants.MAX_PROFILE_DISTANCE_MAX_METERS)
        self.max_profile_distance_qdoublespinbox.setValue(ProfileConstants.MAX_PROFILE_DISTANCE_METERS)
        main_layout.addWidget(
            self.max_profile_distance_qdoublespinbox,
            5, 1, 1, 1)

        main_layout.addWidget(QLabel("Label points using "), 6, 0, 1, 1)

        self.point_label_fld_comboBox = QComboBox()
        main_layout.addWidget(self.point_label_fld_comboBox, 6, 1, 1, 1)

        self.refresh_point_layer_combobox()
        self.refresh_layer_related_comboboxes()
        self.point_layer_combobox.currentIndexChanged.connect(self.refresh_layer_related_comboboxes)

        # the DEM is only read for depths measured below the topographic surface
        self.depth_options.is_depth_qcheckbox.toggled.connect(self._refresh_dem_combobox)
        self.depth_options.reference_combobox.currentIndexChanged.connect(self._refresh_dem_combobox)
        self._refresh_dem_combobox()

        # ok/cancel setup

        okButton = QPushButton("&OK")
        cancelButton = QPushButton("Cancel")

        buttonLayout = QHBoxLayout()
        buttonLayout.addStretch()
        buttonLayout.addWidget(okButton)
        buttonLayout.addWidget(cancelButton)

        main_layout.addLayout(buttonLayout, 7, 0, 1, 2)

        okButton.clicked.connect(self.accept)
        cancelButton.clicked.connect(self.reject)

        # widget final setup

        self.setLayout(main_layout)
        self.setWindowTitle("Project points")

    def _refresh_dem_combobox(self, *args):

        self.dem_combobox.setEnabled(
            self.depth_options.z_is_depth()
            and self.depth_options.depth_reference() == DepthReference.TOPOGRAPHIC_SURFACE
        )

    def refresh_point_layer_combobox(
            self,
    ):

        refresh_combobox(
            self.point_layer_combobox,
            None,
            [layer.name() for layer in self.current_point_layers]
        )

    def refresh_layer_related_comboboxes(self):

        if len(self.current_point_layers) == 0:
            warn_qgis(self.plugin_name, "No point layer available")
            return

        layer_ndx = self.point_layer_combobox.currentIndex()

        layer = self.current_point_layers[layer_ndx]
        fields = layer.dataProvider().fields()
        field_names = [field.name() for field in fields.toList()]

        self.point_label_fld_comboBox.clear()
        self.z_fld_comboBox.clear()
        self.point_label_fld_comboBox.addItems(field_names)
        self.z_fld_comboBox.addItems(field_names)


class GeologicalDataProjectAttitudesDialog(QDialog):
    """
    Projects geological attitudes onto profiles.
    """

    def __init__(self,
                 plugin_name,
                 current_point_layers: List,
                 parent=None
                 ):

        super(GeologicalDataProjectAttitudesDialog, self).__init__(parent)

        self.plugin_name = plugin_name
        self.current_point_layers = current_point_layers

        # gui definition

        main_layout = QVBoxLayout()

        ## input section

        input_groupbox = QGroupBox(self)
        input_groupbox.setTitle('Input parameters')

        input_layout = QGridLayout()

        # input point geological layer

        input_layout.addWidget(
            QLabel("Point layer storing attitudes"),
            0, 0, 1, 1)

        self.point_layer_combobox = QComboBox()
        input_layout.addWidget(self.point_layer_combobox, 0, 1, 1, 3)

        input_layout.addWidget(QLabel("Attitude azimuth field "), 1, 0, 1, 1)

        self.orient_fld_combobox = QComboBox()
        input_layout.addWidget(self.orient_fld_combobox, 1, 1, 1, 1)

        self.use_dipdir_qradiobuttom = QRadioButton("Dip dir.")
        self.use_dipdir_qradiobuttom.setChecked(True)
        input_layout.addWidget(self.use_dipdir_qradiobuttom, 1, 2, 1, 1)

        self.use_rhr_strike_qradiobutton = QRadioButton("RHR str.")
        input_layout.addWidget(self.use_rhr_strike_qradiobutton, 1, 3, 1, 1)

        input_layout.addWidget(QLabel("Attitude dip angle field"), 2, 0, 1, 1)
        self.dipangle_fld_combobox = QComboBox()
        input_layout.addWidget(self.dipangle_fld_combobox, 2, 1, 1, 3)


        input_layout.addWidget(QLabel("Record id/label field"), 3, 0, 1, 1)

        self.point_label_fld_comboBox = QComboBox()
        input_layout.addWidget(self.point_label_fld_comboBox, 3, 1, 1, 3)


        # Creazione del gruppo di RadioButton per rendere le opzioni mutuamente esclusive
        self.radio_group = QButtonGroup()

        # Opzione 1: RadioButton per selezione DEM
        self.radio_dem = QRadioButton("Derive point heights from DEM")
        self.radio_group.addButton(self.radio_dem)
        input_layout.addWidget(self.radio_dem, 4, 0, 1, 1)

        self.dem_combobox = QComboBox()

        input_layout.addWidget(self.dem_combobox, 4, 1, 1, 3)
        self.current_raster_layers = get_dict_of_monoband_raster_layers()
        self.dem_combobox.addItems(self.current_raster_layers.keys())

        # Opzione 2: RadioButton per attributi Z
        self.radio_attributi_z = QRadioButton("Point attributes have z field")
        self.radio_group.addButton(self.radio_attributi_z)
        input_layout.addWidget(self.radio_attributi_z, 5, 0, 1, 1)

        self.combobox_attributi_z = QComboBox()
        input_layout.addWidget(self.combobox_attributi_z, 5, 1, 1, 3)

        # Opzione 3: RadioButton per punti 3D
        self.radio_punti_3d = QRadioButton("Points are already 3D")
        self.radio_group.addButton(self.radio_punti_3d)
        input_layout.addWidget(self.radio_punti_3d, 6, 0, 1, 3)

        # Impostare un'opzione predefinita (opzionale)
        self.radio_dem.setChecked(True)


        input_layout.addWidget(QLabel("Max point distance from profile trace"), 7, 0, 1, 1)

        self.max_profile_distance_qdoublespinbox = QDoubleSpinBox()
        from .constants import ProfileConstants
        self.max_profile_distance_qdoublespinbox.setMaximum(ProfileConstants.MAX_PROFILE_DISTANCE_MAX_METERS)
        self.max_profile_distance_qdoublespinbox.setValue(ProfileConstants.MAX_PROFILE_DISTANCE_METERS)
        input_layout.addWidget(
            self.max_profile_distance_qdoublespinbox,
            7, 1, 1, 3)

        input_groupbox.setLayout(input_layout)
        main_layout.addWidget(input_groupbox)

        # Configurazione spaziatura del layout
        input_layout.setContentsMargins(
            UIConstants.DEFAULT_LAYOUT_MARGINS,
            UIConstants.DEFAULT_LAYOUT_MARGINS,
            UIConstants.DEFAULT_LAYOUT_MARGINS,
            UIConstants.DEFAULT_LAYOUT_MARGINS
        )
        input_layout.setSpacing(5)

        self.refresh_point_layer_combobox()
        self.refresh_layer_related_comboboxes()
        self.point_layer_combobox.currentIndexChanged.connect(self.refresh_layer_related_comboboxes)

        # ok/cancel setup

        okButton = QPushButton("&OK")
        cancelButton = QPushButton("Cancel")

        buttonLayout = QHBoxLayout()
        buttonLayout.addStretch()
        buttonLayout.addWidget(okButton)
        buttonLayout.addWidget(cancelButton)

        main_layout.addLayout(buttonLayout, 3)

        okButton.clicked.connect(self.accept)
        cancelButton.clicked.connect(self.reject)

        # widget final setup

        self.setLayout(main_layout)
        self.setWindowTitle("Project geological attitudes")

    def refresh_point_layer_combobox(
            self,
    ):

        refresh_combobox(
            self.point_layer_combobox,
            None,
            [layer.name() for layer in self.current_point_layers]
        )

    def refresh_layer_related_comboboxes(self):

        if len(self.current_point_layers) == 0:
            warn_qgis(self.plugin_name, "No point layer available")
            return

        layer_ndx = self.point_layer_combobox.currentIndex()

        layer = self.current_point_layers[layer_ndx]
        fields = layer.dataProvider().fields()
        field_names = [field.name() for field in fields.toList()]

        self.point_label_fld_comboBox.clear()
        self.orient_fld_combobox.clear()
        self.dipangle_fld_combobox.clear()
        self.combobox_attributi_z.clear()

        self.point_label_fld_comboBox.addItems(field_names)
        self.orient_fld_combobox.addItems(field_names)
        self.dipangle_fld_combobox.addItems(field_names)
        self.combobox_attributi_z.addItems(field_names)


class GeologicalDataProjectFocalMechanismsDialog(QDialog):
    """
    Projects earthquake focal mechanisms onto profiles.

    Same input shape as the attitudes dialog (the fault plane is an attitude), plus
    the rake field, which is what turns a plane into a double-couple mechanism and
    is therefore mandatory rather than optional here.
    """

    def __init__(self,
                 plugin_name,
                 current_point_layers: List,
                 parent=None
                 ):

        super(GeologicalDataProjectFocalMechanismsDialog, self).__init__(parent)

        self.plugin_name = plugin_name
        self.current_point_layers = current_point_layers

        # gui definition

        main_layout = QVBoxLayout()

        ## input section

        input_groupbox = QGroupBox(self)
        input_groupbox.setTitle('Input parameters')

        input_layout = QGridLayout()

        # input point layer storing the hypocenters

        input_layout.addWidget(
            QLabel("Point layer storing focal mechanisms"),
            0, 0, 1, 1)

        self.point_layer_combobox = QComboBox()
        input_layout.addWidget(self.point_layer_combobox, 0, 1, 1, 3)

        input_layout.addWidget(QLabel("Fault plane azimuth field"), 1, 0, 1, 1)

        self.orient_fld_combobox = QComboBox()
        input_layout.addWidget(self.orient_fld_combobox, 1, 1, 1, 1)

        self.use_dipdir_qradiobuttom = QRadioButton("Dip dir.")
        input_layout.addWidget(self.use_dipdir_qradiobuttom, 1, 2, 1, 1)

        # focal mechanisms are conventionally catalogued as RHR strike (Aki &
        # Richards), so that is the default here, unlike the attitudes dialog
        self.use_rhr_strike_qradiobutton = QRadioButton("RHR str.")
        self.use_rhr_strike_qradiobutton.setChecked(True)
        input_layout.addWidget(self.use_rhr_strike_qradiobutton, 1, 3, 1, 1)

        input_layout.addWidget(QLabel("Fault plane dip angle field"), 2, 0, 1, 1)
        self.dipangle_fld_combobox = QComboBox()
        input_layout.addWidget(self.dipangle_fld_combobox, 2, 1, 1, 3)

        input_layout.addWidget(QLabel("Rake field (Aki & Richards)"), 3, 0, 1, 1)
        self.rake_fld_combobox = QComboBox()
        input_layout.addWidget(self.rake_fld_combobox, 3, 1, 1, 3)

        input_layout.addWidget(QLabel("Record id/label field"), 4, 0, 1, 1)

        self.point_label_fld_comboBox = QComboBox()
        input_layout.addWidget(self.point_label_fld_comboBox, 4, 1, 1, 3)

        # hypocentral depth source

        self.radio_group = QButtonGroup()

        self.radio_dem = QRadioButton("Derive point heights from DEM")
        self.radio_group.addButton(self.radio_dem)
        input_layout.addWidget(self.radio_dem, 5, 0, 1, 1)

        self.dem_combobox = QComboBox()
        input_layout.addWidget(self.dem_combobox, 5, 1, 1, 3)
        self.current_raster_layers = get_dict_of_monoband_raster_layers()
        self.dem_combobox.addItems(self.current_raster_layers.keys())

        self.radio_attributi_z = QRadioButton("Point attributes have z field")
        self.radio_group.addButton(self.radio_attributi_z)
        input_layout.addWidget(self.radio_attributi_z, 6, 0, 1, 1)

        self.combobox_attributi_z = QComboBox()
        input_layout.addWidget(self.combobox_attributi_z, 6, 1, 1, 3)

        self.radio_punti_3d = QRadioButton("Points are already 3D")
        self.radio_group.addButton(self.radio_punti_3d)
        input_layout.addWidget(self.radio_punti_3d, 7, 0, 1, 3)

        # hypocenters carry their own depth far more often than they are draped
        # on a DEM, so the z-attribute option is the default for mechanisms
        self.radio_attributi_z.setChecked(True)

        # and that depth is catalogued as a depth in km below sea level, which is
        # why this is the one dialog where the depth reading is on by default
        self.depth_options = DepthOptionsWidget(default_is_depth=True)
        input_layout.addWidget(self.depth_options, 8, 0, 1, 4)

        input_layout.addWidget(QLabel("Max point distance from profile trace"), 9, 0, 1, 1)

        self.max_profile_distance_qdoublespinbox = QDoubleSpinBox()
        from .constants import ProfileConstants
        self.max_profile_distance_qdoublespinbox.setMaximum(ProfileConstants.MAX_PROFILE_DISTANCE_MAX_METERS)
        self.max_profile_distance_qdoublespinbox.setValue(ProfileConstants.MAX_PROFILE_DISTANCE_METERS)
        input_layout.addWidget(
            self.max_profile_distance_qdoublespinbox,
            9, 1, 1, 3)

        input_groupbox.setLayout(input_layout)
        main_layout.addWidget(input_groupbox)

        input_layout.setContentsMargins(
            UIConstants.DEFAULT_LAYOUT_MARGINS,
            UIConstants.DEFAULT_LAYOUT_MARGINS,
            UIConstants.DEFAULT_LAYOUT_MARGINS,
            UIConstants.DEFAULT_LAYOUT_MARGINS
        )
        input_layout.setSpacing(5)

        self.refresh_point_layer_combobox()
        self.refresh_layer_related_comboboxes()
        self.point_layer_combobox.currentIndexChanged.connect(self.refresh_layer_related_comboboxes)

        # a DEM already yields an elevation, so reading values as depths only
        # applies to the two options that take the value from the layer itself
        self.radio_dem.toggled.connect(self._refresh_depth_options)
        self._refresh_depth_options()

        # ok/cancel setup

        okButton = QPushButton("&OK")
        cancelButton = QPushButton("Cancel")

        buttonLayout = QHBoxLayout()
        buttonLayout.addStretch()
        buttonLayout.addWidget(okButton)
        buttonLayout.addWidget(cancelButton)

        main_layout.addLayout(buttonLayout, 3)

        okButton.clicked.connect(self.accept)
        cancelButton.clicked.connect(self.reject)

        # widget final setup

        self.setLayout(main_layout)
        self.setWindowTitle("Project focal mechanisms")

    def _refresh_depth_options(self, *args):

        self.depth_options.setEnabled(not self.radio_dem.isChecked())

    def refresh_point_layer_combobox(
            self,
    ):

        refresh_combobox(
            self.point_layer_combobox,
            None,
            [layer.name() for layer in self.current_point_layers]
        )

    def refresh_layer_related_comboboxes(self):

        if len(self.current_point_layers) == 0:
            warn_qgis(self.plugin_name, "No point layer available")
            return

        layer_ndx = self.point_layer_combobox.currentIndex()

        layer = self.current_point_layers[layer_ndx]
        fields = layer.dataProvider().fields()
        field_names = [field.name() for field in fields.toList()]

        for combobox in (
            self.point_label_fld_comboBox,
            self.orient_fld_combobox,
            self.dipangle_fld_combobox,
            self.rake_fld_combobox,
            self.combobox_attributi_z,
        ):
            combobox.clear()
            combobox.addItems(field_names)


class StylesTotalGraphicalParamsDialog(QDialog):

    def __init__(self,
        plugin_name: str,
        geoprofiles: GeoProfiles,
        current_graphical_params: GraphicalParameters,
        polygons_intersection_parameters: PolygonsIntersectionParameters,
        parent=None
    ):

        super(StylesTotalGraphicalParamsDialog, self).__init__(parent)

        self.plugin_name = plugin_name
        self.geoprofiles = geoprofiles
        self.current_graphical_params = current_graphical_params
        self.polygons_intersection_parameters = polygons_intersection_parameters

        # Prepare the dialog

        main_layout = QVBoxLayout()

        # Figure parameters

        groupbox_0 = QGroupBox("Figure parameters")

        layout_0 = QGridLayout()

        layout_0.addWidget(
            QLabel(self.tr("Figure width")),
            0, 0, 1, 1)

        self.figure_width_qlineedit = QLineEdit()
        self.figure_width_qlineedit.setText(f"{self.current_graphical_params.figure.width}")
        layout_0.addWidget(
            self.figure_width_qlineedit,
            0, 1, 1, 1)

        layout_0.addWidget(
            QLabel(self.tr(" inches")),
            0, 2, 1, 1)

        layout_0.addWidget(
            QLabel(self.tr("Figure height")),
            1, 0, 1, 1)

        self.figure_height_qlineedit = QLineEdit()
        self.figure_height_qlineedit.setText(f"{self.current_graphical_params.figure.height}")
        layout_0.addWidget(
            self.figure_height_qlineedit,
            1, 1, 1, 1)

        layout_0.addWidget(
            QLabel(self.tr(" inches")),
            1, 2, 1, 1)

        # groupbox final settings

        groupbox_0.setLayout(layout_0)

        main_layout.addWidget(groupbox_0)

        # Plot elevation parameters

        groupbox_1 = QGroupBox("Plot elevation parameters")

        layout_1 = QGridLayout()

        layout_1.addWidget(
            QLabel(self.tr("Vertical exaggeration")),
            0, 0, 1, 1)

        self.vertical_exxageration_ratio_qlineedit = QLineEdit()
        self.vertical_exxageration_ratio_qlineedit.setText(f"{self.current_graphical_params.axis.vertical_exaggeration}")
        layout_1.addWidget(
            self.vertical_exxageration_ratio_qlineedit,
            0, 1, 1, 1)

        layout_1.addWidget(
            QLabel(self.tr("z max value")),
            1, 0, 1, 1)

        self.z_max_value_qlineedit = QLineEdit()
        self.z_max_value_qlineedit.setText(f"{self.current_graphical_params.axis.z_max}")
        layout_1.addWidget(
            self.z_max_value_qlineedit,
            1, 1, 1, 1)

        layout_1.addWidget(
            QLabel(self.tr("z min value")),
            2, 0, 1, 1)

        self.z_min_value_qlineedit = QLineEdit()
        self.z_min_value_qlineedit.setText(f"{self.current_graphical_params.axis.z_min}")
        layout_1.addWidget(
            self.z_min_value_qlineedit,
            2, 1, 1, 1)

        # vertical axis labelling. The values plotted stay elevations in metres:
        # these only change how the axis is annotated, so that a section of
        # hypocentres can be read as depths in km

        layout_1.addWidget(
            QLabel(self.tr("Vertical axis unit")),
            3, 0, 1, 1)

        self.z_axis_units_combobox = QComboBox()
        self.z_axis_units_combobox.insertItems(0, list(axis_units.keys()))
        try:
            self.z_axis_units_combobox.setCurrentIndex(
                list(axis_units.keys()).index(self.current_graphical_params.axis.z_axis_units)
            )
        except ValueError:
            self.z_axis_units_combobox.setCurrentIndex(0)
        layout_1.addWidget(self.z_axis_units_combobox, 3, 1, 1, 1)

        self.z_axis_as_depth_qcheckbox = QCheckBox("Label as depth, positive downwards")
        self.z_axis_as_depth_qcheckbox.setChecked(
            self.current_graphical_params.axis.z_axis_as_depth
        )
        layout_1.addWidget(self.z_axis_as_depth_qcheckbox, 4, 0, 1, 2)

        layout_1.addWidget(
            QLabel(self.tr("Max vertical tick labels")),
            5, 0, 1, 1)

        self.z_axis_max_ticks_qspinbox = QSpinBox()
        self.z_axis_max_ticks_qspinbox.setMinimum(2)
        self.z_axis_max_ticks_qspinbox.setMaximum(20)
        self.z_axis_max_ticks_qspinbox.setValue(
            int(self.current_graphical_params.axis.z_axis_max_ticks)
        )
        layout_1.addWidget(self.z_axis_max_ticks_qspinbox, 5, 1, 1, 1)

        layout_1.addWidget(
            QLabel(self.tr("Along-profile distance unit")),
            6, 0, 1, 1)

        self.s_axis_units_combobox = QComboBox()
        self.s_axis_units_combobox.insertItems(0, list(axis_units.keys()))
        try:
            self.s_axis_units_combobox.setCurrentIndex(
                list(axis_units.keys()).index(self.current_graphical_params.axis.s_axis_units)
            )
        except ValueError:
            self.s_axis_units_combobox.setCurrentIndex(0)
        layout_1.addWidget(self.s_axis_units_combobox, 6, 1, 1, 1)

        self.show_axis_labels_qcheckbox = QCheckBox("Name the axes and their units on the figure")
        self.show_axis_labels_qcheckbox.setChecked(
            self.current_graphical_params.axis.show_axis_labels
        )
        layout_1.addWidget(self.show_axis_labels_qcheckbox, 7, 0, 1, 2)

        # groupbox final settings

        groupbox_1.setLayout(layout_1)

        main_layout.addWidget(groupbox_1)

        # Style parameters

        groupbox_2 = QGroupBox("Plot styles")

        layout_2 = QGridLayout()

        # topographic elevations

        self.topographic_elevations_qpushbutton = QPushButton(self.tr("Topographic elevations"))

        self.topographic_elevations_qpushbutton.clicked.connect(self.define_elevations_style)

        layout_2.addWidget(
            self.topographic_elevations_qpushbutton,
            0, 0, 1, 3)

        # point projections

        self.point_projections_qpushbutton = QPushButton(self.tr("Point projections"))

        self.point_projections_qpushbutton.clicked.connect(self.define_point_projections_style)

        layout_2.addWidget(
            self.point_projections_qpushbutton,
            1, 0, 1, 3)

        # attitudes projections

        self.attitude_projections_qpushbutton = QPushButton(self.tr("Attitude projections"))

        self.attitude_projections_qpushbutton.clicked.connect(self.define_attitude_projections_style)

        layout_2.addWidget(
            self.attitude_projections_qpushbutton,
            2, 0, 1, 3)

# focal mechanism projections

        self.focal_mechanism_projections_qpushbutton = QPushButton(self.tr("Focal mechanism projections"))

        self.focal_mechanism_projections_qpushbutton.clicked.connect(self.define_focal_mechanism_projections_style)

        layout_2.addWidget(
            self.focal_mechanism_projections_qpushbutton,
            3, 0, 1, 3)

        # line intersections

        self.line_intersections_qpushbutton = QPushButton(self.tr("Line intersections"))

        self.line_intersections_qpushbutton.clicked.connect(self.define_line_intersections_style)

        layout_2.addWidget(
            self.line_intersections_qpushbutton,
            4, 0, 1, 3)

        # polygon intersections

        self.polygon_intersections_qpushbutton = QPushButton(self.tr("Polygon intersections"))

        self.polygon_intersections_qpushbutton.clicked.connect(self.define_polygon_intersections_style)

        layout_2.addWidget(
            self.polygon_intersections_qpushbutton,
            5, 0, 1, 3)

        # groupbox final settings

        groupbox_2.setLayout(layout_2)

        main_layout.addWidget(groupbox_2)

        # ok/cancel section

        okButton = QPushButton("&OK")
        cancelButton = QPushButton("Cancel")

        buttonLayout = QHBoxLayout()
        buttonLayout.addStretch()
        buttonLayout.addWidget(okButton)
        buttonLayout.addWidget(cancelButton)

        main_layout.addLayout(buttonLayout)

        self.setLayout(main_layout)

        okButton.clicked.connect(self.accept)
        cancelButton.clicked.connect(self.reject)

        self.setWindowTitle("Graphical parameters")

    def extract_elevation_plot_style(self,
                                     dialog):

        curr_item = dialog.qtwdElevationLayers.topLevelItem(0)
        return dialog.qtwdElevationLayers.itemWidget(curr_item, 1).color()

    def define_elevations_style(self):

        dialog = StylesElevationsParamsDialog(
            self.plugin_name,
            self.current_graphical_params
        )

        if dialog.exec():
            self.elevation_color = self.extract_elevation_plot_style(dialog)
        else:
            return

    def extract_point_plot_style(self,
                                 dialog
                                 ) -> Union[None, PointPlotParams]:

        try:

            marker = ltMarkerStyles[dialog.marker_type_combobox.currentText()]
            marker_size = int(dialog.marker_size_qspinbox.value())
            color = qcolor2rgbmpl(dialog.marker_color_qgscolorbutton.color())
            alpha = dialog.marker_alpha_qgsopacitywidget.opacity()
            labels = dialog.labels.isChecked()

            return PointPlotParams(
                marker=marker,
                markersize=marker_size,
                color=color,
                alpha=alpha,
                labels=labels,
            )

        except:

            return None

    def define_point_projections_style(self):

        dialog = StylesPointDialog(
            self.plugin_name,
            "Point projection style",
            self.current_graphical_params.point_projections
        )

        if dialog.exec():
            self.point_projections_style = self.extract_point_plot_style(dialog)
        else:
            return

    def define_line_intersections_style(self):

        if not self.geoprofiles:
            warn_qt(self,
                    self.plugin_name,
                    f"Before defining line style you must define line intersections sources.")
            return

        dialog = StylesPointDialog(
            self.plugin_name,
            "Line intersections style",
            self.current_graphical_params.line_intersections,
        )

        if dialog.exec():
            self.line_intersections_style = self.extract_point_plot_style(dialog)
        else:
            return

    def extract_attitude_plot_style(self,
                                    dialog):

        try:

            marker = ltMarkerStyles[dialog.marker_type_combobox.currentText()]
            marker_size = int(dialog.marker_size_qspinbox.value())
            color = qcolor2rgbmpl(dialog.marker_color_qgscolorbutton.color())
            alpha = dialog.marker_alpha_qgsopacitywidget.opacity()
            label_orientations = dialog.label_orientations.isChecked()
            label_ids = dialog.label_ids.isChecked()

            return AttitudePlotParams(
                marker=marker,
                markersize=marker_size,
                color=color,
                alpha=alpha,
                label_orientations=label_orientations,
                label_ids=label_ids
            )

        except Exception:

            logger.debug("extract_attitude_plot_style failed", exc_info=True)

    def define_attitude_projections_style(self):

        dialog = StylesAttitudeProjectionsParamsDialog(
            self.plugin_name,
            self.current_graphical_params.attitude_projections,
        )

        if dialog.exec():
            self.attitude_projections_style = self.extract_attitude_plot_style(dialog)
        else:
            return

    def extract_focal_mechanism_plot_style(self,
                                          dialog):

        try:

            return FocalMechanismPlotParams(
                width=int(dialog.width_qspinbox.value()),
                facecolor=qcolor2rgbmpl(dialog.facecolor_qgscolorbutton.color()),
                bgcolor=qcolor2rgbmpl(dialog.bgcolor_qgscolorbutton.color()),
                edgecolor=qcolor2rgbmpl(dialog.edgecolor_qgscolorbutton.color()),
                linewidth=dialog.linewidth_qdoublespinbox.value(),
                alpha=dialog.alpha_qgsopacitywidget.opacity(),
                labels=dialog.labels.isChecked(),
                view=dialog.view_choices[dialog.view_combobox.currentText()],
            )

        except Exception:

            logger.debug("extract_focal_mechanism_plot_style failed", exc_info=True)

    def define_focal_mechanism_projections_style(self):

        dialog = StylesFocalMechanismsParamsDialog(
            self.plugin_name,
            self.current_graphical_params.focal_mechanism_projections,
        )

        if dialog.exec():
            self.focal_mechanism_projections_style = self.extract_focal_mechanism_plot_style(dialog)
        else:
            return

    def extract_classification_colors(self, dialog):

        polygon_classification_colors_dict = dict()

        for classification_ndx in range(dialog.polygon_classifications_treeWidget.topLevelItemCount()):
            class_itemwidget = dialog.polygon_classifications_treeWidget.topLevelItem(classification_ndx)
            classification = str(class_itemwidget.text(0))
            # get color
            color = qcolor2rgbmpl(dialog.polygon_classifications_treeWidget.itemWidget(class_itemwidget, 1).color())
            polygon_classification_colors_dict[classification] = color

        return polygon_classification_colors_dict

    def initialize_polygons_intersections_style(self):

        if self.polygons_intersection_parameters is None or self.polygons_intersection_parameters.polygon_layer is None:
            symbology_colors = make_polygon_colors(self.geoprofiles._polygons_intersections)
        else:
            symbology_colors = extract_qgis_colors(self.polygons_intersection_parameters.polygon_layer)

        self.current_graphical_params.polygon_intersections = symbology_colors

    def define_polygon_intersections_style(self):

        if self.current_graphical_params.polygon_intersections is None:
            try:
                self.initialize_polygons_intersections_style()
            except:
                if self.geoprofiles is not None and hasattr(self.geoprofiles, '_polygons_intersections') and self.geoprofiles._polygons_intersections is not None:
                    colors = make_polygon_colors(self.geoprofiles._polygons_intersections)
                    self.current_graphical_params.polygon_intersections = colors
                else:
                    warn_qgis(self.plugin_name,"First define polygon intersections input layer",)
                    return

        # create windows for user_definition of intersection colors in profile

        dialog = StylesPolygonIntersectionsParamsDialog(
            self.plugin_name,
            self.current_graphical_params.polygon_intersections,
        )

        if dialog.exec():
            polygon_classification_colors_dict = self.extract_classification_colors(dialog)
        else:
            return

        if len(polygon_classification_colors_dict) == 0:
            warn_qt(self,
                    self.plugin_name,
                    "No defined colors")
            return
        else:
            self.current_graphical_params.polygon_intersections = polygon_classification_colors_dict


class StylesPointDialog(QDialog):
    """
    Point plot style.
    """

    def __init__(self,
                 plugin_name,
                 window_title,
                 current_point_style,
                 parent=None
                 ):

        super(StylesPointDialog, self).__init__(parent)

        self.plugin_name = plugin_name
        self.current_point_style = current_point_style

        # gui definition

        main_layout = QGridLayout()

        # input point geological layer

        main_layout.addWidget(
            QLabel("Marker type"),
            0, 0, 1, 1)

        self.marker_type_combobox = QComboBox()

        self.marker_type_combobox.insertItems(0, list(ltMarkerStyles.keys()))
        self.marker_type_combobox.setCurrentIndex(list(ltMarkerStyles.values()).index(self.current_point_style.marker))
        main_layout.addWidget(self.marker_type_combobox, 0, 1, 1, 1)

        main_layout.addWidget(
            QLabel("Marker size"),
            1, 0, 1, 1)

        self.marker_size_qspinbox = QSpinBox()
        self.marker_size_qspinbox.setValue(self.current_point_style.markersize)
        main_layout.addWidget(self.marker_size_qspinbox, 1, 1, 1, 1)

        main_layout.addWidget(
            QLabel("Marker color"),
            2, 0, 1, 1)

        self.marker_color_qgscolorbutton = QgsColorButton()
        self.marker_color_qgscolorbutton.setColor(create_qcolor(self.current_point_style.color))
        main_layout.addWidget(self.marker_color_qgscolorbutton, 2, 1, 1, 1)

        main_layout.addWidget(
            QLabel("Marker alpha"),
            3, 0, 1, 1)

        self.marker_alpha_qgsopacitywidget = QgsOpacityWidget()
        self.marker_alpha_qgsopacitywidget.setOpacity(self.current_point_style.alpha)
        main_layout.addWidget(self.marker_alpha_qgsopacitywidget, 3, 1, 1, 1)

        self.labels = QCheckBox("Add labels")
        self.labels.setChecked(self.current_point_style.labels)

        main_layout.addWidget(self.labels, 4, 0, 1, 1)

        # ok/cancel setup

        okButton = QPushButton("&OK")
        cancelButton = QPushButton("Cancel")

        buttonLayout = QHBoxLayout()
        buttonLayout.addStretch()
        buttonLayout.addWidget(okButton)
        buttonLayout.addWidget(cancelButton)

        main_layout.addLayout(buttonLayout, 5, 0, 1, 2)

        okButton.clicked.connect(self.accept)
        cancelButton.clicked.connect(self.reject)

        # widget final setup

        self.setLayout(main_layout)
        self.setWindowTitle(window_title)


class StylesAttitudeProjectionsParamsDialog(QDialog):
    """
    Definition of attitude projection style.
    """

    def __init__(self,
                 plugin_name,
                 current_attitude_projections_style_params,
                 parent=None
                 ):

        super(StylesAttitudeProjectionsParamsDialog, self).__init__(parent)

        self.plugin_name = plugin_name
        self.current_attitude_projections_style_params = current_attitude_projections_style_params

        # gui definition

        main_layout = QGridLayout()

        # input point geological layer

        main_layout.addWidget(
            QLabel("Marker type"),
            0, 0, 1, 1)

        self.marker_type_combobox = QComboBox()

        self.marker_type_combobox.insertItems(0, list(ltMarkerStyles.keys()))
        self.marker_type_combobox.setCurrentIndex(list(ltMarkerStyles.values()).index(self.current_attitude_projections_style_params.marker))
        main_layout.addWidget(self.marker_type_combobox, 0, 1, 1, 1)

        main_layout.addWidget(
            QLabel("Marker size"),
            1, 0, 1, 1)

        self.marker_size_qspinbox = QSpinBox()
        self.marker_size_qspinbox.setValue(self.current_attitude_projections_style_params.markersize)
        main_layout.addWidget(self.marker_size_qspinbox, 1, 1, 1, 1)

        main_layout.addWidget(
            QLabel("Marker color"),
            2, 0, 1, 1)

        self.marker_color_qgscolorbutton = QgsColorButton()
        self.marker_color_qgscolorbutton.setColor(create_qcolor(self.current_attitude_projections_style_params.color))
        main_layout.addWidget(self.marker_color_qgscolorbutton, 2, 1, 1, 1)

        main_layout.addWidget(
            QLabel("Marker alpha"),
            3, 0, 1, 1)

        self.marker_alpha_qgsopacitywidget = QgsOpacityWidget()
        self.marker_alpha_qgsopacitywidget.setOpacity(self.current_attitude_projections_style_params.alpha)
        main_layout.addWidget(self.marker_alpha_qgsopacitywidget, 3, 1, 1, 1)

        main_layout.addWidget(
            QLabel("Add labels"),
            4, 0, 1, 1)

        self.label_orientations = QCheckBox("Orientations")
        self.label_orientations.setChecked(self.current_attitude_projections_style_params.label_orientations)

        main_layout.addWidget(self.label_orientations, 4, 1, 1, 1)

        self.label_ids = QCheckBox("Ids")
        self.label_ids.setChecked(self.current_attitude_projections_style_params.label_ids)

        main_layout.addWidget(self.label_ids, 5, 1, 1, 1)

        # ok/cancel setup

        okButton = QPushButton("&OK")
        cancelButton = QPushButton("Cancel")

        buttonLayout = QHBoxLayout()
        buttonLayout.addStretch()
        buttonLayout.addWidget(okButton)
        buttonLayout.addWidget(cancelButton)

        main_layout.addLayout(buttonLayout, 6, 0, 1, 2)

        okButton.clicked.connect(self.accept)
        cancelButton.clicked.connect(self.reject)

        # widget final setup

        self.setLayout(main_layout)
        self.setWindowTitle("Project attitude style")


class StylesFocalMechanismsParamsDialog(QDialog):
    """
    Definition of focal mechanism (beachball) projection style.
    """

    # combobox entries -> FocalMechanismView members
    view_choices = OrderedDict(
        [
            ("Map view (as catalogued)", FocalMechanismView.MAP),
            ("Section view (rotated into the profile plane)", FocalMechanismView.SECTION),
        ]
    )

    def __init__(self,
                 plugin_name,
                 current_focal_mechanism_projections_style_params,
                 parent=None
                 ):

        super(StylesFocalMechanismsParamsDialog, self).__init__(parent)

        self.plugin_name = plugin_name
        self.current_focal_mechanism_projections_style_params = current_focal_mechanism_projections_style_params

        current = self.current_focal_mechanism_projections_style_params

        # gui definition

        main_layout = QGridLayout()

        main_layout.addWidget(
            QLabel("Beachball view"),
            0, 0, 1, 1)

        self.view_combobox = QComboBox()
        self.view_combobox.insertItems(0, list(self.view_choices.keys()))
        try:
            self.view_combobox.setCurrentIndex(
                list(self.view_choices.values()).index(current.view)
            )
        except ValueError:
            self.view_combobox.setCurrentIndex(0)
        main_layout.addWidget(self.view_combobox, 0, 1, 1, 1)

        main_layout.addWidget(
            QLabel("Beachball size"),
            1, 0, 1, 1)

        # width is in points (screen units), so the beachball stays circular
        # whatever the profile's vertical exaggeration
        self.width_qspinbox = QSpinBox()
        self.width_qspinbox.setMinimum(1)
        self.width_qspinbox.setMaximum(500)
        self.width_qspinbox.setValue(int(current.width))
        main_layout.addWidget(self.width_qspinbox, 1, 1, 1, 1)

        main_layout.addWidget(
            QLabel("Compressional quadrants"),
            2, 0, 1, 1)

        self.facecolor_qgscolorbutton = QgsColorButton()
        self.facecolor_qgscolorbutton.setColor(create_qcolor(current.facecolor))
        main_layout.addWidget(self.facecolor_qgscolorbutton, 2, 1, 1, 1)

        main_layout.addWidget(
            QLabel("Dilatational quadrants"),
            3, 0, 1, 1)

        self.bgcolor_qgscolorbutton = QgsColorButton()
        self.bgcolor_qgscolorbutton.setColor(create_qcolor(current.bgcolor))
        main_layout.addWidget(self.bgcolor_qgscolorbutton, 3, 1, 1, 1)

        main_layout.addWidget(
            QLabel("Outline color"),
            4, 0, 1, 1)

        self.edgecolor_qgscolorbutton = QgsColorButton()
        self.edgecolor_qgscolorbutton.setColor(create_qcolor(current.edgecolor))
        main_layout.addWidget(self.edgecolor_qgscolorbutton, 4, 1, 1, 1)

        main_layout.addWidget(
            QLabel("Outline width"),
            5, 0, 1, 1)

        # in points, as the beachball size is, and fractional: at the sizes a
        # section calls for, whole points are too coarse a step
        self.linewidth_qdoublespinbox = QDoubleSpinBox()
        self.linewidth_qdoublespinbox.setMinimum(0.0)
        self.linewidth_qdoublespinbox.setMaximum(10.0)
        self.linewidth_qdoublespinbox.setSingleStep(0.1)
        self.linewidth_qdoublespinbox.setDecimals(1)
        self.linewidth_qdoublespinbox.setValue(float(current.linewidth))
        main_layout.addWidget(self.linewidth_qdoublespinbox, 5, 1, 1, 1)

        main_layout.addWidget(
            QLabel("Beachball alpha"),
            6, 0, 1, 1)

        self.alpha_qgsopacitywidget = QgsOpacityWidget()
        self.alpha_qgsopacitywidget.setOpacity(current.alpha)
        main_layout.addWidget(self.alpha_qgsopacitywidget, 6, 1, 1, 1)

        self.labels = QCheckBox("Add id labels")
        self.labels.setChecked(current.labels)
        main_layout.addWidget(self.labels, 7, 1, 1, 1)

        # ok/cancel setup

        okButton = QPushButton("&OK")
        cancelButton = QPushButton("Cancel")

        buttonLayout = QHBoxLayout()
        buttonLayout.addStretch()
        buttonLayout.addWidget(okButton)
        buttonLayout.addWidget(cancelButton)

        main_layout.addLayout(buttonLayout, 8, 0, 1, 2)

        okButton.clicked.connect(self.accept)
        cancelButton.clicked.connect(self.reject)

        # widget final setup

        self.setLayout(main_layout)
        self.setWindowTitle("Project focal mechanism style")


class ExportTopographicProfileDialog(QDialog):

    def __init__(self,
                 plugin_name,
                 parent=None
                 ):

        super(ExportTopographicProfileDialog, self).__init__(parent)

        self.plugin_name = plugin_name

        layout = QVBoxLayout()

        # Output type

        output_type_groupBox = QGroupBox(self.tr("Output format"))

        output_type_layout = QGridLayout()

        self.outtype_shapefile_point_QRadioButton = QRadioButton(self.tr("shapefile - point"))
        output_type_layout.addWidget(self.outtype_shapefile_point_QRadioButton, 0, 0, 1, 1)
        self.outtype_shapefile_point_QRadioButton.setChecked(True)

        self.outtype_shapefile_line_QRadioButton = QRadioButton(self.tr("shapefile - line"))
        output_type_layout.addWidget(
            self.outtype_shapefile_line_QRadioButton,
            1, 0, 1, 1)

        output_type_groupBox.setLayout(output_type_layout)

        layout.addWidget(output_type_groupBox)

        # Output name/path

        output_path_groupBox = QGroupBox(self.tr("Output file"))

        output_path_layout = QGridLayout()

        self.outpath_QLineEdit = QLineEdit()
        output_path_layout.addWidget(
            self.outpath_QLineEdit,
            0, 0, 1, 1)

        self.outpath_QPushButton = QPushButton("....")
        self.outpath_QPushButton.clicked.connect(self.define_outpath)
        output_path_layout.addWidget(
            self.outpath_QPushButton,
            0, 1, 1, 1)

        self.load_output_checkBox = QCheckBox("load output shapefile in project")
        self.load_output_checkBox.setChecked(True)
        output_path_layout.addWidget(
            self.load_output_checkBox,
            1, 0, 1, 2)

        output_path_groupBox.setLayout(output_path_layout)

        layout.addWidget(output_path_groupBox)

        decide_QWiget = QWidget()

        buttonLayout = QHBoxLayout()
        buttonLayout.addStretch()

        okButton = QPushButton("&OK")
        cancelButton = QPushButton("Cancel")

        buttonLayout.addWidget(okButton)
        buttonLayout.addWidget(cancelButton)

        decide_QWiget.setLayout(buttonLayout)

        layout.addWidget(decide_QWiget)

        self.setLayout(layout)

        okButton.clicked.connect(self.accept)
        cancelButton.clicked.connect(self.reject)

        self.setWindowTitle("Export topographic profile")

    def define_outpath(self):

        if self.outtype_shapefile_line_QRadioButton.isChecked() or self.outtype_shapefile_point_QRadioButton.isChecked():
            outfile_path = new_file_path(
                self,
                "Save file",
                "",
                "Shapefiles (*.shp)"
            )
        elif self.outtype_csv_QRadioButton.isChecked():
            outfile_path = new_file_path(
                self,
                "Save file",
                "",
                "Csv (*.csv)"
            )
        else:
            error_qt(
                self,
                self.plugin_name,
                self.tr("Output type definition error")
            )
            return

        self.outpath_QLineEdit.setText(outfile_path)


class OpenGeopackageDialog(QDialog):

    def __init__(self,
                 plugin_name,
                 parent=None
                 ):

        super(OpenGeopackageDialog, self).__init__(parent)

        self.plugin_name = plugin_name

        layout = QGridLayout()

        self.existing_geopackage_QLineEdit = QLineEdit()
        layout.addWidget(
            self.existing_geopackage_QLineEdit,
            0, 0, 1, 1)

        self.outpath_QPushButton = QPushButton("....")
        self.outpath_QPushButton.clicked.connect(self.choose_existing_geopackage_path)
        layout.addWidget(
            self.outpath_QPushButton,
            0, 1, 1, 1)

        layout.addWidget(QLabel("Layer"), 1, 0, 1, 1)

        self.existing_layer_QLineEdit = QLineEdit()
        layout.addWidget(
            self.existing_layer_QLineEdit,
            1, 1, 1, 1)

        decide_QWiget = QWidget()

        buttonLayout = QHBoxLayout()
        buttonLayout.addStretch()

        okButton = QPushButton("&OK")
        cancelButton = QPushButton("Cancel")

        buttonLayout.addWidget(okButton)
        buttonLayout.addWidget(cancelButton)

        decide_QWiget.setLayout(buttonLayout)

        layout.addWidget(decide_QWiget)

        self.setLayout(layout)

        okButton.clicked.connect(self.accept)
        cancelButton.clicked.connect(self.reject)

        self.setWindowTitle("Choose geopackage")

    def choose_existing_geopackage_path(self):

        outfile_path = old_file_path(
            self,
            "Define geopackage",
            "",
            "Geopackages (*.gpkg)"
        )

        self.existing_geopackage_QLineEdit.setText(outfile_path)


class ExportLineDataDialog(QDialog):

    def __init__(self,
                 plugin_name,
                 parent=None
                 ):

        super(ExportLineDataDialog, self).__init__(parent)

        self.plugin_name = plugin_name

        layout = QVBoxLayout()

        # Output type

        output_type_groupBox = QGroupBox(self.tr("Output format"))

        output_type_layout = QGridLayout()

        self.outtype_shapefile_line_QRadioButton = QRadioButton(self.tr("shapefile - line"))
        output_type_layout.addWidget(self.outtype_shapefile_line_QRadioButton, 0, 0, 1, 1)
        self.outtype_shapefile_line_QRadioButton.setChecked(True)

        self.outtype_csv_QRadioButton = QRadioButton(self.tr("csv"))
        output_type_layout.addWidget(
            self.outtype_csv_QRadioButton,
            0, 1, 1, 1)

        output_type_groupBox.setLayout(output_type_layout)

        layout.addWidget(output_type_groupBox)

        # Output name/path

        output_path_groupBox = QGroupBox(self.tr("Output file"))

        output_path_layout = QGridLayout()

        self.outpath_QLineEdit = QLineEdit()
        output_path_layout.addWidget(
            self.outpath_QLineEdit,
            0, 0, 1, 1)

        self.outpath_QPushButton = QPushButton("....")
        self.outpath_QPushButton.clicked.connect(self.define_outpath)
        output_path_layout.addWidget(
            self.outpath_QPushButton,
            0, 1, 1, 1)

        self.load_output_checkBox = QCheckBox("load output in project")
        self.load_output_checkBox.setChecked(True)
        output_path_layout.addWidget(
            self.load_output_checkBox,
            1, 0, 1, 2)

        output_path_groupBox.setLayout(output_path_layout)

        layout.addWidget(output_path_groupBox)

        decide_QWiget = QWidget()

        buttonLayout = QHBoxLayout()
        buttonLayout.addStretch()

        okButton = QPushButton("&OK")
        cancelButton = QPushButton("Cancel")

        buttonLayout.addWidget(okButton)
        buttonLayout.addWidget(cancelButton)

        decide_QWiget.setLayout(buttonLayout)

        layout.addWidget(decide_QWiget)

        self.setLayout(layout)

        okButton.clicked.connect(self.accept)
        cancelButton.clicked.connect(self.reject)

        self.setWindowTitle("Export")

    def define_outpath(self):

        if self.outtype_shapefile_line_QRadioButton.isChecked():
            outfile_path = new_file_path(
                self,
                "Save file",
                "",
                "Shapefiles (*.shp)"
            )
        elif self.outtype_csv_QRadioButton.isChecked():
            outfile_path = new_file_path(
                self,
                "Save file",
                "",
                "Csv (*.csv)"
            )
        else:
            error_qt(
                self,
                self.plugin_name,
                self.tr("Output type definition error")
            )
            return

        self.outpath_QLineEdit.setText(outfile_path)


class BaseDataDigitizeLineDialog(QDialog):

    def __init__(
            self,
            plugin_name,
            canvas,
            parent=None
    ):

        super(BaseDataDigitizeLineDialog, self).__init__(parent)

        self.plugin_name = plugin_name
        self.canvas = canvas
        self.previous_maptool = self.canvas.mapTool()  # Save the standard map tool for restoring it at the end

        layout = QVBoxLayout()

        self.qpbtDigitizeLine = QPushButton(self.tr("Digitize trace"))
        self.qpbtDigitizeLine.setToolTip(
            "Digitize the trace on the map.\n"
            "Left click: add point\n"
            "Right click: end adding point"
        )
        self.qpbtDigitizeLine.clicked.connect(self.digitize_line)

        layout.addWidget(self.qpbtDigitizeLine)

        self.qpbtClearLine = QPushButton(self.tr("Clear"))
        self.qpbtClearLine.clicked.connect(self.clear_rubberband)
        layout.addWidget(self.qpbtClearLine)

        self.qpbtClearLine = QPushButton(self.tr("Save"))
        self.qpbtClearLine.clicked.connect(self.save_rubberband)
        layout.addWidget(self.qpbtClearLine)

        self.setLayout(layout)

        self.setWindowTitle("Digitize line")

    '''
    def connect_digitize_maptool(self):

        self.digitize_maptool.moved.connect(self.canvas_refresh_profile_line)
        self.digitize_maptool.leftClicked.connect(self.profile_add_point)
        self.digitize_maptool.rightClicked.connect(self.canvas_end_profile_line)
    '''

    def digitize_line(self):

        self.clear_rubberband()

        info_qgis(
            self.plugin_name,
            "Now you can digitize a line on the map.\nLeft click: add point\nRight click: end adding point"
        )

        self.rubberband = QgsRubberBand(self.canvas)
        self.rubberband.setWidth(2)
        self.rubberband.setColor(QColor(Qt.GlobalColor.red))

        self.digitize_maptool = MapDigitizeTool(self.canvas)
        self.canvas.setMapTool(self.digitize_maptool)

        self.digitize_maptool.moved.connect(self.canvas_refresh_profile_line)
        self.digitize_maptool.leftClicked.connect(self.profile_add_point)
        self.digitize_maptool.rightClicked.connect(self.canvas_end_profile_line)

    def canvas_refresh_profile_line(self, position):

        x, y = xy_from_canvas(self.canvas, position)

        self.refresh_rubberband(
            self.profile_canvas_points_x + [x],
            self.profile_canvas_points_y + [y]
        )

    def profile_add_point(self, position):

        x, y = xy_from_canvas(self.canvas, position)

        self.profile_canvas_points_x.append(x)
        self.profile_canvas_points_y.append(y)

    def canvas_end_profile_line(self):

        self.refresh_rubberband(
            self.profile_canvas_points_x,
            self.profile_canvas_points_y)

        self.digitized_profile_line2dt = None
        if len(self.profile_canvas_points_x) > 1:
            raw_line = Ln(list(zip(self.profile_canvas_points_x, self.profile_canvas_points_y))).remove_coincident_points()
            if raw_line.num_points() > 1:
                self.digitized_profile_line2dt = raw_line

        self.profile_canvas_points_x = []
        self.profile_canvas_points_y = []

        self.restore_previous_map_tool()

    def restore_previous_map_tool(self):

        self.canvas.unsetMapTool(self.digitize_maptool)
        self.canvas.setMapTool(self.previous_maptool)

    def refresh_rubberband(self,
                           xy_list
                           ):

        self.rubberband.reset(QgsWkbTypes.GeometryType.LineGeometry)
        for x, y in xy_list:
            self.rubberband.addPoint(QgsPointXY(x, y))

    def clear_rubberband(self):

        self.profile_canvas_points_x = []
        self.profile_canvas_points_y = []

        self.digitized_profile_line2dt = None

        try:

            self.rubberband.reset()

        except Exception:

            logger.debug("rubberband.reset() failed", exc_info=True)

    def save_rubberband(self):

        def output_profile_line(
                output_format,
                output_filepath,
                pts2dt,
                proj_sr
        ):

            points = [[n, pt2dt.x, pt2dt.y] for n, pt2dt in enumerate(pts2dt)]
            if output_format == "csv":
                success, msg = write_generic_csv(output_filepath,
                                                 ['id', 'x', 'y'],
                                                 points)
                if not success:
                    warn_qt(
                        self,
                        self.plugin_name,
                        msg
                    )
            elif output_format == "shapefile - line":
                success, msg = write_rubberband_profile_lnshp(
                    output_filepath,
                    ['id'],
                    points,
                    proj_sr)
                if not success:
                    warn_qt(
                        self,
                        self.plugin_name,
                        msg
                    )
            else:
                error_qt(
                    self,
                    self.plugin_name,
                    "Error in export format"
                )
                return

            if success:
                ok_qgis(
                    self.plugin_name,
                    "Line saved"
                )

        def extract_format_type():

            if dialog.outtype_shapefile_line_QRadioButton.isChecked():
                return "shapefile - line"
            elif dialog.outtype_csv_QRadioButton.isChecked():
                return "csv"
            else:
                return ""

        if self.digitized_profile_line2dt is None:

            warn_qt(
                self,
                self.plugin_name,
                "No available line to save [1]"
            )
            return

        elif self.digitized_profile_line2dt.num_points() < 2:

            warn_qt(
                self,
                self.plugin_name,
                "No available line to save [2]"
            )
            return

        dialog = ExportLineDataDialog(self.plugin_name)
        if dialog.exec():
            output_format = extract_format_type()
            if output_format == "":
                warn_qt(
                    self,
                    self.plugin_name,
                    "Error in output format"
                )
                return
            output_filepath = dialog.outpath_QLineEdit.text()
            if len(output_filepath) == 0:
                warn_qt(
                    self,
                    self.plugin_name,
                    "Error in output path"
                )
                return
            add_to_project = dialog.load_output_checkBox.isChecked()
        else:
            warn_qt(
                self,
                self.plugin_name,
                "No export defined"
            )
            return

        # get project CRS information
        project_crs_osr = proj4str()

        output_profile_line(
            output_format,
            output_filepath,
            self.digitized_profile_line2dt.pts,
            project_crs_osr)

        # add theme to QGis project
        if output_format == "shapefile - line" and add_to_project:
            try:

                digitized_line_layer = QgsVectorLayer(output_filepath,
                                                      QFileInfo(output_filepath).baseName(),
                                                      "ogr")
                QgsProject.instance().addMapLayer(digitized_line_layer)

            except:

                error_qt(
                    self,
                    self.plugin_name,
                    "Unable to load layer in project"
                )
                return


class BaseDataProfilerSettingsDialog(QDialog):

    def __init__(
            self,
            plugin_name,
            sampling_distance=None,
            parent=None
    ):
        """
        :param sampling_distance: the proposed grid-sampling spacing within
            the profile, normally the cell size of the chosen DEM, which is
            the finest spacing that still adds information.
        """

        super(BaseDataProfilerSettingsDialog, self).__init__(parent)

        self.plugin_name = plugin_name

        layout = QGridLayout()

        layout.addWidget(QLabel("Number of parallel profiles"), 0, 0, 1, 1)

        self.num_parallel_profiles_wdgt = QSpinBox()
        self.num_parallel_profiles_wdgt.setValue(1)
        self.num_parallel_profiles_wdgt.setMinimum(1)
        self.num_parallel_profiles_wdgt.setSingleStep(2)
        layout.addWidget(self.num_parallel_profiles_wdgt, 0, 1, 1, 1)

        layout.addWidget(QLabel("Spacing between parallel profiles"), 1, 0, 1, 1)

        self.spacing_between_profiles_wdgt = QDoubleSpinBox()
        self.spacing_between_profiles_wdgt.setValue(0.0)
        self.spacing_between_profiles_wdgt.setMinimum(0.0)
        self.spacing_between_profiles_wdgt.setMaximum(10000.0)
        self.spacing_between_profiles_wdgt.setSingleStep(50.0)

        layout.addWidget(self.spacing_between_profiles_wdgt, 1, 1, 1, 1)

        layout.addWidget(QLabel("Grid-sampling spacing within profile"), 2, 0, 1, 1)

        try:
            proposed_sampling_distance = float(sampling_distance) if sampling_distance else 0.0
        except (TypeError, ValueError):
            proposed_sampling_distance = 0.0

        self.spacing_within_profile_wdgt = QDoubleSpinBox()

        # a DEM in a geographic CRS has cell sizes of the order of 1e-4 degrees,
        # that the default two decimals would round down to zero: the number of
        # decimals, the range and the step follow the proposed value

        if 0.0 < proposed_sampling_distance < 1.0:
            self.spacing_within_profile_wdgt.setDecimals(8)
        else:
            self.spacing_within_profile_wdgt.setDecimals(2)

        self.spacing_within_profile_wdgt.setMinimum(0.0)
        self.spacing_within_profile_wdgt.setMaximum(max(1000.0, proposed_sampling_distance * 100.0))
        self.spacing_within_profile_wdgt.setSingleStep(
            proposed_sampling_distance if proposed_sampling_distance > 0.0 else 5.0
        )

        # set last, so that it is not clipped by the range set above

        self.spacing_within_profile_wdgt.setValue(proposed_sampling_distance)

        layout.addWidget(self.spacing_within_profile_wdgt, 2, 1, 1, 1)

        okButton = QPushButton("&OK")
        cancelButton = QPushButton("Cancel")
        okButton.clicked.connect(self.accept)
        cancelButton.clicked.connect(self.reject)

        verticalSpacer = QSpacerItem(20, 30, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
        layout.addItem(verticalSpacer)

        layout.addWidget(okButton,
                         3, 0, 1, 1)
        layout.addWidget(cancelButton,
                         3, 1, 1, 1)

        self.setLayout(layout)

        self.setWindowTitle("Profiles settings")
        self.setMinimumSize(UIConstants.ABOUT_DIALOG_MIN_WIDTH, UIConstants.ABOUT_DIALOG_MIN_HEIGHT)


class StylesPolygonIntersectionsParamsDialog(QDialog):

    def __init__(
            self,
            plugin_name,
            current_polygons_graphical_params,
            parent=None
    ):

        super(StylesPolygonIntersectionsParamsDialog, self).__init__(parent)

        self.plugin_name = plugin_name
        self.polygon_intersections_colors = current_polygons_graphical_params

        self.polygon_classifications_treeWidget = QTreeWidget()
        self.polygon_classifications_treeWidget.setColumnCount(2)
        self.polygon_classifications_treeWidget.headerItem().setText(0, "Name")
        self.polygon_classifications_treeWidget.headerItem().setText(1, "Color")
        self.polygon_classifications_treeWidget.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
        self.polygon_classifications_treeWidget.setDragEnabled(False)
        self.polygon_classifications_treeWidget.setDragDropMode(QAbstractItemView.DragDropMode.NoDragDrop)
        self.polygon_classifications_treeWidget.setAlternatingRowColors(True)
        self.polygon_classifications_treeWidget.setTextElideMode(Qt.TextElideMode.ElideLeft)

        self.update_polygons_classification_colors_treewidget()

        self.polygon_classifications_treeWidget.resizeColumnToContents(0)
        self.polygon_classifications_treeWidget.resizeColumnToContents(1)

        okButton = QPushButton("&OK")
        cancelButton = QPushButton("Cancel")

        buttonLayout = QHBoxLayout()
        buttonLayout.addStretch()
        buttonLayout.addWidget(okButton)
        buttonLayout.addWidget(cancelButton)

        layout = QGridLayout()

        layout.addWidget(self.polygon_classifications_treeWidget, 0, 0, 1, 3)
        layout.addLayout(buttonLayout, 1, 0, 1, 3)

        self.setLayout(layout)

        okButton.clicked.connect(self.accept)
        cancelButton.clicked.connect(self.reject)

        self.setWindowTitle("Polygon intersection colors")

    def update_polygons_classification_colors_treewidget(self):

        self.polygon_classifications_treeWidget.clear()

        for classification_id, color in self.polygon_intersections_colors.items():

            tree_item = QTreeWidgetItem(self.polygon_classifications_treeWidget)
            tree_item.setText(0, str(classification_id))

            color_QgsColorButton = QgsColorButton()

            if classification_id in self.polygon_intersections_colors:
                color = QColor.fromRgbF(*color)
            color_QgsColorButton.setColor(QColor(color))
            self.polygon_classifications_treeWidget.setItemWidget(tree_item, 1, color_QgsColorButton)


class HelpDialog(QDialog):

    def __init__(self,
                 plugin_name,
                 plugin_folder,
                 parent=None):

        super(HelpDialog, self).__init__(parent)

        layout = QVBoxLayout()

        # About section

        helpTextBrwsr = QTextBrowser(self)

        url_path = f"file:///{plugin_folder}/help/geoprofiler/help.html"
        helpTextBrwsr.setSource(QUrl(url_path))
        helpTextBrwsr.setSearchPaths([f'{plugin_folder}/help/geoprofiler/images'])
        helpTextBrwsr.setMinimumSize(700, 600)

        layout.addWidget(helpTextBrwsr)

        self.setLayout(layout)

        self.setWindowTitle("{} - Points-plane distances help".format(plugin_name))



