using GLib; using Gtk; using Gnome.Vfs; using System; using System.Collections; using System.Runtime.InteropServices; namespace Scaffold.FileBrowser { public class VfsNodeView : TreeView { internal enum NodeType { Desktop, Home, FileSystem, Drive, Volume, Uri, Loading } internal class NodeData : IComparable { private static NodeData emptyNode = null; private NodeType type; private Gnome.Vfs.Uri uri; private Drive drive; private Gnome.Vfs.FileInfo info; private Volume volume; private string shortName; public static NodeData Empty { get { if (emptyNode == null) emptyNode = new NodeData (NodeType.Loading, (Gnome.Vfs.Uri) null); return emptyNode; } } public NodeData (Gnome.Vfs.Uri uri) : this (NodeType.Uri, uri) {} public NodeData (NodeType type, Gnome.Vfs.Uri uri) { this.type = type; this.uri = uri; } public NodeData (Gnome.Vfs.Uri uri, Gnome.Vfs.FileInfo info) { type = NodeType.Uri; this.uri = uri; this.info = info; } public NodeData (Drive drive) { type = NodeType.Drive; this.drive = drive; if (drive.ActivationUri == null) uri = new Gnome.Vfs.Uri (drive.DevicePath); else uri = new Gnome.Vfs.Uri (drive.ActivationUri); } public NodeData (Volume volume) : this (NodeType.Volume, volume) {} public NodeData (NodeType type, Volume volume) { this.type = type; this.uri = new Gnome.Vfs.Uri (volume.ActivationUri); this.volume = volume; } public NodeType NodeType { get { return type; } } public Gnome.Vfs.Uri Uri { get { return uri; } } public Drive Drive { get { return drive; } } public Gnome.Vfs.FileInfo FileInfo { get { if (uri != null && info == null) info = uri.GetFileInfo (FileInfoOptions.Default | FileInfoOptions.GetMimeType); return info; } } public Volume Volume { get { return volume; } } public string ShortName { get { if (uri != null && shortName == null) shortName = uri.ExtractShortName (); return shortName; } } public int CompareTo (object obj) { NodeData node = obj as NodeData; if (type == node.NodeType) { if (type == NodeType.Volume) return String.Compare (volume.DisplayName, node.Volume.DisplayName); else if (FileInfo.Type == node.FileInfo.Type) return String.Compare (ShortName, node.ShortName, true); else return info.Type == FileType.Directory ? -1 : 1; } else { return type.CompareTo (node.NodeType); } } } private TreeStore store = null; private TreeModelFilter filter; private Gtk.IconTheme theme = null; private int iconSize; private Hashtable iconCache = null; private Gnome.Vfs.Uri desktopUri; private Gnome.Vfs.Uri homeUri; private bool showHiddenFiles = false; private MainLoop mountLoop; private bool mountDone; private bool mountSucceeded; private string mountError; private string mountErrorDetails; public VfsNodeView () : base () { if (!Vfs.Initialized) Vfs.Initialize (); theme = Gtk.IconTheme.GetForScreen (Screen); int width, height; Settings settings = Settings.GetForScreen (Screen); Gtk.Icon.SizeLookupForSettings (settings, IconSize.Menu, out width, out height); iconSize = Math.Max (width, height); PopulateStore (); store.SetSortFunc (0, new TreeIterCompareFunc (SortFunc)); store.SetSortColumnId (0, SortType.Ascending); filter = new TreeModelFilter (store, null); filter.VisibleFunc = new TreeModelFilterVisibleFunc (FilterVisibleFunc); Model = filter; HeadersVisible = false; TestExpandRow += new TestExpandRowHandler (OnTestExpandRow); RowExpanded += new RowExpandedHandler (OnRowExpanded); TreeViewColumn column = new TreeViewColumn (); CellRenderer renderer = new CellRendererPixbuf (); column.PackStart (renderer, false); column.SetCellDataFunc (renderer, new TreeCellDataFunc (IconDataFunc)); renderer = new CellRendererText (); column.PackEnd (renderer, true); column.SetCellDataFunc (renderer, new TreeCellDataFunc (TextDataFunc)); AppendColumn (column); } public bool ShowHiddenFiles { get { return showHiddenFiles; } set { if (showHiddenFiles != value) { showHiddenFiles = value; filter.Refilter (); } } } public Gnome.Vfs.Uri GetUri (TreePath path) { TreeIter iter; Model.GetIter (out iter, path); NodeData node = (NodeData) filter.GetValue (iter, 0); return node.Uri; } private bool FilterVisibleFunc (TreeModel model, TreeIter iter) { NodeData node = (NodeData) store.GetValue (iter, 0); // Why is node null sometimes? if (node == null) return true; if (node.NodeType == NodeType.Loading) return true; else return showHiddenFiles || !node.ShortName.StartsWith ("."); } private int SortFunc (TreeModel model, TreeIter a, TreeIter b) { NodeData nodeA = (NodeData) store.GetValue (a, 0); NodeData nodeB = (NodeData) store.GetValue (b, 0); return nodeA.CompareTo (nodeB); } private void OnTestExpandRow (object o, TestExpandRowArgs args) { TreeIter iter = filter.ConvertIterToChildIter (args.Iter); NodeData node = (NodeData) store.GetValue (iter, 0); if (node.NodeType != NodeType.Drive || node.Drive.IsMounted) { args.RetVal = false; return; } // Mount the drive if not already mounted. mountLoop = new MainLoop (); node.Drive.Mount (new VolumeOpCallback (OnMount)); mountLoop.Run (); Console.WriteLine ("Done waiting"); args.RetVal = !mountSucceeded; if (!mountSucceeded) { CollapseRow (args.Path); Dialog dialog = new MessageDialog (null, DialogFlags.Modal | DialogFlags.DestroyWithParent, MessageType.Error, ButtonsType.Ok, "{0} ({1})", mountError, mountErrorDetails); dialog.Run (); dialog.Hide (); return; } } private void OnRowExpanded (object o, RowExpandedArgs args) { TreeIter iter = filter.ConvertIterToChildIter (args.Iter); NodeData node = (NodeData) store.GetValue (iter, 0); TreeIter childIter; store.IterChildren (out childIter, iter); NodeData child = (NodeData)store.GetValue (childIter, 0); if (child.NodeType == NodeType.Loading) { Gnome.Vfs.FileInfo[] entries = Directory.GetEntries (node.Uri, FileInfoOptions.Default | FileInfoOptions.GetMimeType); foreach (FileInfo info in entries) { // Skip "." and "..". if (info.Name.Equals (".") || info.Name.Equals ("..")) continue; child = new NodeData (node.Uri.AppendPath (info.Name), info); TreeIter nodeIter = store.AppendValues (iter, child); if (info.Type == FileType.Directory) store.AppendValues (nodeIter, NodeData.Empty); } store.Remove (ref childIter); } } private void OnMount (bool succeeded, string error, string details) { mountSucceeded = succeeded; Console.WriteLine ("{0}, {1}, {2}", succeeded, error, details); if (!succeeded) { mountError = error; mountErrorDetails = details; } //mountDone = true; mountLoop.Quit (); Console.WriteLine ("OnMount done"); } private Gdk.Pixbuf GetCachedIcon (string icon) { if (iconCache == null) iconCache = new Hashtable (); if (!iconCache.Contains (icon)) { Gdk.Pixbuf pixbuf = theme.LoadIcon (icon, iconSize, 0); if (pixbuf != null) iconCache.Add (icon, pixbuf); return pixbuf; } else { return (Gdk.Pixbuf)iconCache[icon]; } } [DllImport ("gnomeui-2")] private static extern string gnome_icon_lookup (IntPtr icon_theme, IntPtr thumbnail_factory, string file_uri, string custom_icon, IntPtr file_info, string mimetype, Gnome.IconLookupFlags flags, out Gnome.IconLookupResultFlags result); private void IconDataFunc (TreeViewColumn column, CellRenderer cell, TreeModel model, TreeIter iter) { CellRendererPixbuf renderer = cell as CellRendererPixbuf; NodeData node = (NodeData) filter.GetValue (iter, 0); switch (node.NodeType) { case NodeType.Drive: if (node.Drive.IsMounted) { Volume volume = node.Drive.MountedVolume; renderer.Pixbuf = GetCachedIcon (volume.Icon); } else { renderer.Pixbuf = GetCachedIcon (node.Drive.Icon); } break; case NodeType.FileSystem: renderer.Pixbuf = GetCachedIcon ("gnome-dev-harddisk"); break; case NodeType.Loading: renderer.Pixbuf = null; break; case NodeType.Volume: renderer.Pixbuf = GetCachedIcon (node.Volume.Icon); break; default: string icon = null; if (node.Uri.Equals (desktopUri)) { icon = "gnome-fs-desktop"; } else if (node.Uri.Equals (homeUri)) { icon = "gnome-fs-home"; } else { Gnome.IconLookupResultFlags result; icon = gnome_icon_lookup (theme.Handle, IntPtr.Zero, null, null, node.FileInfo.Handle, node.FileInfo.MimeType, 0, out result); } renderer.Pixbuf = GetCachedIcon (icon); break; } } private void TextDataFunc (TreeViewColumn column, CellRenderer cell, TreeModel model, TreeIter iter) { CellRendererText renderer = cell as CellRendererText; NodeData node = (NodeData) filter.GetValue (iter, 0); switch (node.NodeType) { case NodeType.Drive: renderer.Text = node.Drive.DisplayName; break; case NodeType.FileSystem: renderer.Text = "File System"; break; case NodeType.Home: renderer.Text = "Home"; break; case NodeType.Loading: renderer.Text = "Retrieving file list..."; break; case NodeType.Volume: renderer.Text = node.Volume.DisplayName; break; default: renderer.Text = node.ShortName; break; } } private void AddRootNode (NodeData node) { TreeIter iter = store.AppendValues (node); store.AppendValues (iter, NodeData.Empty); } private void PopulateStore () { if (store != null) return; store = new TreeStore (typeof (NodeData)); // Add both the Desktop and Home custom node types. desktopUri = new Gnome.Vfs.Uri (Environment.GetFolderPath (Environment.SpecialFolder.Desktop)); homeUri = new Gnome.Vfs.Uri (Environment.GetFolderPath (Environment.SpecialFolder.Personal)); AddRootNode (new NodeData (NodeType.Desktop, desktopUri)); AddRootNode (new NodeData (NodeType.Home, homeUri)); VolumeMonitor monitor = VolumeMonitor.Get (); // Add root filesystem. AddRootNode (new NodeData (NodeType.FileSystem, monitor.GetVolumeForPath ("/"))); // Add connected drives. foreach (Drive d in monitor.ConnectedDrives) { if (d.IsUserVisible) { AddRootNode (new NodeData (d)); } } // Add mounted volumes. foreach (Volume v in monitor.MountedVolumes) { if (v.Drive == null && v.IsUserVisible) { AddRootNode (new NodeData (v)); } } } } }