Tutorial:Dyko file dialog

From Bennu Wiki
Jump to: navigation, search


Introduction

In this tutorial I'm going to show how create an filedialog wich uses processes to create a linked list of files. Instead of using manual forms of memory managment with functions like alloc and realloc, I'm making use of the automatic memory allocation that bennu takes care of when ProcessID's (instances) are created.

As you might know, bennu's process hierarchy is basically a tree like structure. This example is quite big, as it consists of several source files. So it also shows concepts like Include's and Constant definitions.

However, since the complete progam is quite lengthy, I'm not going to cover the enire program. I will focus mainly on the conceptual idea and some of the core routines that make the whole thing tick. If you want to study the whole program and see it in action, you can download the sources, binaries (for windows) and graphics in the download section below.

Downloads

You can download an archive with all the files here


The fileinfo structure

To create a filedialog, we need to work with the filesystem. Bennu has a module for this, it is mod_dir. All of these functions make use the global structure fileinfo. The data in this structure is updated everytime the glob() function is used. The fileinfo structure provides the framework that we need for the filedialogbox.


Reading the directory

The tree structure, relationships

As you pobably know, a filedialog shows a list of files. In an ideal world, there would be a limitation to the maximum amount of files in a directory. This is the case with older filesystems, but not so with modern ones. Therefore, we cannot use something simple like an array to create the list.

The list has to be dynamic, i.e. when a directory only contains 3 files, it's not really much of a problem if the array has the capacity to list 100 files. But when what if there's more then 100 files? What would be a reasonable limitation? We don't know that. Because of this uncertainty, we need a linked list. Now, an obvious thing would be allocate the memory as we need it, but there is an easier way. It's time for processes!

One characteristic of processes is that instances can be created and destroyed at runtime, and that we can give them local variables. However, they also have a more miscellaneous property, and that are Father/Son relationships.

With this knowledge, we can create this process type:

/*****************************************************************************************

 process file_entry: Node in a (linked) list of files. The links are created by the
                     file_entry id's bigbro and smallbro links that bennu process always
                     have. (These are a kind of pointer packed in a variable that holds
                     the process id code of the two adjecent file_entry processes).
                     Bennu automatically allocates memory for each process instance, so
                     hench we get a linked list without having to mess with pointers and
                     memory managment manually.
                     
                     This process is the core part of the "DYKO FILE SYSTEM OBJECT".
 
 arguments: - int count       : the number of the file in the list
            - string path     : the absolute path to the file or directory
            - string name     : the file or directory name
            - byte dir        : TRUE or FALSE, indicates if the entry is a file or a directory
            - byte hidden     : TRUE or FALSE, indicates if the file is a hidden file
            - byte readonly   : TRUE or FALSE, indicated if the file is readonly
            - int size        : the size of the file
            - string created  : the date the file or directory was created
            - string modified : the date the file or directory was last modified
            
 returns: - NOTHING        

******************************************************************************************/
PROCESS file_entry(int count, string path, string name, byte dir, byte hidden, byte readonly, int filesize, string created, string modified);

PRIVATE

BEGIN
     
   // check if we found the .. dir in the current directory, if not, the "dir up button" becomes
   // pointless, since we've reached the root directory of the filesystem.
   IF (dir==TRUE)
      IF (strcasecmp(name,"..")==0) 
         IF (debugging==TRUE)
            say("There is a level above");            
         END         
         parentdir=TRUE;   // this entry is the ".." entry         
      ELSE
         IF (debugging==TRUE)
            say("This is a standard dir");
         END
      END
   END
   
     
   // create a child process that displays the file graphically
   display_file_entry(count,dir);
   
   
   
   
   IF (debugging==TRUE) 
      IF (dir==TRUE)
         say("ID="+id+" file_entry: "+count+"; "+name+" <DIR>"+" parent? > "+parentdir);          
      ELSE
         say("ID="+id+" file_entry: "+count+"; "+name); 
      END
   END
   
   LOOP
      // set the pointers
      prev_id=bigbro;
      next_id=smallbro;
      FRAME;
   END // end of loop
   
END // end of begin

It's the file_entry process, wich is going to be our container in wich we load all the data from the fileinfo structure, each time we glob. Although it's not listed here, somewhere in the program we create a loop wich performs the glob. Each glob's result will be put in an instance of the file_entry, wich is created in the same loop. We are going to use the Father/Son relationship system to create the node links.

Each file_entry instance will have two adjecent instances. These are all son's of the process that does the globs. All of these file_entry instances are thus on the same level of hierarchy, so they are Bigbrothers and Smallbrothers of each other. These local variables are concealed pointers of a linked list or tree structure, so this is exactly how these links are established.

The globbing part, how this list is built

Let's take a look a the file dir_read.prg from the archive. The "dir_read()" process executes the glob and creates the file_entry instance.

/*****************************************************************************************

 process main: discription of what it more or less does
 
 arguments: - string glob_mask : The path to start (e.g. c:\windows\*.*)
            
 returns: NOTHING.      

******************************************************************************************/

PROCESS dir_read(string glob_mask);


PRIVATE

    
BEGIN

   // kill the file selector, when an instance of it already exists, because we
   // create a new instance at the end of the routine.
   IF (exists(TYPE fileselector))
      IF (debugging==TRUE)
         say("killing cursor");
      END
      signal(TYPE fileselector,s_kill);
   END
   
  
   
   glob("");    // reset search

   filename=""; // clear the filename
   filecount=0; // reset the counter 

   IF (debugging==TRUE)
      say("reading directory: "+glob_mask);
   END
   
   LOOP
      filename=glob(glob_mask);
                                    
      // when glob returns a name,  it is a file or directory, so we increase the 
      // filecounter, add it to the list of files, and print it.      
      IF (filename!="")        
         
         
         // add node to the list, depending on the dialog setting, show only dirs, or dirs and files 
         IF (dialog_io.dialogtype==DIR_SELECT)         
            IF (fileinfo.directory==TRUE)
               filecount+=1; 
               current_entry=file_entry(filecount, fileinfo.path, fileinfo.name, fileinfo.directory, fileinfo.hidden, fileinfo.readonly, fileinfo.size, fileinfo.created, fileinfo.modified);                                     
            ELSE
               // do nothing            
            END            
         ELSE // we're in normal mode
           filecount+=1; 
           current_entry=file_entry(filecount, fileinfo.path, fileinfo.name, fileinfo.directory, fileinfo.hidden, fileinfo.readonly, fileinfo.size, fileinfo.created, fileinfo.modified);                 
         END
         
         IF (filecount==1)
            first_entry=current_entry;
         END
               
      ELSE
         last_entry=current_entry; // we have reached the last file/directory in the directory     

         IF (debugging==TRUE)         
            say("reading directory completed.");         
         END
         BREAK;
      END

   END // end of loop
   
   
   
   // update the current path.
   current_path=fileinfo.path;
   
   
   IF (debugging==TRUE)
      say("current_path (3):"+current_path);
   END
   
   // display the scroll with the files, and the selection cursor
   IF (debugging==TRUE)
      say("display file list");    
   END
   
   display_filelist();
   
   IF (debugging==TRUE)
      say("create cursor");    
   END
   
   selector=fileselector();
   
ONEXIT

   
   
END // end of begin


displaying the file list

The child process of file_entry

For practical reasons I give each file_entry instance a child process instance, wich is going to be responsible for the visual display. This Son process, uses the Father fields to get the data from it's parent.


/*****************************************************************************************

 process display_file_entry: Child process of file entry, this is the "visible" part of 
                             a file system object. This process puts the filename on the
                             screen.
 
 arguments: - int count : number of file
            - byte dir  : TRUE or FALSE
            
 returns: - NOTHING.      

******************************************************************************************/
PROCESS display_file_entry(int count, byte dir);

PRIVATE

int label_id;

byte parent_detected=FALSE;

BEGIN

   // reset the parent detector
   parent_detected=FALSE;
  
   // put it in the scroll
   ctype=c_scroll;

   x=20;
   y=(count*scroll_interval);
   
   IF (dir==TRUE)
      set_text_color(COLOR_GREY5);
      // create the text label
      label_id=write_in_map(0,father.name+" <DIR>",ALIGN_CENTER_LEFT);
      
      // when a directory has the parent property, it is the ".." directory.
      // we store the it's id code for use by the "dir up" button.
      IF (father.parentdir==TRUE)
      
         parentdir_id=father.id; // save the id code of the ".." dir     
         parent_detected=TRUE;
         
         IF (debugging==TRUE)
            say("parent detected!!!!!!!!!!!!!!!!! parentdir_ID="+parentdir_id);
         END
         
         set_text_color(COLOR_RED);
         // create the text label
         label_id=write_in_map(0,father.name+" <DIR>",ALIGN_CENTER_LEFT);
         
      // the dir is not a parent dir
      ELSE
         
         // check if the instance still exists, if not, we're in the root dir.
         IF (exists(parentdir_id))
            IF (debugging==TRUE)
               say("parentdir_id:"+parentdir_id);
            END
         ELSE
            parentdir_id=0; // we have no parent, so we're in the root of the filesystem.
            
            IF (debugging==TRUE)
               say("parentdir_id:"+parentdir_id);
            END
         END
         
         set_text_color(COLOR_GREY5);
         // create the text label
         label_id=write_in_map(0,father.name+" <DIR>",ALIGN_CENTER_LEFT);
      END
   
   // the entry is not a dir but a file.   
   ELSE
      set_text_color(COLOR_WHITE);
      // create the text label
      label_id=write_in_map(0,father.name,ALIGN_CENTER_LEFT);
   END
   
   graph=label_id;
   
   LOOP
      FRAME;
   END // end of loop
      
ONEXIT
   // free the resources
   unload_map(file,label_id);
   
END // end of begin


The visual part of the list

The list is drawn by creating maps in memory, with the map_new() function, and putting the label graphics inside a Scroll_window. A Region is also defined, since the scroll should not take up the entire screen.

/*****************************************************************************************

 process display_filelist: List the files on the screen created by dir_read().
                           The filenames are put inside a scroll.
 
 arguments: - 

            
 returns: - NOTHING.      

******************************************************************************************/
PROCESS display_filelist();

PRIVATE

int scrollfile;
int fileslist;




BEGIN
 
 
   // create map for the scroll
   fileslist=new_map(300,100,16);
   
   // reset the scroll's internal upper coordinate, this keeps the scroll always on the
   // same position when this value was modified by the fileselector, since that process
   // is the scroll's camera process, so it modifies the y-value to keep the fileselector
   // centered on the screen.
   scroll.y0=0;
   
   // define the region and create the scroll
   scroll.region1=define_region(1,10,10,300,415);   
   start_scroll(0,scrollfile,fileslist,0,1,2);
   
   IF (exists(TYPE display_label))
      IF (debugging==TRUE)
         say("killing file list");
      END
      signal(TYPE display_label,s_kill);
   END
   
   // display the path
   pathlabel_id=display_label(30,460,TRUE,18,"PATH: "+current_path);
            
   LOOP  
      
      IF (exists(filelabel_id))
         signal(filelabel_id,s_kill);
      END
      
      // display the filename
      filelabel_id=display_label(30,450,FALSE,2,"FILENAME: "+current_selected_previewfilename);
      
      FRAME;
   END // end of loop

   
ONEXIT

   // free the resources
   stop_scroll(0);
   unload_map(scrollfile,fileslist);
   
END // end of begin

The routine above put's labels in a scroll, and the scoll's internal y0 variable is manipulated, to keep the scroll on a fixed position, so that the file selection widget's vertical position doesn't mess up.



Using collisions to select files

The selection of files is a matter of clicking it with the mouse of moving the cursor with the cursor keys. The selection cursor obtains it's data from the file labels. The fileselector() process in the file "fileselector.prg" takes care of it. A collision between the label and the selection cursor exchanges the data. The position of the selection cursor is controlled by the cursor keys, the arrow buttons, or the mouse. When the mouse is clicked on a different label then the one the cursor is colliding with, the cursor is moved to the position of the mouse.

/*****************************************************************************************

 process fileselector: Put's the file selection cursor on the screen, wich can be moved
                       with the cursor keys. This causes the list of files to scroll on
                       the screen.
 
 arguments: - 

            
 returns: - NOTHING.      

******************************************************************************************/
PROCESS fileselector();

PRIVATE

int selectiongraph;
int selectedfile;

string temp_path_string;
int temp_path_length;

BEGIN

   action=FALSE;

   // assign it to the scroll, and make this the process that controls the scroll camera.
   ctype=c_scroll;
   scroll.camera=id;
   
   x=160;
   y=10;

   // make the cursor semi-transparant, so that the text is still visible through it.
   flags=B_TRANSLUCENT;
   
   // create a map
   selectiongraph=new_map(300,10,16);
   
   // make it yellow
   //map_clear(0,selectiongraph,rgb(255,255,200));
   map_clear(0,selectiongraph,COLOR_SKINPINK);

   
   graph=selectiongraph;
   
   LOOP
   
      
      // code for most of the commands
      INCLUDE "fileselector-commands.prg";
      
      
      // code for command that jumps to the root direcory
      INCLUDE "fileselector-command_dir_root.prg";
      
      
      // code for drive selection.
      INCLUDE "fileselector-driveselection.prg";
      
      
      // code for filter selection.
      INCLUDE "fileselector-filterselection.prg";
      
      
      
      // get a preview of the filename
      IF (selectedfile=collision(TYPE display_file_entry))
         IF (selectedfile.father.dir==TRUE)
            current_selected_previewfilename=selectedfile.father.name+" <DIR>"; 
            current_selected_previewdirname=selectedfile.father.name;            // variable used by directory selection dialog
         ELSE
            current_selected_previewfilename=selectedfile.father.name;   
         END
      END
      
      
      
      
      // select the file or directory, and take action, a lot of stuff is going on here.
      IF (key(_enter) OR gui_trigger==M_OK OR action==TRUE)
         FRAME(1000);
         
         // reset gui button event trigger
         gui_trigger=0;
         
         // get the properties from the currently selected file
         IF (selectedfile=collision(TYPE display_file_entry))
            
            // a directory was selected
            IF (selectedfile.father.dir==TRUE)
            
               
               // obtain the name of the selected directory and update the path
               current_selected_directoryname=selectedfile.father.name;
               
               IF (debugging==TRUE)
                  say("current_selected_directoryname: "+current_selected_directoryname);
                  say("current_path:"+selectedfile.father.path);
               END
               
               
               // destroy the list               
               IF (exists(TYPE file_entry))
                  signal(TYPE file_entry,s_kill_tree);
               END
               
               FRAME(500);
               
               // change to the selected directory and recreate the list
               IF (debugging==TRUE)
                  say("changing dir and rebuilding tree");
               END
               
               chdir(current_selected_directoryname);
		       dir_read("*.*");
               
               
               
            // a file was selected and not a directory   
            // Bear in mind that the file adss a \ before the filename!
            
            ELSE // else of "IF (selectedfile.father.dir==TRUE)"
            
            
               // quit the dialog and output the selected filename on the console
               current_selected_filename=selectedfile.father.name;  
               
               // save current dir in struct (i.e. c:\documents\projects\levels)                   
               dialog_io.outputdir=current_path;      
               
               // save selected filename in struct (i.e. level1.lev)             
               dialog_io.outputfilename=current_selected_filename;    
                             
               
               SWITCH (os_id)
                   
                   CASE OS_WIN32:              
                      // save them both together (i.e. c:\documents\projects\levels\level1.lev)
                      dialog_io.outputabsolutepath=current_path+current_selected_filename; 
                                           
                      // save the dialog_io STRUCT to a binary file.
                      save(start_path+"\dialog_io.dat",dialog_io);
                   END
                   
                   CASE OS_LINUX, OS_BSD, OS_BEOS, OS_MACOS, OS_CAANOO, OS_GP2X_WIZ, OS_GP2X, OS_GP32:
                      // save them both together (i.e. c:\documents\projects\levels\level1.lev)
                      dialog_io.outputabsolutepath=current_path+"/"+current_selected_filename; 
                      //dialog_io.outputabsolutepath=current_path+current_selected_filename; 
                      
                     // save the dialog_io STRUCT to a binary file.
                      save(start_path+"/dialog_io.dat",dialog_io);
                   END   
                                   
                   DEFAULT:
                      // save them both together (i.e. c:\documents\projects\levels\level1.lev)
                      dialog_io.outputabsolutepath=current_path+"/"+current_selected_filename; 
                                                              
                      // save the dialog_io STRUCT to a binary file.
                      save(start_path+"/dialog_io.dat",dialog_io);
                   END   
                   
               END
               
               
                  
               
                                 
               IF (debugging==TRUE)
                  say("");
                  say("");
                  say("dialog_io.outputdir: "+dialog_io.outputdir);
                  say("");
                  say("dialog_io.outputfilename: "+dialog_io.outputfilename);
                  say("");                  
                  say("dialog_io.outputabsolutepath: "+dialog_io.outputabsolutepath);
                  say("");
                  say("");
               END
                              
               // output on console 
               SWITCH (dialog_io.file_path_outputmode)
                  
                  CASE MODE_ABSOLUTEPATH:
                     exit(dialog_io.outputabsolutepath); 
                  END                                    
                  
                  CASE MODE_PATH:
                     exit(dialog_io.outputdir); 
                  END
                  
                  CASE MODE_FILENAME:
                     exit(dialog_io.outputfilename); 
                  END
                  
               END
            END // end of "IF (selectedfile.father.dir==TRUE)"
         END // end of "IF (selectedfile=collision(TYPE display_file_entry))"
         
         
      END //end of "IF (key(_enter) OR gui_trigger==M_OK OR action==TRUE)"
           
      FRAME;
   END // end of loop

ONEXIT
   // free resources
   unload_map(0,selectiongraph);
   
END // end of begin


The mouse cursor

Now let's take a look at the file "mousecursor.prg". It contains two processes, as I split the visual part of the cursor and the logic part. The reason for this has to do with the precision of the mouse collision.

The visual part, mousecursor ()

The mousecursor() process creates a child process, called mouse_hit_target(). This process takes care of the logic. The mousecursor() process itself is just a dummy, but it does check if it stays withing the region defined for the scroll.

/*****************************************************************************************

 process mousecursor:  Draws a mouse cursor on the screen and handle it actions, 
                       wich are delegated to a child process with a vry small graph,
                       to get better target accuracy.
 
 arguments: - 
            
            
 returns: - NOTHING.      

******************************************************************************************/
PROCESS mousecursor();

PRIVATE

BEGIN

   ctype=c_scroll;
   
   file=guifile;
   graph=2;
  
   x=100;
   y=100;
  
   z=-512;
  
   // initiate the child process that does most of the work!
   mouse_hit_target();
     
   LOOP
      x=mouse.x;
      y=mouse.y;
      
      // change the ctype to scroll when the mouse cursor is within
      // the area of the file scroll, otherwise use screen coordinates.
      IF (out_region(id,1))         
         ctype=c_screen;
      ELSE        
         ctype=c_scroll;
      END
      
      FRAME;
   END // end of loop
   
END // end of begin


The logic part, with mouse_hit_target()

Because the graphic of the mousecursor is a bit too large for the file labels, I created a child process with a very small graphic as collision target. The mouse_hit_target() process takes all of it's coordinates and ctype data from it's Father, the mousecursor().

So, this process checks for a collision with a file label.

/*****************************************************************************************

 process mouse_hit_target:  Draws a target graph at the mouse cursor's tip (upper left 
                            corner), and deal with collisions with file objects and 
                            handle mouse click events.
                       
 arguments: - 
            
            
 returns: - NOTHING.      

******************************************************************************************/
PROCESS mouse_hit_target();

PRIVATE

// identification code of the display_file_entry instance it collides with
int file_entry_id;
// the identfier of the graph
int hitgraph;

// counter for measuring if the user is clicking once or is dubble clicking
// the left mouse button.
byte clickcount=0;

byte filenamelabel_clicked=FALSE;
byte pathlabel_clicked=FALSE;


BEGIN

   // create the "target" graph
   hitgraph=new_map(2,20,16);
   
   // give the graph an invisible color
   map_clear(file,hitgraph,COLOR_TRANSPARANT);
  
   graph=hitgraph;
     
   ctype=father.ctype;     
   x=father.x;
   y=father.y;     
     
   LOOP
      ctype=father.ctype;     
      x=father.x;
      y=father.y;      
      
      
      // check for a collision with the filename label
      IF (collision(filelabel_id) AND mouse.left AND filenamelabel_clicked==FALSE)
         FRAME(500);
         gui_trigger=M_FILENAMELABEL_CLICKED_1STCLICK;
         filenamelabel_clicked=TRUE;         
      END
      
      IF (collision(filelabel_id) AND mouse.left AND filenamelabel_clicked==TRUE)
         FRAME(500);
         gui_trigger=M_FILENAMELABEL_CLICKED_2NDCLICK;
         filenamelabel_clicked=FALSE;         
      END
      
      // check for a collision with the path label
      IF (collision(pathlabel_id) AND mouse.left AND pathlabel_clicked==FALSE)
         FRAME(500);         
         gui_trigger=M_PATHLABEL_CLICKED_1STCLICK;      
         pathlabel_clicked=TRUE;                 
      END
      
      IF (collision(pathlabel_id) AND mouse.left AND pathlabel_clicked==TRUE)
         FRAME(500);         
         gui_trigger=M_PATHLABEL_CLICKED_2NDCLICK;
         pathlabel_clicked=FALSE;         
      END  
      
      
      // disable menus when mouse hovers over labels
      IF (collision(TYPE display_label))
         IF (OS_ID==OS_WIN32)
            disable_drive_menu();
         END        
         IF (exists(bt_selfilter))
            disable_filter_menu();
         END
      END
      
      // disable menus when mouse hovers over the selection cursor
      IF (collision(TYPE fileselector))
         IF (OS_ID==OS_WIN32)
            disable_drive_menu();
         END        
         IF (exists(bt_selfilter))
            disable_filter_menu();
         END
      END
      
      // disable menus when mouse is clicked on the screen
      IF (NOT collision(TYPE button) AND mouse.left)
        IF (OS_ID==OS_WIN32)
            disable_drive_menu();
         END        
         IF (exists(bt_selfilter))
            disable_filter_menu();
         END
      END
      
      // disable menus when the other buttons are clicked
      IF (collision(bt_open) OR collision(bt_cancel) OR collision(bt_up) OR collision(bt_down)) 
         IF (OS_ID==OS_WIN32)
            disable_drive_menu();
         END
         IF (exists(bt_selfilter))
            disable_filter_menu();
         END
      END
      
      IF (collision(bt_dirselect) AND dialog_io.dialogtype==DIR_SELECT) 
         IF (OS_ID==OS_WIN32)
            disable_drive_menu();
         END
         IF (exists(bt_selfilter))
            disable_filter_menu();
         END
      END
      
      IF (collision(bt_dir_up) OR collision(bt_dir_root)) 
         IF (OS_ID==OS_WIN32)
            disable_drive_menu();
         END
         IF (exists(bt_selfilter))
            disable_filter_menu();
         END
      END
      
      IF (exists(bt_selfilter))
         IF (collision(bt_selfilter)) 
            IF (OS_ID==OS_WIN32)
               disable_drive_menu();
            END
         END
      END
      
      IF (exists(bt_seldrive))
         IF (collision(bt_seldrive))      
            IF (exists(bt_selfilter))
               disable_filter_menu();
            END
         END
      END
      
      IF (exists(bt_mkdir))
         IF (collision(bt_mkdir)) 
            IF (OS_ID==OS_WIN32)
               disable_drive_menu();
            END         
            IF (exists(bt_selfilter))
               disable_filter_menu();
            END
         END
      END
      
      // disable menus when mouse is clicked on a file/dir name
      IF (collision(TYPE display_file_entry)) 
         IF (OS_ID==OS_WIN32)
            disable_drive_menu();
         END
         IF (exists(bt_selfilter))
            disable_filter_menu();
         END
      END
      
      // when the mouse target hits a file/directory name, take some action
      IF (file_entry_id=collision(TYPE display_file_entry))
         
         
         IF (debugging==TRUE)
            say("id: "+file_entry_id+" filename: "+file_entry_id.father.name);
         END
         
         // when the left mouse button has been clicked, put the fileselector on the
         // position of the selected file/directory, when a second time is clicked
         // within a small delay, count it as a dubble click and execute a command.
         IF (mouse.left)        
            clickcount+=1;     
            selector.y=file_entry_id.y;  
            selector.action=FALSE;          
            FRAME(500);
            clickcount=2;            
            
            // the mouse has been "dubble clicked"
            IF (mouse.left AND clickcount==2)
               selector.action=TRUE;  // let the "selector" instance know what to do.   
               clickcount=0;            
            END
            
            // reset the dubbleclick counter
            IF (NOT mouse.left)
               clickcount=0;
               selector.action=FALSE;       
            END
         END         
      END
     
      FRAME;
   END // end of loop
   
END // end of begin


Wrapping it up

Well, that was pretty much all there is to say about the basic design of the filedialog. As you can see, bennu's process system is very versitile for a lot of different situations and uses. I showed how to create a list by using Bigbrother and Smallbrother relationships and how a Son process can assist it's Father.

The end result

Finally, the end result looks like this:

dyko_filedialog.jpg

Used in example: say(), set_text_color(), exists(), start_scroll, signal, glob(), map_unload(), map_new(), map_clear(), fileinfo, collision(), strcasecmp(), out_region(), Bigbro, Smallbro, Son, Father