Recent

Author Topic: SOLVED Impossible to close a modal window...  (Read 4359 times)

pjtuloup

  • New Member
  • *
  • Posts: 45
Re: Impossible to close a modal window...
« Reply #30 on: July 10, 2024, 11:11:52 am »
A thousand apologies to everyone: before giving up I wanted to double-check everything once again and I finally found the solution, everything works perfectly. (the mistake was as big as me)
A huge thank you to everyone who helped me!!!

Zvoni

  • Hero Member
  • *****
  • Posts: 2741
Re: SOLVED Impossible to close a modal window...
« Reply #31 on: July 10, 2024, 11:58:31 am »
Would be nice to know, what the solution is, in case someone else has the same problem.....
One System to rule them all, One Code to find them,
One IDE to bring them all, and to the Framework bind them,
in the Land of Redmond, where the Windows lie
---------------------------------------------------------------------
Code is like a joke: If you have to explain it, it's bad

cdbc

  • Hero Member
  • *****
  • Posts: 1655
    • http://www.cdbc.dk
Re: SOLVED Impossible to close a modal window...
« Reply #32 on: July 10, 2024, 12:16:41 pm »
Hi
I'd hazard a guess here: "He didn't look at his own code".
Regards Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE5 -> FPC 3.2.2 -> Lazarus 2.2.6 up until Jan 2024 from then on it's: KDE5/QT5 -> FPC 3.3.1 -> Lazarus 3.0

MarkMLl

  • Hero Member
  • *****
  • Posts: 8027
Re: SOLVED Impossible to close a modal window...
« Reply #33 on: July 10, 2024, 01:06:44 pm »
I wouldn't be that cruel, but this is definitely a case where we could have done with seeing the entire project in case there was something odd with form/unit ordering etc.

My suspicion is that- as he hinted himself- OP might have messed up something in that area while he was floundering.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

pjtuloup

  • New Member
  • *
  • Posts: 45
Re: SOLVED Impossible to close a modal window...
« Reply #34 on: July 10, 2024, 03:11:30 pm »
To redeem myself I will indeed publish the entire code and indicate what it is for, in case it helps someone with the same needs as me.

The need is, when launching an application, to display a modal window used for user identification.
This identification is carried out by comparing the entry with the elements appearing in a table of a MySQL database, whose name, and IP address of the hosting server, are specified in an ini file read at launch.

In this modal window there are three buttons.
A validation button launches the comparison of the entry with the data in the Mysql table. If successful, a "Continue" button becomes active and is used to close the modal window and allows access to the general menu.
A final button allows you to abandon, close the modal window and terminate the application.

In the code, the main form is called "MasterForm". The identification form is "FormIdentif". A uoutils unit is used to declare global variables.
A DataAccess unit (Form DataModule1) is used for relationships with the MySQL database.
There is also a FormApropos form (About...)
The construction order declared in the project options is as follows:
FormMaster
DataModule1
FormAbout

And now the code (Which would require a little cleaning)

Code: Pascal  [Select][+][-]
  1. unit Maitre;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ActnList, Menus, Unit1, IniFiles, uidentification, uoutils;
  9.  
  10. type
  11.  
  12.   { TFormMaitre }
  13.  
  14.   TFormMaitre = class(TForm)
  15.     MenuGen: TMainMenu;
  16.     MenuItem1: TMenuItem;
  17.     MenuItem10: TMenuItem;
  18.     MenuItem11: TMenuItem;
  19.     MenuItem12: TMenuItem;
  20.     MenuItem13: TMenuItem;
  21.     MenuItem14: TMenuItem;
  22.     MenuItem15: TMenuItem;
  23.     MenuItem16: TMenuItem;
  24.     MenuItem17: TMenuItem;
  25.     MenuItem18: TMenuItem;
  26.     MenuItem19: TMenuItem;
  27.     MenuItem2: TMenuItem;
  28.     MenuItem3: TMenuItem;
  29.     MenuItem4: TMenuItem;
  30.     MenuItem5: TMenuItem;
  31.     MenuItem6: TMenuItem;
  32.     MenuItem7: TMenuItem;
  33.     MenuItem8: TMenuItem;
  34.     MenuItem9: TMenuItem;
  35.     Retour: TMenuItem;
  36.     procedure FormCreate(Sender: TObject);
  37.     procedure FormShow(Sender: TObject);
  38.     procedure MenuItem10Click(Sender: TObject);
  39.     procedure MenuItem9Click(Sender: TObject);
  40.     function iniRead(s : string) : string;
  41.   private
  42.   FirstRun:Boolean;
  43.   Procedure ShowModalForm;
  44.   public
  45.  
  46.   end;
  47.  
  48.   const
  49.   Section =   'Serveur';
  50.  
  51. var
  52.   FormMaitre: TFormMaitre;
  53.   INI       :TINIFile;
  54.  
  55. implementation
  56.  
  57. {$R *.lfm}
  58.  
  59. { TFormMaitre }
  60.  
  61. procedure TFormMaitre.FormCreate(Sender: TObject);
  62. var
  63.    callResult : String;
  64. begin
  65.     iniPath           := Application.Location;
  66.     callResult        := iniRead(iniPath);
  67.     FirstRun:=True;
  68. end;
  69.  
  70. procedure TFormMaitre.FormShow(Sender: TObject);
  71. begin
  72.     If FirstRun Then
  73.     Begin
  74.       ShowModalForm;
  75.       FirstRun:=False;
  76.     end;
  77. end;
  78.  
  79. procedure TFormMaitre.ShowModalForm;
  80. begin
  81.   FormIdentif:=TFormIdentif.Create(Self);
  82.   FormIdentif.ShowModal;
  83.   If FormIdentif.IsCanceled Then
  84.     ShowMessage('Abandon: '+FormIdentif.ReturnValue)
  85.   Else
  86.     ShowMessage('Poursuite: '+FormIdentif.ReturnValue);
  87.   FormIdentif.Close;
  88.   FormIdentif.Free;
  89. end;
  90.  
  91. procedure TFormMaitre.MenuItem10Click(Sender: TObject);
  92. begin
  93. Application.MainForm.Close;
  94. end;
  95.  
  96. procedure TFormMaitre.MenuItem9Click(Sender: TObject);
  97. begin
  98.   FormAPropos.Show;
  99. end;
  100.  
  101. function TFormMaitre.iniRead(s : string) : string;
  102.   var
  103.      str: string;
  104.   begin
  105.      INI :=TINIFile.Create('Luges.ini');
  106.      try
  107.        IpServeur  :=INI.ReadString(Section,'IP','');
  108.        NomBase  :=INI.ReadString(Section,'Base','');
  109.        ShowMessage('Nom de la base lu dans le fichier ini: '+NomBase);
  110.      finally
  111.        INI.Free;
  112.        str := 'Process Worked';
  113.        Result := str;
  114.      end;
  115.   end;
  116.  
  117. end.
  118.  

Code: Pascal  [Select][+][-]
  1.   unit uIdentification;
  2.  
  3.   {$mode ObjFPC}{$H+}
  4.  
  5.   interface
  6.  
  7.   uses
  8.     Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ExtCtrls,
  9.     StdCtrls, DBCtrls, uoutils, DataAccess;
  10.  
  11.   type
  12.  
  13.     { TFormIdentif }
  14.  
  15.     TFormIdentif = class(TForm)
  16.       Btn_Valider: TButton;
  17.       Btn_Quitter: TButton;
  18.       Btn_Continuer: TButton;
  19.       SaisUser: TEdit;
  20.       SaisMdp: TEdit;
  21.       AffProfil: TEdit;
  22.       AffDiv: TEdit;
  23.       GroupBox1: TGroupBox;
  24.       GroupBox2: TGroupBox;
  25.       GroupBox3: TGroupBox;
  26.       GroupBox4: TGroupBox;
  27.       GroupBox5: TGroupBox;
  28.       Label1: TLabel;
  29.       Label2: TLabel;
  30.       Panel1: TPanel;
  31.       procedure Btn_ValiderClick(Sender: TObject);
  32.       procedure Btn_QuitterClick(Sender: TObject);
  33.       procedure Btn_ContinuerClick(Sender: TObject);
  34.       procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
  35.       procedure FormCreate(Sender: TObject);
  36.     private
  37.  
  38.     public
  39.        IsCanceled:Boolean;
  40.        ReturnValue:String;
  41.     end;
  42.  
  43.  
  44.   var
  45.     FormIdentif: TFormIdentif;
  46.     Ok : Boolean = false;
  47.  
  48.   implementation
  49.  
  50.   {$R *.lfm}
  51.  
  52.   { TFormIdentif }
  53.  
  54.   procedure TFormIdentif.Btn_QuitterClick(Sender: TObject);
  55.   begin
  56.     IsCanceled:=True;
  57.     ReturnValue:='Sortie de l''application!';
  58.     Self.Close;
  59.   end;
  60.  
  61. procedure TFormIdentif.Btn_ContinuerClick(Sender: TObject);
  62. begin
  63.   IsCanceled:=False;
  64.   ReturnValue:='Accès autorisé!';
  65.   Self.Close;
  66. end;
  67.  
  68.   procedure TFormIdentif.FormClose(Sender: TObject; var CloseAction: TCloseAction);
  69.     //var CloseAction: TCloseAction);
  70.   begin
  71.     CloseAction:=caHide;
  72.   end;
  73.  
  74. procedure TFormIdentif.FormCreate(Sender: TObject);
  75. begin
  76.   IsCanceled:=False;
  77. end;
  78.  
  79.   procedure TFormIdentif.Btn_ValiderClick(Sender: TObject);
  80.   var
  81.     Ok1 : Boolean;
  82.     Ok2 : Boolean;
  83.     NbUsers : LongInt;
  84.   begin
  85.     Ok1:= false;
  86.     Ok2:= false;
  87.     ReqUserHabil:='SELECT B.Iduser, B.Nom AS NOM ,B.Prenom AS PRENOM, B.Pwd AS PWD, B.IdDiv, C.IdProfil AS IDPROFIL, C.LibProfil AS PROFIL, E.LibDiv AS DIVISION FROM gusers AS B Left Join ghabil AS A on B.IdUser=A.IdUser Left Join gprofils AS C on C.IdProfil=A.IdProfil Left Join gdiv AS E on E.IdDiv=A.IdDiv WHERE B.Nom=';
  88.  
  89.     if (SaisUser.Text='') and (SaisMdp.Text='')  then
  90.        begin
  91.        Application.MessageBox('Veuillez saisir votre nom d utilisateur et votre mot de passe!', 'Alerte Utilisateur');
  92.        end
  93.     else
  94.        begin
  95.        if SaisUser.Text='' then
  96.           begin
  97.           Application.MessageBox('Veuillez saisir votre nom d utilisateur', 'Alerte Utilisateur');
  98.           end
  99.        else
  100.           Ok1:= true;
  101.        if SaisMdp.Text='' then
  102.           begin
  103.             Application.MessageBox('Veuillez saisir votre mot de passe!', 'Alerte Utilisateur');
  104.           end
  105.        else
  106.           Ok2:=true;
  107.  
  108.        if ((Ok1=true) and (Ok2=true)) then
  109.           begin
  110.             IdentifOK:=true;
  111. //ShowMessage('Test: '+boolean.ToString(true=true));
  112. //ShowMessage('Apres controle: '+boolean.ToString(IdentifOK));
  113. //ShowMessage('ok');
  114.           //Initialisation requête
  115.           ReqUserHabil+=#39+SaisUser.Text+#39;
  116.           ReqUserHabil+=';';
  117.           //Réinitialisation des affichages
  118.           AffProfil.Text:='';
  119.           AffDiv.Text:='';
  120.           DataModule1.ChargeUsers;
  121.           NbUsers:=DataModule1.SQLQueryUsers.RecordCount;
  122.           if NbUsers=0 then
  123.              begin
  124.              Application.MessageBox('Aucun utilisateur habilité de ce nom!', 'Alerte Utilisateur');
  125.              DataModule1.SQLQueryUsers.Close;
  126.              end
  127.           else
  128.              begin
  129.              if SaisMdp.Text<>DataModule1.SQLQueryUsers.FieldByName('PWD').AsString  then
  130.                 begin
  131.                 Application.MessageBox('Le mot de passe est erroné!', 'Alerte Utilisateur');
  132.                 end
  133.              else
  134.                 begin
  135.                 AffProfil.Text:=DataModule1.SQLQueryUsers.FieldByName('PROFIL').AsString;
  136.                 AffDiv.Text:=DataModule1.SQLQueryUsers.FieldByName('DIVISION').AsString;
  137.                 UserEncours:=DataModule1.SQLQueryUsers.FieldByName('NOM').AsString;
  138.                 UserEnCours+=' ';
  139.                 UserEnCours:=DataModule1.SQLQueryUsers.FieldByName('PRENOM').AsString;
  140.                 ProfilEnCours:=DataModule1.SQLQueryUsers.FieldByName('PROFIL').AsString;
  141.                 Btn_Continuer.Enabled:=true;
  142.                 end
  143.              end
  144.           end
  145.        end
  146.   end;
  147.  
  148.   end.
  149.  

Code: Pascal  [Select][+][-]
  1. unit uoutils;
  2.  
  3. {$mode ObjFPC}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils;
  9.  
  10. var
  11. IpServeur           :String;
  12. NomBase             :String;
  13. iniPath             :String;
  14. UserEnCours         :String;
  15. ProfilEnCours       :String;
  16. IdentifOK           :Boolean;
  17. //Requetes
  18. ReqUserHabil        :String;
  19.  
  20. implementation
  21.  
  22. end.
  23.  

Code: Pascal  [Select][+][-]
  1. unit DataAccess;
  2.  
  3. {$mode ObjFPC}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, mysql80conn, SQLDB, Dialogs,db,uoutils;
  9.  
  10. type
  11.  
  12.   { TDataModule1 }
  13.  
  14.   TDataModule1 = class(TDataModule)
  15.     DataSourceUsers: TDataSource;
  16.     SQLConnection1: TMySQL80Connection;
  17.     SQLQueryUsers: TSQLQuery;
  18.     SQLTransaction: TSQLTransaction;
  19.     procedure DataModuleCreate(Sender: TObject);
  20.   private
  21.  
  22.   public
  23.      function Login: Boolean;
  24.      procedure Logoff;
  25.      procedure ChargeUsers;
  26.   end;
  27.  
  28. var
  29.   DataModule1: TDataModule1;
  30.  
  31.  
  32. implementation
  33.  
  34. {$R *.lfm}
  35.  
  36. { TDataModule1 }
  37.  
  38. procedure TDataModule1.DataModuleCreate(Sender: TObject);
  39. begin
  40.   If not Login then
  41.       begin
  42.         Login;
  43.         end;
  44. end;
  45.  
  46. function TDataModule1.Login: Boolean;
  47.  
  48. begin
  49. //ShowMessage('Nom de la base dans la variable NomBase: '+NomBase);
  50. Result := true;
  51. SQLConnection1.Hostname := IpServeur;
  52. SQLConnection1.DatabaseName := NomBase;
  53. SQLConnection1.UserName := (censored);
  54. SQLConnection1.Password := (censored);
  55. try
  56.   SQLConnection1.Connected := true;
  57.   SQLTransaction.Active := true;
  58. except
  59.   on e : ESQLDatabaseError do
  60.   begin
  61.     MessageDlg('Erreur de connexion à la base :'#10#10#13 + IntToStr(e.ErrorCode) + ' : ' + e.Message + #10#10#13'Application terminée.',mtError,[mbOK],0);
  62.     Result := false;;
  63.   end;
  64.   on e : EDatabaseError do
  65.   begin
  66.     MessageDlg('Erreur de connexion à la base :'#10#10#13'Application terminée.',mtError,[mbOK],0);
  67.     Result := false;;
  68.   end;
  69. end;
  70. if Result = true then
  71.    //MessageDlg('Base Luges disponible!',mtError,[mbOK],0);
  72.     //Application.MessageBox('Base Luges disponible!', 'Info Utilisateur');
  73.     ShowMessage('Base Luges disponible!');
  74. end;
  75. procedure TDataModule1.Logoff;
  76. begin
  77. if SQLTransaction.Active then
  78.    SQLTransaction.Active := false;
  79. if SQLConnection1.Connected  then
  80.     SQLConnection1.Connected := false;
  81. end;
  82.  
  83. procedure TDataModule1.ChargeUsers;
  84. begin
  85.   with SQLQueryUsers do
  86.       begin
  87.          Close;
  88.          SQL.Text:=ReqUserHabil;
  89.          Open;
  90.       end
  91. end;
  92.  
  93. end.
  94.  

MarkMLl

  • Hero Member
  • *****
  • Posts: 8027
Re: SOLVED Impossible to close a modal window...
« Reply #35 on: July 10, 2024, 04:45:41 pm »
No need to "redeem" yourself :-)

But we do collectively want to help, and since- if there's a real screw-up- we'll need to look at the project and possibly form files the easiest thing would be if you used the IDE's Project -> Publish Project facility to dump the whole lot.

We anticipate that it might not contain any custom libraries you're using, but it's very likely that we'll be able to see the problem if we cut out a whole lot of the DB-related stuff etc. and focus on the form interaction.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

pjtuloup

  • New Member
  • *
  • Posts: 45
Re: SOLVED Impossible to close a modal window...
« Reply #36 on: July 10, 2024, 05:47:50 pm »
Thanks ! I didn't know the Project->Publish function. I will use it in the future!

rvk

  • Hero Member
  • *****
  • Posts: 6582
Re: SOLVED Impossible to close a modal window...
« Reply #37 on: July 10, 2024, 06:44:25 pm »
To redeem myself I will indeed publish the entire code and indicate what it is for, in case it helps someone with the same needs as me.
Great job (also for showing the code).

I do have a few small remarks. And one big one. I hope you don't mind...

1) Creation of the modalform:

First. I take it you moved FormIdentif from being autocreated (in the project options>Forms).
That's ok. But then you used the same FormIdentif variable for creating the modal form.
Because FormIdentif is limited to your OnShow procedure (and you create and free it there), I would remove the FormIdentif from the unit uIdentification and create FormIdentif as LOCAL variable for OnShow. That way you can't make any mistakes later on.

Second... you might want to use the try/finally construction for when there is a problem (to make sure the form is really freed). It just good programming that way.

Third... you don't need Close for FormIdentif because it's a modal form and when you return, it's already closed.

O, and because you are going to free the form itself, you can use Create(nil) instead of Create(Self). You (normally) only use Self if you want that 'Self' to handle the destruction.

So:
Code: Pascal  [Select][+][-]
  1. procedure TFormMaitre.ShowModalForm;
  2. var
  3.   FormIdentif22: TFormIdentif; // I just added 22 to make sure you don't use the other one which should be removed
  4. begin
  5.   FormIdentif22 := TFormIdentif.Create(nil);
  6.   try
  7.     FormIdentif22.ShowModal;
  8.     if FormIdentif22.IsCanceled Then
  9.       ShowMessage('Abandon: ' + FormIdentif22.ReturnValue)
  10.     else
  11.       ShowMessage('Poursuite: ' + FormIdentif22.ReturnValue);
  12.   finally
  13.     FormIdentif22.Free;
  14.   end;
  15. end;

2) Using ModalResult for easy result of the modal form

Instead of IsCanceled you can use ModalResult. If you use the X button at the top, the ShowModal will always return mrCancel.
For the continue button you can set ModalResult := mrOk and for the cancel button you do ModalResult := mrCancel.
DON'T USE Self.Close; Just set ModalResult correctly. It will close the modal form automatically and return the given value as function result.

In the ShowModalForm you can change the IsCancelled check into (so you can remove the IsCancelled variable entirely):

Code: Pascal  [Select][+][-]
  1. procedure TFormMaitre.ShowModalForm;
  2. var
  3.   FormIdentif22: TFormIdentif; // I just added 22 to make sure you don't use the other one which should be removed
  4.   Result: Integer;
  5. begin
  6.   FormIdentif22 := TFormIdentif.Create(nil);
  7.   try
  8.     Result := FormIdentif22.ShowModal;
  9.     if Result = mrOk then
  10.       ShowMessage('Poursuite: Accès autorisé!') // also maybe save the user info??
  11.     else
  12.       ShowMessage('Abandon: Sortie de l''application!');
  13.   finally
  14.     FormIdentif22.Free;
  15.   end;
  16. end;

Above will make the login form a bit more streamlined and work according to correct ShowModal principles.

3) Now one which can be a very BIG problem... !!!

Code: Pascal  [Select][+][-]
  1. ReqUserHabil := 'SELECT B.Iduser, B.Nom AS NOM ,B.Prenom AS PRENOM, B.Pwd AS PWD, B.IdDiv, C.IdProfil AS IDPROFIL, C.LibProfil AS PROFIL, E.LibDiv AS DIVISION FROM gusers AS B Left Join ghabil AS A on B.IdUser=A.IdUser Left Join gprofils AS C on C.IdProfil=A.IdProfil Left Join gdiv AS E on E.IdDiv=A.IdDiv WHERE B.Nom=';
  2. ReqUserHabil += #39+SaisUser.Text+#39; // <---- PROBLEM
  3. ReqUserHabil += ';';

If you are going to use your program only internally, with people you trust, you can get away with this (although even than it's not advised).
But if you want to make it secure (why else have a login), you really need to take care of that SQL injection vulnerability.

ANYONE can mess up your database (delete records, delete the entire database, gain access, etc, etc) when you use SaisUser.Text like that and just append it to your SELECT statement.

See https://www.w3schools.com/sql/sql_injection.asp

So, even if you don't do anything with the first 2 things... that last one is definitely something you really need to look at and understand...

MarkMLl

  • Hero Member
  • *****
  • Posts: 8027
Re: SOLVED Impossible to close a modal window...
« Reply #38 on: July 10, 2024, 08:51:52 pm »
RVK got in first... complete with obligatory xkcd :-)

OP: Does that fix things?

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

pjtuloup

  • New Member
  • *
  • Posts: 45
Re: SOLVED Impossible to close a modal window...
« Reply #39 on: July 24, 2024, 05:33:03 pm »

3) Now one which can be a very BIG problem... !!!

Code: Pascal  [Select][+][-]
  1. ReqUserHabil := 'SELECT B.Iduser, B.Nom AS NOM ,B.Prenom AS PRENOM, B.Pwd AS PWD, B.IdDiv, C.IdProfil AS IDPROFIL, C.LibProfil AS PROFIL, E.LibDiv AS DIVISION FROM gusers AS B Left Join ghabil AS A on B.IdUser=A.IdUser Left Join gprofils AS C on C.IdProfil=A.IdProfil Left Join gdiv AS E on E.IdDiv=A.IdDiv WHERE B.Nom=';
  2. ReqUserHabil += #39+SaisUser.Text+#39; // <---- PROBLEM
  3. ReqUserHabil += ';';

If you are going to use your program only internally, with people you trust, you can get away with this (although even than it's not advised).
But if you want to make it secure (why else have a login), you really need to take care of that SQL injection vulnerability.

ANYONE can mess up your database (delete records, delete the entire database, gain access, etc, etc) when you use SaisUser.Text like that and just append it to your SELECT statement.

See https://www.w3schools.com/sql/sql_injection.asp

So, even if you don't do anything with the first 2 things... that last one is definitely something you really need to look at and understand...

Thanks rvk, I will study that.

 

TinyPortal © 2005-2018