art with code

2008-12-19

Filezoo day 35-37: better context menus, Fitts's law in breadcrumb, wacky single-button navigation

Lots of fancy stuff recently. Moved to using Gnome's thumbnail factory for the thumbs. Which means video thumbs and font thumbs, etc.

Also went over the context menus and removed some superfluous entries ("Copy to..."), rearranged menu items (e.g. moved "Move to trash" away from the bottom-most element, replaced the bottom item with a non-destructive one to decrease the impact of accidental menu invocations), added separators and type-specific handlers for selections, and image rotation commands.

The context menu is still not quite done but it's getting pretty useful. It doesn't have submenus (apart from the under-mouse-submenu when there's a selection), which is good. Submenus are finicky to use and their contents invisible. And the size constraint imposed by the lack of submenus acts as selection pressure towards a better set of menu items. I'd like a pie menu. A common desktop-wide pie menu that works the same in all apps.

The breadcrumb bar at the top of the window now has its active area spanning all the way to the topmost pixel of the window (top of screen in panel mode.) Now you can throw your mouse up and click and ooh, success!

Also made a smoothly zooming and panning middle-button drag navigation thing. It makes the app nicer to use with a Wacom tablet. It's very finicky though, which I need to fix.

Also added inertia scrolling, a.k.a. throw-panning. What is the name of that thing? It's nifty though. And works with zoom. And works great with the new seamless zoom and pan. Want to go to the next dir? Throw the screen upwards and it pans there. Previous dir? Throw downwards. Click or hit a key to stop.

I'm writing a plan for month two but it's gotten stuck in some limbo of literary self-crit.

2008-12-15

One month recap in images

Week one

Week two

Week three

Week four

Week five

Filezoo day 34, part 2: keyboard shortcuts, dragging files


Did I show this mockup already? If not, great!

Anyhow. Got ctrl-x, ctrl-c, and ctrl-v going (though in a very manual fashion, I guess the right way to do it would be AccelGroups?) and added some other keyboard shortcuts (delete to trash, backspace to parent dir, home to home dir.) Made the mouse cursor change depending on what modifiers you have down. And got dragging working. And it was a pain. So I'm going to share the pain in the form of a couple dozen lines of code.

In button press handler:

if (e.Button == 1) {
int w, h;
e.Window.GetSize (out w, out h);
DragSourceEntry = FindHit((uint)w, (uint)h, e.X, e.Y, 8).Target;
DragSourcePath = DragSourceEntry.FullName;
}

In button release handler:

if (e.Button == 1) {
if (!dragInProgress) {
DragSourceEntry = null;
DragSourcePath = null;
}
dragInProgress = false;
}
dragging = false;
panning = panning && dragInProgress;

In motion handler:

bool left = (e.State & Gdk.ModifierType.Button1Mask) == Gdk.ModifierType.Button1Mask;
bool middle = (e.State & Gdk.ModifierType.Button2Mask) == Gdk.ModifierType.Button2Mask;

if (middle) {
dragging = true;
panning = true;
}
if (left) {
dragging = dragging || ((Math.Abs(dragX - dragStartX) + Math.Abs(dragY - dragStartY)) > 4);
panning = panning || (dragStartX > 128+FilesMarginLeft);
if (!dragInProgress && !panning && dragging) {
Gdk.DragAction action = Gdk.DragAction.Move;
if ((e.State & Gdk.ModifierType.ControlMask) == Gdk.ModifierType.ControlMask)
action = Gdk.DragAction.Copy;
if ((e.State & Gdk.ModifierType.ShiftMask) == Gdk.ModifierType.ShiftMask)
action = Gdk.DragAction.Copy;
if ((e.State & Gdk.ModifierType.Mod1Mask) == Gdk.ModifierType.Mod1Mask)
action = Gdk.DragAction.Ask;
dragInProgress = true;

Drag.Begin (this, new TargetList(targets), action, 1, e);

SetCursor (e.State);
}
}

And the drag event handlers:

DragDataGet += delegate (object o, DragDataGetArgs args) {
string items = "file://" + DragSourcePath;
if (Selection.Count > 0 && Selection.ContainsKey(DragSourcePath))
items = GetSelectionData ();
args.SelectionData.Set(args.SelectionData.Target, 8, System.Text.Encoding.UTF8.GetBytes(items));
args.SelectionData.Text = items;
};

DragEnd += delegate {
GetSelectionData ();
dragInProgress = false;
DragSourceEntry = null;
DragSourcePath = null;
};

Such fun, yes? Also, read this.

I still don't know how to change the action in response to modifier key state changes (try dragging something in Konqueror and pressing and releasing ctrl/shift/alt.)

Tomorrow, I think it's time for a recap. Everyone just loves recap episodes, don't they? Not to mention that this blog is like the worst sitcom ever made.

2008-12-14

Oh. List has functional iterators.

I've been doing things like:

List<int> l = new List<int>() {1,2,3,4,5,6,7,8,9};
List<int> m = new List<int>();
foreach (int i in l) if (i >= 5) m.Add(i);
foreach (int i in m) Console.WriteLine(i);

When I could've done:

var l = new List<int>() {1,2,3,4,5,6,7,8,9};
l.FindAll(x => x >= 5).ForEach(Console.WriteLine);

Why didn't I figure this out a month ago.

Filezoo day 34, part 1: extended context menu, cut-copy-paste


Got cut-copy-paste business logic done, still missing the keyboard shortcuts though. The context menu is getting huge!

Patching up yesterday's missed todo items today, next up are clipboard keyboard shortcuts and dragging items.

The clipboard stuff was a bit of a pain, so here's a reference. I don't know if I'm doing it in The Right Way, but it does work. Can paste files to/from Konqueror at least.

void BuildCopyPasteMenu (Menu menu, ClickHit c)
{
string targetPath = c.Target.FullName;
string targetDir = c.Target.IsDirectory ? c.Target.FullName : Helpers.Dirname(c.Target.FullName);

Separator (menu);
string items = "file://" + c.Target.FullName;
if (App.Selection.Count > 0) {
items = App.GetSelectionData ();
}

AddItem(menu, "Cut", delegate {
SetClipBoard(items);
cut = true;
});

AddItem(menu, "Copy", delegate {
SetClipBoard(items);
cut = false;
});

AddItem(menu, "Paste to "+Helpers.Basename(targetDir)+"/", delegate {
bool handled = false;
clipboard.RequestContents(Gdk.Atom.Intern("text/uri-list", true), delegate(Clipboard cb, SelectionData data) {
if (data.Length > -1) {
handled = true;
App.HandleSelectionData(data, cut ? Gdk.DragAction.Move : Gdk.DragAction.Copy, targetPath);
cut = false;
}
});
clipboard.RequestContents(Gdk.Atom.Intern("application/x-color", true), delegate(Clipboard cb, SelectionData data) {
if (data.Length > -1 && !handled) {
handled = true;
App.HandleSelectionData(data, cut ? Gdk.DragAction.Move : Gdk.DragAction.Copy, targetPath);
}
});
clipboard.RequestContents(Gdk.Atom.Intern("text/plain", true), delegate(Clipboard cb, SelectionData data) {
if (data.Length > -1 && !handled) {
handled = true;
App.HandleSelectionData(data, cut ? Gdk.DragAction.Move : Gdk.DragAction.Copy, targetPath);
}
});
});
Separator (menu);
}

void SetClipBoard (string items)
{
clipboard.SetWithData(targets,
delegate (Clipboard cb, SelectionData data, uint info) {
data.Set(data.Target, 8, System.Text.Encoding.UTF8.GetBytes(items));
data.Text = items;
},
delegate (Clipboard cb) {
cut = false;
}
);
}

Filezoo day 33, part 2: more plugged leaks, selection and selection context menu


Plugged the ImageSurface leak (if it was such) and made do with less caching. Now the memory usage stays below 100 megs with the normal measurers and rises to 200+ megs with big recursive traversals. What worries me is that A) 200+ megs is still a huge fucking lot of memory for a glorified ls, even nautilus uses just 45 megs, and, B) the memory use doesn't fluctuate, it just rises.

And that plugfest blew a hole in my start-of-day plans. As you can see in the screenshot above, I got the selections working (ctrl-select, shift-select, alt-click to deselect) and made a little preliminary context menu.

Drag and drop dragging, the logic from the app pov should be easy, I could even draw a pixbuf of the selection to show when dragging. Cut-copy-paste, mmm, I'll try and reuse the dragging handlers. What remains is finding out how to use the clipboard.

Sleep now~

2008-12-13

Filezoo day 33, part 1: memleak fixes


Had a few memleaks in the drawing loop. Pango.CairoHelper.CreateLayout leaks, especially when coupled with Pango.CairoHelper.ShowLayout. So now I'm using a lot of static variables in the text drawing path, which means that it's not thread-safe at all and I'm going to get bitten by it at some point. Creating gradients outside a using-block leaks as well. But now those are fixed and I can draw thousands of frames without the memory usage moving up from 40 megs.

I don't know if my thumbnail ImageSurfaces are space leaks. I do Destroy them. And yet I did dispose the Pango.Layouts as well. I couldn't push memory usage beyond 500 megs though, even with 4000+ thumbnails loaded (but eh, that's not a very rigorous test.) I think they do leak, since once I get mem use up to 300 megs with the thumbs, it's not coming back down.

The thumbnails in the image above take ridiculously long to load too. There are only 3300 drawn. Loading 3300 128x128 thumbnails should take... 0.75 ms per thumb, 2.5 seconds. Oh wait, those would be JPEG thumbnails. PNG thumbs, forever, yeah. I have just the thing for this thumbnail loading and drawing stuff though.

I also added some cache pruning. Now traversal cache is cleared when switching measurers. Which is a hack to enable manual pumping for correct information. And it shouldn't really be there in a perfect world.

In other news, now there's a bit dubious feature when drag-dropping text onto dirs and files. When you drop text onto a dir, it asks whether you want to create a new file. If you drop text on a file, you can either create a new file in the dir, replace the file contents with the text, or append the text to the file. Like mouse-driven xsel -o > file and xsel -o >> file.

Plan for the rest of the day:
  • Cut-Copy-Paste handling
  • Selecting files
  • Dragging files and selections

Blog Archive