--- /dev/null
+#!/usr/bin/env perl
+# vim:ts=4:sw=4:expandtab
+# renders the layout tree using asymptote
+
+use strict;
+use warnings;
+
+use JSON::XS;
+use Data::Dumper;
+use AnyEvent::I3;
+use v5.10;
+
+use Gtk2 '-init';
+use Gtk2::SimpleMenu;
+use Glib qw/TRUE FALSE/;
+
+my $window = Gtk2::Window->new('toplevel');
+$window->signal_connect('delete_event' => sub { Gtk2->main_quit; });
+
+my $tree_store = Gtk2::TreeStore->new(qw/Glib::String/, qw/Glib::String/, qw/Glib::String/, qw/Glib::String/, qw/Glib::String/, qw/Glib::String/, qw/Glib::String/, qw/Glib::String/);
+
+my $i3 = i3("/tmp/nestedcons");
+
+my $tree_view = Gtk2::TreeView->new($tree_store);
+
+my $layout_box = undef;
+
+sub copy_node {
+ my ($n, $parent, $piter, $pbox) = @_;
+
+ my $o = ($n->{orientation} == 0 ? "u" : ($n->{orientation} == 1 ? "h" : "v"));
+ my $w = (defined($n->{window}) ? $n->{window} : "N");
+
+ # convert a rectangle struct to X11 notation (WxH+X+Y)
+ my $r = $n->{rect};
+ my $x = $r->{x};
+ my $y = $r->{y};
+ my $dim = $r->{width}."x".$r->{height}.($x<0?$x:"+$x").($y<0?$y:"+$y");
+
+ # add node to the tree with all known properties
+ my $iter = $tree_store->append($piter);
+ $tree_store->set($iter, 0 => $n->{name}, 1 => $w, 2 => $o, 3 => sprintf("0x%08x", $n->{id}), 4 => $n->{urgent}, 5 => $n->{focused}, 6 => $n->{layout}, 7 => $dim);
+
+ # also create a box for the node, each node has a vbox
+ # for combining the title (and properties) with the
+ # container itself, the container will be empty in case
+ # of no children, a vbox or hbox
+ my $box;
+ if($n->{orientation} == 1) {
+ $box = Gtk2::HBox->new(1, 5);
+ } else {
+ $box = Gtk2::VBox->new(1, 5);
+ }
+
+ # combine label and container
+ my $node = Gtk2::Frame->new($n->{name}.",".$o.",".$w);
+ $node->set_shadow_type('etched-out');
+ $node->add($box);
+
+ # the parent is added onto a scrolled window, so add it with a viewport
+ if(defined($pbox)) {
+ $pbox->pack_start($node, 1, 1, 0);
+ } else {
+ $layout_box = $node;
+ }
+
+ # recurse into children
+ copy_node($_, $n, $iter, $box) for @{$n->{nodes}};
+
+ # if it is a window draw a nice color
+ if(defined($n->{window})) {
+ # use a drawing area to fill a colored rectangle
+ my $area = Gtk2::DrawingArea->new();
+
+ # the color is stored as hex in the name
+ $area->{"user-data"} = $n->{name};
+
+ $area->signal_connect(expose_event => sub {
+ my ($widget, $event) = @_;
+
+ # fetch a cairo context and it width/height to start drawing nodes
+ my $cr = Gtk2::Gdk::Cairo::Context->create($widget->window());
+
+ my $w = $widget->allocation->width;
+ my $h = $widget->allocation->height;
+
+ my $hc = $widget->{"user-data"};
+ my $r = hex(substr($hc, 1, 2)) / 255.0;
+ my $g = hex(substr($hc, 3, 2)) / 255.0;
+ my $b = hex(substr($hc, 5, 2)) / 255.0;
+
+ $cr->set_source_rgb($r, $g, $b);
+ $cr->rectangle(0, 0, $w, $h);
+ $cr->fill();
+
+ return FALSE;
+ });
+
+ $box->pack_end($area, 1, 1, 0);
+ }
+}
+
+# Replaced by Gtk2 Boxes:
+#sub draw_node {
+# my ($n, $cr, $x, $y, $w, $h) = @_;
+#
+# $cr->set_source_rgb(1.0, 1.0, 1.0);
+# $cr->rectangle($x, $y, $w/2, $h/2);
+# $cr->fill();
+#}
+
+my $json_prev = "";
+
+my $layout_sw = Gtk2::ScrolledWindow->new(undef, undef);
+my $layout_container = Gtk2::HBox->new(0, 0);
+$layout_sw->add_with_viewport($layout_container);
+
+sub copy_tree {
+ my $tree = $i3->get_workspaces->recv;
+
+ # convert the tree back to json so we only rebuild/redraw when the tree is changed
+ my $json = encode_json($tree);
+ if($json ne $json_prev) {
+ $json_prev = $json;
+
+ # rebuild the tree and the layout
+ $tree_store->clear();
+ if(defined($layout_box)) {
+ $layout_container->remove($layout_box);
+ }
+ copy_node($tree);
+ $layout_container->add($layout_box);
+ $layout_container->show_all();
+
+ # keep things expanded, otherwise the tree collapses every reload which is more annoying then this :-)
+ $tree_view->expand_all();
+ }
+
+ return(TRUE);
+}
+
+sub new_column {
+ my $tree_column = Gtk2::TreeViewColumn->new();
+ $tree_column->set_title(shift);
+
+ my $renderer = Gtk2::CellRendererText->new();
+ $tree_column->pack_start($renderer, FALSE);
+ $tree_column->add_attribute($renderer, text => shift);
+
+ return($tree_column);
+}
+
+my $col = 0;
+$tree_view->append_column(new_column("Name", $col++));
+$tree_view->append_column(new_column("Window", $col++));
+$tree_view->append_column(new_column("Orientation", $col++));
+$tree_view->append_column(new_column("ID", $col++));
+$tree_view->append_column(new_column("Urgent", $col++));
+$tree_view->append_column(new_column("Focused", $col++));
+$tree_view->append_column(new_column("Layout", $col++));
+$tree_view->append_column(new_column("Rect", $col++));
+
+$tree_view->set_grid_lines("both");
+
+my $tree_sw = Gtk2::ScrolledWindow->new(undef, undef);
+$tree_sw->add($tree_view);
+
+# Replaced by Gtk2 Boxes:
+#my $area = Gtk2::DrawingArea->new();
+#$area->signal_connect(expose_event => sub {
+# my ($widget, $event) = @_;
+#
+# # fetch a cairo context and it width/height to start drawing nodes
+# my $cr = Gtk2::Gdk::Cairo::Context->create($widget->window());
+#
+# my $w = $widget->allocation->width;
+# my $h = $widget->allocation->height;
+#
+# draw_node($gtree, $cr, 0, 0, $w, $h);
+#
+# return FALSE;
+#});
+
+sub menu_export {
+ print("TODO: EXPORT\n");
+}
+
+my $menu_tree = [
+ _File => {
+ item_type => '<Branch>',
+ children => [
+ _Export => {
+ callback => \&menu_export,
+ accelerator => '<ctrl>E',
+ },
+ _Quit => {
+ callback => sub { Gtk2->main_quit; },
+ accelerator => '<ctrl>Q',
+ },
+ ],
+ },
+];
+
+my $menu = Gtk2::SimpleMenu->new(menu_tree => $menu_tree);
+
+my $vbox = Gtk2::VBox->new(0, 0);
+$vbox->pack_start($menu->{widget}, 0, 0, 0);
+$vbox->pack_end($tree_sw, 1, 1, 0);
+$vbox->pack_end($layout_sw, 1, 1, 0);
+
+$window->add($vbox);
+$window->show_all();
+$window->set_size_request(500,500);
+
+Glib::Timeout->add(1000, "copy_tree", undef, Glib::G_PRIORITY_DEFAULT);
+copy_tree();
+
+Gtk2->main();
+