View Issue Details

IDProjectCategoryView StatusLast Update
0007797LazarusLCLpublic2011-09-20 22:58
ReporterChris RordenAssigned ToBart Broersma 
PrioritynormalSeverityminorReproducibilityalways
Status closedResolutionfixed 
Product Version0.9.21 (SVN)Product Build 
Target Version1.0.0Fixed in Version0.9.31 (SVN) 
Summary0007797: SaveDialog.Filter does not change the file's extension.
DescriptionWith Delphi, you can use the SaveDialog.Filter to allow the user to select different formats for the file - and the returned filename includes the selected extension in the filename. Lazarus correctly shows the available filetypes, but selecting between filetypes does not change the file's extension...

Create a form with a TButton (Button1) and TSaveDialog (SaveDialog1). Set the event for pressing Button1 to the code below. When you run the software and press the Button, you can name a new file - however, changing the 'Save as filetype' pulldown to .csv does not change the filename - the only way to change the output to .csv is to explicitly add .csv to the filename.

procedure TForm1.Button1Click(Sender: TObject);
begin
     SaveDialog1.Filter := 'Text|*.txt|CommaSeparatedValues (*.csv)|*.csv';
     SaveDialog1.DefaultExt := '.txt';
     if not SaveDialog1.Execute then exit;
     showmessage(SaveDialog1.Filename);
end;
TagsNo tags attached.
Fixed in Revision31597
LazTarget1.0
WidgetsetGTK, GTK 2, Win32/Win64, WinCE, Carbon, Cocoa, QT, fpGUI
Attached Files
  • 0007797.7z (60,718 bytes)
  • filedialog.diff (2,895 bytes)
    Index: lcl/include/filedialog.inc
    ===================================================================
    --- lcl/include/filedialog.inc	(revision 31484)
    +++ lcl/include/filedialog.inc	(working copy)
    @@ -259,14 +259,72 @@
       end;
     end;
     
    +
    +//Helper function
    +function GetExtensionFromFilterAtIndex(Filter: String; Index: Integer): String;
    +{
    +  Returns a file extension from a filter as used in TOpen/TSaveDialog
    +  - it will return the extension (including the leading period) that matches the index (index starts at 1)
    +  - it will return an empty string if the extension contains a wildcard, or on any failure
    +  - filters have the format of:
    +    'Text files (*.txt)|*.txt|'+
    +    'Pascal files (*.pp;*.pas)|*.pp;*.pas|'+
    +    'All files (*.*)|*.*'
    +  - if a given extension is a composite (like '*.pp;*.pas') it will return the first one from the list
    +}
    +var
    +  p, pipe: Integer;
    +begin
    +  Result := '';
    +  if Index < 1 then Exit;
    +  p := 0;
    +  pipe := 0;
    +  //Find where the filter for the given index starts
    +  while (p < Length(Filter)) do
    +  begin
    +    Inc(p);
    +    //Debugln('p = ',dbgs(p),' Filter[',dbgs(p),'] = ',Filter[p]);
    +    if Filter[p] = '|' then Inc(pipe);
    +    if (pipe = 2 * (Index - 1)) then break;
    +  end;
    +  //debugln('p = ',dbgs(p),' pipe = ',dbgs(pipe));
    +  if (p = length(Filter)) then exit;
    +  System.Delete(Filter,1,p);
    +  //Find the | that splits the filter name and the filter extension
    +  p := Pos('|',Filter);
    +  if (p = 0) then exit;
    +  System.Delete(Filter,1,p);
    +  Filter := Copy(Filter,1,MaxInt);
    +  //debugln('Filter now = ',filter);
    +
    +  //The associated extension ends at the first ; or |, or at the end of the string
    +  p := Pos(';',Filter);
    +  pipe := Pos('|',Filter);
    +  //Debugln('Pos(;/|,Filter) = ',dbgs(p),' ',dbgs(pipe));
    +  if (pipe < p) or (p = 0) then p := pipe;
    +  if (p > 0) then System.Delete(Filter,p,Length(Filter) - p + 1);
    +  //debugln('Filter now = ',filter);
    +
    +  //Get the associated extension
    +  Filter := ExtractFileExt(Filter);
    +  //debugln('Filter now = ',filter);
    +  //if an extension at this point contains a wildcard, reject it
    +  if (Pos('?',Filter) > 0) or (Pos('*',Filter) > 0) then exit;
    +  Result := Filter;
    +end;
    +
     function TOpenDialog.CheckFile(var AFilename: string): boolean;
     var
    -  Dir: string;
    +  Dir, Ext: string;
     begin
       Result:=true;
       if (DefaultExt<>'') and (ExtractFileExt(AFilename)='')
       and (not FileExistsUTF8(AFilename)) then begin
    -    AFilename:=AFilename+DefaultExt;
    +    Ext := GetExtensionFromFilterAtIndex(Filter, FilterIndex);
    +    if (Length(Ext) > 0) then
    +      AFileName := AFileName + Ext
    +    else
    +      AFilename:=AFilename+DefaultExt;
       end;
       //ofOverwritePrompt -> is done in the interface
       if (ofPathMustExist in Options)
    @@ -403,6 +461,8 @@
         Exclude(FOptions, AOption);
     end;
     
    +
    +
     class procedure TSaveDialog.WSRegisterClass;
     begin
       inherited WSRegisterClass;
    
    filedialog.diff (2,895 bytes)

Relationships

has duplicate 0015677 closedPaul Ishenin Filter in SaveFileDialog 

Activities

2010-05-29 18:02

 

0007797.7z (60,718 bytes)

samuel herzog

2010-05-29 18:03

developer   ~0038131

Uploaded example project.

Frederick

2011-03-18 03:17

reporter   ~0046655

This same behavior is confirmed in the Carbon widget set.

lazarus-0.9.31.29809-20110313-i386-macosx
OS X 10.6.6

I read 0015677 but do not understand why it was closed. Expected behavior would be to change the file extension, as in Excel, Photoshop, OpenOffice, etc.

Bart Broersma

2011-07-07 13:58

developer   ~0049751

Last edited: 2011-07-07 14:25

Paul Ishenin said in 0015677

"Moreover this is standard behavior of windows dialog. In your example it does not change file extension when you choose csv because this file type is not registered. I tried with .pas files and it changes the extension in this case."

Testing with my Delphi (3) on WinMe, XP and Win7 shows this is incorrect:

Code:

begin
  SaveDlg.Filter := 'Text|*.txt|'+
                    'Pascal|*.pas|'+
                    'Csv|*.csv|'+
                    'Unregistered qxq|*.qxq|'+
                    'LongExt|*.LongExt|'+
                    'Composite|*.111;*.222|'+
                    'Any|*.*';
  SaveDlg.DefaultExt := 'txt';
  SaveDlg.FileName := '';
  if SaveDlg.Execute then Memo.Lines.Add(SaveDlg.FileName);
end;

I just type a name, without any extenstion and select the appropriate filter.

Results:

Delphi
F:\LazarusProjecten\text.txt
F:\LazarusProjecten\Pascal.pas
F:\LazarusProjecten\Csv.csv
F:\LazarusProjecten\Unregistered.qxq
F:\LazarusProjecten\Long.LongExt
F:\LazarusProjecten\Composite.111
F:\LazarusProjecten\Any.txt

Lazarus (0.9.31 r31473M FPC 2.4.4 i386-win32-win32/win64)
F:\LazarusProjecten\text.txt
F:\LazarusProjecten\Pascal.txt
F:\LazarusProjecten\Csv.txt
F:\LazarusProjecten\Unregistered.txt
F:\LazarusProjecten\Long.txt
F:\LazarusProjecten\Composite.txt
F:\LazarusProjecten\Any.txt

Lazarus appends the DefaultExt to all filenames, if no extension is given.
Delphi instead honours the value of the filter.
If the filter is a composite, it appends the first value. (*.111;*.222 -> .111)
In Delphi wether the extension is registered does not matter.
It also does not seem to matter wether "Hide extensions for known file types" in Windows is set or not (tested on WinME, XP and Win7).

Zeljan Rikalo

2011-07-07 14:32

developer   ~0049753

Since Bart tested Delphi behaviour, I assume that this should be fixed in LCL, so all widgetsets added as affected.

Bart Broersma

2011-07-07 16:54

developer   ~0049759

Last edited: 2011-07-07 16:56

Someone tested this using Delphi 2010, and the results are the same as my D3.

Here's a little helper function that can give us the desired extension:

function GetExtensionFromFilterAtIndex(Filter: String; Index: Integer): String;
{
  Returns a file extension from a filter as used in TOpen/TSaveDialog
  - it will return the extension (including the leading period) that matches the index (index starts at 1)
  - it will return an empty string if the extension contains a wildcard, or on any failure
  - filters have the format of:
    'Text files (*.txt)|*.txt|'+
    'Pascal files (*.pp;*.pas)|*.pp;*.pas|'+
    'All files (*.*)|*.*'
  - if a given extension is a composite (like '*.pp;*.pas') it will return the first one from the list
}
var
  p, pipe: Integer;
begin
  Result := '';
  if Index < 1 then Exit;
  p := 0;
  pipe := 0;
  //Find where the filter for the given index starts
  while (p < Length(Filter)) do
  begin
    Inc(p);
    if Filter[p] = '|' then Inc(pipe);
    if (pipe = 2 * (Index - 1)) then break;
  end;
  if (p = length(Filter)) then exit;
  System.Delete(Filter,1,p);
  //Find the | that splits the filter name and the filter extension
  p := Pos('|',Filter);
  if (p = 0) then exit;
  System.Delete(Filter,1,p);
  Filter := Copy(Filter,1,MaxInt);
  //The associated extension ends at the first ; or |, or at the end of the string
  p := Pos(';',Filter);
  pipe := Pos('|',Filter);
  if (pipe < p) or (p = 0) then p := pipe;
  if (p > 0) then System.Delete(Filter,p,Length(Filter) - p + 1);
  //Get the associated extension
  Filter := ExtractFileExt(Filter);
  //if an extension at this point contains a wildcard, reject it
  if (Pos('?',Filter) > 0) or (Pos('*',Filter) > 0) then exit;
  Result := Filter;
end;



In TOpenDialog.Execute (or only in TSaveDialog.DoExecute) we can use this and change file-extensions for all names in Files[] (and FileName) when:
- DefaultExt is not an empty string
- FilterIndex <> 0
- Current Extension = DefaultExt
- GetExtensionFromFilterAtIndex() returns a non-empty string

Zeljan Rikalo

2011-07-07 17:02

developer   ~0049760

Why in TOpenDialog ? Who cares about it.
Please prepare patch .... :)

Bart Broersma

2011-07-07 18:27

developer   ~0049765

> Why in TOpenDialog ? Who cares about it.

Because Delphi does this. (Just checked).
(I'm pretty sure though that it is actually the OS that does it).

If we want all widgetsets to behave like described in note 0049751, I can write a patch implementing it in dialogs unit.

Just let me know, if we want it for TSaveDialog only, or for both TOpen- and TSaveDialog.

Zeljan Rikalo

2011-07-07 20:01

developer   ~0049769

If delphi says both then it should be prepared for both.

2011-07-07 20:43

 

filedialog.diff (2,895 bytes)
Index: lcl/include/filedialog.inc
===================================================================
--- lcl/include/filedialog.inc	(revision 31484)
+++ lcl/include/filedialog.inc	(working copy)
@@ -259,14 +259,72 @@
   end;
 end;
 
+
+//Helper function
+function GetExtensionFromFilterAtIndex(Filter: String; Index: Integer): String;
+{
+  Returns a file extension from a filter as used in TOpen/TSaveDialog
+  - it will return the extension (including the leading period) that matches the index (index starts at 1)
+  - it will return an empty string if the extension contains a wildcard, or on any failure
+  - filters have the format of:
+    'Text files (*.txt)|*.txt|'+
+    'Pascal files (*.pp;*.pas)|*.pp;*.pas|'+
+    'All files (*.*)|*.*'
+  - if a given extension is a composite (like '*.pp;*.pas') it will return the first one from the list
+}
+var
+  p, pipe: Integer;
+begin
+  Result := '';
+  if Index < 1 then Exit;
+  p := 0;
+  pipe := 0;
+  //Find where the filter for the given index starts
+  while (p < Length(Filter)) do
+  begin
+    Inc(p);
+    //Debugln('p = ',dbgs(p),' Filter[',dbgs(p),'] = ',Filter[p]);
+    if Filter[p] = '|' then Inc(pipe);
+    if (pipe = 2 * (Index - 1)) then break;
+  end;
+  //debugln('p = ',dbgs(p),' pipe = ',dbgs(pipe));
+  if (p = length(Filter)) then exit;
+  System.Delete(Filter,1,p);
+  //Find the | that splits the filter name and the filter extension
+  p := Pos('|',Filter);
+  if (p = 0) then exit;
+  System.Delete(Filter,1,p);
+  Filter := Copy(Filter,1,MaxInt);
+  //debugln('Filter now = ',filter);
+
+  //The associated extension ends at the first ; or |, or at the end of the string
+  p := Pos(';',Filter);
+  pipe := Pos('|',Filter);
+  //Debugln('Pos(;/|,Filter) = ',dbgs(p),' ',dbgs(pipe));
+  if (pipe < p) or (p = 0) then p := pipe;
+  if (p > 0) then System.Delete(Filter,p,Length(Filter) - p + 1);
+  //debugln('Filter now = ',filter);
+
+  //Get the associated extension
+  Filter := ExtractFileExt(Filter);
+  //debugln('Filter now = ',filter);
+  //if an extension at this point contains a wildcard, reject it
+  if (Pos('?',Filter) > 0) or (Pos('*',Filter) > 0) then exit;
+  Result := Filter;
+end;
+
 function TOpenDialog.CheckFile(var AFilename: string): boolean;
 var
-  Dir: string;
+  Dir, Ext: string;
 begin
   Result:=true;
   if (DefaultExt<>'') and (ExtractFileExt(AFilename)='')
   and (not FileExistsUTF8(AFilename)) then begin
-    AFilename:=AFilename+DefaultExt;
+    Ext := GetExtensionFromFilterAtIndex(Filter, FilterIndex);
+    if (Length(Ext) > 0) then
+      AFileName := AFileName + Ext
+    else
+      AFilename:=AFilename+DefaultExt;
   end;
   //ofOverwritePrompt -> is done in the interface
   if (ofPathMustExist in Options)
@@ -403,6 +461,8 @@
     Exclude(FOptions, AOption);
 end;
 
+
+
 class procedure TSaveDialog.WSRegisterClass;
 begin
   inherited WSRegisterClass;
filedialog.diff (2,895 bytes)

Bart Broersma

2011-07-07 20:44

developer   ~0049775

I attached a possible patch for it.
It seems to work like Delphi.

Zeljan Rikalo

2011-07-08 08:37

developer   ~0049783

Please test and close if ok.

Bart Broersma

2011-09-20 22:58

developer   ~0052072

Fixed.
Noo feedback from original reporter, so closing the issue.

Issue History

Date Modified Username Field Change
2006-11-09 18:49 Chris Rorden New Issue
2006-11-09 18:49 Chris Rorden Widgetset => GTK
2006-12-02 10:23 Vincent Snijders LazTarget => 1.0
2006-12-02 10:23 Vincent Snijders Status new => acknowledged
2008-04-24 09:57 Vincent Snijders Target Version => 1.0.0
2010-02-16 20:41 Vincent Snijders Relationship added has duplicate 0015677
2010-05-29 18:02 samuel herzog File Added: 0007797.7z
2010-05-29 18:03 samuel herzog Status acknowledged => confirmed
2010-05-29 18:03 samuel herzog Note Added: 0038131
2011-03-18 03:17 Frederick Note Added: 0046655
2011-07-07 13:58 Bart Broersma Note Added: 0049751
2011-07-07 14:25 Bart Broersma Note Edited: 0049751
2011-07-07 14:32 Zeljan Rikalo Widgetset GTK => GTK, GTK 2, Win32/Win64, WinCE, Carbon, Cocoa, QT, fpGUI
2011-07-07 14:32 Zeljan Rikalo Note Added: 0049753
2011-07-07 16:54 Bart Broersma Note Added: 0049759
2011-07-07 16:55 Bart Broersma Note Edited: 0049759
2011-07-07 16:56 Bart Broersma Note Edited: 0049759
2011-07-07 17:02 Zeljan Rikalo Note Added: 0049760
2011-07-07 18:27 Bart Broersma Note Added: 0049765
2011-07-07 20:01 Zeljan Rikalo Note Added: 0049769
2011-07-07 20:43 Bart Broersma File Added: filedialog.diff
2011-07-07 20:44 Bart Broersma Note Added: 0049775
2011-07-08 00:13 Bart Broersma Status confirmed => assigned
2011-07-08 00:13 Bart Broersma Assigned To => Bart Broersma
2011-07-08 08:37 Zeljan Rikalo Fixed in Revision => 31597
2011-07-08 08:37 Zeljan Rikalo Status assigned => resolved
2011-07-08 08:37 Zeljan Rikalo Resolution open => fixed
2011-07-08 08:37 Zeljan Rikalo Note Added: 0049783
2011-09-20 22:58 Bart Broersma Status resolved => closed
2011-09-20 22:58 Bart Broersma Note Added: 0052072
2011-09-20 22:58 Bart Broersma Fixed in Version => 0.9.31 (SVN)