// created on 11/17/2006 at 6:16 PM /* TODO: * CellRendererText.Elipsize - I want to throw some default size on the Name column * that isn't too evil and also auto-elipsizes, and then un-elipsizes as the column * grows. * Cell Columns: * - add in the sort indicator for the currently sorted column * - make sorting work up and down * Drag and Drop - Get dragging and dropping at least passing messages between the * two FileViews * PathBar: * - Auto-sizing: The PathBar has to be adaptable to the hbox size * (there is a lot of annoying logic to code here) * - < and > buttons for the Auto-sizing */ import System import System.IO import System.Collections import Gtk class FileView: public static drag_targets = [Gtk.TargetEntry('text/plain', Gtk.TargetFlags.App, 0)] public reader as FileReader public showHidden = false protected view as Gtk.TreeView protected store as Gtk.ListStore protected pathbar as PathBar def constructor(reader as FileReader, view as Gtk.TreeView, pathbar as Gtk.HBox): self.view = view IconCol = TreeViewColumn("", Gtk.CellRendererPixbuf(), "pixbuf", 0) IconCol.Resizable = false NameCol = TreeViewColumn("Name", Gtk.CellRendererText(), "text", 1) NameCol.Resizable = true # NameCol.SortIndicator = true SizeCol = TreeViewColumn("Size", Gtk.CellRendererText(), "text", 2) SizeCol.Resizable = true TypeCol = TreeViewColumn("Type", Gtk.CellRendererText(), "text", 3) TypeCol.Visible = false self.view.AppendColumn(IconCol) self.view.AppendColumn(NameCol) self.view.AppendColumn(SizeCol) self.view.AppendColumn(TypeCol) self.view.HeadersClickable = true # gtk2 v. 2.10 #self.view.RubberBanding = true self.view.Selection.Mode = SelectionMode.Multiple # set up drag & drop self.view.EnableModelDragSource( Gdk.ModifierType.Button1Mask, array(Gtk.TargetEntry, FileView.drag_targets), Gdk.DragAction.Default | Gdk.DragAction.Copy) self.view.EnableModelDragDest( array(Gtk.TargetEntry, FileView.drag_targets), Gdk.DragAction.Default | Gdk.DragAction.Copy) self.view.DragDataGet += EventHandler(drag_data_get) self.view.DragDataReceived += EventHandler(drag_data_received) self.reader = reader self.pathbar = PathBar(pathbar, reader.cwd, self) public def populate(): dirIcon = FileType.directory.icon # lets not create this all the time if not store: store = Gtk.ListStore(Gdk.Pixbuf, typeof(string), typeof(string), typeof(string)) for entry as DirectoryEntry in reader.directories: if not entry.isHidden or showHidden: store.AppendValues(dirIcon, entry.name, "", "d") for entry as DirectoryEntry in reader.files: if not entry.isHidden or showHidden: type = FileType(entry.name) store.AppendValues(type.icon, entry.name, entry.humanSize(), "f") view.Model = store view.ColumnsAutosize() public def initialize(): store = Gtk.ListStore(Gdk.Pixbuf, typeof(string), typeof(string), typeof(string)) view.Model = store view.ColumnsAutosize() protected def drag_data_get(tree as TreeView, args as Gtk.DragDataGetArgs): # why use args.SelectionData? sel = tree.Selection.GetSelectedRows() for i in sel: print i print tree print args protected def drag_data_received(tree as TreeView, args as Gtk.DragDataReceivedArgs): print tree print args print args.SelectionData print args.SelectionData.Text public def chdir(entry as DirectoryEntry): chdir(entry.path) # change this FileView's dir to 'path' public def chdir(path as string): reader.chdir(path) pathbar.chdir(path) store.Clear() populate() # returns the DirectoryEntry for the given Gtk.TreePath public def getDirectoryEntry(treepath as Gtk.TreePath): iter = Gtk.TreeIter() store.GetIter(iter, treepath) name = store.GetValue(iter, 1) type = store.GetValue(iter, 3) if type == "d": return reader.getDirectory(name) elif type == "f": return reader.getFile(name) else: # if this exception occurs, check that 'name' and 'type' above # are coming from the right treeview column raise "User double clicked on something non-existant." class PathBar: public cwd as string protected view as FileView protected path as string protected pathlist as List protected box as Gtk.HBox def constructor(box as Gtk.HBox, path as string, view as FileView): self.box = box self.path = path self.view = view # set up "/" pathlist = _pathList() cwd = pathlist[-1] makepath() def chdir(path as string): cwd = path for button as Gtk.ToggleButton in box.Children: if button.Name == path: button.Active = true else: button.Active = false for p as string in pathlist: comp = path.CompareTo(p) if comp > 0: continue elif comp == 0: return else: break # FIXME: add correct logic here to merely create one button #p when we only have to create one button for button as Gtk.ToggleButton in box.Children: button.Destroy() self.path = cwd pathlist = _pathList() makepath() def makepath(): button = makeButton('/') button.Show() packButton(button) for item in pathlist[1:]: button = makeButton(item) button.Show() packButton(button) for item as Gtk.ToggleButton in box.Children: if item.Name == cwd: item.Active = true item.Toggled += EventHandler(button_clicked) private def packButton(button as Gtk.Button): box.PackStart(button) box.SetChildPacking(button, false, false, 0, PackType.Start) private def makeButton(path as string): button = Gtk.ToggleButton() if path != "/": button.Label = _label(path) else: button.Image = Gtk.Image(FileType.harddisk.icon) button.Name = path return button def button_clicked(obj as object, args as EventArgs): button as Gtk.ToggleButton = obj if not button.Active: return else: view.chdir(button.Name) public def _label(path as string): if path == '/': return path return path.Split(char('/'))[-2] private def _pathList(): l = [] pl = path.Split(char('/')) for item in pl: if len(item) > 0: l.Add(item) paths = [] paths.Add("/") for item in l: paths.Add((paths[-1] as String) + item + "/") return paths class FileReader: public cwd as string public files as List public directories as List protected fileHash as Hash protected directoryHash as Hash def constructor(path as string): cwd = path readList() def chdir(dir as DirectoryEntry): chdir(dir.path) def chdir(path as string): cwd = path readList() def readList(): di = DirectoryInfo(cwd) fileHash = makeEntryHash(List(di.GetFiles()), false) directoryHash = makeEntryHash(List(di.GetDirectories()), true) files = List(fileHash.Values) directories = List(directoryHash.Values) def makeEntryHash(l as List, isDir as bool): f = {} for item in l: if isDir: entry = DirectoryEntry(item as DirectoryInfo) else: entry = DirectoryEntry(item as FileInfo) f[entry.name] = entry return f def getDirectory(name as string) as DirectoryEntry: return directoryHash[name] def getFile(name as string) as DirectoryEntry: return fileHash[name] def consoleDump(): for directory as DirectoryEntry in directories: print "${directory}" for file as DirectoryEntry in files: print "${file}" class DirectoryEntry: public path as string public size as int public name as string public parent = "" public isHidden as bool public isDirectory = false public type as FileType def constructor(info as FileInfo): isDirectory = false self.size = info.Length makeSelf(info.FullName) def constructor(info as DirectoryInfo): isDirectory = true self.size = 0 makeSelf(info.FullName) private def makeSelf(path as string): self.path = path name = path.Split(char('/'))[-1] for dir as string in path.Split(char('/'))[1:-1]: parent += "/" + dir parent += "/" if isDirectory: type = FileType.directory if self.path[len(self.path)-1] != char('/'): self.path = self.path + '/' else: type = FileType(name) if name[0] == char('.'): isHidden = true else: isHidden = false def ToString(): s = "> ${path} " if isHidden: s += "[!]" return s def humanSize(): if isDirectory: return "" hsize = size * 1.0 ordersOfMagnitude = 0 while hsize > 1024: hsize = hsize / 1024.0 ordersOfMagnitude++ if ordersOfMagnitude == 0: units = " B " elif ordersOfMagnitude == 1: units = " KB" elif ordersOfMagnitude == 2: units = " MB" elif ordersOfMagnitude == 3: units = " GB" else: return "" return hsize.ToString("F1") + units #return string.Format("{0:#.#}", hsize) + units class FileType: public static directory = FileType("directory", "gnome-fs-directory", isExecutable:false) public static harddisk = FileType("harddisk", "gtk-harddisk", isExecutable:false) public static Types = MimeTypes() public icon as Gdk.Pixbuf public name as string public mimetype as MimeType public isExecutable as bool public ext = "" def constructor(name as string): self.ext = Path.GetExtension(name) if len(ext) > 0 and ext.CompareTo('.') > 0: ext = ext[1:] self.mimetype = Types.ByExtension(ext) self.icon = Gtk.IconTheme.Default.LoadIcon(mimetype.icon, 16, IconLookupFlags.ForceSvg) def constructor(name as string, mime as string): self.name = name self.icon = Gtk.IconTheme.Default.LoadIcon(mime, 16, IconLookupFlags.ForceSvg) def ToString(): return "FD.Mime: ${mimetype} FT.Name: ${name}" # this is a helper class only meant to be used statically by the FileType object # through FileType.MimeTypes class MimeTypes: protected types = [] def constructor(): if not File.Exists("/etc/mime.types"): raise "Error constructing mimes" using reader = File.OpenText("/etc/mime.types"): while line = reader.ReadLine(): if line.StartsWith('#') or len(line) in [0, 1]: continue type = MimeType(line) if type.hasExtensions: types.Add(type) types.Add(MimeType.gzip) types.Add(MimeType.bzip2) def ByExtension(ext as string): for type as MimeType in types: if ext in type.extensions: return type return MimeType.default class MimeType: public static default = MimeType("text/plain text txt") public static gzip = MimeType("application/x-gzip gz gzip") public static bzip2 = MimeType("application/x-bzip bz2 bz") public type as string public extensions = [] public hasExtensions as bool public icon as string # expects a non-empty line from /etc/mime.types def constructor(line as string): split = line.Split() type = split[0] for str in split[1:]: if len(str) != 0: extensions.Add(str) icon = _getMimeIcon() hasExtensions = _hasExtensions() def ToString(): return "${type} ${extensions} <${icon}>" private def _hasExtensions(): if len(extensions) != 0: return true return false private def _getMimeIcon(): _ = Gtk.IconTheme.Default.HasIcon full = type.Replace("/", "-") base = type.Split(char('/'))[0] defaultIcon = "gnome-mime-text" if _("gnome-mime-" + full): icon = "gnome-mime-" + full elif _("gnome-mime-" + base): icon = "gnome-mime-" + base else: icon = defaultIcon return icon