Clone in a terminal:
FileDialogTab.vala
/*
	Copyright (C) 2014 2015 Johan Mattsson

	This library is free software; you can redistribute it and/or modify 
	it under the terms of the GNU Lesser General Public License as 
	published by the Free Software Foundation; either version 3 of the 
	License, or (at your option) any later version.

	This library is distributed in the hope that it will be useful, but 
	WITHOUT ANY WARRANTY; without even the implied warranty of 
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
	Lesser General Public License for more details.
*/
using Cairo;
using Math;

namespace BirdFont {

public class FileDialogTab : Table {
	Gee.ArrayList<Row> rows;
	
	const int DIRECTORY = -3;
	const int FILE = -2;
	
	Gee.ArrayList<string> files;
	Gee.ArrayList<string> directories;
	
	string title;
	FileChooser action;
	
	File current_dir;
	
	string selected_filename;
	TextListener listener;
	
	private static bool has_drive_letters = false;
	private static Gee.ArrayList<string> drive_letters;

	bool select_folder = false;

#if LINUX
	public const string path_separator = "/";
#else
	public const string path_separator = "\\";
#endif

	public FileDialogTab (string title, FileChooser action, bool folder) {
		this.title = title;
		this.action = action;
		
		select_folder = folder;
		rows = new Gee.ArrayList<Row> ();
		files = new Gee.ArrayList<string> ();
		directories = new Gee.ArrayList<string> ();
		
		selected_filename = "";
		selected_canvas ();
	}

	public static void add_drive_letter (char c) {
		if (!has_drive_letters) {
			drive_letters = new Gee.ArrayList<string> ();
			has_drive_letters = true;
		}
		
		drive_letters.add (@"$((!) c.to_string ()):\\");
	}

	public override void update_rows () {
		Row row;
		
		rows.clear ();
	
		// add empty rows under the text area
		row = new Row.headline ("");
		rows.add (row);	
	
		if (select_folder) {
			row = new Row.headline (t_("Select a Folder"));
		}
	
		if (directories.size > 0) {
			row = new Row.headline (t_("Folders"));
			rows.add (row);	
		}
		
		foreach (string dir in directories) {
			row = new Row.columns_1 (dir, DIRECTORY, false);
			row.set_row_data (new SelectedFile (dir));
			rows.add (row);
		}

		if (files.size > 0) {
			row = new Row.headline (t_("Files"));
			rows.add (row);	
		}
		
		foreach (string f in files) {
			row = new Row.columns_1 (f, FILE, false);
			row.set_row_data (new SelectedFile (f));
			rows.add (row);
		}
		
		GlyphCanvas.redraw ();
	}

	public override Gee.ArrayList<Row> get_rows () {
		return rows;
	}
	
	public override void selected_row (Row row, int column, bool delete_button) {	
		SelectedFile f;

		if (row.get_index () == FILE) {
			if (!select_folder) {
				return_if_fail (row.get_row_data () is SelectedFile);
				f = (SelectedFile) row.get_row_data ();
				selected_filename = f.file_name;
			}
		} else if (row.get_index () == DIRECTORY) {
			return_if_fail (row.get_row_data () is SelectedFile);
			f = (SelectedFile) row.get_row_data ();
			
			if (select_folder && f.file_name != "..") {
				selected_filename = f.file_name;
			} else {
				if (f.file_name.index_of (":") > -1) {
					propagate_files (f.file_name);
				} else {
					string d = (!) current_dir.get_path ();
					printd (@"Subdir: $(f.file_name) in $d path_separator: $path_separator\n");
					string p = d + path_separator + f.file_name;
					
					while (p.index_of ("\\\\") != -1) {
						p = p.replace ("\\\\", "\\");
					}
					
					propagate_files (p);
				}
				
				selected_filename = "";
			}
		}
		
		show_text_area (selected_filename);
	}
	
	public override void selected_canvas () {
		string d;
		show_text_area ("");
		
		d = Preferences.get ("file_dialog_dir");
		
		if (d == "") {
			d = Environment.get_home_dir ();
		}
		
		propagate_files (d);
		base.selected_canvas ();
	}

	public void propagate_files (string dir) {
		FileEnumerator enumerator;
		FileInfo? file_info;
		string fn;
		
		printd (@"Propagate_files in directory: $dir\n");
		
		files.clear ();
		directories.clear ();
		
		current_dir = File.new_for_path (dir);
		
		Preferences.set ("file_dialog_dir", dir);
		
		if (current_dir.get_parent () != null) {
			directories.add ("..");
		}

		try {
			enumerator = current_dir.enumerate_children (FileAttribute.STANDARD_NAME + "," + FileAttribute.STANDARD_TYPE, 0);
			
			while ((file_info = enumerator.next_file ()) != null) {
				fn = ((!)file_info).get_name ();
				if (!fn.has_prefix (".")) {
					if (((!)file_info).get_file_type () == FileType.DIRECTORY) {
						directories.add (fn);
					} else {
						files.add (fn);
					}
				}
			}
		} catch (Error e) {
			warning (e.message);
		}
		
		directories.sort ();

		if (has_drive_letters) {
			for (int i = drive_letters.size - 1; i >= 0; i--) {
				directories.insert (0, drive_letters.get (i));
			}
		}

		files.sort ();
		
		layout ();
		base.selected_canvas ();
		scroll_to (0);
		MainWindow.show_scrollbar ();
	}

	public void show_text_area (string text) {
		listener = new TextListener ("", text, this.title);
		
		listener.signal_text_input.connect ((text) => {
			selected_filename = text;
		});
		
		listener.signal_submit.connect (() => {
			File f;
			
			MainWindow.get_tab_bar ().close_display (this);
			
			if (selected_filename == "") {
				action.cancel ();
			} else {
				if (selected_filename.index_of (":\\") != -1) {
					f = File.new_for_path (selected_filename);
				} else {
					f = get_child (current_dir, selected_filename);
				}
				
				action.file_selected ((!)f.get_path ());
			}
		});
		
		TabContent.show_text_input (listener);
	}
	
	public override void double_click (uint button, double ex, double ey) {	
		File f;

		button_release ((int) button, ex, ey);

		if (is_null (selected_filename)) {
			warning ("No file.");
			return;
		}

		if (select_folder) {
			if (selected_filename.index_of (":") > -1) {
				propagate_files (selected_filename);
			} else {
				propagate_files (((!) current_dir.get_path ()) + path_separator + selected_filename);
			}
		} else {		
			if (selected_filename != "") {
				f = get_child (current_dir, selected_filename);
				action.file_selected ((!) f.get_path ());
			}
		}
	}

	public override string get_label () {
		return title;
	}

	public override string get_name () {
		return "FileDialogTab";
	}
	
	public override void button_release (int button, double ex, double ey) {
		base.button_release (button, ex, ey);
		show_text_area (selected_filename);
	}
	
	class SelectedFile : GLib.Object {
		public string file_name;
		
		public SelectedFile (string fn) {
			file_name = fn;
		}
	}
}

}

Contact

Email: johan.mattsson.m@gmail.com
Mastodon: typo.social/@birdfont
GitHub: johanmattssonm
LinkedIn: johan-mattsson-2ab324112