GTK FileChooser

Linux specific forum
User avatar
StarBootics
Addict
Addict
Posts: 984
Joined: Sun Jul 07, 2013 11:35 am
Location: Canada

GTK FileChooser

Post by StarBootics »

Hello everyone,

A tiny library, OOP style, to replace the OpenFileRequester() as well as the SaveFileRequester(). It took me one day and a half to make it to work as I wanted. See the Programming Notes for more details.

EDIT 1 : A little update, it is now possible to filter more that one type of file extension. For example you want to be capable to select *.pb and *.pbi files at the same time. See the example 3rd settings for more details.

Best regards
StarBootics

Code: Select all

; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
; Project Name : GTK FileChooser
; File Name : FileChooser - OOP.pb
; File Version : 1.0.2
; Programmation : OK
; Programmed by : StarBootics
; Date : March 19th, 2022
; Update : March 26th, 2022
; Coded for PureBasic : V6.00 Beta 5
; Platform : Linux
; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
; Programming Notes
;
; This FileChooser can be used as a OpenFileRequester as well 
; as a SaveFileRequester replacement.
;
; In Open mode, it's possible to open a single file or multiple
; files.
;
; In Save mode, the filename is being verified automatically
; to make sure that the file extension, if any, is the same as
; the selected one. If not, the file extension will be 
; corrected. The "SelectMultiple" flag is ignored in Save mode.
;
; It's possible to add as much Settings as needed. Just make
; sure to call the FileChooser\UseSettings() method with the
; right settings index. 
;
; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

DeclareModule GtkFileChooser
  
  #MODE_OPEN = 0
  #MODE_SAVE = 1
  
  Interface FileChooser
    
    GetFileName.s()
    AddSettings()
    UseSettings(SettingID.i)
    SetMode(Mode.i)
    SetTitle(Title.s)
    SetButtonText(ButtonID.i, Text.s)
    SetPath(Path.s)
    SetSelectMultiple(SelectMultiple.i)
    SetDefaultPatternID(DefaultPatternID.i)
    SetParent(*Parent)
    AddPatterns(Label.s, Extension.s)
    ClearPatterns()
    SelectedPatternID.i()
    SelectFileNames.i(Index.l)
    NextFileNames.i()
    ResetFileNames()
    FileNamesSize.l()
    Open.i()
    Free()
    
  EndInterface
  
  Declare.i New()
  
EndDeclareModule

Module GtkFileChooser
  
  ; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  ; <<<<< GTK Methods importation <<<<<
  
  ImportC ""
    gtk_file_chooser_dialog_new(title.p-utf8, *parent.GtkWidget, action.i, b1.p-utf8, a1.i, b2.p-utf8, a2.i, null)
    gtk_file_filter_set_name(*filter, label.p-utf8)                                                               
    gtk_file_filter_add_pattern(*filter, pattern.p-utf8)
    gtk_file_chooser_set_current_folder(*Dialog, path.p-utf8) 
    gtk_file_chooser_set_select_multiple(*Dialog, SelectMultiple.i)
  EndImport
  
  ; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  ; <<<<< Structures declaration <<<<<
  
  Structure Pattern
    
    *Filter
    Label.s
    Extension.s
    
  EndStructure
  
  Structure Setting
    
    Mode.i
    Title.s
    ButtonText.s[2]
    Path.s
    SelectMultiple.i
    DefaultPatternID.i
    List Patterns.Pattern()
    
  EndStructure
 
  Structure Private_Members
    
    VirtualTable.i
    *Parent
    List FileNames.s()
    List Settings.Setting()
    
  EndStructure
  
  ; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  ; <<<<< The IsBetween macro <<<<<
  
  Macro IsBetween(Value, Lower, Upper)
    
    ((Value) >= (Lower) And (Value) <= (Upper))
    
  EndMacro
  
  ; <<<<<<<<<<<<<<<<<<<<<<
  ; <<<<< The getter <<<<<

  Procedure.s GetFileName(*This.Private_Members)
    
    If ListSize(*This\FileNames()) >= 1
      FileName.s = *This\FileNames()
    Else
      FileName = ""
    EndIf
    
    ProcedureReturn FileName
  EndProcedure
  
  ; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  ; <<<<< The AddSettings operator <<<<<
  
  Procedure AddSettings(*This.Private_Members)
    
    AddElement(*This\Settings())
    
  EndProcedure
  
   ; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  ; <<<<< The UseSettings operator <<<<<
  
  Procedure UseSettings(*This.Private_Members, SettingID.i)
    
    If IsBetween(SettingID, 0, ListSize(*This\Settings()) - 1)
      SelectElement(*This\Settings(), SettingID)
    EndIf
    
  EndProcedure
  
  ; <<<<<<<<<<<<<<<<<<<<<<<
  ; <<<<< The Setters <<<<<

  Procedure SetMode(*This.Private_Members, Mode.i)
    
    *This\Settings()\Mode = Mode
    
  EndProcedure
  
  Procedure SetTitle(*This.Private_Members, Title.s)
    
    *This\Settings()\Title = Title
    
  EndProcedure
 
  Procedure SetButtonText(*This.Private_Members, ButtonID.i, Text.s)
    
    *This\Settings()\ButtonText[ButtonID] = Text
    
  EndProcedure

  Procedure SetPath(*This.Private_Members, Path.s)
    
    *This\Settings()\Path = Path
    
  EndProcedure
  
  Procedure SetSelectMultiple(*This.Private_Members, SelectMultiple.i)
    
    *This\Settings()\SelectMultiple = SelectMultiple
    
  EndProcedure
  
  Procedure SetDefaultPatternID(*This.Private_Members, DefaultPatternID.i)
    
    *This\Settings()\DefaultPatternID = DefaultPatternID
    
  EndProcedure
  
  Procedure SetParent(*This.Private_Members, *Parent)
    
    *This\Parent = *Parent
    
  EndProcedure
  
  ; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  ; <<<<< The AddPatterns operator <<<<<
  
  Procedure AddPatterns(*This.Private_Members, Label.s, Extension.s)
    
    AddElement(*This\Settings()\Patterns())
    *This\Settings()\Patterns()\Label = Label
    *This\Settings()\Patterns()\Extension = Extension
    
  EndProcedure
  
  ; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  ; <<<<< The ClearPatterns operator <<<<<

  Procedure ClearPatterns(*This.Private_Members)
    
    ClearList(*This\Settings()\Patterns())
    
  EndProcedure
  
  ; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  ; <<<<< The SelectedPatternID operator <<<<<
  
  Procedure.i SelectedPatternID(*This.Private_Members)
  
    ProcedureReturn ListIndex(*This\Settings()\Patterns())
  EndProcedure
  
  ; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  ; <<<<< The SelectFileNames operator <<<<<

  Procedure.i SelectFileNames(*This.Private_Members, Index.l)
    
    ProcedureReturn SelectElement(*This\FileNames(), Index)
  EndProcedure
  
  ; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  ; <<<<< The NextFileNames operator <<<<<

  Procedure.i NextFileNames(*This.Private_Members)
    
    ProcedureReturn NextElement(*This\FileNames())
  EndProcedure
  
  ; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  ; <<<<< The ResetFileNames operator <<<<<

  Procedure ResetFileNames(*This.Private_Members)
    
    ResetList(*This\FileNames())
    
  EndProcedure
  
  ; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  ; <<<<< The FileNamesSize operator <<<<<

  Procedure.l FileNamesSize(*This.Private_Members)
    
    ProcedureReturn ListSize(*This\FileNames())
  EndProcedure
  
  ; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  ; <<<<< The Open operator <<<<<
  
  Procedure.i Open(*This.Private_Members)
    
    Protected *Dialog, *Mem 
    
    If *This\Settings()\Mode = #MODE_OPEN
      Action.i = #GTK_FILE_CHOOSER_ACTION_OPEN
      Btn1.s = *This\Settings()\ButtonText[0]
      Btn2.s = *This\Settings()\ButtonText[1]
      Title.s = *This\Settings()\Title
    ElseIf *This\Settings()\Mode = #MODE_SAVE
      Action = #GTK_FILE_CHOOSER_ACTION_SAVE
      Btn1 = *This\Settings()\ButtonText[0]
      Btn2 = *This\Settings()\ButtonText[1]
      Title = *This\Settings()\Title
    EndIf
    
    ClearList(*This\FileNames())
    
    *Dialog = gtk_file_chooser_dialog_new(Title, *This\Parent, Action, Btn1, #GTK_RESPONSE_CANCEL, Btn2, #GTK_RESPONSE_ACCEPT, 0)
    
    gtk_window_set_modal_(*Dialog, #True)
    gtk_file_chooser_set_current_folder_(*Dialog, *This\Settings()\Path)
    
    If *This\Settings()\Mode = #MODE_OPEN
      gtk_file_chooser_set_select_multiple(*Dialog, *This\Settings()\SelectMultiple)
    EndIf
    
    ForEach *This\Settings()\Patterns()
      
      *This\Settings()\Patterns()\Filter = gtk_file_filter_new_()
      gtk_file_filter_set_name(*This\Settings()\Patterns()\Filter, *This\Settings()\Patterns()\Label)
      
      If FindString(*This\Settings()\Patterns()\Extension, ";") = 0
        gtk_file_filter_add_pattern(*This\Settings()\Patterns()\Filter, *This\Settings()\Patterns()\Extension)
      Else
        
        ExtensionMax.l = CountString(*This\Settings()\Patterns()\Extension, ";") + 1
        
        For EntensionID = 1 To ExtensionMax
          gtk_file_filter_add_pattern(*This\Settings()\Patterns()\Filter, StringField(*This\Settings()\Patterns()\Extension, EntensionID, ";"))
        Next
        
      EndIf

      gtk_file_chooser_add_filter_(*Dialog, *This\Settings()\Patterns()\Filter)
      
    Next
    
    If IsBetween(*This\Settings()\DefaultPatternID, 0, ListSize(*This\Settings()\Patterns()) - 1)
      SelectElement(*This\Settings()\Patterns(), *This\Settings()\DefaultPatternID)
      gtk_file_chooser_Set_filter_(*Dialog, *This\Settings()\Patterns()\Filter)
    EndIf
    
    r1.l = gtk_dialog_run_(*Dialog)
    
    If r1 = #GTK_RESPONSE_ACCEPT
      
      Output.i = #True
      
      *CurrentFilter = gtk_file_chooser_get_filter_(*Dialog)
      
      If *CurrentFilter <> #Null
        
        ForEach *This\Settings()\Patterns()
          
          If *CurrentFilter = *This\Settings()\Patterns()\Filter
            Break
          EndIf
          
        Next
        
      EndIf
      
      If *This\Settings()\Mode = #MODE_OPEN
        
        If *This\Settings()\SelectMultiple = #False
          
          *Mem = gtk_file_chooser_get_filename_(*Dialog)
          
          If *Mem <> #Null
            AddElement(*This\FileNames())
            *This\FileNames() = PeekS(*Mem, -1, #PB_UTF8) 
            g_free_(*Mem)
          EndIf
          
        ElseIf *This\Settings()\SelectMultiple = #True
          
          *FileList.GSList = gtk_file_chooser_get_filenames_(*Dialog)
          *Node.GSList = *FileList
          
          While *Node <> #Null
            AddElement(*This\FileNames())
            *This\FileNames() = PeekS(*Node\Data, -1, #PB_UTF8)
            g_free_(*Node\Data)
            *Node = *Node\Next
          Wend
          
          g_slist_free_(*FileList)
          
        EndIf
        
      ElseIf *This\Settings()\Mode = #MODE_SAVE
        
        *Mem = gtk_file_chooser_get_filename_(*Dialog)
        
        If *Mem <> #Null
          
          ; On considère que le chemin initiale est bon
          GoodPath.s = PeekS(*Mem, -1, #PB_UTF8) 
          g_free_(*Mem)
          
          SelectedExtension.s = StringField(*This\Settings()\Patterns()\Extension, 2, ".")
          
          If SelectedExtension <> ""
            ; on découpe l'info du nom de fichier
            
            Path.s = GetPathPart(GoodPath)
            Name.s = GetFilePart(GoodPath)
            CurrentExtension.s = GetExtensionPart(GoodPath)
            
            ; S'il y a des problèmes on reconstruit le chemin
            
            If CurrentExtension = ""
              GoodPath = Path + Name + "." + SelectedExtension
            EndIf 
            
            If CurrentExtension <> SelectedExtension
              GoodPath = Path + StringField(Name, 1, ".") + "." + SelectedExtension
            EndIf 
            
          EndIf
          
          AddElement(*This\FileNames())
          *This\FileNames() = GoodPath
          
        EndIf
        
      EndIf
      
    EndIf 
    
    gtk_widget_destroy_(*Dialog) 
    
    ProcedureReturn Output
  EndProcedure
  
  ; <<<<<<<<<<<<<<<<<<<<<<<<<<
  ; <<<<< The Destructor <<<<<

  Procedure Free(*This.Private_Members)
    
    FreeStructure(*This)
    
  EndProcedure
  
  ; <<<<<<<<<<<<<<<<<<<<<<<<<<<
  ; <<<<< The Constructor <<<<<

  Procedure.i New()
    
    *This.Private_Members = AllocateStructure(Private_Members)
    *This\VirtualTable = ?START_METHODS
    
    ProcedureReturn *This
  EndProcedure
  
  ; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  ; <<<<< The virtual table entries <<<<<

  DataSection
    START_METHODS:
    Data.i @GetFileName()
    Data.i @AddSettings()
    Data.i @UseSettings()
    Data.i @SetMode()
    Data.i @SetTitle()
    Data.i @SetButtonText()
    Data.i @SetPath()
    Data.i @SetSelectMultiple()
    Data.i @SetDefaultPatternID()
    Data.i @SetParent()
    Data.i @AddPatterns()
    Data.i @ClearPatterns()
    Data.i @SelectedPatternID()
    Data.i @SelectFileNames()
    Data.i @NextFileNames()
    Data.i @ResetFileNames()
    Data.i @FileNamesSize()
    Data.i @Open()
    Data.i @Free()
    END_METHODS:
  EndDataSection
  
EndModule

; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

CompilerIf #PB_Compiler_IsMainFile
  
  gtk_init_(0, 0)
  
  ; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  ; The FileChooser creation
  
  FileChooser.GtkFileChooser::FileChooser = GtkFileChooser::New()
  
  ; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  ; The FileChooser AddSettings
  ; (Need to be done only once)
  
  FileChooser\AddSettings()
  FileChooser\SetTitle("Test Open")
  FileChooser\SetMode(GtkFileChooser::#MODE_OPEN)
  FileChooser\SetButtonText(0, #GTK_STOCK_CANCEL)
  FileChooser\SetButtonText(1, #GTK_STOCK_OPEN)
  FileChooser\SetSelectMultiple(#True)
  FileChooser\SetPath("/home/")
  FileChooser\SetDefaultPatternID(1)
  FileChooser\AddPatterns("PureBasic Source (*.pb)", "*.pb")
  FileChooser\AddPatterns("PureBasic Include (*.pbi)", "*.pbi")
  
  FileChooser\AddSettings()
  FileChooser\SetTitle("Test Save")
  FileChooser\SetMode(GtkFileChooser::#MODE_SAVE)
  FileChooser\SetButtonText(0, #GTK_STOCK_CANCEL)
  FileChooser\SetButtonText(1, #GTK_STOCK_SAVE)
  FileChooser\SetSelectMultiple(#False)
  FileChooser\SetPath("/home/")
  FileChooser\SetDefaultPatternID(0)
  FileChooser\AddPatterns("PureBasic Source (*.pb)", "*.pb")
  FileChooser\AddPatterns("PureBasic Include (*.pbi)", "*.pbi")
  
  FileChooser\AddSettings()
  FileChooser\SetTitle("Test Open 2")
  FileChooser\SetMode(GtkFileChooser::#MODE_OPEN)
  FileChooser\SetButtonText(0, #GTK_STOCK_CANCEL)
  FileChooser\SetButtonText(1, #GTK_STOCK_OPEN)
  FileChooser\SetSelectMultiple(#True)
  FileChooser\SetPath("/home/")
  FileChooser\SetDefaultPatternID(0)
  FileChooser\AddPatterns("PureBasic Code (*.pb, *.pbi)", "*.pb;*.pbi")
  
  ; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  ; The FileChooser Open Multiple files test
  
  FileChooser\UseSettings(0)
  
  If FileChooser\Open()
    
    FileChooser\ResetFileNames()
    
    While FileChooser\NextFileNames()
      
      Debug FileChooser\GetFileName()
      
    Wend
    
  EndIf
  
  ; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  ; The FileChooser Save file test
  
  FileChooser\UseSettings(1)
  
  If FileChooser\Open()
    Debug FileChooser\GetFileName()
  EndIf
  
  ; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  ; The FileChooser Open file test
  
  FileChooser\UseSettings(0)
  
  ; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  ; We change the "SelectMultiple" flag and the 
  ; "DefaultPatternID" on the fly
  
  FileChooser\SetSelectMultiple(#False)
  FileChooser\SetDefaultPatternID(0)
  
  If FileChooser\Open()
    Debug FileChooser\GetFileName()
  EndIf
  
  ; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  ; The FileChooser Open file(s) 2 possible extensions test
  
  FileChooser\UseSettings(2)
 
  If FileChooser\Open()
    
    FileChooser\ResetFileNames()
    
    While FileChooser\NextFileNames()
      
      Debug FileChooser\GetFileName()
      
    Wend
    
  EndIf
  
  ; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  ; The FileChooser is no longer needed
  
  FileChooser\Free()
  
CompilerEndIf

; <<<<<<<<<<<<<<<<<<<<<<<
; <<<<< END OF FILE <<<<<
; <<<<<<<<<<<<<<<<<<<<<<<
The Stone Age did not end due to a shortage of stones !