art with code

2008-11-12

Filezoo, end of day 3: open terminal, threads



GitHub page

Implemented "Open Terminal" and managed to get more responsive panning behaviour in huge dirs like /usr/lib (was doing a relayout on every UI event instead of on every redraw, which spammed several relayouts on every mouse drag), and proceeded to piss away the performance gains with increased dir opening latency from launching a bunch of worker threads to do the recursive directory traversal.

Opening a terminal window with the filename already in it appeared to be bothersome, so didn't work on it apart from reading the bash man page. Could use a Gtk text input widget instead.

Split the measurers, comparers and zoomers from filezoo.cs into their own files and rearranged the source a bit. The custom widgets are getting nasty to manage, need to look into replacing them with Gtk widgets or XAML or something wacky like that. The text should be drawn with [something that uses] Pango at least, now there's no missing glyph fallback, and I get to enjoy filenames like "[][][][][][][][].jpg". I'm not familiar with this stuff, suggestions?

The performance still sucks for large dirs, even when zoomed in (need to profile to see if it's from too much draw or from too much layout), so that's a good project for day 4, along with fixing the initial dir opening latency. If it takes longer than 0.1s to draw out the first frame of an opened dir, it's too slow.

In other news, tried out Eagle Mode and it's awesome. Reading text, PostScript and PDF documents inline is really nifty. However, I don't like the theme and the massive waste of screen space for trivial information, especially the file info next to the contents box. 10% of directory box space taken to tell me that the directory is a directory. Oh come on, really?

Eagle Mode also has a raytraced chess game, which takes it beyond awesome into the rarefied sphere of retarded giggling at the sublime beauty of the universe and all that is in it. And it's responsive too!

And that's it for day 3, more tomorrow.

P.S. Is there something like NaNoWriMo for writing applications? "One month, 6000 lines of code!"

Filezoo, start of day 3: zooming and panning



Got [uniform] zooming and panning done. Non-linear zoom would be an interesting exercise, but there are more pressing things to do. So, the todo for the rest of the day:
  1. A button "Open terminal here".

  2. For files, a button "In terminal" -> opens terminal window with "prompt$ [] file_name_here".

  3. Separating the drawing, UI and directory traversal into their own threads.

  4. "I'm doing something that's going to take forever"-spinner for operations that so do.

  5. Partial layouts for showing the status of abovementioned operations.

  6. End-of-day refactoring.



Furthermore, I have this crazy idea that the application and its constituents should be simple single-function programs, which call each other to do their magic. And to take that to the UI level, the UI should consist of small reusable widgets that can be combined to do more complex things.

For example, the file browser view for a single directory should be like let browser dir = ls dir | zoomable -window Filezoo -action:Directory browser -action:File open. And if you wanted to use that to read your mails: let mailer dir = ls ~/mail/$dir | zoomable -window Mail -action:Directory mailer -action:File read_mail. Simple programs, defining a data source and behaviour.

To detour into a parallel, the HTML A- and IMG-tags define behaviour. The src-attribute of the IMG-tag defines a data source. (And if TABLE worked like IMG, the world would be a better place; <TABLE src="stuff.csv"/> and <TABLE src="stuff.xls" width="500" height="400"/>. Why not <IMG src="stuff.csv" width="500" height="400" style="table-style: bar-graph;"/> as well.)

2008-11-11

Filezoo: end of day 2



Ok, got the sort and size widgets done, along with small dotfiles and the refactoring. Didn't get to zooming and showing dir contents yet. Guess I have something to do tomorrow as well.

The git repo is at http://github.com/kig/filezoo/tree/master

Filezoo: visual du with aspirations towards file management


Continuing from yesterday, added a click handler for navigating the directory tree and opening files with gnome-open, and fiddled around a bit with the layout. Currently it works as a very bad filesystem browser, which is what I aim to change today. So, onto the plan!

My plan for today consists of five items:
  1. Sort the directory entries by size or name, and have a button to switch between the two.

  2. Make dotfiles appear small when not scaling by size.

  3. Scale directories by the [logarithm of] directory tree size in files.

  4. Zoom with the mouse.

  5. Show directory contents.


I've done prototypes of the first three, so now I need to make them work together nicely. To draw the listing, I need to get the directory entries of the current directory, then compute the relative size for each, sort them, and finally add the parent dir link at the top (unless the current dir is the root dir.) Then, to do zooming with the mouse, I need to further scale each entry at the drawing phase by a factor gotten from the zooming function (some kind of a gaussian hump, I think), which needs to have the definite integral from 0 to 1 amount to 1.

Showing directory contents can be done with limited-depth recursion.

Ok, so, this is probably how I should refactor the code:

let buildDirInfo dir = map fileInfo (ls dir)

let buildLayout files scaler zoomer =
let fileScales = map (tupleWith scaler) files in
let totalSize = sum (map fst fileScales) in
let fileScales = map (fun (s,f) -> (s / totalSize, f) fileScales in
snd (mapAccum (fun acc (s, f) -> (acc+s, (s * zoomer acc, f)) ) 0.0 fileScales)

let stack_do cr f = Cairo.save cr; f cr; Cairo.restore cr

let draw cr scaler zoomer files level =
match level with
| x when x < 0 -> ()
| x -> let layout = buildLayout files scaler zoomer in
stack_do cr
(fun cr -> iter (fun (s, f) ->
drawFile cr scaler zoomer s f level;
translate 0 s) layout)

let drawFile cr scaler zoomer scale file level =
stack_do cr (fun cr ->
drawFileModel f scale;
if (file.type = directory) then
draw cr scaler zoomer (entries file.path) (level - 1))

let fileInfo f = {
name = basename f;
dirname = dirname f;
path = f;
type = fileType f;
size = fileSize f;
recursiveSize = lazy computeRecursiveSize f;
recursiveFileCount = lazy computeRecursiveFileCount f;
permissions = filePermissions f;
}

2008-11-10

Visual disk usage app using Mono


Continuing on my epic quest to learn .NET and GUI dev with Mono, I wrote today a small du-clone that renders the result visually using Cairo. As you might guess from the color scheme, I'm thinking of integrating it into my desktop theme as a file manager (hey, what good is a theme if you have to use apps that don't conform to the look...)

This one has a GitHub repo as well, http://github.com/kig/filezoo/tree/master

The next thing I'm going to do is add a click handler that navigates the directories and calls gnome-open on the files.

2008-11-09

A simple C# analog clock with Mono and Cairo



In the previous post I said that I had to write my own analog clock app to get the look I wanted. Originally, I had written it in Ruby, using the Librend library, which drew the clock on an OpenGL texture using Cairo, and then displayed it. Today I thought to learn some C# and Gtk#, and rewrote the clock as a Mono application that draws in a Gtk window using Cairo.

I set up a GitHub project for the clock, it's at http://github.com/kig/simpleclock/tree/master

The source code is a documented 200 lines, so it's no big deal to read if you're interested. I'll walk through the perhaps most interesting bit below; the clock transform, which makes it simple to draw the clock parts.

To understand the clock transform, you first need to know how Cairo's coordinate system works. In Cairo's default transform the origin (i.e. 0,0) lies at the top-left corner of the canvas, Y grows down, and rotation increases clockwise from the right. In other words, it's the math 2D cartesian coordinate system with the Y-axis flipped.

What we want for the clock is to have the origin at the center of the clock face with rotation increasing clockwise from the top (i.e. 12:00.) That way, we can simply rotate by e.g. 2π(minute / 60) to draw the minute clock hand. Furthermore, we want to normalize the coordinates to run from -1.0 to 1.0 for the clock area, so that we can trivially scale the clock to different sizes without changing the drawing code.

Here's how I achieved that:

uint boxSize = Math.Min (width, height); // size of the clock box

// First, we center the clock box to the window.
cr.Translate ((width - boxSize) / 2.0, (height - boxSize) / 2.0);

// Then we scale the box so that -1.0 .. 1.0 spans the whole box.
cr.Scale (boxSize / 2.0, boxSize / 2.0);

// And move the origin to the center of the box.
cr.Translate (1.0, 1.0);

// Finally, rotate CCW by 90 degrees to make rotation 0 point up.
// We don't need to flip the rotation direction, because angle grows
// clockwise in the "Y grows down" default transform.
cr.Rotate (-Math.PI / 2.0);


Now drawing the hands is quite trivial, as you can see from the DrawMinuteHand -method:

void DrawMinuteHand (Context cr, uint minute)
{
double rot = (double)minute / 60.0; // => rot is between 0.0 and 1.0
cr.Save ();
cr.Rotate (rot * Math.PI * 2.0); // 2π(minute/60)
cr.Rectangle (0.0, -0.05, 0.8, 0.1); // draw the hand model
cr.Color = new Color (0, 0, 0);
cr.Fill ();
cr.Restore ();
}


To sum up my experiences from writing this little C# project: compiling programs with the Mono compiler is pretty nifty (not Ada-level nifty though, more like OCaml-nifty) and C# is straightforward enough. I was pleasantly surprised that I managed to get the program written in a couple hours :)

2008-11-08

Black on white desktop theme

What with the winter coming and all, I felt like it was the time to do a bit of a refresh on my desktop.

Before, the scheme was light gray and cyan on dark gray.



Now it's black on white.



Using FVWM2 for the window manager and the panel. The drop shadows are courtesy of xcompmgr. Drew the window icons a few years back. Had to make a custom analog clock (using Cairo) after failing to find a good one. Urxvt for the terminals, using its matcher-module for clickable urls. The irssi theme is a customized version of Simplicity.

There are no launchers, app icons or a taskbar. I have the F-keys set to launch apps (MacOS 9 -style) and there's a right-click-summonable desktop menu. I don't really need a taskbar, since I have the pager and Win-[number] hotkeys for desktop switching. To shuffle through windows, a right-click on the window border or titlebar moves it back / brings it to front. I'm using the mouse to switch between windows (with focus follows mouse) and keyboard shortcuts to do the maximize/winshade/close/sticky -tango. No minimize because no taskbar.

All my windowing-related hotkeys are behind the Win-key, leaving the other modifier keys free for apps. Now if apps could just stop using the F-keys :P

Also made Firefox work more like Chrome by installing the following extensions: Hide Menubar, autoHideStatusbar, CyberSearch and Edit Middle. Then hid the bookmarks toolbar and the search bar, replaced the Home-button with New Tab, and added the Bookmarks-button to the Navigation toolbar. Now I have only a single toolbar and the tab list on top, no separate search bar and a statusbar that only shows when loading page, hovering a link or hovering the statusbar area. See screenshot below.

Blog Archive