unit CadreGHTopoContext2D;
// Cadre de dessin 2D pour la nouvelle version des données compîlées
// 05/11/2013: Modes de représentation OK (secteurs, réseaux, expés)
// 28/11/2013: Gestion des dégradés dans les onglets
// 17/01/2014: Nouveau dialogue Distance
// 23/01/2014: Calcul altitude d'un point de surface si maillage MNT OK
// 14/04/2014: Implémentation du support des cotes "spéléo" (-268, -140)
// 04/08/2014: Nouvelle procédure de dessin des entrées
// 20/11/2014: Support des largeurs de trait pour les polygonales (lié aux visées en antenne)
// 20/11/2014: Les visées en antenne deviennent une catégorie d'éléments affichés
// 13/05/2015: Possibilité optionnelle de TBGRABitmap
// 28/05/2015: Surbrillance de la série courante
// 19/06/2015: Noms des entrées sont affichés
// 22/02/2015: Recherche de stations OK. Si trouvée, un marqueur est affiché
// 06/05/2016: La méthode de zoom et pan est unifiée et alignée sur celle de GHCaveDraw (AutoCAD-like)
// 14/06/2016: Ajout du mode de visu 'Type de visées'
// 07/04/2017: Suppression du cadre 'Filtres', trop complexe
// 21/04/2017: Optimisations de l'affichage par tests IsPointInRect() et IsSegmentInRect()
// 12/11/2017: La barre de statut/infos du viewer passe en haut. Motif: Le bas de la fenêtre est souvent masqué
// 20/09/2018: Fix de l'affichage des cotes: les visées radiantes étaient également cotées
// 13/06/2019: Point de contrôle temporel (contrôle de version)
// 12/12/2019: Une seule variable pour la station de référence: FCurrentStation
// 17/04/2025: Point d'étape.


{$INCLUDE CompilationParameters.inc}
interface
uses
  {$INCLUDE SelectionLangues.inc} // insère les unités en fonction de la langue
  StructuresDonnees, GHTopoGeneralFunctions, unitDatesUtils, unitGHTopoViseesUtils, unitGHTopoStringUtils,  unitColorsUtils, unitSolveEquationsPolynomes,  Classes, SysUtils, Math, // anciennement Include
  LCLIntf, LCLType,
  UnitListesSimplesWithGeneriques,
  {$IFDEF GHTOPO_SIMPLIFIE}
  unitBRIC4Structures,
  {$endif GHTOPO_SIMPLIFIE}
  ToporobotClasses2012,
  UnitEntitesExtended,
  //ConvertisseurJPC,
  unitCroquisTerrain,
  unitobjetserie,
  UnitClasseMaillage,
  UnitTPathBetweenNodes,
  UnitGraphes1,
  unitProfilTopo,
  FastGEO,
  //types,
  dateutils,
  LazFileUtils,
  Graphics,
  Clipbrd
  , BGRABitmap
  , BGRABitmapTypes
  , BGRAGradients
  , UnitTGHTopoDrawDrawingContext
  , CallDialogsStdVersion,
  unitUtilsComposants,
  FileUtil, curredit, Forms, Dialogs, Controls,
  ExtCtrls, StdCtrls, ActnList, Menus, Buttons, Spin, EditBtn;


// Si utilisation de GHTopo simplifé
{$IFDEF GHTOPO_SIMPLIFIE}
CONST FCurrIdxOnglet = 0;
{$ENDIF GHTOPO_SIMPLIFIE}
const NB_MAX_PTS_CLICKED = 64;
const TAILLE_BUFFER_ZOOMS = 100;

type TMousePointToReset = set of (DoFC1, DoFC2, DoPT1, DoPT2, DoPT3, DoPT4);

type TGhostPolygone = class(TListeSimple<TPoint2Df>)
  private

  public

end;
type

{ TGHTopoContext2DA }

  TGHTopoContext2DA = class(TFrame)
    acIsovaleurSurZStation: TAction;
    acMetaFiltreCode: TAction;
    acMetaFiltreExpe: TAction;
    acMetaFiltreReseau: TAction;
    acMetaFiltreSerie: TAction;
    acMetaFiltreTopoAnnee: TAction;
    acMetaFiltreTopoDuJour: TAction;
    acMetaFiltreVueCourante: TAction;
    acMetaFiltreZone: TAction;
    acMetaFiltreSecteur: TAction;
    acAddSerieHere: TAction;
    acSetCurrentBaseStation: TAction;
    acPanVue: TAction;
    acDistanceP2P: TAction;
    acMetaFiltreEntree: TAction;
    acDisplayVoisinageCurrStation: TAction;
    acCreerEntreeFromThisPoint: TAction;
    acAddserieEntreCesPoints: TAction;
    acLocaliserUneStation: TAction;
    acSaveCroquis: TAction;
    acOpenCroquis: TAction;
    acGetStationCoords: TAction;
    acHighlightThisSerie: TAction;
    acHighLightThisCode: TAction;
    acHighLightThisExpe: TAction;
    acHighLightThisReseau: TAction;
    acHighLightThisEntrance: TAction;
    acContinueHere: TAction;

    acRemoveLastVisee: TAction;
    acDisplayMiniformVisee: TAction;
    acBtnPanNW: TAction;
    acBtnPanSW: TAction;
    acBtnPanSE: TAction;
    acBtnPanNE: TAction;
    acBtnPanEE: TAction;
    acBtnPanNN: TAction;
    acBtnPanSS: TAction;
    acBtnPanWW: TAction;
    acEditThisSerie: TAction;
    acDispPopUp: TAction;
    acDeleteThisPOI: TAction;
    acAddPOIHere: TAction;
    acDisplLastPointsClicked: TAction;
    acAddSerieToHighlight: TAction;
    acAltitudeTNOverStation: TAction;
    acClearListePointsCliques: TAction;
    acDefinirPointBut: TAction;
    acDefineEndSerieAsEntrance: TAction;
    acGenererCoupesCTByRect: TAction;
    acFiltrerSurLasso: TAction;
    acEditThisStation: TAction;
    acCentrerMapAtCurrStationDistoX: TAction;
    acGenererCoupesCTByPolygon: TAction;
    acRemoveStFromGraphe: TAction;
    acVuesSuivantes: TAction;
    acVuesPrecedentes: TAction;
    acVueRefresh: TAction;
    acZoomPlus: TAction;
    acZoomMoins: TAction;
    acTransmitBasePointForHighlighting: TAction;
    acZoomFenetre: TAction;
    acZoomTout: TAction;
    ActListCdrVue2D: TActionList;
    btnApplyMetaFiltre: TButton;
    btnCommentaire: TButton;
    btnHelpMetaFiltre: TButton;
    btnLong1: TButton;
    btnLG: TButton;
    Button1: TButton;
    btnMaillageOK: TButton;
    btnMoreInfos: TButton;
    btnCopyCoords: TButton;
    btnListeProfils: TButton;
    btnSerieCouranteDistoX: TButton;
    btnQSAddVisee: TButton;
    btnUnloadMNT: TButton;
    btnCancelLast: TButton;
    btnQSCloseMiniFormVisee: TButton;
    Button2: TButton;
    btnLong0: TButton;
    btnAz0: TButton;
    btnIncl0: TButton;
    btnAz1: TButton;
    btnIncl1: TButton;
    btnHZ: TButton;
    btnLD: TButton;
    btnHN: TButton;
    chkDrawMaillage: TCheckBox;
    btnLineContourColor: TColorButton;
    chkMetaFiltreActif: TCheckBox;
    editIsoValeur: TCurrencyEdit;
    editMetaFiltre: TEdit;
    imgLstCtxtV2D: TImageList;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    lbFiltres: TLabel;
    lbColorReseau: TStaticText;
    lbCouleurVisee: TStaticText;
    lbColorSecteur: TStaticText;
    lbColorSeance: TStaticText;
    lbCoordonnees: TStaticText;
    lbGCSMouse: TStaticText;
    lbInfos: TStaticText;
    lbMesures: TStaticText;
    lbAdditionalInfos: TStaticText;
    lbNomReseau: TStaticText;
    lbNomSecteur: TStaticText;
    lbModeTravail: TStaticText;
    lbNumSerie: TStaticText;
    lbInfosExpe: TStaticText;
    lbNumStation: TStaticText;
    lbIDTerrainStation: TStaticText;
    MenuItem1: TMenuItem;
    MenuItem10: TMenuItem;
    MenuItem11: TMenuItem;
    MenuItem12: TMenuItem;
    MenuItem13: TMenuItem;
    MenuItem14: TMenuItem;
    MenuItem15: TMenuItem;
    MenuItem16: TMenuItem;
    MenuItem17: TMenuItem;
    MenuItem18: TMenuItem;
    MenuItem19: TMenuItem;
    MenuItem2: TMenuItem;
    MenuItem20: TMenuItem;
    MenuItem21: TMenuItem;
    MenuItem22: TMenuItem;
    MenuItem23: TMenuItem;
    MenuItem24: TMenuItem;
    MenuItem25: TMenuItem;
    MenuItem26: TMenuItem;
    MenuItem27: TMenuItem;
    MenuItem28: TMenuItem;
    MenuItem29: TMenuItem;
    MenuItem30: TMenuItem;
    MenuItem31: TMenuItem;
    MenuItem32: TMenuItem;
    MenuItem34: TMenuItem;
    MenuItem35: TMenuItem;
    MenuItem36: TMenuItem;
    MenuItem37: TMenuItem;
    MenuItem38: TMenuItem;
    MenuItem43: TMenuItem;
    MenuItem44: TMenuItem;
    MenuItem45: TMenuItem;
    MenuItem47: TMenuItem;
    mnuStationCourante: TMenuItem;
    MenuItem3: TMenuItem;
    MenuItem33: TMenuItem;
    MenuItem39: TMenuItem;
    MenuItem40: TMenuItem;
    MenuItem41: TMenuItem;
    MenuItem42: TMenuItem;
    MenuItem46: TMenuItem;
    mnuHighLight: TMenuItem;
    MenuItem4: TMenuItem;
    MenuItem5: TMenuItem;
    MenuItem6: TMenuItem;
    MenuItem7: TMenuItem;
    MenuItem8: TMenuItem;
    MenuItem9: TMenuItem;
    mnuMetaFiltreStation: TMenuItem;
    pnlVue: TPanel;
    Panel2: TPanel;
    Panel3: TPanel;
    pnlToast: TPanel;
    pnlQuickSaisieVisee: TPanel;
    pnlFiltres: TPanel;
    pnlFullInfos: TPanel;
    pnlMaillage: TPanel;
    PopUpCdrVue2D: TPopupMenu;
    lbCode: TStaticText;
    lbSerie: TStaticText;
    lbEntreeRattachement: TStaticText;
    lbMiscMaillage: TStaticText;
    lbLongViseesVisibles: TStaticText;
    lbTitreQuickSaisieVisee: TStaticText;
    editLineContourOpacity: TSpinEdit;
    Separator1: TMenuItem;
    Separator2: TMenuItem;
    Separator3: TMenuItem;
    Separator4: TMenuItem;
    Separator5: TMenuItem;
    SpeedButton1: TSpeedButton;
    SpeedButton10: TSpeedButton;
    SpeedButton11: TSpeedButton;
    SpeedButton12: TSpeedButton;
    SpeedButton13: TSpeedButton;
    SpeedButton14: TSpeedButton;
    btnCoinBasDroitVue: TSpeedButton;
    SpeedButton16: TSpeedButton;
    SpeedButton17: TSpeedButton;
    SpeedButton18: TSpeedButton;
    SpeedButton19: TSpeedButton;
    SpeedButton2: TSpeedButton;
    SpeedButton3: TSpeedButton;
    SpeedButton4: TSpeedButton;
    SpeedButton5: TSpeedButton;
    SpeedButton6: TSpeedButton;
    SpeedButton7: TSpeedButton;
    SpeedButton8: TSpeedButton;
    SpeedButton9: TSpeedButton;
    Vue: TPaintBox;

    procedure acAddAnnotationHereExecute(Sender: TObject);
    procedure acAddPOIHereExecute(Sender: TObject);
    procedure acAddserieEntreCesPointsExecute(Sender: TObject);
    procedure acAddSerieHereExecute(Sender: TObject);
    procedure acAddSerieToHighlightExecute(Sender: TObject);
    procedure acAddViseeRadianteHereExecute(Sender: TObject);
    procedure acAltitudeTNOverStationExecute(Sender: TObject);
    procedure acBtnPanEEExecute(Sender: TObject);
    procedure acBtnPanNNExecute(Sender: TObject);
    procedure acBtnPanSSExecute(Sender: TObject);
    procedure acBtnPanWWExecute(Sender: TObject);
    procedure acCentrerMapAtCurrStationDistoXExecute(Sender: TObject);
    procedure acClearListePointsCliquesExecute(Sender: TObject);
    procedure acContinueHereExecute(Sender: TObject);
    procedure acCreerEntreeFromThisPointExecute(Sender: TObject);
    procedure acDefineEndSerieAsEntranceExecute(Sender: TObject);
    procedure acDefinirPointButExecute(Sender: TObject);
    procedure acDeleteThisPOIExecute(Sender: TObject);
    procedure acDisplayMiniformViseeExecute(Sender: TObject);
    procedure acDisplayVoisinageCurrStationExecute(Sender: TObject);
    procedure acDispPopUpExecute(Sender: TObject);
    procedure acDistanceP2PExecute(Sender: TObject);
    procedure acEditThisStationExecute(Sender: TObject);
    procedure acFiltrerSurLassoExecute(Sender: TObject);
    procedure acGenererCoupesCTByPolygonExecute(Sender: TObject);
    procedure acGenererCoupesCTByRectExecute(Sender: TObject);
    procedure acGetStationCoordsExecute(Sender: TObject);
    procedure acHighLightThisCodeExecute(Sender: TObject);
    procedure acHighLightThisExpeExecute(Sender: TObject);
    procedure acHighlightThisSerieExecute(Sender: TObject);
    procedure acIsovaleurSurZStationExecute(Sender: TObject);
    procedure acLocaliserUneStationExecute(Sender: TObject);
    procedure acMetaFiltreCodeExecute(Sender: TObject);
    procedure acMetaFiltreEntreeExecute(Sender: TObject);
    procedure acMetaFiltreExpeExecute(Sender: TObject);
    procedure acMetaFiltreReseauExecute(Sender: TObject);
    procedure acMetaFiltreSecteurExecute(Sender: TObject);
    procedure acMetaFiltreSerieExecute(Sender: TObject);
    procedure acMetaFiltreTopoAnneeExecute(Sender: TObject);
    procedure acMetaFiltreTopoDuJourExecute(Sender: TObject);
    procedure acMetaFiltreVueCouranteExecute(Sender: TObject);
    procedure acMetaFiltreZoneExecute(Sender: TObject);
    procedure acOpenCroquisExecute(Sender: TObject);
    procedure acPanVueExecute(Sender: TObject);
    procedure acRemoveLastViseeExecute(Sender: TObject);
    procedure acRemoveStFromGrapheExecute(Sender: TObject);
    procedure acSaveCroquisExecute(Sender: TObject);
    procedure acSetCurrentBaseStationExecute(Sender: TObject);
    procedure acSetCurrentSerieExecute(Sender: TObject);
    procedure acSupprimerSerieExecute(Sender: TObject);
    procedure acEditThisSerieExecute(Sender: TObject);
    procedure acDisplLastPointsClickedExecute(Sender: TObject);
    procedure acTransmitBasePointForHighlightingExecute(Sender: TObject);
    procedure acVueRefreshExecute(Sender: TObject);
    procedure acVuesPrecedentesExecute(Sender: TObject);
    procedure acVuesSuivantesExecute(Sender: TObject);
    procedure acZoomFenetreExecute(Sender: TObject);
    procedure acZoomMoinsExecute(Sender: TObject);
    procedure acZoomPlusExecute(Sender: TObject);
    procedure acZoomToutExecute(Sender: TObject);
    procedure btnApplyMetaFiltreClick(Sender: TObject);
    procedure btnAz1Click(Sender: TObject);
    procedure btnCancelLastClick(Sender: TObject);
    procedure btnCommentaireClick(Sender: TObject);
    procedure btnCopyCoordsClick(Sender: TObject);
    procedure btnDefinirCommeActiveClick(Sender: TObject);
    procedure btnHelpMetaFiltreClick(Sender: TObject);
    procedure btnHNClick(Sender: TObject);
    procedure btnHZClick(Sender: TObject);
    procedure btnIncl0Click(Sender: TObject);
    procedure btnIncl1Click(Sender: TObject);
    procedure btnLDClick(Sender: TObject);
    procedure btnLGClick(Sender: TObject);
    procedure btnListeProfilsClick(Sender: TObject);
    procedure btnLong0Click(Sender: TObject);
    procedure btnLong1Click(Sender: TObject);
    procedure btnMoreInfosClick(Sender: TObject);
    procedure btnSerieCouranteDistoXClick(Sender: TObject);
    procedure btnUnloadMNTClick(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure btnMaillageOKClick(Sender: TObject);
    procedure btnQSAddViseeClick(Sender: TObject);
    procedure btnQSCloseMiniFormViseeClick(Sender: TObject);
    procedure btnAz0Click(Sender: TObject);
    procedure chkDrawMaillageChange(Sender: TObject);
    procedure chkMetaFiltreActifChange(Sender: TObject);
    procedure editMetaFiltreKeyPress(Sender: TObject; var Key: char);
    procedure lbInfosClick(Sender: TObject);
    procedure pnlToastClick(Sender: TObject);
    procedure PopUpCdrVue2DPopup(Sender: TObject);

    procedure SetStationInfo(const S: TBaseStation);
    procedure SpeedButton13Click(Sender: TObject);
    procedure SpeedButton13MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
    procedure VueMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
    procedure VueMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
    procedure VueMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
    procedure VueMouseWheel(Sender: TObject; Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
    procedure VuePaint(Sender: TObject);

  strict private

    FInitialized: boolean;
    // pour le dessin des étiquettes au survol du plan
    // très bonne rapidité avec la Coume
    // très bonne rapidité avec Langoiran et Citon
    (*
    FEtiquetteOnSurvolPlan_First: boolean;
    FEtiquetteOnSurvolPlan_Tampon: TImage;   // et non TBitmap
    FEtiquetteOnSurvolPlan_ox: integer;
    FEtiquetteOnSurvolPlan_oy: integer;
    FEtiquetteOnSurvolPlan_ow: integer;
    FEtiquetteOnSurvolPlan_oh: integer;
    FEtiquetteOnSurvolPlan_X1: integer;
    FEtiquetteOnSurvolPlan_Y1: integer;
    //*)


    FModeFonctionnementGHTopoContext2D: TModeFonctionnementGHTopoContext2D;
    // Overlay (à la manière des sites web merdiques)
    FOverlayed: boolean;
    // liste des derniers points cliqués cur le plan
    FListeLastPointsClicked: TListePoints2Df;

    FGhostPolygone         : TGhostPolygone;


    procedure AddNewPointClicked(const X, Y: double);
    procedure CalculerRecouvrements(const B: boolean; const P: TProcDisplayProgression);

    procedure DispClavierNumForBTN(const MyButton: TButton; const QCaption: string);
    procedure DisplayToast(const Msg: string);




    procedure QDeplacerVue(const QX, QY: integer);
     // conversion de coordonnées écran<>générales
    function  GetCoordsMonde(const PP: TPoint): TPoint2Df;
    function  GetCoordsPlan(const QX, QY: double): TPoint; overload;
    function  GetCoordsPlan(const PM: TPoint2Df): TPoint; overload; inline;
    procedure QMakePlan(const OutputBuffer: TGHTopoDrawingContext; const Onglet: TVue2DParams; const ImgWidth, ImgHeight: integer; const DoRecalcDegrades: boolean);


  private
    { private declarations }

    FRedessinInProcess   : boolean;                   // carré (signal) indiquant que le dessin est en cours de reconstruction
    FBDDEntites          : TBDDEntites;               // BDD entités
    FDocuTopo            : TToporobotStructure2012;   // interaction avec le document GHTopo
    //FConvertisseurJPC    : TConversionSysteme;
    //{$IFDEF GHTOPO_SIMPLIFIE}
    FGraphe              : TPathFindingGraphe;
    FShortestPath        : TPathBetweenNodes;
    FDoIncludeVerticales : boolean;
    //{$ENDIF GHTOPO_SIMPLIFIE}
    {$IFNDEF GHTOPO_SIMPLIFIE}
    FCurrIdxOnglet    : byte;
    {$ENDIF GHTOPO_SIMPLIFIE}

    FCroquisTerrain      : TCroquisTerrain;           // crobard de terrain
    FCanDraw             : Boolean;                   // La BDD entités est valide ?
    FAttenduSecondPoint  : boolean; // zoom, pan et ligne
    // modes de travail
    FModesTravail        : TModesTravail;
    // buffer circulaire de zooms et déplacements

    FBufferRegionsZoom   : TListeRegionsZoom;

    // callbacks
    FProcActualiserAffichagesBDD: TProcOfObjectWithOneBoolParameter;         // pour actualisation de la liste des séries après un ajout sur le plan
    FProcPerformActionRecalcul  : TProcOfObjectWithOneBoolParameter;         // pour recalcul du réseau
    FProcUseMetaFiltre          : TProcedureOfObject;                        // pour transmission du contenu du Métafiltre
    FProcTransmitBasePoint      : TProcOfObjectWithATBaseStation;
    FProcTransmitTwoBasePoint   : TProcOfObjectWithTwoTBaseStation;
    //FProcReconnectDistoX        : TProcOfObjectReturnsAsBoolean;             // pour reconnexion du DistoX
    // coordonnées souris en GCS
    FMyPos        : TPoint2Df;
    // surlignage de visées
    FModeSelectionListe : TModeSelectionListe;
    FDoHighLightCurrentItem: boolean; // mettre la série courante en surbrillance
    // station courante
    // La station courante ne peut être définie que dans les cas suivants:
    // 1. Au démarrage
    // 2. Explicitement par la commande acSetCurrentBaseStation()
    // 3. Explicitement par la commande LocaliserUneStationByCle()
    // 4. Par la commande d'insertion d'une série

    FCurrentStation                   : TBaseStation;
    FStationNearToMouse               : TBaseStation;
    FCurrentInternalIdxEntite         : integer;
    FCurrentStationOfDistoX           : TBaseStation;
    // Point but = à atteindre lors d'une topographie
    // (utilisé lorsqu'il faut se diriger vers un puits connu par exemple)
    FPointBut                         : TPointBut;

    FCurrentDistoXTypeVisee           : TTypeDeVisee;
    FCurrentDistoXNumeroEntrance      : TNumeroEntrance;
    FCurrentDistoXNumeroReseau        : TNumeroStation;
    FCurrentDistoXNumeroSecteur       : TNumeroSecteur;
    FCurrentDistoXNumeroCode          : TNumeroCode;
    FCurrentDistoXNumeroExpe          : TNumeroExpe;
    //*)
    // marqueurs
    FCurrentMarker: TGHTopoMarker;
    // bipoint courant
    FCurrentBipoint: TBipointBaseStation;
    // bouclage proposé
    FCurrentNearST : TStationProcheOfEndSerie;
    // onglets
    FArrOngletsParams : TArrOngletsParams;
    // gestion des zooms
    FZC1, FZC2    : TPoint2Df;
    FZP1, FZP2    : TPoint;
    FRegionCMini  : TPoint2Df;
    FRegionCMaxi  : TPoint2Df;

    FPC1, FPC2, FPC3, FPC4: TPoint2Df;
    FPP1, FPP2, FPP3, FPP4: TPoint;
    // paramètres de vues
    FRappHLVue      : double;
    FRappScrReal    : double;
    FInvRappScrReal : double;
    // Maillages
    FListeMaillages: TListeOfMaillages;
    FMyMaillageDisplayed : boolean;
    // Notifier la Minimap
    FOnViewChanged: TNotifyEvent;
    procedure AppliquerLeFiltre(const FF: string);
    procedure DisplayFullInfosStation(const S: TBaseStation);
    procedure DisplayQuickAddVisee(const OS: TObjSerie);
    // onglets
    function  GetNomOnglet(): string;
    procedure InitCaptions();
    procedure QSetPen(const QPM: TPenMode; const QPS: TPenStyle; const QPW: integer; const QPC: TColor);
    procedure Rearmer();
    function  UpdateBaseStationWithOffset(const QE: TBaseStation; const QQX, QQY, QQZ: double): TBaseStation;
    procedure SetMarker(const M: TGHTopoMarker);
    procedure SetNomOnglet(const S: string);
    function  GetBackGrdColorOnglet(const QIdxOnglet: integer): TColor;
    procedure SetBackGrdColorOnglet(const QIdxOnglet: integer; const Col: TColor);
    function  GetLargeurViseesOngletInPX(const QIdxOnglet: integer): integer;
    procedure SetLargeurViseesOngletInPX(const QIdxOnglet: integer; const qL: integer);
    function  GetLargeurViseesOngletInMM(const QIdxOnglet: integer): double;
    procedure SetLargeurViseesOngletInMM(const QIdxOnglet: integer; const qL: double);
    // quadrillage
    function  GetQuadrillageColor(): TColor;
    function  GetQuadrillageSpc(): double;
    procedure SetQuadrillageColor(const Col: TColor);
    procedure SetQuadrillageSpc(const Spacing: double);
    // dessin
    procedure RedessinEcran(const ImgWidth, ImgHeight: integer; const DoRecalcDegrades: boolean);
    // krobard: Visibilité
    function  getDoDrawKrobard(): boolean;
    procedure setDoDrawKrobard(const B: boolean);


  public
    { public declarations }
    procedure Loaded;
    destructor Destroy;
    property  OnViewChanged: TNotifyEvent read FOnViewChanged write FOnViewChanged;
    property  CanDraw: boolean read FCanDraw write FCanDraw;
    property  DoDrawKrobard: boolean read getDoDrawKrobard write setDoDrawKrobard;
    property  PointBut: TPointBut read FPointBut write FPointBut;
    property  ModeFonctionnementGHTopoContext2D: TModeFonctionnementGHTopoContext2D read FModeFonctionnementGHTopoContext2D;
    // index courants
    property  CurrentStationOfDistoX        : TBaseStation     read FCurrentStationOfDistoX;
    property  CurrentDistoXNumeroEntrance   : TNumeroEntrance  read FCurrentDistoXNumeroEntrance;
    property  CurrentDistoXNumeroReseau     : TNumeroStation   read FCurrentDistoXNumeroReseau;
    property  CurrentDistoXNumeroSecteur    : TNumeroSecteur   read FCurrentDistoXNumeroSecteur;
    property  CurrentDistoXNumeroCode       : TNumeroCode      read FCurrentDistoXNumeroCode;
    property  CurrentDistoXNumeroExpe       : TNumeroExpe      read FCurrentDistoXNumeroExpe;

    procedure InitialiserOnParentShow();
    procedure FinaliserOnParentClose();


    // /!\ Reinitialiser est appelé après chaque recalcul
    function  ReInitialiser(const MF : TModeFonctionnementGHTopoContext2D;
                            const QDT: TToporobotStructure2012;
                            const QBDD: TBDDEntites;
                            const QCT: TCroquisTerrain;
                            const QGraphe: TPathFindingGraphe;
                            const QShortestPath: TPathBetweenNodes;
                            const AM: TListeOfMaillages;
                            const QProcUseMetaFiltre: TProcedureOfObject;
                            const QProcRefresh: TProcOfObjectWithOneBoolParameter;
                            const QProcRecalculerReseau: TProcOfObjectWithOneBoolParameter;
                            const QProcTransmitBasePoint: TProcOfObjectWithATBaseStation;
                            const QProcTransmitTwoBasePoint: TProcOfObjectWithTwoTBaseStation;
                            const DoIncludeVerticales: boolean): boolean;
    procedure SetCurrentSecteurTypeviseeCodeExpe(const SC: TNumeroSecteur;
                                                 const TV: TTypeDeVisee;
                                                 const CC: TNumeroCode;
                                                 const EE: TNumeroExpe;
                                                 const DoNotifyByDialog: boolean = false);

    procedure SetModeFonctionnement(const MF : TModeFonctionnementGHTopoContext2D);
     // extraire les pointeurs sur les bases internes
    function GetPtrBDDEntites(): TBDDEntites; inline;
    function GetPtrDocuTopo(): TToporobotStructure2012; inline;
    function GetPtrCroquisTerrain(): TCroquisTerrain; inline;
    function GetPtrMaillage(const Idx: integer): TMaillage; inline;

    // assignation des callbacks
    procedure SetProcUseMetaFiltre(const P: TProcedureOfObject);
    procedure DeplacerVue(const QOffsetX, QOffsetY: double; const QSaveView: boolean = false);
    procedure DeplacerVueInt(const DepX, DepY: integer; const QSaveView: boolean = false);

    procedure SetViewLimits(const X1, Y1, X2, Y2: double; const DebugTag: string; const QSaveView: boolean = false); overload;
    procedure SetViewLimits(const QCoinBasGauche, QCointHautDroit: TPoint2Df; const DebugTag: string; const QSaveView: boolean = false); overload;
    procedure SetViewLimits(const R: TRect2Df; const DebugTag: string; const QSaveView: boolean = false); overload;
    // Retourne les limites du dessin, stockées dans l'onglet indexé par QIdxOnglet
    function  GetViewLimitsFromOnglet(const QIdxOnglet: integer; out QCoinBasGauche, QCointHautDroit: TPoint2Df): boolean;

    procedure CentrerVueSurPointXY(const QX, QY: double; const DoMarker: boolean; const LbMarker: string);

    procedure ZoomFact(const Fact: double);
    procedure MetaFiltrer(const F: string);
    procedure ResetVue();
    // Numéros internes d'items
    procedure SetCurrentNumeroCode(const S: TNumeroCode);
    procedure SetCurrentNumeroExpe(const S: TNumeroExpe);
    procedure SetCurrentNumeroEntrance(const S: TNumeroEntrance);
    procedure SetCurrentNumeroReseau(const S: TNumeroReseau);
    procedure SetCurrentNumeroSecteur(const S: TNumeroSecteur);
    procedure SetKrobardCurrentNumeroStyleAnnotation(const S: byte);
    procedure SetKrobardNumeroStylePolyligne(const S: integer);
    function  GetKrobardNumeroStyleAnnotation(): byte;
    function  GetKrobardNumeroStylePolyligne(): integer;

    // station courante
    function  GetCurrentStation(): TBaseStation;
    procedure SetCurrentStation(const E: TBaseStation; const DoRedraw: boolean);
    procedure SetCurrentStationBySerSt(const S: TNumeroSerie; const P: TNumeroStation);
    function  GetCurrentStationNearToMousePos(): TBaseStation;

    // série courante pour le DistoX
    procedure SetCurrentStationOfDistoX(const S: TNumeroSerie; const P: TNumeroStation = -1);
    // Marqueurs
    procedure setCurrentMarker(const M: TGHTopoMarker);
    procedure SetCurrentBipoint(const BP: TBipointBaseStation);
    procedure SetCurrentNearSt(const NearST: TStationProcheOfEndSerie);
    // flags
    procedure SetElementsDrawn(const ED: TSetElementsDrawn);
    function  GetElementsDrawn(): TSetElementsDrawn;
    procedure SetModeTravail(const MT: TModesTravail);
    // gestion des onglets
    procedure SetCurrentIdxOnglet(const Idx: byte);
    function  GetCurrentOngletIdx(): integer;
    function  GetNbOngletsVues(): integer;
    function  GetOngletByIndex(const Idx: integer): TVue2DParams;
    function  GetCurrentOnglet(): TVue2DParams;
    procedure PutOngletByIndex(const Idx: integer;
                               const O: TVue2DParams;
                               const DoRedrawVue: boolean);
    procedure SetHighLightCurrentItem(const B: boolean);
    // ajout
    function AjouterUneViseeALaSerieCouranteDisto(const QTypeVisee: TTypeDeVisee;
                                             const QHorodatage: TDateTime;
                                             const QLongueur, QAzimut, QPente, QLG, QLD, QHZ, QHN: double;
                                             const QIDLitteral, QCommentaire: string;
                                             const QTemperature: double): boolean;
    function  AjouterUneAntenneALaStationCouranteDisto(const QLongueur, QAzimut, QPente: double): boolean;

    procedure RollbackLastVisee();

    procedure SetModeSelectionListe(const M: TModeSelectionListe); inline;
    // localiser une station
    procedure LocaliserUneStation();
    procedure LocaliserUneStationByCle(const K: string);
    // créer un timelapse
    procedure CreerTimeLapse(const QDirectory: TStringDirectoryFilename);
    // gestion du croquis de terrain
    function  CroquisDeTerrainIsReady(): boolean;
    procedure SaveCroquis(const QFilename: TStringDirectoryFilename);
    function  OpenCroquis(const QFilename: TStringDirectoryFilename): boolean;
    // chargement du maillage

    function  ChargerMaillage(const QFileName: string; const P: TProcDisplayProgression): boolean;
    procedure ActiverPanelMaillage();
    function IsMaillageDisplayed(const Idx: integer): boolean;
    function IsMaillageValide(const Idx: integer): boolean;
    {$IFDEF GHTOPO_SIMPLIFIE}
    procedure OnReceiveVisee(const QHorodate: TDateTime;
                             const QLongueur, QAzimut, QPente, QTemperature: double;
                             const QTypeMesure: TBRIC4_TypeViseeDevice;
                             const QTagString: string;
                             const QDoRefreshPlan: boolean);

    {$ENDIF GHTOPO_SIMPLIFIE}
    procedure SetShortestPath(const P: TPathBetweenNodes);
    procedure ExporterPlanEnImage(const ImgWidth, ImgHeight: integer; const DoRecalcDegrades: boolean; const QFileName: string);
    procedure RefreshDessin(); // Indispensable: Appelé par TCdrListesPourVisualisateurs
    // filtre
    procedure ApplyMetaFiltre(const B: boolean; const QFiltre: string);
    function  getMetaFiltre(): string;
    procedure setDoIncludeVerticales(const B: boolean);

    function  GetDrwCoinBasGauche(): TPoint2Df;
    function  GetDrwCoinHautDroit(): TPoint2Df;


  end;

implementation
uses
  DGCDummyUnit,
  frmMinimap;
{$R *.lfm}
const FRACTION_OF_EXTENSION = 0.05;
const COLOR_GHOST_LINE      = clGreen;


procedure TGHTopoContext2DA.InitialiserOnParentShow();
begin
  //ShowMessage('InitialiserOnParentShow()');
end;
procedure TGHTopoContext2DA.FinaliserOnParentClose();
begin
  try
    FBufferRegionsZoom.ClearListe();
    FListeLastPointsClicked.ClearListe();
    FGhostPolygone.ClearListe();
  finally
    // libérer correctement
    if Assigned(FListeLastPointsClicked) then FreeAndNil(FListeLastPointsClicked);
    if Assigned(FGhostPolygone) then FreeAndNil(FGhostPolygone);

    if Assigned(FBufferRegionsZoom) then FreeAndNil(FBufferRegionsZoom);

    if Assigned(dlgMinimap) then FreeAndNil(dlgMinimap);
  end;
  //ShowMessage('FinaliserOnParentClose()');
end;

procedure TGHTopoContext2DA.Loaded;
begin
  inherited Loaded;

  if (FInitialized) then Exit;
  FInitialized := True;

  // initialisation propre
  ShowMessage('InitialiserOnLoaded()');
end;
destructor TGHTopoContext2DA.Destroy;
begin
  FinaliserOnParentClose();
  inherited Destroy;
end;

function TGHTopoContext2DA.ReInitialiser(const MF: TModeFonctionnementGHTopoContext2D;
                                         const QDT: TToporobotStructure2012;
                                         const QBDD: TBDDEntites;
                                         const QCT: TCroquisTerrain;
                                         const QGraphe: TPathFindingGraphe;
                                         const QShortestPath: TPathBetweenNodes;
                                         const AM: TListeOfMaillages;
                                         const QProcUseMetaFiltre: TProcedureOfObject;
                                         const QProcRefresh: TProcOfObjectWithOneBoolParameter;
                                         const QProcRecalculerReseau: TProcOfObjectWithOneBoolParameter;
                                         const QProcTransmitBasePoint: TProcOfObjectWithATBaseStation;
                                         const QProcTransmitTwoBasePoint: TProcOfObjectWithTwoTBaseStation;
                                         const DoIncludeVerticales: boolean): boolean;
const
  NIVEAU_GRIS = 100;
var
  C1, C2: TPoint3Df;
  NoOnglet: Integer;
  DbgTag: String;
  MyOnglet: TVue2DParams;
  QMaillage: TMaillage;
begin
  Result := false;
  //if (GLOBAL_UNIT_AlreadyInitialized) then //raise exception.CreateFmt('%s.Initialiser() peut être appelée une seule fois', [self.ClassName]);
  //begin
  //  FreeAndNil(dlgMinimap);   // <-- destruction immédiate et sûre
  //  ShowMessage('La minimap est détruite');
  //end;
  if Assigned(dlgMinimap) then FreeAndNil(dlgMinimap);
  dlgMinimap := TdlgMinimap.Create(self);

  AfficherMsgClass(self, 'Initialiser()');
  AfficherMemoryUsage();
  FRedessinInProcess      := false;
  FModeSelectionListe     := mslSERIE;
  FModesTravail           := mtREADY;
  FDoHighLightCurrentItem := false;
  FDoIncludeVerticales    := DoIncludeVerticales;


  FBufferRegionsZoom      := TListeRegionsZoom.Create(TAILLE_BUFFER_ZOOMS);
  FBufferRegionsZoom.ClearListe();

  {$IFDEF GHTOPO_SIMPLIFIE}
  acGenererCoupesCTByRect.visible   := false;
  acDisplLastPointsClicked.Visible := false;
  acLocaliserUneStation.Visible    := false;
  // Filtres: On ne garde que le lasso
  mnuMetaFiltreStation.Visible     := false;
  acMetaFiltreZone.Visible         := false;
  acMetaFiltreVueCourante.Visible  := false;

  btnCancelLast.Visible            := false;
  {$ELSE GHTOPO_SIMPLIFIE}
  FCurrIdxOnglet          := 0; // En GHTopo simplifié, il y a un seul onglet utilisé

  {$ENDIF GHTOPO_SIMPLIFIE}
  FModeFonctionnementGHTopoContext2D := MF;
  // liste des derniers points cliqués
  AfficherMessageErreur(' -- Liste des derniers points');
  if (not assigned(FListeLastPointsClicked) )then FListeLastPointsClicked := TListePoints2Df.Create;
  FListeLastPointsClicked.ClearListe();
  // polygone 'ghost';
  if (not assigned(FGhostPolygone) )then FGhostPolygone := TGhostPolygone.Create;
  FGhostPolygone.ClearListe();

  FOverlayed               := false;
  FCanDraw                 := False;
  //pnlVue.DoubleBuffered    := true;
  pnlToast.Visible         := false;
  pnlMaillage.Visible      := false;
  // Init des stations courantes
  // NON: Bug méchant sinon - Du ressort du chargeur de topos
    //SetCurrentStationBySerSt(1,0);
  //SetCurrentStationOfDistoX(1, 0);
  //FCurrentBipoint.setfrom(GetCurrentStation(), GetCurrentStation());
  //FCurrentNearST.BaseStation.setFrom(1, 0);
  //FCurrentNearST.NearestStation.setFrom(1, 0);
  // Marqueurs (ajouté le 20/03/2025)
  FCurrentMarker.setFrom(false, 0.0, 0.0, clAqua, '');

  // pointeurs sur les tables
  FDocuTopo            := QDT;
  FCroquisTerrain      := QCT;
  FBDDEntites          := QBDD;
  FGraphe              := QGraphe;
  FShortestPath        := QShortestPath;
  FListeMaillages      := AM;
  FMyMaillageDisplayed   := False;
  // Callbacks
  FProcUseMetaFiltre                   := QProcUseMetaFiltre;
  FProcActualiserAffichagesBDD         := QProcRefresh;
  FProcPerformActionRecalcul           := QProcRecalculerReseau;
  FProcTransmitBasePoint               := QProcTransmitBasePoint;
  FProcTransmitTwoBasePoint            := QProcTransmitTwoBasePoint;
  // index courants
  AfficherMessageErreur(' -- Station courante: %s', [FCurrentStation.toString()]);
  try
    SetCurrentSecteurTypeviseeCodeExpe(0, tgDEFAULT, 1, 1);
    SetCurrentNumeroEntrance(0);
    SetCurrentNumeroReseau(0);
    SetCurrentNumeroSecteur(0);
    SetCurrentNumeroCode(1);
    SetCurrentNumeroExpe(1);
    SetCurrentStationBySerSt(1, 0);
    SetCurrentStationOfDistoX(1, 0);
    FStationNearToMouse := FCurrentStation;
    FPointBut.setFrom(FCurrentStation.PosStation, FCurrentStation.toString());
  except
    ShowMessage('Erreur dans la fixation des items courants '      + System.LineEnding +
                'Cause probable: Document au format Text'          + System.LineEnding +
                'Solution: Enregistrer en *.xtb puis recharger');
  end;
  AfficherMessageErreur(' -- SetMinMax');
  FBDDEntites.SetMinMax(false);
  C1 := FBDDEntites.CoinBasGauche;
  C2 := FBDDEntites.CoinHautDroit;
  AfficherMessageErreur(' -- CalcLengthReseau');
  FBDDEntites.CalcLengthReseau();
  AfficherMessageErreur(' -- Sort by Depth');
  FBDDEntites.SortByDepth();       // trier par profondeurs

  SetNomOnglet(rsCDR_VUE2D_TAB_VUE_INITIALE);
  SetQuadrillageSpc(ProposerEquidistanceDef(C1, C2, 100.00));



  DbgTag := Format('%s: %s', [{$I %FILE%}, {$I %LINE%}]);
  AfficherMessageErreur(' -- Reset view limits');
  SetViewLimits(C1.X, C1.Y, C2.X, C2.Y, DbgTag); // renseigne aussi l'onglet courant
  //***********************************
  // /!\ Ne pas aller plus loin dans le débogage si çà plante ici
  //***********************************
  AfficherMessageErreur(' -- Tableau des onglets');
  ApplyMetaFiltre(True, '');                   // renseigne aussi l'onglet courant
  ResetVue();
  // initialiser le tableau d'onglets avec les paramètres de l'onglet 0
  MyOnglet                         := GetOngletByIndex(0);
  MyOnglet.ongDrawFastCenterline   := FBDDEntites.LongueurDuReseau > LONGUEUR_LIMITE_RESEAU;
  MyOnglet.ongQdrType              := qtGRID;
  MyOnglet.ongDegradeReseaux.setFrom(FBDDEntites.ColorZMin, FBDDEntites.ColorZMax, -1, 1);
  MyOnglet.ongDegradeMNT.setFrom(DEFAULT_COLOR_MAILLAGE_Z_MIN, DEFAULT_COLOR_MAILLAGE_Z_MAX, -1, 1);
  MyOnglet.ongColorDefaultMNT.setFrom(DEFAULT_COLOR_MAILLAGE, 128);
  MyOnglet.ongModeRepresentation   := rgSEANCES;
  MyOnglet.ongName                 := Format(rsCDR_VUE2D_TAB_VUE, [0]);

  MyOnglet.ongTailleTexteIDStation := 3.80;
  MyOnglet.ongTailleTexteAltitudes := 1.80;
  MyOnglet.ongTailleTexteCotation  := 1.80;
  MyOnglet.ongTailleTexteEntrances := 2.00;
  MyOnglet.ongTailleTexteNoeuds    := 4.00;
  MyOnglet.ongTailleTextePOIs      := 2.10;

  MyOnglet.ongCouleurIDStation     := clBlack;
  MyOnglet.ongCouleurAltitudes     := clBlue;
  MyOnglet.ongCouleurCotation      := clBlue;
  MyOnglet.ongCouleurEntrances     := clGreen;
  MyOnglet.ongCouleurNoeuds        := clFuchsia;
  MyOnglet.ongCouleurPOIs          := clRed;

  MyOnglet.ongDoDispViseesNonRetenues  := True;
  MyOnglet.ongCouleurViseesNonRetenues := clSilver;
  {$IFDEF GHTOPO_SIMPLIFIE}
  MyOnglet.ongBackGround           := clWhite; //GrayScaleToTColor(NIVEAU_GRIS);//clGray;
  MyOnglet.ongElementsDrawn        := [edQuadrilles, edPolygonals, edStations, edCrossSections, edANTENNES, edCROQUIS];
  MyOnglet.ongLineCenterlines.LineWidthInMillimeters := 0.25;
  MyOnglet.ongLineCenterlines.LineWidthInPixels      := 3;
  MyOnglet.ongQdrColor             := clSilver;
  PnlMaillage.Visible              := false;
  btnSerieCouranteDistoX.Visible   := true;
  acEditThisSerie.Visible          := True;
  acDispPopUp.Visible              := false;
  acClearListePointsCliques.Visible:= false;
  acTransmitBasePointForHighlighting.Visible := false;
  btnCoinBasDroitVue.Action        := acVueRefresh;
  {$ELSE}
  MyOnglet.ongBackGround           := clWhite;
  MyOnglet.ongElementsDrawn        := [edQuadrilles, edPolygonals, edCrossSections, edANTENNES, edCROQUIS, edENTRANCE_MKS];
  MyOnglet.ongLineCenterlines.LineWidthInMillimeters := 0.25;
  MyOnglet.ongLineCenterlines.LineWidthInPixels      := 1;

  MyOnglet.ongQdrColor             := clSilver;
  // Initialisation des onglets restants
  for NoOnglet := 1 to High(FArrOngletsParams) do
  begin
    MyOnglet.ongLineCenterlines.LineWidthInMillimeters := 0.25;
    MyOnglet.ongLineCenterlines.LineWidthInPixels      := 1;
    MyOnglet.ongName := Format(rsCDR_VUE2D_TAB_VUE, [NoOnglet]);
    PutOngletByIndex(NoOnglet, MyOnglet, False);
  end;
  btnSerieCouranteDistoX.Visible   := false;
  btnCoinBasDroitVue.Action := acDisplLastPointsClicked;
  {$ENDIF GHTOPO_SIMPLIFIE}
  PutOngletByIndex(0, MyOnglet, True);
  // Panneau Maillage inhibé si le maillage n'est pas assigné
  QMaillage := FListeMaillages.GetElement(0);
  btnLineContourColor.ButtonColor := IIF(QMaillage.IsValid, QMaillage.ColorDefault, DEFAULT_COLOR_MAILLAGE);
  editLineContourOpacity.Value    := IIF(QMaillage.IsValid, QMaillage.Opacity, 128);
  pnlMaillage.Visible             := QMaillage.IsValid;
  FCanDraw := True;
  InitCaptions();                       // Initialisation de l'interface
  Vue.Invalidate;                       // redessiner
  AfficherMemoryUsage();                // afficher mémoire utilisée
  pnlQuickSaisieVisee.Visible := false; // cacher le miniform de visées
  //***************************************
  // MiniMap
  dlgMinimap.Initialiser(self);
  dlgMinimap.Show;
  Result   := True;
  //except
 //end;
end;

procedure TGHTopoContext2DA.SetCurrentSecteurTypeviseeCodeExpe(const SC: TNumeroSecteur;
                                                               const TV: TTypeDeVisee;
                                                               const CC: TNumeroCode;
                                                               const EE: TNumeroExpe;
                                                               const DoNotifyByDialog: boolean = false);
begin
  FCurrentDistoXNumeroSecteur       := SC;
  FCurrentDistoXTypeVisee           := TV;
  FCurrentDistoXNumeroCode          := CC;
  FCurrentDistoXNumeroExpe          := EE;
  if (DoNotifyByDialog) then
  begin
    ShowMessageFmt('Index courants: Secteur %d, Code %d, Expe %d', [FCurrentDistoXNumeroSecteur, FCurrentDistoXNumeroCode, FCurrentDistoXNumeroExpe]);

  end;
end;




procedure TGHTopoContext2DA.SetModeFonctionnement(const MF: TModeFonctionnementGHTopoContext2D);
begin
  FModeFonctionnementGHTopoContext2D := MF;
  case FModeFonctionnementGHTopoContext2D of
    mfgcSURVEYING : FOverlayed := false;
    mfgcNAVIGATION: FOverlayed := true;
  end;
  Vue.Invalidate;
end;

// pointeurs sur les bases
function TGHTopoContext2DA.GetPtrBDDEntites(): TBDDEntites;
begin
  Result := FBDDEntites;
end;

function TGHTopoContext2DA.GetPtrDocuTopo(): TToporobotStructure2012;
begin
  Result := FDocuTopo;
end;
function TGHTopoContext2DA.GetPtrCroquisTerrain(): TCroquisTerrain; inline;
begin
  Result := FCroquisTerrain;
end;

procedure TGHTopoContext2DA.AddNewPointClicked(const X, Y: double);
var
  PP: TPoint2Df;
begin
  PP.setFrom(X, Y);
  FListeLastPointsClicked.AddElement(PP);
  if (FListeLastPointsClicked.GetNbElements() > NB_MAX_PTS_CLICKED) then FListeLastPointsClicked.RemoveElement(0);
end;

//*****************************************************************************
procedure TGHTopoContext2DA.InitCaptions();
  procedure SetAcHint(const ACDC: TAction; const QCaption: string);
  begin
    ACDC.Caption := GetResourceString(QCaption);
    ACDC.Hint    := GetResourceString(QCaption);
  end;
begin
  SetAcHint(acAddSerieHere                    , Format(rsCDR_VUE2D_ADD_SERIE_HERE      , [1, '1.0']));
  SetAcHint(acContinueHere                    , Format(rsCDR_VUE2D_CONTINUE_SERIE_HERE , [1]));
  SetAcHint(acDefineEndSerieAsEntrance        , format(rsCDR_VUE2D_AC_SET_END_SERIE_AS_ENTRANCE, [1]));
  SetAcHint(acZoomTout                        , rsCDR_VUE2D_AC_ZOOM_ALL);
  SetAcHint(acZoomFenetre                     , rsCDR_VUE2D_AC_ZOOM_WINDOW);
  SetAcHint(acPanVue                          , rsCDR_VUE2D_AC_PAN_VUE);
  SetAcHint(acDistanceP2P                     , rsCDR_VUE2D_AC_DISTANCE_BT_STATIONS);
  setAcHint(acVueRefresh                      , rsCDR_VUE2D_AC_REFRESH_VUE);

  SetAcHint(acDisplayVoisinageCurrStation     , rsCDR_VUE2D_DISP_STATIONS_VOISINES);
  SetAcHint(acAddserieEntreCesPoints          , rsCDR_VUE2D_ADDSERIE_BETWEEN_STATION);
  SetAcHint(acIsovaleurSurZStation            , rsCDR_VUE2D_ISOVALEUR_FROM_Z);
  SetAcHint(acLocaliserUneStation             , rsCDR_VUE2D_LOCALISER_STATION);
  //SetAcHint(acAddAnnotationHere               , rsCDR_VUE2D_ADD_ANNOTATION_HERE);
  SetAcHint(acSaveCroquis                     , rsCDR_VUE2D_AC_CROQUIS_SAVE);
  SetAcHint(acOpenCroquis                     , rsCDR_VUE2D_AC_CROQUIS_OPEN);
  SetAcHint(acDisplayMiniformVisee            , rsCDR_VUE2D_AC_DISP_MINIFORM_VISEE);
  SetAcHint(acSetCurrentBaseStation           , rsCDR_VUE2D_SET_ACTIVE_STATION);
  SetAcHint(acDispPopUp                       , rsCDR_VUE2D_DISP_POPUP);

  SetAcHint(acDisplLastPointsClicked          , rsCDR_VUE2D_AC_DISP_LAST_CLICKED);
  SetAcHint(acClearListePointsCliques         , rsCDR_VUE2D_AC_CLEAR_LAST_CLICKED);

  SetAcHint(acTransmitBasePointForHighlighting, rsCDR_VUE2D_AC_ADD_SERIE_AT_HIGHLIGHT);
  SetAcHint(acDefinirPointBut                 , rsCDR_VUE2D_AC_DEFINE_POINT_BUT);

  SetAcHint(acMetaFiltreZone                  , rsCDR_VUE2D_AC_METAFILTRE_BY_ZONE);
  SetAcHint(acFiltrerSurLasso                 , rsCDR_VUE2D_AC_FILTRE_SUR_LASSO);
  SetAcHint(acCentrerMapAtCurrStationDistoX   , rsCDR_VUE2D_AC_CENTRER_MAP_AT_STATION_BRIC);

  SetAcHint(acVuesPrecedentes                 , rsCDR_VUE2D_AC_VUE_PRECEDENTE);
  SetAcHint(acVuesSuivantes                   , rsCDR_VUE2D_AC_VUE_SUIVANTE);


end;


procedure TGHTopoContext2DA.SetCurrentNumeroCode(const S: TNumeroCode);
begin
  FDocuTopo.SetCurrentNumeroCode(S);
  FCurrentDistoXNumeroCode := S;
  Vue.Invalidate;
end;
procedure TGHTopoContext2DA.SetCurrentNumeroExpe(const S: TNumeroExpe);
begin
  FDocuTopo.SetCurrentNumeroExpe(S);
  FCurrentDistoXNumeroExpe := S;
  Vue.Invalidate;
end;

procedure TGHTopoContext2DA.SetCurrentNumeroEntrance(const S: TNumeroEntrance);
begin
  FDocuTopo.SetCurrentNumeroEntrance(S);
  FCurrentDistoXNumeroEntrance := S;
  Vue.Invalidate;
end;

procedure TGHTopoContext2DA.SetCurrentNumeroReseau(const S: TNumeroReseau);
begin
  FDocuTopo.SetCurrentNumeroReseau(S);
  FCurrentDistoXNumeroReseau := S;
  Vue.Invalidate;
end;

procedure TGHTopoContext2DA.SetCurrentNumeroSecteur(const S: TNumeroSecteur);
begin
  FDocuTopo.SetCurrentNumeroSecteur(S);
  FCurrentDistoXNumeroSecteur := S;
  Vue.Invalidate;
end;


procedure TGHTopoContext2DA.SetKrobardCurrentNumeroStyleAnnotation(const S: byte);
begin
  FCroquisTerrain.SetCurrentIdxStyleAnnotation(S);
end;

procedure TGHTopoContext2DA.SetKrobardNumeroStylePolyligne(const S: integer);
begin
  pass;
end;

function TGHTopoContext2DA.GetKrobardNumeroStyleAnnotation(): byte;
begin
  Result := FCroquisTerrain.CurrentIdxStyleAnnotation;
end;

function TGHTopoContext2DA.GetKrobardNumeroStylePolyligne(): integer;
begin
  Result := Ord(FCroquisTerrain.TypePolylinePolygone);
end;


function TGHTopoContext2DA.GetCurrentStation(): TBaseStation;
begin
  Result := FCurrentStation;
end;
procedure TGHTopoContext2DA.SetCurrentStation(const E: TBaseStation; const DoRedraw: boolean);
begin
  FCurrentStation := E;
  FDocuTopo.SetCurrentNumeroSerie(FCurrentStation.Entite_Serie);
  FDocuTopo.SetCurrentIndexPointOfSerie(FCurrentStation.Entite_Station);
  SetStationInfo(FCurrentStation);
  //pnlCurrentStation.Caption := Trim(Format('%d.%d %s', [FCurrentStation.Entite_Serie, FCurrentStation.Entite_Station, FCurrentStation.IDTerrain]));
  Vue.Invalidate;
end;

procedure TGHTopoContext2DA.SetCurrentStationBySerSt(const S: TNumeroSerie; const P: TNumeroStation);
var
  QMyCurrStation: TBaseStation;
  PP: TNumeroStation;
begin
  PP := IIF(P = -1, 1, p);
  try
    if (FBDDEntites.GetEntiteViseeFromSerSt(S, PP, QMyCurrStation)) then SetCurrentStation(QMyCurrStation, false) else ShowMessage('Station not found');
  except
    QMyCurrStation.Empty();
    QMyCurrStation.setSerPt(1, 0);
    QMyCurrStation.oCommentaires := 'Unknown';
    //SetCurrentStation(QMyCurrStation, false);
  end;
end;

function TGHTopoContext2DA.GetCurrentStationNearToMousePos(): TBaseStation;
begin
  result := FStationNearToMouse;
end;

procedure TGHTopoContext2DA.setCurrentMarker(const M: TGHTopoMarker);
begin
  FCurrentMarker:= M;
end;

procedure TGHTopoContext2DA.SetCurrentBipoint(const BP: TBipointBaseStation);
begin
  FCurrentBipoint := BP;
end;

procedure TGHTopoContext2DA.SetCurrentNearSt(const NearST: TStationProcheOfEndSerie);
begin
  FCurrentNearST := NearST;
end;


//******************************************************************************
// Un peu bancal mais au moins, on a tout le réseau dans la vue ;)
// Repris du ResetVue() de GHCaveDraw
procedure TGHTopoContext2DA.ResetVue();
var
  cnMini, cnMaxi   : TPoint3Df;
  PD               : TPoint2Df;
  M, L             : double;
begin
  if (not FCanDraw) then Exit;
  cnMini := FBDDEntites.CoinBasGauche;
  cnMaxi := FBDDEntites.CoinHautDroit;
  PD.setFrom(cnMaxi.X - cnMini.X, cnMaxi.Y - cnMini.Y);
  M  := 0.02 * PD.getNorme();
  L  := IIF(PD.X > PD.Y, PD.X, PD.Y);
  SetViewLimits(cnMini.X - M, cnMini.Y - M, cnMini.X + L + M, cnMini.Y + L + M, '');
  Vue.Invalidate;
end;

procedure TGHTopoContext2DA.SetStationInfo(const S: TBaseStation);
var
  beuh                   : TPoint3Df;
  EWE                    : String;
  QNoSerie               , QIdxNameSpace: integer;
  QCap: Double;
  QDist: Float;
begin
  beuh := FBDDEntites.GetDeltaXYZFromPositionSt0(S);
  lbInfos.Color            := IIF(S.IsPOI, clYellow, clDefault);
  acDeleteThisPOI.Visible  := S.IsPOI;
  DecomposeNumeroSerie(S.Entite_Serie, QIdxNameSpace, QNoSerie);
  EWE := Format('%d.%d', [QNoSerie, S.Entite_Station]);
  lbInfos.Caption := Trim(Format('%s (X = %s; Y = %s; Alt = %s; Z = %s ) %s',
                          [EWE,
                           FormatterNombreAvecSepMilliers(S.PosStation.X, 0),
                           FormatterNombreAvecSepMilliers(S.PosStation.Y, 0),
                           FormatterNombreAvecSepMilliers(S.PosStation.Z, 0),
                           FormatterNombreAvecSepMilliers(beuh.Z, 0),
                           S.oCommentaires]));
  // point but
  QCap  := GetAzimut(FPointBut.X - S.PosStation.X, FPointBut.Y - S.PosStation.Y, 360.00);
  QDist := hypot(FPointBut.X - S.PosStation.X, FPointBut.Y - S.PosStation.Y);
  lbInfos.Caption := lbInfos.Caption +
                     format(' - Point but: Dist: %.2f, Cap: %.3f', [QDist, QCap]);

end;

procedure TGHTopoContext2DA.SpeedButton13Click(Sender: TObject);
begin
  pass;
end;

procedure TGHTopoContext2DA.SpeedButton13MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  QDeplacerVue(1, 0);
end;




procedure TGHTopoContext2DA.PopUpCdrVue2DPopup(Sender: TObject);
var
  QNoSerie   , QIdxNamespace: integer;
  NS: TNumeroSerie;
  procedure S666(const ACDC: TAction; const QCaption: string; const QEnabled: boolean = True);
  begin
    ACDC.Caption := GetResourceString(QCaption);
    ACDC.Hint    := GetResourceString(QCaption);
    ACDC.Enabled := QEnabled;
  end;
  procedure S777(const ACDC: TMenuItem; const QCaption: string);
  begin
    ACDC.Caption := GetResourceString(QCaption);
    ACDC.Hint    := GetResourceString(QCaption);

  end;
begin
  DecomposeNumeroSerie(FStationNearToMouse.Entite_Serie, QIdxNamespace, QNoSerie);
  S777(mnuStationCourante      , Format(rsCDR_VUE2D_MNU_CURRENT_STATION           , [FStationNearToMouse.toStringWithIDTerrain()]));
  S777(mnuMetaFiltreStation    , Format(rsCDR_VUE2D_MNU_FILTRES_STATION           , [FStationNearToMouse.toStringWithIDTerrain()]));
  S777(mnuHighLight            , Format(rsCDR_VUE2D_MNU_HIGHLIGHT_BY_STATION      , [FStationNearToMouse.toStringWithIDTerrain()]));

  // titres du sous-menu
  S666(acZoomTout                 , rsCDR_VUE2D_AC_ZOOM_ALL);
  S666(acZoomFenetre              , rsCDR_VUE2D_AC_ZOOM_WINDOW);
  S666(acPanVue                   , rsCDR_VUE2D_AC_PAN_VUE);
  S666(acDistanceP2P              , rsCDR_VUE2D_AC_DISTANCE_BT_STATIONS);
  S666(acMetaFiltreSerie          , Format(rsCDR_VUE2D_AC_METAFILTRE_SERIE        , [QNoSerie]));
  S666(acMetaFiltreTopoDuJour     , Format(rsCDR_VUE2D_AC_METAFILTRE_TOPO_DU_JOUR , [DateToStr(FStationNearToMouse.DateLeve)]));
  S666(acMetaFiltreReseau         , Format(rsCDR_VUE2D_AC_METAFILTRE_RESEAU       , [FStationNearToMouse.eReseau]));
  S666(acMetaFiltreEntree         , Format(rsCDR_VUE2D_AC_METAFILTRE_ENTRANCE     , [FStationNearToMouse.eReseau]));
  S666(acMetaFiltreSecteur        , Format(rsCDR_VUE2D_AC_METAFILTRE_SECTEUR      , [FStationNearToMouse.eSecteur]));
  S666(acMetaFiltreCode           , Format(rsCDR_VUE2D_AC_METAFILTRE_CODE         , [FStationNearToMouse.eCode]));
  S666(acMetaFiltreExpe           , Format(rsCDR_VUE2D_AC_METAFILTRE_EXPE         , [FStationNearToMouse.eExpe]));
  S666(acMetaFiltreTopoAnnee      , Format(rsCDR_VUE2D_AC_METAFILTRE_YEAR         , [YearOf(FStationNearToMouse.DateLeve)]));
  S666(acMetaFiltreVueCourante    , rsCDR_VUE2D_AC_METAFILTRE_CURRENT_VIEW);
  S666(acIsovaleurSurZStation     , Format(rsCDR_VUE2D_AC_ISOVALEUR_Z             , [FStationNearToMouse.PosStation.Z]));
  // highlight
  S666(acHighlightThisSerie       , Format(rsCDR_VUE2D_AC_METAFILTRE_SERIE        , [QNoSerie]));
  S666(acHighLightThisCode        , Format(rsCDR_VUE2D_AC_METAFILTRE_CODE         , [FStationNearToMouse.eCode]));
  S666(acHighLightThisExpe        , Format(rsCDR_VUE2D_AC_METAFILTRE_EXPE         , [FStationNearToMouse.eExpe]));
  S666(acEditThisSerie            , Format(rsCDR_VUE2D_AC_EDIT_THIS_SERIE         , [QNoSerie]));
  // POI
  S666(acDeleteThisPOI            , rsCDR_VUE2D_AC_DELETE_THIS_POI);
  S666(acAddPOIHere               , rsCDR_VUE2D_AC_ADD_POI_HERE);
  S666(acEditThisStation          , rsCDR_VUE2D_AC_EDIT_THIS_STATION, {$IFDEF GHTOPO_SIMPLIFIE} true {$ELSE} false{$ENDIF});
  // Ajout et continuation

  //NS := FDocuTopo.getMaxIdxSerie() + 1;
  NS := FDocuTopo.GetLastSerie().NumeroDeSerie + 1;

  S666(acAddSerieHere             , Format(rsCDR_VUE2D_ADD_SERIE_HERE               , [NS, FStationNearToMouse.toStringWithIDTerrain()]));
  S666(acDefineEndSerieAsEntrance , format(rsCDR_VUE2D_AC_SET_END_SERIE_AS_ENTRANCE , [FStationNearToMouse.Entite_Serie]));
  S666(acContinueHere             , Format(rsCDR_VUE2D_CONTINUE_SERIE_HERE          , [FStationNearToMouse.Entite_Serie]));
  S666(acRemoveStFromGraphe       , Format(rsCDR_VUE2D_REMOVE_ST_OF_GRAPHE          , [FStationNearToMouse.toStringWithIDTerrain()]));
end;


procedure TGHTopoContext2DA.QDeplacerVue(const QX, QY: integer);
var
  WUX, WUY: Double;
begin
  WUX := -FRACTION_OF_EXTENSION * (FRegionCMaxi.X - FRegionCMini.X) * QX;
  WUY := -FRACTION_OF_EXTENSION * (FRegionCMaxi.Y - FRegionCMini.Y) * QY;
  DeplacerVue(WUX, WUY);
  self.RefreshDessin();
end;
procedure TGHTopoContext2DA.btnMaillageOKClick(Sender: TObject);
begin
  self.RefreshDessin();
end;

procedure TGHTopoContext2DA.btnQSAddViseeClick(Sender: TObject);
var
  L0, A0, P0, L1, A1, P1: Double;
  function QCheck(const V0, V1: double; const Tag: string): boolean;
  begin
    result := (abs(V1 - V0) < 0.025);
    if (not result) then ShowMessageFmt('%s: %.3f != %.3f', [GetResourceString(Tag), V0, V1]);
  end;
begin
  // double saisie requise en mode manuel
  L0 := ConvertirEnNombreReel(btnLong0.Caption, 0.00);
  A0 := ConvertirEnNombreReel(btnAz0.Caption  , 0.00);
  P0 := ConvertirEnNombreReel(btnIncl0.Caption, 0.00);

  L1 := ConvertirEnNombreReel(btnLong1.Caption, 0.00);
  A1 := ConvertirEnNombreReel(btnAz1.Caption  , 0.00);
  P1 := ConvertirEnNombreReel(btnIncl1.Caption, 0.00);
  //*)
  if (QCheck(L0, L1, rsCDR_SERIE_COL_LEGNTH) AND
      QCheck(A0, A1, rsCDR_SERIE_COL_AZIMUTH) AND
      QCheck(P0, P1, rsCDR_SERIE_COL_INCLIN)) then
  begin
    AjouterUneViseeALaSerieCouranteDisto(tgDEFAULT,
                                        Now(),
                                        0.50 * (L0 + L1),
                                        0.50 * (A0 + A1),
                                        0.50 * (P0 + P1),
                                        ConvertirEnNombreReel(btnLG.Caption, 0.00),
                                        ConvertirEnNombreReel(btnLD.Caption, 0.00),
                                        ConvertirEnNombreReel(btnHZ.Caption, 0.00),
                                        ConvertirEnNombreReel(btnHN.Caption, 0.00),
                                        '',
                                        btnCommentaire.Caption,
                                        0.00);
  end;
  //editQSLongueur0.SetFocus;
end;



procedure TGHTopoContext2DA.btnQSCloseMiniFormViseeClick(Sender: TObject);
begin
  pnlQuickSaisieVisee.Visible := false;
end;




procedure TGHTopoContext2DA.btnAz0Click(Sender: TObject);
begin
  DispClavierNumForBTN(btnAz0, 'Azimut: Lecture 1');
end;

procedure TGHTopoContext2DA.acMetaFiltreSerieExecute(Sender: TObject);
var
  FF: String;
begin
  FF := Format('%s=%d',[rsMETAFILTRE_SERIE, FStationNearToMouse.Entite_Serie]);
  ApplyMetaFiltre(True, FF);
  self.RefreshDessin();
end;

procedure TGHTopoContext2DA.acMetaFiltreTopoAnneeExecute(Sender: TObject);
var
  YYYY,MM,DD: word;
  S1, S2, FF: string;
begin
  DecodeDate(FStationNearToMouse.DateLeve, YYYY, MM, DD);
  S1:=DateToStr(GetSecuredDate(YYYY, 01, 01));
  S2:=DateToStr(GetSecuredDate(YYYY, 12, 31));
  FF := Format('%s=(%s, %s)',[rsMETAFILTRE_DATE, S1, S2]);
  ApplyMetaFiltre(True, FF);

end;

procedure TGHTopoContext2DA.acMetaFiltreTopoDuJourExecute(Sender: TObject);
var
  YYYY, MM, DD: Word;
  S1, FF: String;
begin
  DecodeDate(FStationNearToMouse.DateLeve, YYYY, MM, DD);
  S1 := DateToStr(GetSecuredDate(YYYY, MM, DD));
  FF := Format('%s=%s',[rsMETAFILTRE_DATE, S1]);
  ApplyMetaFiltre(True, FF);
end;

procedure TGHTopoContext2DA.acMetaFiltreVueCouranteExecute(Sender: TObject);
var
  PP: TPoint;
  FC1, FC2: TPoint2Df;
  FF: String;
begin
  PP.X := 0;
  PP.Y := Vue.Height;
  FC1 := GetCoordsMonde(PP);
  PP.X := Vue.Width;
  PP.Y := 0;
  FC2 := GetCoordsMonde(PP);
  FF := Format('COORD_X=(%.2f, %.2f) & COORD_Y=(%.2f, %.2f)', [FC1.X, FC2.X, FC1.Y, FC2.Y]);
  ApplyMetaFiltre(True, FF);
end;

procedure TGHTopoContext2DA.acMetaFiltreZoneExecute(Sender: TObject);
begin
  SetModeTravail(mtMETAFILTRE_ZONE_PREMIER_COIN);
end;

procedure TGHTopoContext2DA.acOpenCroquisExecute(Sender: TObject);
var
  QFilename: TStringDirectoryFilename;
begin
  QFilename := '';
  if (DoDialogOpenFile(GetGHTopoDirectory(), 'Fichiers croquis (*.xml)|*.xml', '.xml', QFilename)) then
  begin
    OpenCroquis(QFilename);
  end;
end;

procedure TGHTopoContext2DA.acPanVueExecute(Sender: TObject);
begin
  self.SetModeTravail(mtPAN_PREMIER_POINT);
end;



procedure TGHTopoContext2DA.acRemoveLastViseeExecute(Sender: TObject);
var
  MySerie: TObjSerie;
  QIdx: integer;
begin
  MySerie := nil;
  if (GHTopoQuestionOuiNon('Supprimer la dernière visée')) then
  begin
    if (FDocuTopo.GetSerieByNumeroSerie(self.FCurrentStationOfDistoX.Entite_Serie, MySerie, QIdx)) then
    begin
      if (MySerie.NbVisees > 1) then
      begin
        MySerie.RemoveLastElement();
        if (assigned(FProcPerformActionRecalcul)) then FProcPerformActionRecalcul(False);
      end;
    end;
  end;
end;

procedure TGHTopoContext2DA.acRemoveStFromGrapheExecute(Sender: TObject);
var
  QIdx: integer;
  BP: TBaseStation;
  EN: TEntrance;
  Dist: double;
  ER: TBDDEntitesFindViseeOrEntrance;
begin
  if (FBDDEntites.GetStationOrEntranceFromXYZ(FMyPos.X, FMyPos.Y, 0.00, 20.00, [tpVISEES], True, QIdx, BP, EN, Dist, ER)) then
  begin
    if (not GHTopoQuestionOuiNon(format('Remove node %s', [BP.toString()]))) then exit;
    FGraphe.RemoveNode(BP.Entite_Serie, BP.Entite_Station);
  end;
end;

procedure TGHTopoContext2DA.acSaveCroquisExecute(Sender: TObject);
var
  MyFolderQSave: TStringDirectoryFilename;
  WU: TDateTime;
  YYYY, MM, DD, HH, MN, SS, MS: word;
  MyQSaveFileName: TStringDirectoryFilename;
begin
  if (not FCroquisTerrain.IsReady) then Exit;
  MyFolderQSave := GetGHTopoDirectory() + MON_DOSSIER_CROQUIS + PathDelim;
  ForceDirectoriesUTF8(MyFolderQSave);
  WU := Now();
  DecodeDate(WU, YYYY, MM, DD);
  DecodeTime(WU, HH, MN, SS, MS);
  (*
  MyQSaveFileName := MyFolderQSave +
                     Format('%s_%.04d-%.02d-%.02d_%.02dh%.02dm%.02ds%.03dms_croquis.xml',
                            ['QSave',
                              YYYY, MM, DD,
                              HH, MN, SS, MS]);
                            //*)
  MyQSaveFileName := MyFolderQSave + 'Crobard001.xml';
  SaveCroquis(MyQSaveFileName);
end;

procedure TGHTopoContext2DA.acSetCurrentBaseStationExecute(Sender: TObject);
var
  EWE: String;
begin
  EWE := format('Définir %d.%d comme station courante', [FStationNearToMouse.Entite_Serie, FStationNearToMouse.Entite_Station]);
  if (GHTopoQuestionOuiNon(EWE)) then
  begin
    SetCurrentStationOfDistoX(FStationNearToMouse.Entite_Serie, FStationNearToMouse.Entite_Station);

  end;
end;

procedure TGHTopoContext2DA.acSetCurrentSerieExecute(Sender: TObject);
begin
  pass;
end;

procedure TGHTopoContext2DA.SaveCroquis(const QFilename: TStringDirectoryFilename);
begin
  FCroquisTerrain.SaveToXML(QFilename);
end;
function  TGHTopoContext2DA.OpenCroquis(const QFilename: TStringDirectoryFilename): boolean;
begin
  case FCroquisTerrain.LoadFromXML(QFileName) of
    tkeNO_ERROR: pass;
    tkeEMPTY         : ShowMessage(GetResourceString(rsKROBARD_IS_EMPTY));
    tkeFILE_NOT_FOUND: ShowMessage(GetResourceString(rsKROBARD_FILE_NOT_FOUND));
    tkeXML_ERR_READ  : ShowMessage(GetResourceString(rsKROBARD_FILE_INVALID));
  end;
  result := True;
  self.RefreshDessin();
end;


procedure TGHTopoContext2DA.acSupprimerSerieExecute(Sender: TObject);
var
  MyNumeroSerie: TNumeroSerie;
  MySerie: TObjSerie;
  QInternaIdxSerie: integer;
begin
  MyNumeroSerie := self.FCurrentStation.Entite_Serie;
  if (FDocuTopo.GetSerieByNumeroSerie(MyNumeroSerie, MySerie, QInternaIdxSerie)) then
  begin
    if (GHTopoQuestionOuiNon(Format('Supprimer %d: %s', [MySerie.NumeroDeSerie, MySerie.NomSerie]))) then
    begin
      FDocuTopo.RemoveSerie(QInternaIdxSerie);
    end;
  end
  else
  begin
    ShowMessage(GetResourceString(rsMATCHNOTFOUND));
  end;
end;

procedure TGHTopoContext2DA.acEditThisSerieExecute(Sender: TObject);
var
  MyNumeroSerie: TNumeroSerie;
  MySerie: TObjSerie;
  QInternaIdxSerie, QIdx: integer;
begin
  {$IFDEF GHTOPO_SIMPLIFIE}
  MyNumeroSerie := self.FStationNearToMouse.Entite_Serie;
  if (FDocuTopo.GetSerieByNumeroSerie(MyNumeroSerie, MySerie, QInternaIdxSerie)) then
  begin
    if (DisplayUneSerieAsGrid(FDocuTopo, FBDDEntites, QIdx, MySerie)) then
    begin
      showmessage(GetResourceString(rsMSG_QUESTION_RECALCULER));
    end;
  end;
  {$ELSE}
  if (Assigned(FProcTransmitBasePoint)) then FProcTransmitBasePoint(FStationNearToMouse, tdoAcnBASESTATION_EDITSERIE);
  {$ENDIF}
end;

procedure TGHTopoContext2DA.acDisplLastPointsClickedExecute(Sender: TObject);
var
  Nb, i: Integer;
  EWE: String;
  PT: TPoint2Df;
begin
  Nb := FListeLastPointsClicked.GetNbElements();
  if (0 = Nb) then
  begin
    DisplayToast('Aucun point - Alt+Click pour en créer');
    Exit;
  end;
  EWE := '';
  for i := 0 to Nb - 1 do
  begin
    PT := FListeLastPointsClicked.GetElement(i);
    EWE += FormatterNombreWithDotDecimal(PT.X) + ', ' + FormatterNombreWithDotDecimal(PT.Y) + ', ' + System.LineEnding;
  end;

  DisplayTextEditorFromString(tehmNONE, rsCDR_VUE2D_LISTE_CLICKED_POINTS, EWE, True);
end;

procedure TGHTopoContext2DA.acTransmitBasePointForHighlightingExecute(Sender: TObject);
begin
  if (Assigned(FProcTransmitBasePoint)) then FProcTransmitBasePoint(FStationNearToMouse, tdoAcnBASESTATION_ADD_TO_HIGHLIGHT);
end;

procedure TGHTopoContext2DA.acVueRefreshExecute(Sender: TObject);
begin
  RefreshDessin();
end;

procedure TGHTopoContext2DA.acVuesPrecedentesExecute(Sender: TObject);
var
  R: TRect2Df;
begin
  //showmessage('Vue précédente');
  if (FBufferRegionsZoom.Previous(R)) then
  begin
    AfficherMessageErreur('C: %d - R: %s', [FBufferRegionsZoom.CurrentCursor, R.DebugString('R1', 'R2')]);
    self.SetViewLimits(R, '', False);
    self.RefreshDessin();
  end;
end;

procedure TGHTopoContext2DA.acVuesSuivantesExecute(Sender: TObject);
var
  R: TRect2Df;
begin
  if (FBufferRegionsZoom.Next(R)) then
  begin
    AfficherMessageErreur('C: %d - R: %s', [FBufferRegionsZoom.CurrentCursor, R.DebugString('R1', 'R2')]);
    self.SetViewLimits(R, '', False);
    self.RefreshDessin();
  end;
end;

procedure TGHTopoContext2DA.acZoomFenetreExecute(Sender: TObject);
begin
  Self.SetModeTravail(mtZOOM_PREMIER_COIN);
end;

procedure TGHTopoContext2DA.acZoomMoinsExecute(Sender: TObject);
begin
  if (FCanDraw) then ZoomFact(1 - 0.05);
end;

procedure TGHTopoContext2DA.acZoomPlusExecute(Sender: TObject);
begin
  if (FCanDraw) then ZoomFact(1 + 0.05);
end;

procedure TGHTopoContext2DA.acZoomToutExecute(Sender: TObject);
begin
  self.ResetVue();
end;

procedure TGHTopoContext2DA.btnMoreInfosClick(Sender: TObject);
begin
  pnlFullInfos.Visible := false;
end;

procedure TGHTopoContext2DA.btnSerieCouranteDistoXClick(Sender: TObject);
var
  QIdxSelected: integer;
  SR: TObjSerie;
begin

  if (GHTopoQuestionOuiNon('Continuer la dernière série')) then
  begin
    SR := FDocuTopo.GetLastSerie();
    SetCurrentStationOfDistoX(SR.NumeroDeSerie);
  end
  else
  begin
    if (SelectionDansListe(FDocuTopo, mslSERIE, false, QIdxSelected, True)) then SetCurrentStationOfDistoX(QIdxSelected);

  end;
  //*)
 // if (SelectionDansListe(FDocuTopo, mslSERIE, false, QIdxSelected, false)) then SetCurrentStationOfDistoX(QIdxSelected);
end;

procedure TGHTopoContext2DA.btnUnloadMNTClick(Sender: TObject);
var
  QMaillage: TMaillage;
begin
  if (GHTopoQuestionOuiNon('Décharger le MNT')) then
  begin
    QMaillage := FListeMaillages.GetElement(0);
    QMaillage.ResetMaillage();
    pnlMaillage.Visible := QMaillage.IsValid;
    CalculerRecouvrements(false, nil);
    Vue.Invalidate;
  end;
end;

procedure TGHTopoContext2DA.Button1Click(Sender: TObject);
var
  EWE: TCaption;
begin
  EWE := editMetaFiltre.Text;
  if (DisplayAndEditFiltres(FDocuTopo, EWE)) then
  begin
    editMetaFiltre.Text := EWE;
    ApplyMetaFiltre(chkMetaFiltreActif.Checked, trim(editMetaFiltre.Text));
  end;
end;

procedure TGHTopoContext2DA.acLocaliserUneStationExecute(Sender: TObject);
begin
  LocaliserUneStation();
end;

procedure TGHTopoContext2DA.acAddserieEntreCesPointsExecute(Sender: TObject);
begin
   self.SetModeTravail(mtADD_SERIE_PREMIER_POINT);
end;
procedure TGHTopoContext2DA.acAddSerieHereExecute(Sender: TObject);
var
  MySerie            : TObjSerie;
  QCBG, QCHD         : TPoint2Df;
  QParamOnglet       : TVue2DParams;
  QValue, WU         : String;
  DbgTag             : string;
  n                  : TNumeroSerie;
  QMargeX, QMargeY   : double;
  MyBaseStation      : TBaseStation;
  QIdx               : integer;
begin

  //n := 1 + FDocuTopo.getMaxIdxSerie();
  n := 1 + FDocuTopo.GetLastSerie().NumeroDeSerie;
  MyBaseStation := FStationNearToMouse;
  QValue := Format(FORMAT_NB_INTEGER, [n]);
  if (DisplayClavierNumerique(Format('Nouvelle série depuis %d.%d', [MyBaseStation.Entite_Serie, MyBaseStation.Entite_Station]),
                                     pnmINTEGER, QValue)) then
  //if (InputQuery(Format('Nouvelle série depuis la station %d.%d', [MyBaseStation.Entite_Serie, MyBaseStation.Entite_Station]), 'Numéro de série', QValue)) then
  begin
    //n := StrToIntDef(Trim(QValue), 1 + FDocuTopo.getMaxIdxSerie());
    n := StrToIntDef(Trim(QValue), 1 + FDocuTopo.GetLastSerie().NumeroDeSerie);
    // série déjà attribuée ?
    if (FDocuTopo.GetSerieByNumeroSerie(n, MySerie, QIdx)) then
    begin
      WU := Format(rsNUM_SERIE_ALREADY_ATTRIBUTED, [n]);
      if (not GHTopoQuestionOuiNon(WU + ' - ' + rsALLOW_ATTRIBUTE_NUM_SERIE)) then Exit;
      n := -1;
    end;
    QParamOnglet := GetOngletByIndex(FCurrIdxOnglet);    // on récupère les paramètres de vue et d'onglet
    GetViewLimitsFromOnglet(FCurrIdxOnglet, QCBG, QCHD);
    QMargeX := 0.50 * (QCHD.X - QCBG.X);
    QMargeY := 0.50 * (QCHD.Y - QCBG.Y);
    FDocuTopo.BackupConnexionsSeries();
    FDocuTopo.SetCurrentNumeroSecteur(FCurrentDistoXNumeroSecteur);
    FDocuTopo.SetCurrentNumeroCode(FCurrentDistoXNumeroCode);
    FDocuTopo.SetCurrentNumeroExpe(FCurrentDistoXNumeroExpe);

    if (FDocuTopo.CreateNewSerie(MyBaseStation.Entite_Serie, MyBaseStation.Entite_Station,
                                 n,
                                 Format('Série ajoutée par pointage en %d.%d', [MyBaseStation.Entite_Serie, MyBaseStation.Entite_Station]))) then
    begin
      MySerie.SetNoPointArr(-1);                              // attribution automatique du point d'arrivée
      MySerie := FDocuTopo.GetLastSerie();                    //on récupère la station nouvellement créée
      SetViewLimits(MyBaseStation.PosStation.X - QMargeX, MyBaseStation.PosStation.Y - QMargeY,
                    MyBaseStation.PosStation.X + QMargeX, MyBaseStation.PosStation.Y + QMargeY,
                    'Centrage', True);
      PutOngletByIndex(FCurrIdxOnglet, QParamOnglet, false);
      DbgTag := Format('%s: %s', [{$I %FILE%}, {$I %LINE%}]);
      CentrerVueSurPointXY(MyBaseStation.PosStation.X, MyBaseStation.PosStation.Y, true, 'Nouvelle série ici');
      //SetCurrentStation(MyBaseStation, True);
      SetCurrentStationOfDistoX(MySerie.NumeroDeSerie, 0);
      {$IFDEF GHTOPO_SIMPLIFIE}
      //pnlQuickSaisieVisee.Visible := True;
      {$ELSE}
      lbTitreQuickSaisieVisee.caption := Format('Station: %d.%d', [MySerie.NumeroDeSerie, MySerie.NbVisees]);
      //pnlQuickSaisieVisee.Visible := True;
      {$ENDIF}
      FDocuTopo.CheckConnexionsSeries(false);
    end
    else
    begin
      ShowMessage(GetResourceString(rsFAILED_TO_ADD_SERIE));
    end;
  end;
end;

procedure TGHTopoContext2DA.acAddAnnotationHereExecute(Sender: TObject);
begin
  pass;
end;

procedure TGHTopoContext2DA.acAddPOIHereExecute(Sender: TObject);
var
  n: TNumeroSerie;
  QValue: String;
  SR: TObjSerie;
  QIdx: integer;
  QVisee: TUneVisee;
begin
  QValue := '';
  if (FDocuTopo.GetSerieByNumeroSerie(FStationNearToMouse.Entite_Serie, SR, QIdx)) then
  begin
    QVisee := SR.GetVisee(FStationNearToMouse.Entite_Station);
    if (InputQuery(Format('Nouveau POI depuis la station %d.%d', [FStationNearToMouse.Entite_Serie, FStationNearToMouse.Entite_Station]), 'Point d''intérêt', QValue)) then
    begin
      if (QValue = '') then exit;
      QVisee.Commentaires := '$POI:' + QValue;
      SR.PutVisee(FStationNearToMouse.Entite_Station, QVisee);
    end;

  end;
end;

// Met aussi la station courante à jour
procedure TGHTopoContext2DA.SetCurrentStationOfDistoX(const S: TNumeroSerie; const P: TNumeroStation);
var
  MySerie: TObjSerie;
  QIdx, PPP: integer;

  procedure UpdateUIAndState();
  begin
    AfficherMessageErreur('UpdateUIAndState: Maj bouton avec %s', [FCurrentStationOfDistoX.toString()]);
    SetCurrentStationBySerSt(FCurrentStationOfDistoX.Entite_Serie, FCurrentStationOfDistoX.Entite_Station);

    FCurrentDistoXNumeroEntrance := MySerie.NumeroEntrance;
    FCurrentDistoXNumeroReseau   := MySerie.NumeroReseau;
    FCurrentStationOfDistoX.setEntranceReseauSecteurTypeCodeExpe(
      FCurrentDistoXNumeroEntrance,
      FCurrentDistoXNumeroReseau,
      FCurrentDistoXNumeroSecteur,
      FCurrentDistoXTypeVisee,
      FCurrentDistoXNumeroCode,
      FCurrentDistoXNumeroExpe
    );

    btnSerieCouranteDistoX.Caption := Format('Ser.: %d', [FCurrentStationOfDistoX.Entite_Serie]);
    btnSerieCouranteDistoX.Hint := 'Station actuelle pour les mesures au DistoX: ' + FCurrentStationOfDistoX.toString + #10 +
                                    Format('Entrance: %d, Network: %d', [FCurrentDistoXNumeroEntrance, FCurrentDistoXNumeroReseau]) + #10 +
                                    Format('Secteur: %d, Type: %d, Code: %d, Explo: %d', [
                                      FCurrentDistoXNumeroSecteur,
                                      Ord(FCurrentDistoXTypeVisee),
                                      FCurrentDistoXNumeroCode,
                                      FCurrentDistoXNumeroExpe
                                    ]);
  end;

begin
  AfficherMessageErreur('Appel SetCurrentStationOfDistoX(S = %d, P = %d)', [S, P]);

  if (not FDocuTopo.GetSerieByNumeroSerie(S, MySerie, QIdx)) then
  begin
    ShowMessage(GetResourceString(rsCDR_SERIE_MSG_ERR_SERIE_NOT_FOUND));
    Exit;
  end;

  FCurrentStationOfDistoX.Empty();

  // Cas 0: Station 1.0 ou 0.0
  if ((S <= 1) and (P = 0)) then
  begin
    AfficherMessageErreur('Cas: (S <= 1) and (P = 0)');
    if (not FBDDEntites.GetEntiteViseeFromSerSt(1, 0, FCurrentStationOfDistoX)) then
    begin
      SetCurrentStationOfDistoX(1, 1);
      AfficherMessageErreur('Cas S = 1: Série 1 sélectionnée: %d.%d',
                            [FCurrentStationOfDistoX.Entite_Serie, FCurrentStationOfDistoX.Entite_Station]);
      Exit;
    end;
    UpdateUIAndState();
    Exit;
  end;

  // Cas spécial: P = 0 = création de série
  if (P = 0) then
  begin
    AfficherMessageErreur('Cas: (S > 1) and (P = 0)');
    if (not FBDDEntites.GetEntiteViseeFromSerSt(MySerie.NoSerieDep, MySerie.NoPointDep, FCurrentStationOfDistoX)) then Exit;
    FCurrentStationOfDistoX.Entite_Serie   := FDocuTopo.GetLastSerie.NumeroDeSerie;//FDocuTopo.getMaxIdxSerie();
    FCurrentStationOfDistoX.Entite_Station := 0;
    AfficherMessageErreur('Cas P = 0: Poursuite de série: %d.%d',
                          [FCurrentStationOfDistoX.Entite_Serie, FCurrentStationOfDistoX.Entite_Station]);
    UpdateUIAndState();
    Exit;
  end;

  // Cas général: P > 0 ou P = -1
  AfficherMessageErreur('Cas: Autre (S = %d, P = %d, NbVisees = %d)', [S, P, MySerie.NbVisees]);

  if ((P = -1) OR (P > 0)) then
  begin

    PPP := IfThen(P = -1, MySerie.NbVisees - 1, P);
    AfficherMessageErreur('PPP = %d', [PPP]);
    //
    if (PPP = 0) then
    begin
      FCurrentStationOfDistoX.setSerPt(S, 0);

    end
    else
    begin
      if (not FBDDEntites.GetEntiteViseeFromSerSt(S, PPP, FCurrentStationOfDistoX)) then Exit;
    end;
    AfficherMessageErreur('Cas standard: Poursuite de série: %d.%d',  [FCurrentStationOfDistoX.Entite_Serie, FCurrentStationOfDistoX.Entite_Station]);
    UpdateUIAndState();



  end;
end;



procedure TGHTopoContext2DA.acAddSerieToHighlightExecute(Sender: TObject);
begin
  if (Assigned(FProcTransmitBasePoint)) then FProcTransmitBasePoint(FStationNearToMouse, tdoAcnBASESTATION_ADD_TO_HIGHLIGHT);
end;

function TGHTopoContext2DA.UpdateBaseStationWithOffset(const QE: TBaseStation; const QQX, QQY, QQZ: double): TBaseStation;
begin
  Result            := QE;
  Result.PosExtr0   := QE.PosStation;
  Result.PosStation.setFrom(Result.PosExtr0.X + QQX, Result.PosExtr0.Y + QQY, Result.PosExtr0.Z + QQZ);
  Result.PosOPG     := Result.PosExtr0;
  Result.PosOPD     := Result.PosExtr0;
  Result.PosPD      := Result.PosStation;
  Result.PosPG      := Result.PosStation;
end;
function TGHTopoContext2DA.AjouterUneViseeALaSerieCouranteDisto(const QTypeVisee: TTypeDeVisee;
                                                                const QHorodatage: TDateTime;
                                                                const QLongueur, QAzimut, QPente, QLG, QLD, QHZ, QHN: double;
                                                                const QIDLitteral, QCommentaire: string;
                                                                const QTemperature: double): boolean;
var
  QPt0: TPoint3Df;
  QP: double;
  CC: TCode;
  QInternalIdxserie, NbVisees: integer;
  MySerie: TObjSerie;
  MyBaseStation: TBaseStation;
  EE: TExpe;
  VS: TUneVisee;
begin
  Result := false;
  MySerie := nil;

  if (not FDocuTopo.GetSerieByNumeroSerie(FCurrentStationOfDistoX.Entite_Serie, MySerie, QInternalIdxserie)) then
  begin
    ShowMessage('-- EE: Serie not found');
    Exit(false);
  end;

  NbVisees := MySerie.NbVisees;
  // Si le nombre de visées de la série courante est <= 1, on attrappe la station d'accrochage pour le paramétrage
  // (nota: Lors de la création d'une série, une visée zéro est toujours créée, donc NbVisées sera égal à 1 au minimum)
  if (NbVisees <= 1) then FBDDEntites.GetEntiteViseeFromSerSt(MySerie.NoSerieDep, MySerie.NoPointDep, MyBaseStation)
                     else FBDDEntites.GetEntiteViseeFromSerSt(MySerie.NumeroDeSerie, MySerie.NbVisees - 1, MyBaseStation);

  MyBaseStation := FCurrentStationOfDistoX;
  AfficherMsgClass(self, 'AjouterUneViseeALaSerieCouranteDisto(): %f %f %f', [QLongueur, QAzimut, QPente]);
  AfficherMessageErreur(MyBaseStation.DebugString());
  // c'est une visée de cheminement ?
  if (QTypeVisee in [tgDEFAULT, tgFOSSILE, tgVADOSE, tgENNOYABLE, tgSIPHON, tgSURFACE, tgTUNNEL, tgMINE, tgIGNORED_FOR_GRAPHE]) then
  begin
    CC := FDocuTopo.GetCodeByNumero(MyBaseStation.eCode);
    EE := FDocuTopo.GetExpeByNumero(MyBaseStation.eExpe);
    VS.IDSecteur := FCurrentDistoXNumeroSecteur;//MyBaseStation.eSecteur;
    VS.TypeVisee := QTypeVisee;
    VS.Code      := FCurrentDistoXNumeroCode; //CC.IDCode;
    VS.Expe      := FCurrentDistoXNumeroExpe; //EE.IDExpe;
    VS.setLongAzIncLRUD(QLongueur, QAzimut, QPente, QLG, QLD, QHZ, QHN);
    VS.IDTerrainStation := QIDLitteral;
    VS.Commentaires := QCommentaire;
    VS.Temperature  := QTemperature;
    VS.Horodatage   := QHorodatage;
    MySerie.AddVisee(VS);                                                         // on ajoute la visée:
    MySerie.SetSeriePtArr(MySerie.NumeroDeSerie, MySerie.NbVisees - 1); // et on met à jour l'objet MySerie:
    // Pour éviter de devoir recalculer tout le réseau, on crée une nouvelle entité dans FBDDEntites;
    QPt0.Empty();
    QP := 0.00;
    CalculerVisee(VS, CC, EE, QPt0, QP);
    MyBaseStation := UpdateBaseStationWithOffset(MyBaseStation, QPt0.X, QPt0.Y, QPt0.Z);
    MyBaseStation.Entite_Serie   := MySerie.NumeroDeSerie;
    MyBaseStation.Entite_Station := MySerie.NbVisees - 1;
    MyBaseStation.IDTerrain      := QIDLitteral;
    MyBaseStation.oCommentaires  := QCommentaire;
    MyBaseStation.IsPOI          := false;
    MyBaseStation.Highlighted    := false;
    // on ajoute l'entité créée à la table des entités
    FBDDEntites.AddEntiteVisee(MyBaseStation);
    // On définit la nouvelle station courante
    MyBaseStation.Entite_Serie   := MySerie.NumeroDeSerie;
    MyBaseStation.Entite_Station := MySerie.NbVisees - 1;
    MyBaseStation.CouleurOfCode  := COULEUR_DEFAULT_CODE;
    MyBaseStation.CouleurOfExpe  := FBDDEntites.GetCouleurEntiteByIdxExpe(EE.IDExpe);

    // on actualise la base sans recompiler
    if (assigned(FProcActualiserAffichagesBDD)) then FProcActualiserAffichagesBDD(false);
    FBDDEntites.SetMinMax(false);                                            // on redéfinit les limites de vue
    SetCurrentStationOfDistoX(MySerie.NumeroDeSerie, MySerie.NbVisees - 1);                   // ainsi que celle du DistoX
    // -- SetCurrentStation(MyBaseStation, True);                                  // et on met à jour la station courante
    // On centre le plan sur la nouvelle visée créée
    (*
    if (DoRefreshPlan) then
    begin
      CentrerVueSurPointXY(FCurrentStation.PosStation.X, FCurrentStation.PosStation.Y, true, 'Station added');
      // et le label du mini-formulaire
      lbTitreQuickSaisieVisee.Caption := format('Station %d.%d', [MySerie.NumeroDeSerie, MySerie.NbVisees]);
    end;
    //*)
  end;
end;

function TGHTopoContext2DA.AjouterUneAntenneALaStationCouranteDisto(const QLongueur, QAzimut, QPente: double): boolean;
var
  QPt0: TPoint3Df;
  QP: double;
  CC: TCode;
  EE: TExpe;
  QNouvelleEntite: TBaseStation;
  VS: TUneVisee;
  VA: TViseeAntenne;
begin
  AfficherMsgClass(self, 'AjouterUneAntenneALaStationCouranteDisto(): %d.%d: %f %f %f',
                          [FCurrentStationOfDistoX.Entite_Serie, FCurrentStationOfDistoX.Entite_Station,
                           QLongueur, QAzimut, QPente]);
  Result := false;


  CC := FDocuTopo.GetCodeByNumero(FCurrentStationOfDistoX.eCode);
  EE := FDocuTopo.GetExpeByNumero(FCurrentStationOfDistoX.eExpe);
  VA.setFrom(FCurrentStation.eEntrance, FCurrentStation.eReseau,
             FCurrentStationOfDistoX.eSecteur,
             FCurrentStationOfDistoX.Entite_Serie, FCurrentStationOfDistoX.Entite_Station,
             QLongueur, QAzimut, QPente);
  FDocuTopo.AddViseeAntenne(VA);
  QPt0.Empty();
  QP := 0.00;
  VS.setFrom(0, tgVISEE_RADIANTE,
             FCurrentStationOfDistoX.eSecteur,
             FCurrentStationOfDistoX.eCode, FCurrentStationOfDistoX.eExpe,
             QLongueur, QAzimut, QPente,
             0.0, 0.0, 0.0, 0.0, Now(), '', '');
  CalculerVisee(VS, CC, EE, QPt0, QP);
  QNouvelleEntite := UpdateBaseStationWithOffset(FCurrentStation, QPt0.X, QPt0.Y, QPt0.Z);
  QNouvelleEntite.Type_Entite    := tgVISEE_RADIANTE;
  QNouvelleEntite.Entite_Serie   := FCurrentStationOfDistoX.Entite_Serie;
  QNouvelleEntite.Entite_Station := FCurrentStationOfDistoX.Entite_Station;
  QNouvelleEntite.IDTerrain      := '';
  QNouvelleEntite.oCommentaires  := '';
  FBDDEntites.AddEntiteAntenne(QNouvelleEntite);
  // Pas besoin de redessiner le plan: inutile et causes de plantage
  //if (DoRefreshPlan) then RedessinEcran(Vue.Width, Vue.Height, true);   // on actualise la base sans recompiler
  Result := True;
end;

procedure TGHTopoContext2DA.RollbackLastVisee();
var
  MySerie: TObjSerie;
  QIdx, n: integer;
begin
  MySerie := nil;
  AfficherMsgClass(self, 'RollbackLastVisee: %d.%d', [FCurrentStationOfDistoX.Entite_Serie, FCurrentStationOfDistoX.Entite_Station]);
  if (FDocuTopo.GetSerieByNumeroSerie(FCurrentStationOfDistoX.Entite_Serie, MySerie, QIdx)) then
  begin
    n := MySerie.NbVisees;
    ShowMessageFmt('%d visées', [MySerie.NbVisees]);
    if (n <= 1) then
    begin
      ShowMessage('Pas de visée à supprimer');
      exit;
    end;
    if (FCurrentStationOfDistoX.Entite_Station <> (n - 1))then
    begin
      ShowMessageFmt('Impossible de supprimer une station intermédiaire %d - %d', [FCurrentStationOfDistoX.Entite_Station, n - 1]);
      exit;
    end;
    MySerie.RemoveLastVisee();
    FProcPerformActionRecalcul(false);  // /!\ Recalcul indispensable !
  end;
end;




procedure TGHTopoContext2DA.acAddViseeRadianteHereExecute(Sender: TObject);
begin
  pass;
end;

procedure TGHTopoContext2DA.acAltitudeTNOverStationExecute(Sender: TObject);
var
  QLat, QAltTerrain, QAcc, QLon: double;
  QErrMsg: String;
  QMaillage: TMaillage;
begin
  QErrMsg := '';

  ConversionCoordonneesIsoleesEPSG(FDocuTopo.CodeEPSGSystemeCoordonnees.CodeEPSG, CODE_EPSG_WGS84,
                                   FStationNearToMouse.PosStation.X, FStationNearToMouse.PosStation.Y,
                                   QLat, QLon);
  QErrMsg := 'Requête service WXS de l''IGN';
  if (GetAltitudeByWebServiceIGN(QLat, QLon, QAltTerrain, QAcc) = 0) then
  begin
    ShowMessageFmt('St %d.%d - X = %.0f, Y = %.0f, Z = %.2f, Lat = %.5f, Lon = %.5f, TN = %.2f, Rec = %.2f',
    [FStationNearToMouse.Entite_Serie, FStationNearToMouse.Entite_Station,
     FStationNearToMouse.PosStation.X, FStationNearToMouse.PosStation.Y, FStationNearToMouse.PosStation.Z,
     QLat, QLon, QAltTerrain, QAltTerrain - FStationNearToMouse.PosStation.Z]);
  end else
  begin
    AfficherMessageErreur('Echec requête IGN - Tentative d''extraction depuis le MNT');
    if (FMyMaillageDisplayed) then
    begin
      QErrMsg := 'Extraction depuis le maillage courant';
      AfficherMessageErreur(' -- Maillage OK - Extraction depuis le MNT');
      QMaillage := FListeMaillages.GetElement(0);
      if (QMaillage.CalcAltitudeMaillageAtPoint(FStationNearToMouse.PosStation.X, FStationNearToMouse.PosStation.Y, QAltTerrain)) then
      begin
        ShowMessageFmt('St %d.%d - X = %.0f, Y = %.0f, Z = %.2f, Alt terrain = %.2f, Recouvrement = %.2f',
                       [FStationNearToMouse.Entite_Serie, FStationNearToMouse.Entite_Station,
                        FStationNearToMouse.PosStation.X, FStationNearToMouse.PosStation.Y, FStationNearToMouse.PosStation.Z,
                        QAltTerrain,
                        QAltTerrain - FStationNearToMouse.PosStation.Z])
      end;
    end else
    begin
      ShowMessageFmt('Echec en extraction de l''altitude (erreur lors de %s)', [QErrMsg]);
    end;
  end;
end;

procedure TGHTopoContext2DA.acBtnPanEEExecute(Sender: TObject);
begin
  QDeplacerVue(-1,  0);
end;

procedure TGHTopoContext2DA.acBtnPanNNExecute(Sender: TObject);
begin
  QDeplacerVue( 0, -1);
end;

procedure TGHTopoContext2DA.acBtnPanSSExecute(Sender: TObject);
begin
  QDeplacerVue( 0,  1);

end;

procedure TGHTopoContext2DA.acBtnPanWWExecute(Sender: TObject);
begin
  QDeplacerVue( 1,  0);
end;

procedure TGHTopoContext2DA.acCentrerMapAtCurrStationDistoXExecute(Sender: TObject);
begin
  CentrerVueSurPointXY(FCurrentStationOfDistoX.PosStation.X,
                       FCurrentStationOfDistoX.PosStation.Y,
                       True,
                       FCurrentStationOfDistoX.toString()
                       );
end;

procedure TGHTopoContext2DA.acClearListePointsCliquesExecute(Sender: TObject);
begin
  FListeLastPointsClicked.ClearListe();
end;

// behaviour of 'Continue here' of PocketTopo  - Même comportement que le 'Continuer ici' de PocketTopo
procedure TGHTopoContext2DA.acContinueHereExecute(Sender: TObject);
var
  QSerie: TObjSerie;
  QIdx, P: integer;
  QSR: TNumeroSerie;
  QPT: TNumeroStation;
begin
  if (FDocuTopo.GetSerieByNumeroSerie(FStationNearToMouse.Entite_Serie, QSerie, QIdx)) then
  begin
    if (not FDocuTopo.SerieConnectedAtOther(QSerie.NumeroDeSerie, QSR, QPT)) then
    begin
      P := QSerie.NbVisees - 1;
      SetCurrentStationOfDistoX(QSerie.NumeroDeSerie, P); // SetCurrentStationOfDistoX() actualise aussi FCurrentStation;
      lbTitreQuickSaisieVisee.Caption := format('Point %d.%d', [QSerie.NumeroDeSerie, P+1]);
    end
    else
    begin
      ShowMessageFmt('La série %d aboutit en %d.%d - Ne peut être poursuivie', [QSerie.NumeroDeSerie, QSerie.NoSerieArr, QSerie.NoPointArr]);
    end;
  end;
end;

procedure TGHTopoContext2DA.acCreerEntreeFromThisPointExecute(Sender: TObject);
var
  EE: TEntrance;
  WU: String;
begin
  WU := FStationNearToMouse.toString();
  EE.setFrom('Entrance ' + WU, WU,
             FStationNearToMouse.PosStation,
             FStationNearToMouse.Entite_Serie, FStationNearToMouse.Entite_Station,
             clRed, '', True, tteGROTTE);
  if (EditerEntrance(FDocuTopo, FDocuTopo.NbEntrances, FBDDEntites, nil, EE)) then
  begin
    FDocuTopo.AddEntrance(EE);
    self.SetModeTravail(mtREADY);
  end;
end;

procedure TGHTopoContext2DA.acDefineEndSerieAsEntranceExecute(Sender: TObject);
var
  NS: TNumeroSerie;
  MySerie: TObjSerie;
  QIdx: integer;
begin
  NS := FStationNearToMouse.Entite_Serie;
  if (NS < 1) then exit;
  if (FDocuTopo.GetSerieByNumeroSerie(NS, MySerie, QIdx)) then
  begin
    MySerie.SetObstacle(10);
  end;
end;

procedure TGHTopoContext2DA.acDefinirPointButExecute(Sender: TObject);
var
  EWE: TPoint3Df;
  QCaption: String;
  QIdx: integer;
  BP: TBaseStation;
  EN: TEntrance;
  Dist: double;
  ER: TBDDEntitesFindViseeOrEntrance;
begin
  EWE.setFrom(FMyPos.X, FMyPos.Y, FCurrentStation.PosStation.Z);
  QCaption := 'Point But';
  if (FBDDEntites.GetStationOrEntranceFromXYZ(FMyPos.X, FMyPos.Y, 0.00, 20.00, [tpVISEES, tpENTRANCES], True, QIdx, BP, EN, Dist, ER)) then
  begin
    case ER of
      tpVISEES:
        begin
          EWE := BP.PosStation;
          QCaption:= BP.getLibelleStationTopo();
       end;
     tpENTRANCES:
       begin
         EWE := EN.ePosition;
         QCaption := EN.toTToporobotIDStation().ToString();
       end;
    end;
  end;
  if (InputTPoint3Df(FBDDEntites, rsCDR_VUE2D_AC_DEFINE_POINT_BUT, EWE, QCaption)) then
  begin
    FPointBut.setFrom(EWE, QCaption);
    Vue.Invalidate;
  end;
end;

procedure TGHTopoContext2DA.acDeleteThisPOIExecute(Sender: TObject);
var
  SR: TObjSerie;
  ST: TUneVisee;
  QIdx: integer;
  EWE: String;
begin
  if (FDocuTopo.GetSerieByNumeroSerie(FStationNearToMouse.Entite_Serie, SR, QIdx)) then
  begin
    ST := SR.GetVisee(FStationNearToMouse.Entite_Station);
    EWE := StringReplace(ST.Commentaires, KEYWORD_POI_TODO, '', [rfIgnoreCase, rfReplaceAll]);
    if (GHTopoInputQuery('Supprimer un POI', Format('Nouveau commentaire pour %d.%d', [FStationNearToMouse.Entite_Serie, FStationNearToMouse.Entite_Station]), EWE, pnmALL)) then
    begin
      ST.Commentaires := EWE;
      SR.PutVisee(FStationNearToMouse.Entite_Station, ST);
      ShowMessage('Recalculer le réseau pour actualiser la liste des POIs');
    end;
  end
  else
  begin
    ShowMessage('Station introuvable');
  end;
end;

procedure TGHTopoContext2DA.acDisplayMiniformViseeExecute(Sender: TObject);
begin
  pnlQuickSaisieVisee.Visible := Not pnlQuickSaisieVisee.Visible;
end;

procedure TGHTopoContext2DA.acDisplayVoisinageCurrStationExecute(Sender: TObject);
begin
  DisplayNearStationsAtPoint(FDocuTopo, FBDDEntites, FStationNearToMouse.Entite_Serie, FStationNearToMouse.Entite_Station, RAYON_CAPTURE_NEAR_STATIONS);
end;

procedure TGHTopoContext2DA.acDispPopUpExecute(Sender: TObject);
var
  PP: TPoint;
begin
  // La fenêtre contenant le cadre
  //                       >le conteneur du cadre (pnlPlan)
  //                       |     >l'onglet (tabVueEnPlan)
  //                       |     |     > le PageControl
  //                       |     |     |      > La fenêtre principale
  //                       |     |     |      |
  //                       V     V     V      V
  //MyGHTopoMainWnd := self.Parent.Parent.Parent.Parent;
  (*
  MyPnlPlan       := self.Parent;
  MyTabShtPlan    := MyPnlPlan.Parent;
  MyPageCtrl      := MyTabShtPlan.Parent;
  MyGHTopoMainWnd := MyPageCtrl.Parent;
  EWE := Format('%s: %d, %d' + #10, [MyPnlPlan.Name, MyPnlPlan.Left, MyPnlPlan.Top]);
  EWE += Format('%s: %d, %d' + #10, [MyTabShtPlan.Name, MyTabShtPlan.Left, MyTabShtPlan.Top]);
  EWE += Format('%s: %d, %d' + #10, [MyPageCtrl.Name, MyPageCtrl.Left, MyPageCtrl.Top]);
  EWE += Format('%s: %d, %d' + #10, [MyGHTopoMainWnd.Name, MyGHTopoMainWnd.Left, MyGHTopoMainWnd.Top]);
  //ShowMessage(EWE);
  QX := MyGHTopoMainWnd.Left +
        MyPageCtrl.Left +
        MyTabShtPlan.Left +
        MyPnlPlan.Left +
        self.Left +
        pnlVue.Left;
  QY := MyGHTopoMainWnd.Top +
        MyPageCtrl.Top +
        MyTabShtPlan.Top +
        MyPnlPlan.Top +
        self.Top +
        pnlVue.Top;
  PP := GetCoordsPlan(FCurrentStation.PosStation.X, FCurrentStation.PosStation.Y);
  PopUpCdrVue2D.PopUp(QX + PP.X, QY + PP.Y);
  //*)
  PP := GetCoordsPlan(FCurrentStation.PosStation.X, FCurrentStation.PosStation.Y);
  PP := pnlVue.ClientToScreen(PP);
  PopUpCdrVue2D.PopUp(PP.X, PP.Y);
end;

procedure TGHTopoContext2DA.acDistanceP2PExecute(Sender: TObject);
begin
  self.SetModeTravail(mtDISTANCE_PREMIER_POINT);
end;

procedure TGHTopoContext2DA.acEditThisStationExecute(Sender: TObject);
begin
  {$IFDEF GHTOPO_SIMPLIFIE}
  if (EditerVisee(FDocuTopo, FStationNearToMouse.Entite_Serie, FStationNearToMouse.Entite_Station)) then
  begin
    pass;
  end;
  {$ENDIF}
  pass;
end;

procedure TGHTopoContext2DA.acFiltrerSurLassoExecute(Sender: TObject);
begin
  SetModeTravail(TModesTravail.mtFILTRE_SUR_LASSO_SELECTION);
end;

procedure TGHTopoContext2DA.acGenererCoupesCTByPolygonExecute(Sender: TObject);
begin
  SetModeTravail(TModesTravail.mt4P_FIRST_POINT);
  //showmessage('Générer coupe by polygone');
end;

procedure TGHTopoContext2DA.acGenererCoupesCTByRectExecute(Sender: TObject);
var
  QMaillage: TMaillage;
begin
  QMaillage := FListeMaillages.GetElement(0);
  if (not QMaillage.IsValid) then
    ShowMessage(GetResourceString(rsMSG_INVALID_MAILLAGE))
  else
    SetModeTravail(mt3P_FIRST_POINT);
end;

procedure TGHTopoContext2DA.acGetStationCoordsExecute(Sender: TObject);
var
  EWE: String;
  MyClipBoard: TClipboard;
begin
  EWE := Format('%d.%d; %s; %s; %s; %s',
         [FStationNearToMouse.Entite_Serie, FStationNearToMouse.Entite_Station,
          FormatterNombreWithDotDecimal(FStationNearToMouse.PosStation.X),
          FormatterNombreWithDotDecimal(FStationNearToMouse.PosStation.Y),
          FormatterNombreWithDotDecimal(FStationNearToMouse.PosStation.Z),
          FStationNearToMouse.IDTerrain
         ]);
  InputQuery('', '', EWE);
  AfficherMessage(EWE);
  MyClipBoard := TClipboard.Create(ctClipBoard);
  try
    MyClipBoard.Clear;
    MyClipBoard.AsText := EWE;
  finally
    FreeAndNil(MyClipBoard);
  end;
end;

procedure TGHTopoContext2DA.acHighLightThisCodeExecute(Sender: TObject);
begin
  FBDDEntites.HighLightVisees(mslCODE, FStationNearToMouse.eCode);
  self.RefreshDessin();
end;

procedure TGHTopoContext2DA.acHighLightThisExpeExecute(Sender: TObject);
begin
  FBDDEntites.HighLightVisees(mslEXPE, FStationNearToMouse.eExpe);
  self.RefreshDessin();
end;

procedure TGHTopoContext2DA.acHighlightThisSerieExecute(Sender: TObject);
begin
  FBDDEntites.HighLightVisees(mslSERIE, FCurrentStation.Entite_Serie);
  self.RefreshDessin();
end;

procedure TGHTopoContext2DA.acMetaFiltreCodeExecute(Sender: TObject);
begin
  AppliquerLeFiltre(Format('%s=%d',[rsMETAFILTRE_CODE, FStationNearToMouse.eCode]));
end;

procedure TGHTopoContext2DA.acMetaFiltreEntreeExecute(Sender: TObject);
begin
  AppliquerLeFiltre(Format('%s=%d',[rsMETAFILTRE_ENTREE_RATT, FStationNearToMouse.eEntrance]));
end;
procedure TGHTopoContext2DA.acMetaFiltreExpeExecute(Sender: TObject);
begin
  AppliquerLeFiltre(Format('%s=%d',[rsMETAFILTRE_EXPE, FStationNearToMouse.eExpe]));
end;


procedure TGHTopoContext2DA.acMetaFiltreReseauExecute(Sender: TObject);
begin
  AppliquerLeFiltre(Format('%s=%d',[rsMETAFILTRE_RESEAU, FStationNearToMouse.eReseau]));
end;



procedure TGHTopoContext2DA.acMetaFiltreSecteurExecute(Sender: TObject);
begin
  AppliquerLeFiltre(Format('%s=%d',[rsMETAFILTRE_SECTEUR, FStationNearToMouse.eSecteur]));

end;

procedure TGHTopoContext2DA.AppliquerLeFiltre(const FF: string);
var
  QListeSeriesCapturees: TArrayOfIntegers;
begin
  ApplyMetaFiltre(True, FF);
  if (Trim(FF) <> '') then FBDDEntites.RecenserSeriesCaptureesParFiltre(QListeSeriesCapturees);
  AfficherMessageErreur('Séries: ' + QListeSeriesCapturees.toCondensedString(true, ';'));
  self.RefreshDessin();
end;

procedure TGHTopoContext2DA.DisplayFullInfosStation(const S: TBaseStation);
var
  RS: TReseau;
  SC: TSecteur;
  QAltitudeEpicentre: double;
  L, A, P, G,D,H,B, dx, dy, dz: Double;
  EWE: TExpe;
  beuh: TPoint3Df;
  CDS: TCode;
  QIdxSerie: Integer;
  MySerie: TObjSerie;
  WU: String;
  QEntree: TEntrance;
  QMaillage: TMaillage;
begin
  dx := S.PosStation.X - S.PosExtr0.X;
  dy := S.PosStation.Y - S.PosExtr0.Y;
  dz := S.PosStation.Z - S.PosExtr0.Z;
  L := 0.00; A := 0.00; P := 0.00;
  GetBearingIncFromDeltaXYZ(360.0,360.0, dx, dy, dz, L,A,P);
  lbInfos.Caption:=Format(rsVUE2D_FMT_INFOS_STATION_RAPIDE,
                            [S.IDTerrain,
                             S.Entite_Serie,
                             S.Entite_Station,
                             S.PosStation.X,
                             S.PosStation.Y,
                             S.PosStation.Z]);
  lbNumSerie.Caption         := IntToStr(S.Entite_Serie);
  lbNumStation.Caption       := IntToStr(S.Entite_Station);
  lbIDTerrainStation.Caption := S.IDTerrain;
  if (S.Type_Entite in [tgENTRANCE, tgFIXPOINT]) then
  begin
    lbInfosExpe.Caption     := '';
    lbMesures.Caption       := '';
    lbCouleurVisee.Color    := S.CouleurStd;
    lbColorReseau.Color     := clGray;
    lbColorReseau.Caption   := '';
    lbNomReseau.Caption     := '';
    lbColorSecteur.Color    := clGray;
    lbColorSecteur.Caption  := '';
    lbNomSecteur.Caption    := '';
    lbColorSeance.Color     := clGray;
    lbColorSeance.Caption   := '';
    lbCode.Caption          := '';
  end
  else
  begin
    G := Hypot2D((S.PosPG.X - S.PosStation.X), (S.PosPG.Y - S.PosStation.Y));
    D := Hypot2D((S.PosPD.X - S.PosStation.X), (S.PosPD.Y - S.PosStation.Y));
    H := S.PosPD.Z - S.PosStation.Z;
    B := S.PosStation.Z - S.PosPG.Z;

    RS  := FBDDEntites.GetReseau(S.eReseau);
    SC  := FBDDEntites.GetSecteur(S.eSecteur);
    EWE := FBDDEntites.GetExpeByIndex(S.eExpe);
    CDS := FBDDEntites.GetCodeByIndex(S.eCode);
    lbCouleurVisee.Color    := S.CouleurStd;
    lbColorReseau.Color     := RS.ColorReseau;
    lbColorReseau.Caption   := Format(FORMAT_NB_INTEGER,[S.eReseau]);
    lbNomReseau.Caption     := _AnsiToLCLStr(RS.NomReseau);
    lbColorSecteur.Color    := SC.CouleurSecteur;
    lbColorSecteur.Caption  := Format(FORMAT_NB_INTEGER,[S.eSecteur]);
    lbNomSecteur.Caption    := _AnsiToLCLStr(SC.NomSecteur);
    lbColorSeance.Color     := FBDDEntites.GetCouleurEntiteByIdxExpe(S.eExpe);
    lbColorSeance.Caption   := Format(FORMAT_NB_INTEGER,[S.eExpe]);
    // infos sur le code
    lbCode.Caption          := CDS.getLibelle();
    // infos sur l'expé
    WU                      := EWE.getDateStr();
    lbInfosExpe.Caption     := EWE.getLibelle();
    lbMesures.Caption       := Format(rsVUE2D_FMT_INFOS_MESURES, [L, A, P, G,D,H,B]);
    // infos sur la série
    if (S.Type_Entite = tgVISEE_RADIANTE) then
    begin
      lbSerie.Caption := Format('Antenne #%d', [S.Entite_Serie]);
    end
    else                // Pascal programming language    I'm the author 650.000 lines
    begin
      QIdxSerie := FDocuTopo.GetIdxSerieByNumero(S.Entite_Serie);
      if (QIdxSerie > 0)  then
      begin
        MySerie := FDocuTopo.GetSerie(QIdxSerie);
        lbSerie.Caption := Format('Serie %d - %s', [MySerie.NumeroDeSerie, _AnsiToLCLStr(MySerie.NomSerie)]);
      end;
    end;
  end;
  // coordonnées
  beuh := FBDDEntites.GetDeltaXYZFromPositionSt0(S);
  lbCoordonnees.Caption := Format(rsVUE2D_FMT_INFOS_COORDONNEES, [
                                 FormatterNombreAvecSepMilliers(S.PosStation.X),
                                 FormatterNombreAvecSepMilliers(S.PosStation.Y),
                                 FormatterNombreAvecSepMilliers(S.PosStation.Z),
                                 FormatterNombreAvecSepMilliers(beuh.X),
                                 FormatterNombreAvecSepMilliers(beuh.Y),
                                 FormatterNombreAvecSepMilliers(beuh.Z)
                                 ]);
  // entrée de rattachement
  QEntree := FBDDEntites.GetEntrance(S.eEntrance);
  lbEntreeRattachement.Caption := format('%d - %s', [S.eEntrance, QEntree.eNomEntree]);
  // distances au versant si maillage OK
  QMaillage := FListeMaillages.GetElement(0);
  if (QMaillage.IsValid) then
  begin
    if (QMaillage.CalcAltitudeMaillageAtPoint(S.PosStation.X, S.PosStation.Y, QAltitudeEpicentre)) then
      lbMiscMaillage.Caption := format('Altitude surface à l''aplomb: %.2f - Dist: %.2f m',
                                       [QAltitudeEpicentre, QAltitudeEpicentre - S.PosStation.Z])
    else
      lbMiscMaillage.Caption := 'Station hors maillage';
  end
  else
  begin
    lbMiscMaillage.Caption := 'Aucun maillage valide';
  end;
end;

procedure TGHTopoContext2DA.DisplayQuickAddVisee(const OS: TObjSerie);
begin
  pass;
end;
//******************************************************************************
procedure TGHTopoContext2DA.VueMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
  // pointer la station la plus proche
  QDist, QDistance      : double;
  QQ1, QQ2              , QIsStation: Boolean;
  QOffset, qC1, qC2, qD : TPoint2Df;
  QPP1                  : TPoint3DF;
  ST1, ST2              : TBaseStation;
  MyAnnotation          : TKrobardAnnotation;
  DbgTag, WU            , QSvgProfilName: String;
  MySerieARaccorder     : TObjSerie;
  QIdx, QLastIdxPolyline, IdxMaillage: integer;
  QEntrance1            , QEntrance2: TEntrance;
  QE: TBDDEntitesFindViseeOrEntrance;
  QAT: TLineAttributes;
  QListeSeriesCapturees: TArrayOfIntegers;
  EWE: TActionAfterDispDistance;
  CC: TCode;
  MySymbole: TKrobardSymbole;
  QMaillage: TMaillage;
  MyProfilTopo: TProfilTopo;
  procedure ReinitFZC(const P: TMousePointToReset);
    procedure QCreateTPoint(var PC0: TPoint2Df; var PP0: TPoint);
    begin
      PC0 := FMyPos;
      PP0.Create(X, Y);
    end;
  begin
    if (DoFC1 in P) then QCreateTPoint(FZC1, FZP1);
    if (DoFC2 in P) then QCreateTPoint(FZC2, FZP2);
    if (DoPT1 in P) then QCreateTPoint(FPC1, FPP1);
    if (DoPT2 in P) then QCreateTPoint(FPC2, FPP2);
    if (DoPT3 in P) then QCreateTPoint(FPC3, FPP3);
    if (DoPT4 in P) then QCreateTPoint(FPC4, FPP4);
  end;
begin
  if (not FCanDraw) then Exit;
  if (not FBDDEntites.GetStationOrEntranceFromXYZ(FMyPos.X, FMyPos.Y, 0.0, MAX_DISTANCE_CAPTURE,
                                                 [tpVISEES],
                                                  false,
                                                  FCurrentInternalIdxEntite, FStationNearToMouse, QEntrance1, QDistance, QE)) then Exit;
  //// si Shift est enfoncé,
  acSetCurrentBaseStation.Caption := Format('Définir %d.%d comme station courante', [FStationNearToMouse.Entite_Serie, FStationNearToMouse.Entite_Station]);
  SetStationInfo(FStationNearToMouse);

  if ((ssShift in Shift) and (ssCtrl in Shift)) then
  begin
    FBDDEntites.HighLightVisees(mslSERIE, FStationNearToMouse.Entite_Serie);
    self.RefreshDessin();
    exit;
  end;
  if (pnlFullInfos.Visible) then DisplayFullInfosStation(FStationNearToMouse);
  case FModesTravail of
    mtREADY:
      begin
        if (ssShift in Shift) then
        begin
          ReinitFZC([DoFC1, DoFC2, DoPT1, DoPT2, DoPT3, DoPT4]);
          SetModeTravail(mtPAN_SECOND_POINT);
        end;
        if (ssCtrl  in Shift) then
        begin
          ReinitFZC([DoFC1, DoFC2, DoPT1, DoPT2, DoPT3, DoPT4]);
          SetModeTravail(mtZOOM_SECOND_COIN);
        end;
        AddNewPointClicked(FMyPos.X, FMyPos.Y);

      end;
    mtPAN_PREMIER_POINT:
      begin
        ReinitFZC([DoFC1, DoFC2, DoPT1, DoPT2, DoPT3, DoPT4]);
        SetModeTravail(mtPAN_SECOND_POINT);
      end;
    mtPAN_SECOND_POINT:
      begin
        ReinitFZC([DoFC2, DoPT1, DoPT2, DoPT3, DoPT4]);
        QOffset.setFrom(FZC2.X - FZC1.X, FZC2.Y - FZC1.Y);
        DeplacerVue(QOffset.X, QOffset.Y, True);
        self.RefreshDessin();
        SetModeTravail(mtREADY);
      end;
    mtZOOM_PREMIER_COIN:
      begin
        ReinitFZC([DoFC1, DoFC2, DoPT1, DoPT2, DoPT3, DoPT4]);
        SetModeTravail(mtZOOM_SECOND_COIN);
      end;
    mtZOOM_SECOND_COIN:
      begin
        ReinitFZC([DoFC2, DoPT1, DoPT2, DoPT3, DoPT4]);
        DbgTag := Format('%s: %s', [{$I %FILE%}, {$I %LINE%}]);
        SetViewLimits(FZC1.X, FZC1.Y, FZC2.X, FZC2.Y, DbgTag, True);
        self.RefreshDessin();
        SetModeTravail(mtREADY);
      end;
    mtMETAFILTRE_ZONE_PREMIER_COIN:
      begin
        ReinitFZC([DoFC1, DoFC2, DoPT1, DoPT2, DoPT3, DoPT4]);
        SetModeTravail(mtMETAFILTRE_ZONE_SECOND_COIN);
      end;
    mtMETAFILTRE_ZONE_SECOND_COIN:
      begin
        ReinitFZC([DoFC2, DoPT1, DoPT2, DoPT3, DoPT4]);
        qC1.setFrom(FZC1.X, FZC1.Y);
        qC2.setFrom(FZC2.X, FZC2.Y);
        qD.setFrom(qC2.X - qC1.X, qc2.Y - qC1.Y);
        // si zone trop étroite, abandonner
        if (Abs(qD.X) < 1.00) or (Abs(qD.Y) < 1.00) then Exit;
        // échanger de manière à rendre indifférent le sens du rectangle de sélection
        if (qC2.X < qC1.X) then Swap(qC1.X, qC2.X);
        if (qC2.Y < qC1.Y) then Swap(qC1.Y, qC2.y);
        AppliquerLeFiltre(Format('COORD_X=(%.2f, %.2f) & COORD_Y=(%.2f, %.2f)', [qC1.X, qC2.X, qC1.Y, qC2.Y]));
        QListeSeriesCapturees.ReDim(0);
        FBDDEntites.RecenserSeriesCaptureesParFiltre(QListeSeriesCapturees);
        SetModeTravail(mtREADY);
      end;
    mtDISTANCE_PREMIER_POINT:
      begin
        ReinitFZC([DoFC1, DoFC2, DoPT1, DoPT2, DoPT3, DoPT4]);
        SetModeTravail(mtDISTANCE_SECOND_POINT);
      end;
    mtDISTANCE_SECOND_POINT:
      begin
        ReinitFZC([DoFC2, DoPT1, DoPT2, DoPT3, DoPT4]);
        SetModeTravail(mtREADY);
        QQ1 := FBDDEntites.GetStationOrEntranceFromXYZ(FZC1.X, FZC1.Y, 0.00, MAX_DISTANCE_CAPTURE, [tpVISEES], false, FCurrentInternalIdxEntite, ST1, QEntrance1, QDistance, QE);
        QQ2 := FBDDEntites.GetStationOrEntranceFromXYZ(FZC2.X, FZC2.Y, 0.00, MAX_DISTANCE_CAPTURE, [tpVISEES], false, FCurrentInternalIdxEntite, ST2, QEntrance2, QDistance, QE);
        if (QQ1 and QQ2) then
        begin
          DispDistanceEntreStations(FDocuTopo, FBDDEntites, FGraphe, ST1, ST2, [], FDoIncludeVerticales);
          self.RefreshDessin();
          if (Assigned(FProcTransmitTwoBasePoint)) then FProcTransmitTwoBasePoint(ST1, ST2);
        end;
      end;
    mtADD_SERIE_PREMIER_POINT:
      begin
        ReinitFZC([DoFC1, DoFC2, DoPT1, DoPT2, DoPT3, DoPT4]);
        SetModeTravail(mtADD_SERIE_SECOND_POINT);
      end;
    mtADD_SERIE_SECOND_POINT:
      begin
        ReinitFZC([DoFC2, DoPT1, DoPT2, DoPT3, DoPT4]);
        SetModeTravail(mtREADY);
        QQ1 := FBDDEntites.GetStationOrEntranceFromXYZ(FZC1.X, FZC1.Y, 0.00, MAX_DISTANCE_CAPTURE, [tpVISEES], false, FCurrentInternalIdxEntite, ST1, QEntrance1, QDistance, QE);
        QQ2 := FBDDEntites.GetStationOrEntranceFromXYZ(FZC2.X, FZC2.Y, 0.00, MAX_DISTANCE_CAPTURE, [tpVISEES], false, FCurrentInternalIdxEntite, ST2, QEntrance2, QDistance, QE);
        if (QQ1 and QQ2) then
        begin
          pass;
          //ShowMessage('Insérer une série');
          //AfficherDistanceEntreStations(FBDDEntites, ST1, ST2);
        end;
      end;
    mtBOUCLER_PREMIER_POINT: // Raccordement graphique du terminus d'un cheminement sur une station d'un autre cheminement
      begin
        ReinitFZC([DoFC1, DoFC2, DoPT1, DoPT2, DoPT3, DoPT4]);
        SetModeTravail(mtBOUCLER_SECOND_POINT);
      end;
    mtBOUCLER_SECOND_POINT:
      begin
        ReinitFZC([DoFC2, DoPT1, DoPT2, DoPT3, DoPT4]);
        SetModeTravail(mtREADY);
        QQ1 := FBDDEntites.GetStationOrEntranceFromXYZ(FZC1.X, FZC1.Y, 0.00, MAX_DISTANCE_CAPTURE, [tpVISEES], false, FCurrentInternalIdxEntite, ST1, QEntrance1, QDistance, QE);
        QQ2 := FBDDEntites.GetStationOrEntranceFromXYZ(FZC2.X, FZC2.Y, 0.00, MAX_DISTANCE_CAPTURE, [tpVISEES], false, FCurrentInternalIdxEntite, ST2, QEntrance2, QDistance, QE);
        if (QQ1 and QQ2) then
        begin
          // Inclure les verticales
          EWE := DispDistanceEntreStations(self.FDocuTopo, self.FBDDEntites, FGraphe, ST1, ST2, [aadCREATE_LOOP], TRUE);
          if ((aadCREATE_LOOP in EWE) and (FDocuTopo.GetSerieByNumeroSerie(ST1.Entite_Serie, MySerieARaccorder, QIdx))) then
          begin
            QDist := DistanceBetweenTwoTPoint3Df(ST1.PosStation, ST2.PosStation);

            WU    := 'Accrocher le point:' + #10 + ST1.DebugString() + #10 +
                               'Au point:' + #10 + ST2.DebugString() + #10 +
                     Format('Distance: %.3f m', [QDist]);
            if (GHTopoQuestionOuiNon(WU)) then
            begin
              MySerieARaccorder.SetSeriePtArr(ST2.Entite_Serie, ST2.Entite_Station);
              if (aadADD_SHOT in EWE) then
              begin
                CC := FDocuTopo.GetCodeByNumero(0);
                MySerieARaccorder.AddViseeBetweenTwoBasepoints(CC, ST1, ST2, 'From graphic input');
              end;
              if (GHTopoQuestionOuiNon('Le réseau va être recalculé')) then
              begin
                if (assigned(FProcPerformActionRecalcul)) then FProcPerformActionRecalcul(False);
              end;
            end;
          end;
        end;
      end;
    // Extraction d'un profil MNT
    mtPROFIL_MNT_PREMIER_POINT:
      begin
        ReinitFZC([DoFC1, DoFC2, DoPT1, DoPT2, DoPT3, DoPT4]);
        SetModeTravail(mtPROFIL_MNT_SECOND_POINT);
      end;
    mtPROFIL_MNT_SECOND_POINT:
      begin
        ReinitFZC([DoFC2, DoPT1, DoPT2, DoPT3, DoPT4]);
        SetModeTravail(mtREADY);
        QQ1 := FBDDEntites.GetStationOrEntranceFromXYZ(FZC1.X, FZC1.Y, 0.00, MAX_DISTANCE_CAPTURE, [tpVISEES], false, FCurrentInternalIdxEntite, ST1, QEntrance1, QDistance, QE);
        QQ2 := FBDDEntites.GetStationOrEntranceFromXYZ(FZC2.X, FZC2.Y, 0.00, MAX_DISTANCE_CAPTURE, [tpVISEES], false, FCurrentInternalIdxEntite, ST2, QEntrance2, QDistance, QE);
        if (QQ1 and QQ2) then
        begin
          MyProfilTopo := TProfilTopo.Create;
          MyProfilTopo.Initialiser(FZC1, FZC2);
          FListeMaillages.AddProfilTopo(MyProfilTopo);
          QSvgProfilName := format('SVGProfil%d', [FListeMaillages.GetNbProfilsTopo() - 1]);
          QAT.SetAttributes(QSvgProfilName, clRed, 192, 1, 0.015);
          FListeMaillages.ExtractProfilTraceFromAllMaillages(MyProfilTopo, FZC1, FZC2, QAT, QSvgProfilName);

          self.RefreshDessin();
        end;
      end;
    mtNEW_ANNOTATION:
      begin
        if (not FCroquisTerrain.IsReady) then exit;
        QQ1 := FBDDEntites.GetStationOrEntranceFromXYZ(FMyPos.X, FMyPos.Y, 0.00, MAX_DISTANCE_CAPTURE, [tpVISEES], false, FCurrentInternalIdxEntite, ST1, QEntrance1, QDistance, QE);
        MyAnnotation.IDStyle := FCroquisTerrain.CurrentIdxStyleAnnotation;
        MyAnnotation.Position.IDBaseStation := ST1.toTIDBaseStation(); //MakeTIDBaseStation(ST1.Entite_Serie, ST1.Entite_Station, false);
        MyAnnotation.Position.Offset.setFrom(FMyPos.X - ST1.PosStation.X, FMyPos.Y - ST1.PosStation.Y, ST1.PosStation.Z);
        MyAnnotation.Texte  := '';
        if (EditerAnnotation(FCroquisTerrain, FDocuTopo, 0, MyAnnotation)) then
        begin
          SetModeTravail(mtREADY);
          FCroquisTerrain.AddAnnotation(MyAnnotation);
          MyAnnotation := FCroquisTerrain.getLastAnnotation();
          self.RefreshDessin();
        end;
      end;
    mtNEW_SYMBOLE:
     begin
        if (not FCroquisTerrain.IsReady) then exit;
        QQ1 := FBDDEntites.GetStationOrEntranceFromXYZ(FMyPos.X, FMyPos.Y, 0.00, MAX_DISTANCE_CAPTURE, [tpVISEES], false, FCurrentInternalIdxEntite, ST1, QEntrance1, QDistance, QE);
        // S'il y a déjà des symboles, on attrappe le dernier pour paramétrer le symbole à ajouter
        if (FCroquisTerrain.GetNbSymboles() > 0) then
        begin
          MySymbole := FCroquisTerrain.getLastSymbole();
        end
        else
        begin // on définit un symbole par défaut: le gros bloc
          MySymbole.Dimensions.setFrom(1.01, 1.02);
          MySymbole.NatureSymbole := nosBLOC;
        end;
        // Position du nouveau symbole
        MySymbole.Position.IDBaseStation := ST1.toTIDBaseStation(); //MakeTIDBaseStation(ST1.Entite_Serie, ST1.Entite_Station, false);
        MySymbole.Position.Offset.setFrom(FMyPos.X - ST1.PosStation.X, FMyPos.Y - ST1.PosStation.Y, ST1.PosStation.Z);

        if (EditerSymbole(FCroquisTerrain, 0, MySymbole)) then
        begin

          FCroquisTerrain.AddSymbole(MySymbole);
          MySymbole := FCroquisTerrain.getLastSymbole();
          AfficherMessageErreur('%d symboles - Dernier: %s, basepoint: %s',
                        [FCroquisTerrain.GetNbSymboles(),
                         MySymbole.DescribeKrobardNatureSymbole(),
                         MakeLabelStationFromTIDBaseStation(MySymbole.Position.IDBaseStation)]);
          SetModeTravail(mtNEW_SYMBOLE);   // et on est prêt pour un autre symbole
          self.RefreshDessin();
        end;
        //EditerSymbole(const FC: TCroquisTerrain; const QIdx: integer; var AN: TKrobardSymbole
     end;
    mtNEW_POLYLINE:
      begin
        if (not FCroquisTerrain.IsReady) then exit;
        QLastIdxPolyline := FCroquisTerrain.FindIdxPolylineWithExtr2NearToXY(FMyPos.X, FMyPos.Y, 0.50);
        QPP1.setFrom(FMyPos.X, FMyPos.Y, 0.00);
        FCroquisTerrain.BeginPolyline(QPP1, QLastIdxPolyline);
      end;
    mtDELETE_ANNOTATION:
      begin
        if (FCroquisTerrain.TryDeleteAnnnotationNearToXY(FMyPos)) then self.RefreshDessin();
      end;
    mtDELETE_SYMBOLE:
      begin
        if (FCroquisTerrain.TryDeleteSymboleNearToXY(FMyPos)) then self.RefreshDessin();
      end;
    mtDELETE_POLYLINE:
      begin
        if (FCroquisTerrain.TryDeletePolylineNearToXY(FMyPos)) then self.RefreshDessin();
      end;
    mt3P_FIRST_POINT:
      begin
        ReinitFZC([DoPT1, DoPT2, DoPT3, DoPT4]);//false, false, True, True, True, True);
        SetModeTravail(mt3P_SECOND_POINT);
      end;
    mt3P_SECOND_POINT:
      begin
        ReinitFZC([DoPT2, DoPT3, DoPT4]); //false, false, false, True, True, True);
        SetModeTravail(mt3P_THIRD_POINT);
      end;
    mt3P_THIRD_POINT:
      begin
        DisplayCoupesTomographiques(FListeMaillages, FPC1, FPC2, FPC3, FPC4);
        self.RefreshDessin();
        SetModeTravail(mtREADY);
      end;
    // sélection d'un quadrilatère convexe
    mt4P_FIRST_POINT:
      begin
        FGhostPolygone.AddElement(FMyPos);
        SetModeTravail(mt4P_SECOND_POINT);
      end;
    mt4P_SECOND_POINT:
      begin
        FGhostPolygone.AddElement(FMyPos);
        SetModeTravail(mt4P_THIRD_POINT);
      end;
    mt4P_THIRD_POINT:
      begin
        FGhostPolygone.AddElement(FMyPos);
        SetModeTravail(mt4P_FOURTH_POINT);
      end;

    mt4P_FOURTH_POINT:
      begin
        FGhostPolygone.AddElement(FMyPos);
        ShowMessage('Quadrilatere defini');
        DisplayCoupesTomographiques(FListeMaillages,
                                    FGhostPolygone.GetElement(0),
                                    FGhostPolygone.GetElement(1),
                                    FGhostPolygone.GetElement(2),
                                    FGhostPolygone.GetElement(3)
                                    );
        self.RefreshDessin();
        SetModeTravail(mtREADY);
      end;

    else
      pass;
  end;
end;

procedure TGHTopoContext2DA.VueMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
var
  PP: TPoint;
  QDistanceXYZ: Double;
  QDistance, QAzimut: double;
  QNb, QCurrentInternalIdxEntite: Integer;
  QEntrance1: TEntrance;

  QStationNearToMouse: TBaseStation;
  QE: TBDDEntitesFindViseeOrEntrance;
  QEtiquetteOk: Boolean;
  procedure DrawFantomePolyline();
  var
    i, Nb: Integer;
    QPM: TPoint3Df;
    QPP: TPoint;
  begin
    Vue.Canvas.Pen.Color := COLOR_GHOST_LINE;
    Vue.Canvas.Pen.Width := 1;
    if (not FCroquisTerrain.IsReady) then exit;
    Nb := FCroquisTerrain.GetNbBufferVertexes();
    if (Nb < 2) then Exit;
    QPM := FCroquisTerrain.GetBufferVertex(0);
    QPP := GetCoordsPlan(QPM.X, QPM.Y);
    Vue.Canvas.MoveTo(QPP);
    for i := 1 to Nb - 1 do
    begin
      QPM := FCroquisTerrain.GetBufferVertex(i);
      QPP := GetCoordsPlan(QPM.X, QPM.Y);
      Vue.Canvas.LineTo(QPP);
    end;
  end;


  procedure DrawLigne(const P1, P2: TPoint); inline;
  begin
    Vue.Canvas.Pen.Color := COLOR_GHOST_LINE;
    Vue.Canvas.Line(P1.X,P1.Y, P2.X, P2.Y);
  end;
  procedure DrawRectangle(const P1, P2: TPoint);
  begin
    Vue.Canvas.Pen.Color := COLOR_GHOST_LINE;
    Vue.Canvas.Brush.Style := bsClear;
    Vue.Canvas.Rectangle(P1.X, P1.Y, P2.X, P2.Y);
  end;
  procedure DrawRectangleBarre(const P1, P2: TPoint);
  begin
    Vue.Canvas.Pen.Color := COLOR_GHOST_LINE;
    Vue.Canvas.Brush.Style := bsClear;
    Vue.Canvas.Rectangle(P1.X, P1.Y, P2.X, P2.Y);
    Vue.Canvas.Line(P1.X, P1.Y, P2.X, P2.Y);
    Vue.Canvas.Line(P1.X, P2.Y, P2.X, P1.Y);
  end;

  // Code inactivé à conserver. Peut être réutilisé
  (*
  procedure DrawPolygonaleRapide();
  var
    QW, QH: Double;
    i, Nb: integer;
    QPP1, QPP2: TPoint;
    MyEntite: TBaseStation;

    QLimitesVue: TRect2Df;
    FP1, FP2: TPoint2Df;
    qdx, qdy: LongInt;
  begin
    Nb := FBDDEntites.NbEntitesVisees;
    // lenteurs constatées sur certains postes => au delà d'un certain seuil, on ne trace rien
    if (Nb > 40000) then Exit;
    // pour éviter de voir la silhouette fantome tronquée, on spécifie une marge
    // autour du rectangle de vue initial égale au déplacement maximal possible de la vue
    // ie: marges horizontales = largeur de la fenêtre vue initiale
    //     marges verticales   = hauteur de la fenêtre vue initiale
    QW := FRegionCMaxi.X - FRegionCMini.X;
    QH := FRegionCMaxi.Y - FRegionCMini.Y;
    QLimitesVue := MakeTRect2Df(FRegionCMini.X - QW, FRegionCMini.Y - QH,
                                FRegionCMaxi.X + QW, FRegionCMaxi.Y + QH);
    for i := 0 to Nb - 1 do
    begin
      MyEntite := FBDDEntites.GetEntiteVisee(i);
      if (Not (MyEntite.Type_Entite in [tgVISEE_RADIANTE, tgENTRANCE, tgFIXPOINT])) then
      begin
        FP1 := MakeTPoint2Df(MyEntite.PosExtr0.X, MyEntite.PosExtr0.Y);
        FP2 := MakeTPoint2Df(MyEntite.PosStation.X, MyEntite.PosStation.Y);
        if (SegmentInRectangle(FP1, FP2, QLimitesVue)) then
        begin
          QPP1 := GetCoordsPlan(FP1);
          QPP2 := GetCoordsPlan(FP2);
          //DrawLigne(QPP1, QPP2);
          qdx := FZP2.X - FZP1.X;
          qdy := FZP2.Y - FZP1.Y;
          QPP1.X := QPP1.X + qdx;
          QPP1.Y := QPP1.Y + qdy;
          QPP2.X := QPP2.X + qdx;
          QPP2.Y := QPP2.Y + qdy;
          DrawLigne(QPP1, QPP2);
        end;
      end;
    end;
  end;
  //*)
  procedure DrawShapeElastique(const QX, QY: integer; const SType: byte; const DoDrawSilhouetteFantome: boolean = false);
    procedure CuiCui();
    begin
      case SType of
        1: DrawLigne(FZP1, FZP2); //if (DoDrawSilhouetteFantome) then DrawPolygonaleRapide();
        2: DrawRectangle(FZP1, FZP2);
        3: DrawRectangleBarre(FZP1, FZP2);
        4: pass;
      end;
    end;
  begin
    CuiCui();
    FZP2.Create(QX, QY);
    CuiCui();
  end;
  procedure DrawLineElastique2(const QX, QY: integer; const SType: byte);
    procedure CuiCui();
    begin
      case SType of
       1: DrawLigne(FPP1, FPP2); //if (DoDrawSilhouetteFantome) then DrawPolygonaleRapide();
       2: DrawLigne(FPP2, FPP3);
      end;
    end;
  begin
    CuiCui();
    case SType of
      1: FPP2.Create(QX, QY);
      2: FPP3.Create(QX, QY);
    end;
    CuiCui();
  end;

  procedure DrawRectangle2();
  var
    PT: TGeoPoint2D;
    S12: TGeoSegment2D;
    d: TGeoFloat;
    Alpha: Float;
    dx, dy: ValReal;
    procedure CuiCui();
    begin
      DrawLigne(FPP1, FPP2);
      DrawLigne(FPP2, FPP3);
      DrawLigne(FPP3, FPP4);
      DrawLigne(FPP4, FPP1);
      DrawLigne(FPP1, FPP3);
      DrawLigne(FPP2, FPP4);
    end;
  begin
    CuiCui();
      PT.X := FMyPos.X; PT.Y := FMyPos.Y;
      S12[1].x := FPC1.X; S12[1].Y := FPC1.Y;
      S12[2].x := FPC2.X; S12[2].Y := FPC2.Y;
      d := Distance(PT, S12);
      Alpha := arctan2(FPC2.Y-FPC1.Y, FPC2.X - FPC1.X) + PI/2;
      dx := d * cos(Alpha);
      dy := d * sin(Alpha);
      FPC3.setFrom(FPC2.X + dx, FPC2.Y + dy);
      FPP3 := GetCoordsPlan(FPC3.X, FPC3.Y);
      FPC4.setFrom(FPC1.X + dx,
                   FPC1.Y + dy);
      FPP4 := GetCoordsPlan(FPC4.X, FPC4.Y);
    CuiCui();
  end;
  procedure DrawGhostPolygone();
  var
    p, Nb: Integer;
    QPM: TPoint2Df;
    QPP: TPoint;
  begin
    Nb := FGhostPolygone.GetNbElements();
    AfficherMessageErreur('DrawGhostPolygone(): %d', [Nb]);
    if (Nb < 2) then exit;
    Vue.Canvas.Pen.Color := COLOR_GHOST_LINE;
    Vue.Canvas.Pen.Width := 1;
    QPM := FGhostPolygone.GetElement(0);
    QPP := GetCoordsPlan(QPM.X, QPM.Y);
    Vue.Canvas.MoveTo(QPP);
    for p := 1 to Nb - 1 do
    begin
      QPM := FGhostPolygone.GetElement(p);
      QPP := GetCoordsPlan(QPM.X, QPM.Y);
      Vue.Canvas.LineTo(QPP);
    end;

  end;
begin
  QDistanceXYZ := 0.00;
  QAzimut      := 0.00;
  if (not FCanDraw) then Exit;
  PP.Create(X, Y);
  FMyPos   := GetCoordsMonde(PP);

  QEtiquetteOk := (FBDDEntites.GetStationOrEntranceFromXYZ(FMyPos.X, FMyPos.Y, 0.00, MAX_DISTANCE_CAPTURE, [tpVISEES], false, QCurrentInternalIdxEntite, QStationNearToMouse, QEntrance1, QDistanceXYZ, QE));
  lbGCSMouse.Caption := Format('%s, %s [%s]', [FormatterNombreAvecSepMilliers(FMyPos.X, 0), FormatterNombreAvecSepMilliers(FMyPos.Y, 0),
                                               QStationNearToMouse.toString()]);
  Vue.Canvas.Pen.Color := clSilver;
  Vue.Canvas.Pen.Width := 0;
  case FModesTravail of
    mtREADY:
      begin
        FZP1.Create(X, Y);
        FZP2.Create(X, Y);
      end;
    mtPAN_SECOND_POINT           : DrawShapeElastique(X, Y, 1, True);
    mtBOUCLER_SECOND_POINT       : DrawShapeElastique(X, Y, 1);
    mtDISTANCE_SECOND_POINT      :
      begin
        DrawShapeElastique(X, Y, 1);
        QDistance := hypot(FMyPos.X - FZC1.X, FMyPos.Y - FZC1.Y);
        QAzimut   := GetAzimut(FMyPos.X - FZC1.X, FMyPos.Y - FZC1.Y, DEGRES_PAR_TOUR);
        lbAdditionalInfos.Caption := Format('%.2f m < %.2f°', [QDistance, QAzimut]);
      end;
    mtADD_SERIE_SECOND_POINT      : DrawShapeElastique(X, Y, 1);
    mtZOOM_SECOND_COIN            : DrawShapeElastique(X, Y, 2);
    mtMETAFILTRE_ZONE_SECOND_COIN : DrawShapeElastique(X, Y, 3);
    mtPROFIL_MNT_SECOND_POINT     : DrawShapeElastique(X, Y, 1);
    mtNEW_POLYLINE, mtFILTRE_SUR_LASSO_SELECTION:
      begin
        if (not FCroquisTerrain.IsReady) then exit;
        if (ssLeft in Shift) then
        begin
          FCroquisTerrain.AddVertexAtBuffer(FMyPos.X, FMyPos.Y);
          DrawFantomePolyline();
        end;
      end;
    mt3P_SECOND_POINT:  DrawLineElastique2(X, Y, 1);
    mt3P_THIRD_POINT :  DrawRectangle2();

    mt4P_SECOND_POINT,
    mt4P_THIRD_POINT ,
    mt4P_FOURTH_POINT:
      begin
        DrawGhostPolygone();
      end
  else
    pass;
  end;
end;

procedure TGHTopoContext2DA.VueMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
  MyPoly: TArrayOfPoints3Df;
  Metrage, QAire, QPerimetre: double;
  QFiltreExpr, EWE: string;
  QCentroid: TPoint3Df;
  QListeSeriesCapturees: TArrayOfIntegers;
begin
  QListeSeriesCapturees.ReDim(0); // pour initialiser proprement
  case FModesTravail of
    mtNEW_POLYLINE:
      begin
        if (not FCroquisTerrain.isReady) then exit;
        FCroquisTerrain.EndPolyline(True, MyPoly);
        SetModeTravail(mtNEW_POLYLINE); // on est pret pour une autre polyligne
        self.RefreshDessin();
      end;
    mtFILTRE_SUR_LASSO_SELECTION:
      begin
        if (not FCroquisTerrain.isReady) then exit;
        FCroquisTerrain.EndPolyline(false, MyPoly);
        FBDDEntites.FiltrerSurLassoSelection(MyPoly, Metrage, QFiltreExpr);
        FBDDEntites.RecenserSeriesCaptureesParFiltre(QListeSeriesCapturees);
        AfficherMessageErreur(QFiltreExpr);

        lbLongViseesVisibles.Caption := FormatterNombreAvecSepMilliers(Metrage, 0, 'm');
        EWE := QListeSeriesCapturees.toCondensedString(true, '; ');
        AfficherMessageErreur('Séries capturées par le lasso: ' + EWE);
        //DisplayTextEditorFromString(tehmNONE, 'Séries capturées par le filtre', EWE, True, True);

        (*
        // extraire valeurs
        AfficherMessageErreur('Interprétation du filtre');
        AfficherMessageErreur(QFiltreExpr);
        MyPoly.fromString(QFiltreExpr, ';', '|');
        Nb := MyPoly.getNbElements();

        AfficherMessageErreur('%d sommets - Tag: %s', [Nb, MyPoly.Tag]);
        //*)
        // périmètre, aire et centroid
        QAire      := MyPoly.CalcArea();
        QPerimetre := MyPoly.CalcPerimeter();
        QCentroid  := MyPoly.CalcCentroid();
        AfficherMessageErreur('Aire: %.2f, Perimetre: %.2f, Centroid: %.2f, %.2f', [ QAire, QPerimetre, QCentroid.X, QCentroid.Y]);

        editMetaFiltre.Text := QFiltreExpr;
        self.RefreshDessin();
        SetModeTravail(mtREADY);
      end
  else
    pass;
  end;
end;

// En Lazarus, cet événement n'est pas traité
procedure TGHTopoContext2DA.VueMouseWheel(Sender: TObject; Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
const
  Q_WHEEL_VERTICAL   = 0;
  Q_WHEEL_HORIZONTAL = 1;
  Q_WHEEL_ZOOM       = 2;
var
  QOnglet            : TVue2DParams;
  QModeDeplVue       : byte;
  QZoom, Qdx, Qdy, QR: double;
  DbgTag             : String;
begin
  // Valeur de WheelDelta retournée: = 120 ; négatif en tirant, positif en poussant
  // Utilisation des touches: convention Inkscape
  // Shift: []         = zoom
  //        [ssShift]  = pan horizontal
  //        [ssCtrl]   = pan vertical
  //QModeStr := 'Pan vertical';
  AfficherMessage('Wheel: ' + inttostr(WheelDelta));
  if      (ssShift in Shift) then QModeDeplVue := Q_WHEEL_HORIZONTAL  // pan H
  else if (ssCtrl  in Shift) then QModeDeplVue := Q_WHEEL_VERTICAL    // pan V
  else                            QModeDeplVue := Q_WHEEL_ZOOM;       // zoom
  QOnglet := FArrOngletsParams[FCurrIdxOnglet];
  QZoom := 1.00;
  Qdx   := 0.00;
  Qdy   := 0.00;
  QR    := Hypot2D(QOnglet.ongC2.X - QOnglet.ongC1.X, QOnglet.ongC2.Y - QOnglet.ongC1.Y);
  case QModeDeplVue of
    Q_WHEEL_VERTICAL:
    begin
      Qdy := FRACTION_OF_EXTENSION * QR * IIF(WheelDelta < 0, -1.0, 1.0);
      DbgTag := Format('%s: %s', [{$I %FILE%}, {$I %LINE%}]);
      SetViewLimits(QOnglet.ongC1.X, QOnglet.ongC1.Y + Qdy,
                    QOnglet.ongC2.X, QOnglet.ongC2.Y + Qdy,
                    DbgTag);
    end;
    Q_WHEEL_HORIZONTAL:
    begin
      Qdx := FRACTION_OF_EXTENSION * QR * IIF(WheelDelta < 0, -1.0, 1.0);
      DbgTag := Format('%s: %s', [{$I %FILE%}, {$I %LINE%}]);
      SetViewLimits(QOnglet.ongC1.X + Qdx, QOnglet.ongC1.Y,
                    QOnglet.ongC2.X + Qdx, QOnglet.ongC2.Y,
                    DbgTag);
    end;
    Q_WHEEL_ZOOM:
    begin
      QZoom := 1 + FRACTION_OF_EXTENSION * IIF(WheelDelta < 0, -1.0, 1.0);
      ZoomFact(QZoom);
    end;
  end;
  self.RefreshDessin();
end;

procedure TGHTopoContext2DA.VuePaint(Sender: TObject);
begin
  RedessinEcran(Vue.Width, Vue.Height, False);
end;



//******************************************************************************
// lbFiltres
procedure TGHTopoContext2DA.ApplyMetaFiltre(const B: boolean; const QFiltre: string);
var
  FF: String;
  QDevelViseesVisibles: double;
  EWE: TCaption;
  QColor: TColor;
  QListeSeriesCapturees: TArrayOfIntegers;
begin
  QListeSeriesCapturees.ReDim(0);
  QDevelViseesVisibles := 0.00;
  Application.ProcessMessages;
  try
    EWE    := lbInfos.Caption;
    QColor := lbInfos.Color;
    lbInfos.Caption := 'Calcul du filtre en cours';
    lbInfos.Color   := clYellow;
    Application.ProcessMessages;
    AfficherMsgClass(self, 'ApplyMetaFiltre()');
    FArrOngletsParams[FCurrIdxOnglet].ongVueFiltres := QFiltre;
    FF := BoolToStr(B, QFiltre, '');
    FBDDEntites.MetaFiltre(FF, QDevelViseesVisibles);     // traitements
    lbLongViseesVisibles.Caption := FormatterNombreAvecSepMilliers(QDevelViseesVisibles, 0, 'm');// Format(FORMAT_NB_REAL_0_DEC, [QDevelViseesVisibles]);
    FBDDEntites.SetMinMax(False);   // reset Mini et Maxi sans utilisation du filtre
    self.RefreshDessin();
    editMetaFiltre.Text := QFiltre; // restauration du filtre
    // recensement des séries recoupées par le filtre
    FBDDEntites.RecenserSeriesCaptureesParFiltre(QListeSeriesCapturees);
    AfficherMessageErreur('Séries: ' + QListeSeriesCapturees.toCondensedString(true, ';'));
  finally
    lbinfos.Caption := EWE;
    lbInfos.Color   := QColor;
  end;
end;

function TGHTopoContext2DA.getMetaFiltre(): string;
begin
  result := editMetaFiltre.text;
end;

procedure TGHTopoContext2DA.setDoIncludeVerticales(const B: boolean);
begin
  FDoIncludeVerticales := B;
end;

function TGHTopoContext2DA.GetDrwCoinBasGauche(): TPoint2Df;
begin
  Result  := FArrOngletsParams[FCurrIdxOnglet].ongC1;
end;

function TGHTopoContext2DA.GetDrwCoinHautDroit(): TPoint2Df;
begin
  Result  := FArrOngletsParams[FCurrIdxOnglet].ongC2;

end;


procedure TGHTopoContext2DA.btnApplyMetaFiltreClick(Sender: TObject);
begin
  ApplyMetaFiltre(chkMetaFiltreActif.Checked, trim(editMetaFiltre.Text));
end;

procedure TGHTopoContext2DA.btnAz1Click(Sender: TObject);
begin
  DispClavierNumForBTN(btnAz1,  'Azimut: Lecture 2')
end;

procedure TGHTopoContext2DA.btnCancelLastClick(Sender: TObject);
begin
  pass;
end;

procedure TGHTopoContext2DA.btnCommentaireClick(Sender: TObject);
var
  EWE: TCaption;
begin
  EWE := btnCommentaire.Caption;
  if (DisplayClavierVirtuel('Commentaire', 'Observ.', EWE)) then btnCommentaire.Caption := EWE;
end;


procedure TGHTopoContext2DA.btnCopyCoordsClick(Sender: TObject);
var
  MyClipBoard: TClipboard;
begin
  MyClipBoard := TClipboard.Create();
  try
    MyClipBoard.AsText := lbCoordonnees.Caption;
  finally
    FreeAndNil(MyClipBoard);
  end;
end;

procedure TGHTopoContext2DA.btnDefinirCommeActiveClick(Sender: TObject);
begin
  SetCurrentStation(FStationNearToMouse, True);
end;

procedure TGHTopoContext2DA.btnHelpMetaFiltreClick(Sender: TObject);
begin
  //DisplayHelpSystem('METAFILTRE');
  DisplayToast('toto');
end;

procedure TGHTopoContext2DA.btnHNClick(Sender: TObject);
begin
  DispClavierNumForBTN(btnHN, 'Distance sol');
end;

procedure TGHTopoContext2DA.btnHZClick(Sender: TObject);
begin
  DispClavierNumForBTN(btnHZ, 'Distance plafond');
end;

procedure TGHTopoContext2DA.btnIncl0Click(Sender: TObject);
begin
  DispClavierNumForBTN(btnIncl0,  'Pente: Lecture 1');
end;

procedure TGHTopoContext2DA.btnIncl1Click(Sender: TObject);
begin
  DispClavierNumForBTN(btnIncl1, 'Pente: Lecture 2');
end;

procedure TGHTopoContext2DA.btnLDClick(Sender: TObject);
begin
  DispClavierNumForBTN(btnLD, 'Distance droite');
end;

procedure TGHTopoContext2DA.btnLGClick(Sender: TObject);
begin
  DispClavierNumForBTN(btnLG, 'Distance gauche');
end;

procedure TGHTopoContext2DA.btnListeProfilsClick(Sender: TObject);
begin
  DisplayListeProfilsTopo(FDocuTopo, FBDDEntites, FListeMaillages);
  self.RefreshDessin();
end;

procedure TGHTopoContext2DA.DispClavierNumForBTN(const MyButton: TButton; const QCaption: string);
var
  EWE: TCaption;
begin
  EWE := MyButton.Caption;
  if (DisplayClavierNumerique(QCaption, TPaveNumModeSaisie.pnmREAL, EWE)) then MyButton.Caption := EWE;
end;

procedure TGHTopoContext2DA.btnLong0Click(Sender: TObject);
begin
  DispClavierNumForBTN(btnLong0,  'Longueur: Lecture 1');
end;

procedure TGHTopoContext2DA.btnLong1Click(Sender: TObject);
begin
  DispClavierNumForBTN(btnLong1,  'Longueur: Lecture 2');
end;

//******************************************************************************
// gestion des onglets
procedure TGHTopoContext2DA.PutOngletByIndex(const Idx: integer; const O: TVue2DParams; const DoRedrawVue: boolean);
var
  DbgTag: String;
begin
  FArrOngletsParams[Idx] := O;
  editMetaFiltre.Text := O.ongVueFiltres;
  DbgTag := Format('%s: %s', [{$I %FILE%}, {$I %LINE%}]);
  if (DoRedrawVue) then  // acquittement par redessin
  begin
    FBDDEntites.CalcCouleursByDepth(O.ongDegradeReseaux.ColorMini, O.ongDegradeReseaux.ColorMaxi);
    SetViewLimits(O.ongC1, O.ongC2, DbgTag);
    self.RefreshDessin();
  end;
end;

procedure TGHTopoContext2DA.SetHighLightCurrentItem(const B: boolean);
begin
  if (FDoHighLightCurrentItem <> B) then
  begin
    FDoHighLightCurrentItem := B;
    self.RefreshDessin();
  end;
end;

procedure TGHTopoContext2DA.SetCurrentIdxOnglet(const Idx: byte);
var
  FCurrentOnglet: TVue2DParams;
  DbgTag: String;
begin
  {$IFNDEF GHTOPO_SIMPLIFIE}
  FCurrIdxOnglet := Idx;
  {$ENDIF GHTOPO_SIMPLIFIE}
  FCurrentOnglet := FArrOngletsParams[FCurrIdxOnglet];
  // mise à jour du champ lbFiltres
  AppliquerLeFiltre(FArrOngletsParams[FCurrIdxOnglet].ongVueFiltres);
  // recadrage
  DbgTag := Format('%s: %s', [{$I %FILE%}, {$I %LINE%}]);
  SetViewLimits(FCurrentOnglet.ongC1, FCurrentOnglet.ongC2, DbgTag);
  // recalculer les dégradés
  FBDDEntites.CalcCouleursByDepth(FCurrentOnglet.ongDegradeReseaux.ColorMini, FCurrentOnglet.ongDegradeReseaux.ColorMaxi);
  self.RefreshDessin();
end;

function TGHTopoContext2DA.GetNbOngletsVues(): integer;
begin
  Result := 1 + High(FArrOngletsParams);
end;

function TGHTopoContext2DA.GetCurrentOngletIdx(): integer;
begin
  Result := FCurrIdxOnglet;
end;

function TGHTopoContext2DA.GetOngletByIndex(const Idx: integer): TVue2DParams;
begin
  Result := FArrOngletsParams[Idx];
end;

function TGHTopoContext2DA.GetCurrentOnglet(): TVue2DParams;
begin
  Result := FArrOngletsParams[FCurrIdxOnglet];
end;

procedure TGHTopoContext2DA.SetNomOnglet(const S: string);
begin
  FArrOngletsParams[FCurrIdxOnglet].ongName := S;
end;

procedure TGHTopoContext2DA.SetProcUseMetaFiltre(const P: TProcedureOfObject);
begin
  FProcUseMetaFiltre := P;
end;
function TGHTopoContext2DA.GetNomOnglet(): string;
begin
  Result := FArrOngletsParams[FCurrIdxOnglet].ongName;
end;
procedure TGHTopoContext2DA.SetBackGrdColorOnglet(const QIdxOnglet: integer; const Col: TColor);
begin
  FArrOngletsParams[QIdxOnglet].ongBackGround := Col;
end;

function TGHTopoContext2DA.GetLargeurViseesOngletInPX(const QIdxOnglet: integer): integer;
begin
  result := FArrOngletsParams[QIdxOnglet].ongLineCenterlines.LineWidthInPixels;
end;

function TGHTopoContext2DA.GetLargeurViseesOngletInMM(const QIdxOnglet: integer): double;
begin
  result := FArrOngletsParams[QIdxOnglet].ongLineCenterlines.LineWidthInMillimeters;
end;
procedure TGHTopoContext2DA.SetLargeurViseesOngletInPX(const QIdxOnglet: integer; const qL: integer);
begin
  FArrOngletsParams[QIdxOnglet].ongLineCenterlines.LineWidthInPixels := qL;
end;
procedure TGHTopoContext2DA.SetLargeurViseesOngletInMM(const QIdxOnglet: integer; const qL: double);
begin
  FArrOngletsParams[QIdxOnglet].ongLineCenterlines.LineWidthInMillimeters := qL;
end;
function TGHTopoContext2DA.GetBackGrdColorOnglet(const QIdxOnglet: integer): TColor;
begin
  Result := FArrOngletsParams[QIdxOnglet].ongBackGround;
end;

//******************************************************************************
// quadrillage
procedure TGHTopoContext2DA.SetQuadrillageSpc(const Spacing: double);
begin
  //FQuadrillageSpc := Spacing;
  FArrOngletsParams[FCurrIdxOnglet].ongQdrSpc := Spacing;
end;

function TGHTopoContext2DA.GetQuadrillageSpc(): double;
begin
  Result := FArrOngletsParams[FCurrIdxOnglet].ongQdrSpc;
end;

procedure TGHTopoContext2DA.SetQuadrillageColor(const Col: TColor);
begin
  if (Col <> FArrOngletsParams[FCurrIdxOnglet].ongQdrColor) then  FArrOngletsParams[FCurrIdxOnglet].ongQdrColor := Col;
end;

function TGHTopoContext2DA.GetQuadrillageColor(): TColor;
begin
  Result := FArrOngletsParams[FCurrIdxOnglet].ongQdrColor;
end;

procedure TGHTopoContext2DA.SetElementsDrawn(const ED: TSetElementsDrawn);
begin
  FArrOngletsParams[FCurrIdxOnglet].ongElementsDrawn := ED;
end;

function  TGHTopoContext2DA.GetElementsDrawn(): TSetElementsDrawn;
begin
  Result := FArrOngletsParams[FCurrIdxOnglet].ongElementsDrawn;
end;

procedure TGHTopoContext2DA.QSetPen(const QPM: TPenMode; const QPS: TPenStyle; const QPW: integer; const QPC: TColor);
 begin
   Vue.Canvas.Pen.Mode := QPM;
   Vue.Canvas.Pen.Style:= QPS;
   Vue.Canvas.Pen.Color:= QPC;
   Vue.Canvas.Pen.Width:= QPW;
 end;
 procedure TGHTopoContext2DA.Rearmer();
 begin
   FAttenduSecondPoint        := False;
   QSetPen(pmCopy, psSolid, 0, clGray);
 end;
procedure TGHTopoContext2DA.SetModeTravail(const MT: TModesTravail);
begin
  if (MT = FModesTravail) then Exit; // ne rien faire si le mode de travail est inchangé

  if ((not FCroquisTerrain.IsReady) AND // ne rien faire ici si le croquis de terrain est KO
      (MT in [mtNEW_ANNOTATION, mtSELECT_ANNOTATION, mtDELETE_ANNOTATION,
              mtNEW_POLYLINE, mtADD_VERTEX_POLYLINE, mtDELETE_POLYLINE,
              mtNEW_SYMBOLE, mtSELECT_SYMBOLE, mtDELETE_SYMBOLE,
              mtFILTRE_SUR_LASSO_SELECTION])) then
  begin
    ShowMessage('Croquis de terrain HS');
    Exit;
  end;
  FModesTravail := MT;
  case FModesTravail of
     mtREADY: Rearmer();
     mtPAN_PREMIER_POINT,
     mtDISTANCE_PREMIER_POINT,
     mtADD_SERIE_PREMIER_POINT,
     mtBOUCLER_PREMIER_POINT,
     mtPROFIL_MNT_PREMIER_POINT:
      begin
        FZC1 := FMyPos;
        FZC2 := FMyPos;
        FAttenduSecondPoint := false;
        QSetPen(pmCopy, psSolid, 0, COLOR_GHOST_LINE);
      end;


      mtPAN_SECOND_POINT,
      mtDISTANCE_SECOND_POINT,
      mtADD_SERIE_SECOND_POINT,
      mtBOUCLER_SECOND_POINT,
      mtPROFIL_MNT_SECOND_POINT:
      begin
        FAttenduSecondPoint := true;
        QSetPen(pmNotXor, psSolid, 0, COLOR_GHOST_LINE); // pmNotXor;
      end;
      mtZOOM_PREMIER_COIN, mtMETAFILTRE_ZONE_PREMIER_COIN:
      begin
        FZC1 := FMyPos;
        FZC2 := FMyPos;
        FAttenduSecondPoint := false;
        QSetPen(pmCopy, psSolid, 0, COLOR_GHOST_LINE);
      end;
      mtZOOM_SECOND_COIN, mtMETAFILTRE_ZONE_SECOND_COIN:
      begin
        FAttenduSecondPoint := true;
        QSetPen(pmNotXor, psSolid, 0, COLOR_GHOST_LINE);// pmNotXor;
      end;

      mt3P_FIRST_POINT:
      begin
        FPC1 := FMyPos;
        FPC2 := FMyPos;
        FPC3 := FMyPos;

        FAttenduSecondPoint := false;
        QSetPen(pmCopy, psSolid, 0, COLOR_GHOST_LINE);// pmNotXor;
      end;
      mt3P_SECOND_POINT:
      begin
        FPC2 := FMyPos;
        FPC3 := FMyPos;
        FAttenduSecondPoint := true;
        QSetPen(pmNotXor, psSolid, 0, COLOR_GHOST_LINE);// pmNotXor;
      end;
      mt3P_THIRD_POINT:
      begin
        FAttenduSecondPoint := true;
        QSetPen(pmNotXor, psSolid, 0, COLOR_GHOST_LINE);// pmNotXor;
      end;
      mtFILTRE_SUR_LASSO_SELECTION:
      begin
        pass;
      end;

      mt4P_FIRST_POINT:
      begin
        FPC1 := FMyPos;
        FPC2 := FMyPos;
        FPC3 := FMyPos;
        FPC4 := FMyPos;


        FGhostPolygone.ClearListe();
        FAttenduSecondPoint := false;
      end;
      mt4P_SECOND_POINT:
      begin
        FAttenduSecondPoint := true;
      end;

      mt4P_THIRD_POINT:
      begin
        FAttenduSecondPoint := true;
      end;
      mt4P_FOURTH_POINT:
      begin
        FAttenduSecondPoint := true;
      end;

  else
    pass;
  end;
  lbModeTravail.Caption    := ChooseString(Ord(MT),
                                       ['READY',                         //TModesTravail  = (mtREADY,
                                        'PAN_PT1', 'PAN_PT2',                    // mtPAN_PREMIER_POINT, mtPAN_SECOND_POINT,
                                        'ZOOM_C1', 'ZOOM_C2',                  //mtZOOM_PREMIER_COIN, mtZOOM_SECOND_COIN,
                                        'MF_ZONE', 'MF_ZONE',            //mtMETAFILTRE_ZONE_PREMIER_COIN, mtMETAFILTRE_ZONE_SECOND_COIN,
                                        'DISTANCE_PT1', 'DISTANCE_PT2',          //mtDISTANCE_PREMIER_POINT, mtDISTANCE_SECOND_POINT,
                                        'ADD_SERIE_PT1', 'ADD_SERIE_PT2', // mtADD_SERIE_PREMIER_POINT, mtADD_SERIE_SECOND_POINT,
                                        'RACCORDER_PT1', 'RACCORDER_PT2', //mtBOUCLER_PREMIER_POINT, mtBOUCLER_SECOND_POINT
                                        'PROFIL_MNT_PT1','PROFIL_MNT_PT1', //mtPROFIL_MNT_PREMIER_POINT, mtPROFIL_MNT_SECOND_POINT
                                        'ADD_NOTE', 'SELECT_NOTE', 'DEL_NOTE',  //mtNEW_ANNOTATION, mtSELECT_ANNOTATION, mtDELETE_ANNOTATION,
                                        'ADD_POLY', 'SELECT_POLY', 'DEL_POLY',
                                        'ADD_SYM', 'SELECT_SYM', 'DEL_SYM', // mtNEW_SYMBOLE, mtSELECT_SYMBOLE, mtDELETE_SYMBOLE,               /
                                        '3P_FIRST_PT', '3P_SECOND_PT', '3P_THIRD_PT',
                                        'LASSO',
                                        '4P_FIRST_PT', '4P_SECOND_PT', '4P_THIRD_PT', '4P_FOURTH_POINT'
                                       ]);
  lbAdditionalInfos.Caption := ChooseString(Ord(MT),
                                       ['--',
                                        'Premier point', 'Second point',         // mtPAN_PREMIER_POINT, mtPAN_SECOND_POINT,
                                        'Premier coin', 'Second coin',           //mtZOOM_PREMIER_COIN, mtZOOM_SECOND_COIN,
                                        'Premier coin', 'Second coin',            //mtMETAFILTRE_ZONE_PREMIER_COIN, mtMETAFILTRE_ZONE_SECOND_COIN,
                                        'Premier point', 'Second point',           //mtDISTANCE_PREMIER_POINT, mtDISTANCE_SECOND_POINT,
                                        'Série à raccorder', 'Station de raccordement', // mtADD_SERIE_PREMIER_POINT, mtADD_SERIE_SECOND_POINT,
                                        'Premier point', 'Second point',              //mtBOUCLER_PREMIER_POINT, mtBOUCLER_SECOND_POINT
                                        'Premier point', 'Second point',             //mtPROFIL_MNT_PREMIER_POINT, mtPROFIL_MNT_SECOND_POINT
                                        'Au point', 'Pick near', 'Pick near',        //mtNEW_ANNOTATION, mtSELECT_ANNOTATION, mtDELETE_ANNOTATION,
                                        'Du point', 'Au point', 'Pick near',
                                        'Au point', 'Pick near', 'Pick near',
                                        'Premier point', 'Second point', 'Troisième point',
                                        'Au point'
                                       ]);
end;



//******************************************************************************
(*
procedure TGHTopoContext2DA.SetViewLimits(const X1, Y1, X2, Y2: double; const DebugTag: string; const QSaveView: boolean = false);
const
  Epsilon = 1e-2;
var
  qX1, qX2, qY1, qY2: double;
  d1, d2: double;
  R: TRect2Df;
  EWE: String;
  Nb, i: Integer;
  // cette fonction retourne d'autres paramètres
  function GetRYMaxi(): double;
  var
    qdx: Double;
  begin
    qdx := FRegionCMaxi.X - FRegionCMini.X;
    FRappHLVue      := Vue.Height / Vue.Width;            // rapport Hauteur/largeur de vue
    FRappScrReal    := Vue.Width / qdx;                   // rapport Ecran/Réel
    FInvRappScrReal := 1 / FRappScrReal;
    Result          := FRegionCMini.Y + qdx * FRappHLVue; // hauteur de visualisation
  end;
begin
  EWE := format( 'SetViewLimits(): %.2f, %.2f, %.2f, %.2f - Tag: %s - BZ: %s', [X1, Y1, X2, Y2, DebugTag, BoolToStr(QSaveView, 'OUI', 'NON')]);
  AfficherMsgClass(self, EWE);
  AfficherMsgErrClass(self, EWE);
  qX1 := Min(X1, X2);
  qY1 := Min(Y1, Y2);
  qX2 := Max(X1, X2);
  qY2 := Max(Y1, Y2);
  d1 := qX2 - qX1;
  d2 := qY2 - qY1;
  // si zone trop étroite, abandonner
  if (Abs(d1) < Epsilon) or (Abs(d2) < Epsilon) then Exit;
  // échanger de manière à rendre indifférent le sens du rectangle de sélection
  //if (qX2 < qX1) then Swap(qX1, qX2);
  //if (qY2 < qY1) then Swap(qY1, qY2);
  FRegionCMini.setFrom(qX1, qY1);
  FRegionCMaxi.setFrom(qX2, qY2);
  // Redéfinition de la hauteur maxi
  FRegionCMaxi.Y := GetRYMaxi();
  // on met tout çà dans l'onglet
  FArrOngletsParams[FCurrIdxOnglet].ongC1 := FRegionCMini;
  FArrOngletsParams[FCurrIdxOnglet].ongC2 := FRegionCMaxi;
  if (QSaveView) then
  begin
    R.setFrom(FRegionCMini, FRegionCMaxi);
    FBufferRegionsZoom.AddAtCircularBuffer(R);



  end;
  // Afficher le buffer circulaire
  Nb := FBufferRegionsZoom.GetNbElements();
  AfficherMessageErreur('%d éléments dans le buffer circulaire', [Nb]);
  for i := 0 to Nb-1 do
  begin
    R := FBufferRegionsZoom.GetElement(i);
    AfficherMessageErreur('  %d: %s', [i, R.DebugString('C1', 'C2')]);
  end;
end;
//*)

// Version ChatGPT: Fonctionne mieux ;)
procedure TGHTopoContext2DA.SetViewLimits(const X1, Y1, X2, Y2: double; const DebugTag: string; const QSaveView: boolean = false);
const
  Epsilon = 1e-2;
var
  qX1, qX2, qY1, qY2: double;
  dReelX, dReelY: double;
  CenterX, CenterY: Double;
  AspectVue, AspectReel: Double;
  R: TRect2Df;
  Nb, i: Integer;
begin
  qX1 := Min(X1, X2);
  qX2 := Max(X1, X2);
  qY1 := Min(Y1, Y2);
  qY2 := Max(Y1, Y2);

  dReelX := qX2 - qX1;
  dReelY := qY2 - qY1;

  if (Abs(dReelX) < Epsilon) or (Abs(dReelY) < Epsilon) then Exit;

  CenterX := 0.50 * (qX1 + qX2);
  CenterY := 0.50 * (qY1 + qY2);

  AspectVue  := Vue.Height / Vue.Width;
  AspectReel := dReelY / dReelX;

  if (AspectReel > AspectVue) then dReelX := dReelY / AspectVue        // Trop haut → élargir en X pour respecter le ratio
                              else dReelY := dReelX * AspectVue;

  FRegionCMini.setFrom(CenterX - dReelX / 2, CenterY - dReelY / 2);
  FRegionCMaxi.setFrom(CenterX + dReelX / 2, CenterY + dReelY / 2);

  // Mise à jour des rapports
  FRappHLVue      := Vue.Height / Vue.Width;
  FRappScrReal    := Vue.Width / (FRegionCMaxi.X - FRegionCMini.X);
  FInvRappScrReal := 1 / FRappScrReal;
  // On met tout çà dans l'onglet
  FArrOngletsParams[FCurrIdxOnglet].ongC1 := FRegionCMini;
  FArrOngletsParams[FCurrIdxOnglet].ongC2 := FRegionCMaxi;
  if (QSaveView) then
  begin
    R.setFrom(FRegionCMini, FRegionCMaxi);
    FBufferRegionsZoom.AddAtCircularBuffer(R);
  end;
  // Afficher le buffer circulaire
  Nb := FBufferRegionsZoom.GetNbElements();
  AfficherMessageErreur('%d éléments dans le buffer circulaire', [Nb]);
  for i := 0 to Nb-1 do
  begin
    R := FBufferRegionsZoom.GetElement(i);
    AfficherMessageErreur('  %d: %s', [i, R.DebugString('C1', 'C2')]);
  end;

  if (assigned(FOnViewChanged)) then FOnViewChanged(self);
end;

//*)
procedure TGHTopoContext2DA.SetViewLimits(const QCoinBasGauche, QCointHautDroit: TPoint2Df; const DebugTag: string; const QSaveView: boolean = false);
begin
  SetViewLimits(QCoinBasGauche.X, QCoinBasGauche.Y, QCointHautDroit.X, QCointHautDroit.Y, DebugTag, QSaveView);
end;

procedure TGHTopoContext2DA.SetViewLimits(const R: TRect2Df; const DebugTag: string; const QSaveView: boolean);
begin
  SetViewLimits(R.X1, R.Y1, R.X2, R.Y2, DebugTag, QSaveView);
end;




procedure TGHTopoContext2DA.DeplacerVue(const QOffsetX, QOffsetY: double; const QSaveView: boolean);
var
  DbgTag: String;
begin
  DbgTag := Format('%s: %s', [{$I %FILE%}, {$I %LINE%}]);
  SetViewLimits(FRegionCMini.X - QOffsetX, FRegionCMini.Y - QOffsetY,
                FRegionCMaxi.X - QOffsetX, FRegionCMaxi.Y - QOffsetY,
                DbgTag, QSaveView);
end;


function TGHTopoContext2DA.GetViewLimitsFromOnglet(const QIdxOnglet: integer; out QCoinBasGauche, QCointHautDroit: TPoint2Df): boolean;
var
  EWE: TVue2DParams;
begin
  result := false;
  try
    EWE := FArrOngletsParams[QIdxOnglet];
    QCoinBasGauche  := EWE.ongC1;
    QCointHautDroit := EWE.ongC2;
    result := true;
  except
    pass;
  end;
end;



//******************************************************************************
//******************************************************************************
function TGHTopoContext2DA.GetCoordsMonde(const PP: TPoint): TPoint2Df;
begin
  Result.X :=  FInvRappScrReal * PP.X + FRegionCMini.X;
  Result.Y := -FInvRappScrReal * PP.Y + FRegionCMaxi.Y;
end;



function TGHTopoContext2DA.GetCoordsPlan(const QX, QY: double): TPoint;
begin
  Result.X := Round((QX - FRegionCMini.X) * FRappScrReal);
  Result.Y := Round((FRegionCMaxi.Y - QY) * FRappScrReal);
end;
function TGHTopoContext2DA.GetCoordsPlan(const PM: TPoint2Df): TPoint;
begin
  Result := GetCoordsPlan(PM.X, PM.Y);
end;

//******************************************************************************
procedure TGHTopoContext2DA.DeplacerVueInt(const DepX, DepY: integer; const QSaveView: boolean);
var
  P : TPoint;
  dq, dx, dy: Double;
  DbgTag: String;
begin
  P.X := 0;
  P.Y := 0;
  FZC1:= GetCoordsMonde(P);
  P.X := Vue.Width   ; P.Y:=Vue.Height;
  FZC2:= GetCoordsMonde(P);

  dq  := 0.02 * (FZC2.X - FZC1.X);
  dx  :=  dq * DepX;
  dy  := -dq * DepY;
  DbgTag := Format('%s: %s', [{$I %FILE%}, {$I %LINE%}]);
  SetViewLimits(FRegionCMini.X + dx, FRegionCMini.Y + dy,
                FRegionCMaxi.X + dx, FRegionCMaxi .Y+ dy,
                DbgTag);
  self.RefreshDessin();
end;


procedure TGHTopoContext2DA.SetModeSelectionListe(const M: TModeSelectionListe);
begin
  FModeSelectionListe := M;
end;
//------------------------------------------------------------------------------
// centrer le dessin sur le point trouvé
procedure TGHTopoContext2DA.CentrerVueSurPointXY(const QX, QY: double; const DoMarker: boolean; const LbMarker: string);
var
  C0, Offset: TPoint2Df;
  DbgTag: String;
  MK: TGHTopoMarker;
begin
  try
    C0.setFrom(0.5 * (FRegionCMini.X + FRegionCMaxi.X),
               0.5 * (FRegionCMini.Y + FRegionCMaxi.Y));
    Offset.X := QX - C0.X;
    Offset.Y := QY - C0.Y;
    DbgTag := Format('%s: %s', [{$I %FILE%}, {$I %LINE%}]);
    SetViewLimits(FRegionCMini.X + Offset.X, FRegionCMini.Y + Offset.Y,
                  FRegionCMaxi.X + Offset.X, FRegionCMaxi.Y + Offset.Y,
                  DbgTag);
    MK.setFrom(DoMarker, QX, QY, clAqua, LbMarker);
    SetMarker(MK);
    RedessinEcran(Vue.Width, Vue.Height, False);
  except
  end;
end;
procedure TGHTopoContext2DA.SetMarker(const M: TGHTopoMarker);
begin
  FCurrentMarker := M;
end;


procedure TGHTopoContext2DA.lbInfosClick(Sender: TObject);
begin
  pnlFullInfos.Visible := not pnlFullInfos.Visible;
  if (pnlFullInfos.Visible) then DisplayFullInfosStation(FStationNearToMouse);
end;

procedure TGHTopoContext2DA.pnlToastClick(Sender: TObject);
begin
  pass;
end;

procedure TGHTopoContext2DA.MetaFiltrer(const F: string);
var
  QDevelViseesVisibles: double;
  QListeSeriesCapturees: TArrayOfIntegers;
begin
  FBDDEntites.MetaFiltre(F, QDevelViseesVisibles);
  lbLongViseesVisibles.Caption := Format(FORMAT_NB_REAL_3_DEC, [QDevelViseesVisibles]);
  FBDDEntites.SetMinMax(False);
  RedessinEcran(Vue.Width, Vue.Height, True);
end;

//******************************************************************************
procedure TGHTopoContext2DA.ZoomFact(const Fact: double);
var
  ZoomCentre: TPoint2Df;
  RX, RY: Double;
  DbgTag: String;
begin
  // point central
  ZoomCentre.setFrom(0.50 * (FRegionCMaxi.X + FRegionCMini.X),
                     0.50 * (FRegionCMaxi.Y + FRegionCMini.Y));
  // demi-rayon
  RX := 0.50 * (FRegionCMaxi.X - FRegionCMini.X) / Fact;
  RY := 0.50 * (FRegionCMaxi.Y - FRegionCMini.Y) / Fact;
  DbgTag := Format('%s: %s', [{$I %FILE%}, {$I %LINE%}]);
  self.SetViewLimits(ZoomCentre.X - RX, ZoomCentre.Y - RY,
                     ZoomCentre.X + RX, ZoomCentre.Y + RY,
                     DbgTag);
  self.RefreshDessin();
end;

procedure TGHTopoContext2DA.LocaliserUneStation();
var
  EWE: String;
begin
  EWE := '';
  if (InputQuery('Localiser une station', 'ID station', EWE)) then LocaliserUneStationByCle(EWE);
end;
procedure TGHTopoContext2DA.LocaliserUneStationByCle(const K: string);
var
  EWE: String;
  QMyBaseStation: TBaseStation;
begin
  EWE := Trim(K);
  if (FBDDEntites.FindStationByCle(false, EWE, QMyBaseStation)) then
  begin
    EWE := QMyBaseStation.toString();
    self.CentrerVueSurPointXY(QMyBaseStation.PosStation.X, QMyBaseStation.PosStation.Y, True, EWE);
    self.SetCurrentStation(QMyBaseStation, True);
  end
  else
  begin
    ShowMessage(rsMATCHNOTFOUND);
  end;
end;


procedure TGHTopoContext2DA.CreerTimeLapse(const QDirectory: TStringDirectoryFilename);
var
  i: Integer;
  QMetaFiltre: String;
  QFilename  : string;
  MyExpe: TExpe;
  procedure CreerImg(const QFiltre: string; const QFileOutput: string);
  begin
    AfficherMessage('--> %s -> %s', [QFiltre, QFileOutput]);
    ApplyMetaFiltre(True, QMetaFiltre);
    //RedessinEcran(Vue.Width, Vue.Height, false, True, QFileOutput);
    Application.ProcessMessages;
  end;
begin
  AfficherMsgClass(self, 'CreerTimeLapse(): %s', [QDirectory]);
  ForceDirectories(QDirectory);         // forcer la création du dossier
  i := 0;
  while (i < FBDDEntites.NbExpes) do
  begin
    QMetaFiltre := Format('%s < %d', [rsMETAFILTRE_EXPE, i]);
    MyExpe    := FBDDEntites.GetExpe(i);
    QFilename := QDirectory + DirectorySeparator + Format('Img_Expe_%d_%s.png', [i, DatePascalToDateSQL(MyExpe.DateExpe)]);
    if (not FRedessinInProcess) then
    begin
      CreerImg(QMetaFiltre, QFilename);
      Inc(i);
    end
    else
      AfficherMessage('** Is busy **');
  end;
end;

function TGHTopoContext2DA.CroquisDeTerrainIsReady(): boolean;
begin
  Result := FCroquisTerrain.IsReady;
end;

procedure TGHTopoContext2DA.chkMetaFiltreActifChange(Sender: TObject);
begin
  ApplyMetaFiltre(chkMetaFiltreActif.Checked, trim(editMetaFiltre.Text));
end;

procedure TGHTopoContext2DA.editMetaFiltreKeyPress(Sender: TObject; var Key: char);
begin
  case Ord(Key) of
    VK_RETURN: ApplyMetaFiltre(chkMetaFiltreActif.Checked, trim(editMetaFiltre.Text));
    VK_F1    : DisplayHelpSystem('METAFILTRE');
  else;
    pass;
  end;
end;

//******************************************************************************
function TGHTopoContext2DA.GetPtrMaillage(const Idx: integer): TMaillage;
begin
  result := FListeMaillages.GetElement(Idx);
end;
procedure TGHTopoContext2DA.chkDrawMaillageChange(Sender: TObject);
var
  QMaillage: TMaillage;
begin
  FMyMaillageDisplayed := false;
  QMaillage := FListeMaillages.GetElement(0);
  FMyMaillageDisplayed := (chkDrawMaillage.Checked and QMaillage.IsValid);
  self.RefreshDessin();
end;



procedure TGHTopoContext2DA.acIsovaleurSurZStationExecute(Sender: TObject);
var
  QMaillage: TMaillage;
begin
  try
    QMaillage := FListeMaillages.GetElement(0);
    if (QMaillage.IsValid and FMyMaillageDisplayed) then
    begin
      editIsoValeur.Value := FCurrentStation.PosStation.Z;
      self.RefreshDessin();
    end;
  except
    pass;
  end;
end;

function TGHTopoContext2DA.ChargerMaillage(const QFileName: string; const P: TProcDisplayProgression): boolean;
var
  QMaillage: TMaillage;
begin
  AfficherMessage('Chargement du maillage ' + QFileName);
  QMaillage := FListeMaillages.GetElement(0);

  Result  := QMaillage.LoadMaillage(QFileName);
  if (Result) then
  begin
    FListeMaillages.CalcBoundingBoxOfAllMaillages();
    if ((FBDDEntites.NbEntitesVisees > 15000) or (QMaillage.GetNbVertex() > 20000)) then
    begin
      if (GHTopoQuestionOuiNon(rsMSG_WARN_LONG_PROCESS_CALC_RECOUVREMENT)) then CalculerRecouvrements(True, P);
    end;
    ActiverPanelMaillage();
  end;
end;
procedure TGHTopoContext2DA.ActiverPanelMaillage();
var
  QMaillage: TMaillage;
begin
  QMaillage := FListeMaillages.GetElement(0);
  if (QMaillage.IsValid) then
  begin
    btnLineContourColor.ButtonColor := QMaillage.ColorDefault;
    editLineContourOpacity.Value    := QMaillage.Opacity;
    pnlMaillage.Visible:= true;
    DisplayToast(format('Maillage "%s" valide, %d vertex, %d triangles', [QMaillage.NomDuMaillage, QMaillage.GetNbVertex(), QMaillage.GetNbTriangles()]));

  end;
end;

procedure TGHTopoContext2DA.CalculerRecouvrements(const B: boolean; const P: TProcDisplayProgression);
const
  QEtape = 'Calcul des recouvrements';
  QSTEP  = 100;
var
  i, Nb: Integer;
  EE: TBaseStation;
  function QCalcRecouvrement(const QE: TBaseStation): double;
  var
    QZ: double;
    QMaillage: TMaillage;
  begin
    Result := 0.00;
    if (not B) then exit;
    QMaillage := FListeMaillages.GetElement(0);
    if (QMaillage.CalcAltitudeMaillageAtPoint(QE.PosStation.X, QE.PosStation.Y, QZ)) then Result := QZ - QE.PosStation.Z;
  end;
begin
  Application.ProcessMessages;
  Nb := FBDDEntites.NbEntitesVisees;
  lbAdditionalInfos.Caption := QEtape;
  for i := 0 to Nb - 1 do
  begin
    EE := FBDDEntites.GetEntiteVisee(i);
    EE.Recouvrement := QCalcRecouvrement(EE);
    FBDDEntites.PutEntiteVisee(i, EE);
    if (i mod QSTEP = 0) then
    begin
      if (Assigned(P)) then P(QEtape, i, 0, Nb-1, QSTEP);
    end;
  end;
  lbAdditionalInfos.Caption := '--';
end;

function TGHTopoContext2DA.IsMaillageDisplayed(const Idx: integer): boolean;
var
  QMaillage: TMaillage;
begin
  QMaillage := FListeMaillages.GetElement(0);
  Result := (FMyMaillageDisplayed and QMaillage.IsValid);
end;
function TGHTopoContext2DA.IsMaillageValide(const Idx: integer): boolean;
begin
  Result := FListeMaillages.GetElement(Idx).IsValid;
end;

// Version actuelle de RedessinEcran
procedure TGHTopoContext2DA.QMakePlan(const OutputBuffer: TGHTopoDrawingContext;
                                      const Onglet      : TVue2DParams;
                                      const ImgWidth, ImgHeight: integer;
                                      const DoRecalcDegrades: boolean);
var
  CDS: TGHTopoColor;
  procedure QDrawCurrentStation();
  begin
    OutputBuffer.DrawStation(FCurrentStation, clAqua, clBlue, 1, 8);
  end;
  procedure QDrawCurrentStationDisto();
  begin
    //AfficherMessageErreur(FCurrentStationOfDistoX.PosStation.DebugString(FCurrentStationOfDistoX.toString()));
    OutputBuffer.DrawStation(FCurrentStationOfDistoX, clAqua, clBlue, 1, 16);
    OutputBuffer.DrawStation(FCurrentStationOfDistoX, clYellow, clRed, 1, 4);
  end;
  procedure QDrawPointBut();
  const sz = 16;
  var
    QBG, QFG: TGHTopoColor;
  begin
    QBG.setFrom(clGreen, 128);
    QFG.setFrom(clBlue, 255);
    OutputBuffer.DefineBrosseEtCrayon(bsSolid, QBG, psSolid, 0, QFG);
    OutputBuffer.DrawShape(FPointBut.X, FPointBut.Y, 1, sz, sz);
    OutputBuffer.RestoreBrosseEtCrayon();
    QFG.setFrom(clNavy, 192);
    OutputBuffer.DefineCrayon(psDot, 0, QFG);
    OutputBuffer.DrawSegment(FCurrentStation.PosStation.X, FCurrentStation.PosStation.Y, FPointBut.X, FPointBut.Y);
    OutputBuffer.RestoreCrayon();
    QFG.setFrom(clBlue, 255);
    OutputBuffer.DefineFonte(DEFAULT_FONT_NAME, QFG, [fsBold], 18);
    OutputBuffer.DrawTexte(FPointBut.X, FPointBut.Y, 1, sz, sz, FPointBut.Caption);
    OutputBuffer.RestoreFonte();
  end;
  procedure QDrawCurrentMarker();
  const
    sz = 8;
  var
    QBG, QFG: TGHTopoColor;
  begin
    QBG.setFrom(clBlue, 128);
    QFG.setFrom(clAqua, 255);
    OutputBuffer.DefineBrosseEtCrayon(bsSolid, QBG, psSolid, 0, QFG);
      OutputBuffer.DrawShape(FCurrentMarker.X, FCurrentMarker.Y, 1, sz, sz);
    OutputBuffer.RestoreBrosseEtCrayon();

  end;
  procedure QDrawCurrentBipoint();
  const
    sz = 8;
  var
    QBG, QFG, QTC: TGHTopoColor;
  begin
    QBG.setFrom(clYellow, 128);
    QFG.setFrom(clRed, 255);
    OutputBuffer.DefineBrosseEtCrayon(bsSolid, QBG, psDash, 2, QFG);
      OutputBuffer.DrawSegment(FCurrentBipoint.S1.PosStation.X,
                               FCurrentBipoint.S1.PosStation.Y,
                               FCurrentBipoint.S2.PosStation.X,
                               FCurrentBipoint.S2.PosStation.Y);
      OutputBuffer.DrawShape(FCurrentBipoint.S1.PosStation.X,
                             FCurrentBipoint.S1.PosStation.Y, 1, sz, sz);
      OutputBuffer.DrawShape(FCurrentBipoint.S2.PosStation.X,
                             FCurrentBipoint.S2.PosStation.Y, 1, sz, sz);
      QTC.setFrom(clBlue);
      OutputBuffer.DefineFonte('Arial', QTC, [fsBold], 16);
        OutputBuffer.DrawTexte(FCurrentBipoint.S1.PosStation.X, FCurrentBipoint.S1.PosStation.Y, 1, 4, 4, FCurrentBipoint.S1.toStringWithIDTerrain());
        OutputBuffer.DrawTexte(FCurrentBipoint.S2.PosStation.X, FCurrentBipoint.S2.PosStation.Y, 1, 4, 4, FCurrentBipoint.S2.toStringWithIDTerrain());
       OutputBuffer.RestoreFonte();
    OutputBuffer.RestoreBrosseEtCrayon();
  end;
  procedure QDrawCurrentNearST();
  const
    sz = 8;
  var
    QBG, QFG, QTC: TGHTopoColor;
    BP0, BP1: TBaseStation;
    EWE: Boolean;
  begin
    QBG.setFrom(clYellow, 128);
    QFG.setFrom(clYellow, 128);
    EWE := FBDDEntites.GetEntiteViseeFromSerSt(FCurrentNearST.BaseStation.aSerie   , FCurrentNearST.BaseStation.aStation   , BP0) AND
           FBDDEntites.GetEntiteViseeFromSerSt(FCurrentNearST.NearestStation.aSerie, FCurrentNearST.NearestStation.aStation, BP1);
    if (not EWE) then exit;
    OutputBuffer.DefineBrosseEtCrayon(bsSolid, QBG, psSolid, 6, QFG);
      OutputBuffer.DrawSegment(BP0.PosStation.X,
                               BP0.PosStation.Y,
                               BP1.PosStation.X,
                               BP1.PosStation.Y);
    OutputBuffer.RestoreBrosseEtCrayon();

    QBG.setFrom(clYellow, 128);
    QFG.setFrom(clGreen, 255);

    OutputBuffer.DefineBrosseEtCrayon(bsSolid, QBG, psDash, 2, QFG);
      OutputBuffer.DrawSegment(BP0.PosStation.X,
                               BP0.PosStation.Y,
                               BP1.PosStation.X,
                               BP1.PosStation.Y);
    OutputBuffer.RestoreBrosseEtCrayon();
    OutputBuffer.DefineBrosseEtCrayon(bsSolid, QBG, psSolid, 2, QFG);
      OutputBuffer.DrawShape(BP0.PosStation.X,
                             BP0.PosStation.Y, 1, sz, sz);
      OutputBuffer.DrawShape(BP1.PosStation.X,
                             BP1.PosStation.Y, 1, sz, sz);
      QTC.setFrom(clBlack);
      OutputBuffer.DefineFonte('Arial', QTC, [fsBold], 16);
        OutputBuffer.DrawTexte(BP0.PosStation.X, BP0.PosStation.Y, 1, 4, 4, BP0.toStringWithIDTerrain());
        OutputBuffer.DrawTexte(BP1.PosStation.X, BP1.PosStation.Y, 1, 4, 4, BP1.toStringWithIDTerrain());
      OutputBuffer.RestoreFonte();
    OutputBuffer.RestoreBrosseEtCrayon();
  end;
begin
  // recalculer les dégradés si DoRecalcDegrades est armé
  if (DoRecalcDegrades) then FBDDEntites.CalcCouleursByDepth(Onglet.ongDegradeReseaux.ColorMini, Onglet.ongDegradeReseaux.ColorMaxi);
  try
    if (Not OutputBuffer.Initialiser(FDocuTopo,
                                  FBDDEntites,
                                  FCroquisTerrain,
                                  FListeMaillages, // On n'affique que le MNT de surface sur le plan
                                  Onglet,
                                  FModeSelectionListe,
                                  FDoHighLightCurrentItem)) then exit;
    OutputBuffer.SetBounds(FRegionCMini.X, FRegionCMini.Y, FRegionCMaxi.X, FRegionCMaxi.Y);
    OutputBuffer.BeginDrawing();
    CDS.setFrom(Onglet.ongQdrColor);
    if (edQuadrilles     in Onglet.ongElementsDrawn) then OutputBuffer.DrawQuadrillage(Onglet.ongQdrType, CDS, Onglet.ongQdrSpc);
    if (edANTENNES       in Onglet.ongElementsDrawn) then OutputBuffer.DrawAntennes();
    if (edCrossSections  in Onglet.ongElementsDrawn) then OutputBuffer.DrawSections();
    if (edPolygonals     in Onglet.ongElementsDrawn) then OutputBuffer.DrawPolygonals(Onglet.ongDrawFastCenterline, FCurrentStationOfDistoX.Entite_Serie);
    if (edFillGalerie    in Onglet.ongElementsDrawn) then OutputBuffer.DrawGaleries(Onglet.ongDrawFastCenterline);
    if (edWalls          in Onglet.ongElementsDrawn) then OutputBuffer.DrawGaleries(Onglet.ongDrawFastCenterline);
    if (edStations       in Onglet.ongElementsDrawn) then OutputBuffer.DrawStations();
    if (edIDStations     in Onglet.ongElementsDrawn) then OutputBuffer.DrawIDStations();
    if (edJONCTIONS      in Onglet.ongElementsDrawn) then OutputBuffer.DrawJonctions();

    if (edAltitudes      in Onglet.ongElementsDrawn) then OutputBuffer.DrawCotation(false);
    if (edCotes          in Onglet.ongElementsDrawn) then OutputBuffer.DrawCotation(true);
    if (edPOI            in Onglet.ongElementsDrawn) then OutputBuffer.DrawPOIs();
    if (edENTRANCE_MKS   in Onglet.ongElementsDrawn) then OutputBuffer.DrawEntrancesOrPointEntities(edENTRANCE_NAMES in Onglet.ongElementsDrawn);
    if (edCROQUIS        in Onglet.ongElementsDrawn) then OutputBuffer.DrawCroquisTerrain();
    if (edGrilleBatailleNavale in Onglet.ongElementsDrawn) then OutputBuffer.DrawGrilleBatailleNavale();
    CDS.setFrom(btnLineContourColor.ButtonColor, editLineContourOpacity.Value);
    OutputBuffer.DrawMaillage(chkDrawMaillage.Checked, editIsoValeur.Value, CDS);
    OutputBuffer.DrawCurrentStationTopo(FCurrentStation);
    if (FOverlayed) then // Overlay éventuel (GHTopo simplifié uniquement)
    begin
      OutputBuffer.DrawOverlay();
      OutputBuffer.DrawShortestPath(FGraphe, FShortestPath);
    end;
    // La station courante
    QDrawCurrentStation();
    QDrawCurrentStationDisto();
    QDrawPointBut();
    // Le marqueur
    QDrawCurrentMarker();
    // Le bipoint courant
    QDrawCurrentBipoint();
    // Le near point courant
    QDrawCurrentNearST();

    DrawPipistrelle(OutputBuffer);
    OutputBuffer.EndDrawing();
  except
    on E: Exception do AfficherMessageErreur(E.Message);
  end;
end;

procedure TGHTopoContext2DA.RedessinEcran(const ImgWidth, ImgHeight: integer; const DoRecalcDegrades: boolean);
var
  MyOnglet: TVue2DParams;
  BGC: TBGRAPixel;
  TmpBuffer: TGHTopoDrawingContext;
begin
  if (FRedessinInProcess) then exit; // carré 'FRedessinInProcess' est armé ? = On quitte (le dessin est en cours)
  FRedessinInProcess := True;        // Armement du carré
  MyOnglet := GetCurrentOnglet();
  BGC.FromColor(MyOnglet.ongBackGround, 255);
  TmpBuffer:= TGHTopoDrawingContext.Create(ImgWidth, ImgHeight, BGC);
  try
    try
      QMakePlan(TmpBuffer, MyOnglet, ImgWidth, ImgHeight, DoRecalcDegrades);
      TmpBuffer.Draw(Vue.Canvas, 0, 0, True);

    except
    end;
  finally
    FreeAndNil(TmpBuffer);
    FRedessinInProcess := false;     // dessin terminé = ouverture du carré
  end;
end;

function TGHTopoContext2DA.getDoDrawKrobard(): boolean;
begin
  Result := (edCROQUIS in GetCurrentOnglet().ongElementsDrawn);
end;

procedure TGHTopoContext2DA.setDoDrawKrobard(const B: boolean);
var
  Ong: TVue2DParams;
begin
  Ong := GetCurrentOnglet();
  if (B) then Ong.ongElementsDrawn := Ong.ongElementsDrawn + [edCROQUIS]
         else Ong.ongElementsDrawn := Ong.ongElementsDrawn - [edCROQUIS];
  PutOngletByIndex(FCurrIdxOnglet, Ong, True);
end;

procedure TGHTopoContext2DA.ExporterPlanEnImage(const ImgWidth, ImgHeight: integer; const DoRecalcDegrades: boolean; const QFileName: string);
var
  MyOnglet: TVue2DParams;
  BGC: TBGRAPixel;
  TmpBuffer: TGHTopoDrawingContext;
begin
  MyOnglet := GetCurrentOnglet();
  BGC.FromColor(MyOnglet.ongBackGround, 255);
  TmpBuffer:= TGHTopoDrawingContext.Create(ImgWidth, ImgHeight, BGC);
  try
    QMakePlan(TmpBuffer, MyOnglet, ImgWidth, ImgHeight, DoRecalcDegrades);
    TmpBuffer.SaveToFile(QFileName);
  finally
    FreeAndNil(TmpBuffer);
  end;
end;

procedure TGHTopoContext2DA.RefreshDessin();
begin
  Vue.Invalidate;
end;
// affiche un message toast
procedure TGHTopoContext2DA.DisplayToast(const Msg: string);
var
  QW, QH, QL, QT: Integer;
begin
  //try
  pnlToast.Visible := false;
  QW := trunc(0.7 * self.Width);
  QH := self.Height div 8;
  QL := (self.Width - QW) div 2;
  QT := (self.Height - QH) div 2;
  pnlToast.Left   := QL;
  pnlToast.Top    := QT;
  pnlToast.Width  := QW;
  pnlToast.Height := QH;
  pnlToast.Color  := clYellow;
  pnlToast.ParentFont := false;
  pnlToast.Font.Style := [fsBold];
  pnlToast.Caption    := Msg;
  pnlToast.Visible:= true;
  application.ProcessMessages;
  Sleep(2000);
  pnlToast.Visible:= false;
  //finally
  //end;
  Vue.Invalidate;
end;
{$IFDEF GHTOPO_SIMPLIFIE}
procedure TGHTopoContext2DA.OnReceiveVisee(const QHorodate: TDateTime;
                                           const QLongueur, QAzimut, QPente, QTemperature: double;
                                           const QTypeMesure: TBRIC4_TypeViseeDevice;
                                           const QTagString: string;
                                           const QDoRefreshPlan: boolean);
var
  EWE: string;
begin
  //AfficherMsgErrClass(self, 'OnReceiveVisee(): %f, %f, %f',[QLongueur, QAzimut, QPente]);
  EWE := Format('%s: Ajoute visée à %d.%d: %.3f, %.3f, %.3f',
                     [DateTimePascalToDateTimeSQL(QHorodate), FCurrentStationOfDistoX.Entite_Serie, FCurrentStationOfDistoX.Entite_Station,
                      QLongueur, QAzimut, QPente]);
  case QTypeMesure of
   tvdSPLAY:
    begin
      AjouterUneAntenneALaStationCouranteDisto(QLongueur, QAzimut, QPente);          // Ne pas demander le redessin du plan (crash si appelé depuis le BT)
    end;
   tvdSHOT:
    begin
      AjouterUneViseeALaSerieCouranteDisto(tgDEFAULT, QHorodate,
                                           QLongueur, QAzimut, QPente,
                                           0.00, 0.00, 0.00, 0.00, '',
                                           QTagString, QTemperature);
    end;
  else
    pass;
  end;
end;
{$ENDIF GHTOPO_SIMPLIFIE}
procedure TGHTopoContext2DA.SetShortestPath(const P: TPathBetweenNodes);
begin
  FShortestPath := P;
end;

initialization
end.
