summary refs log tree commit diff
path: root/nixos/gui/chrome
diff options
context:
space:
mode:
authorEelco Dolstra <eelco.dolstra@logicblox.com>2013-10-10 13:28:20 +0200
committerEelco Dolstra <eelco.dolstra@logicblox.com>2013-10-10 13:28:20 +0200
commit5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010 (patch)
treea6c0f605be6de3f372ae69905b331f9f75452da7 /nixos/gui/chrome
parent6070bc016bd2fd945b04347e25cfd3738622d2ac (diff)
downloadnixlib-5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010.tar
nixlib-5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010.tar.gz
nixlib-5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010.tar.bz2
nixlib-5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010.tar.lz
nixlib-5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010.tar.xz
nixlib-5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010.tar.zst
nixlib-5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010.zip
Move all of NixOS to nixos/ in preparation of the repository merge
Diffstat (limited to 'nixos/gui/chrome')
-rw-r--r--nixos/gui/chrome/chrome.manifest1
-rw-r--r--nixos/gui/chrome/content/io.js137
-rw-r--r--nixos/gui/chrome/content/main.js70
-rw-r--r--nixos/gui/chrome/content/myviewer.xul63
-rw-r--r--nixos/gui/chrome/content/nixos.js255
-rw-r--r--nixos/gui/chrome/content/optionView.js242
6 files changed, 768 insertions, 0 deletions
diff --git a/nixos/gui/chrome/chrome.manifest b/nixos/gui/chrome/chrome.manifest
new file mode 100644
index 000000000000..775445ee17bf
--- /dev/null
+++ b/nixos/gui/chrome/chrome.manifest
@@ -0,0 +1 @@
+content nixos-gui content/
diff --git a/nixos/gui/chrome/content/io.js b/nixos/gui/chrome/content/io.js
new file mode 100644
index 000000000000..8d9c8c173656
--- /dev/null
+++ b/nixos/gui/chrome/content/io.js
@@ -0,0 +1,137 @@
+
+function inspect(obj, maxLevels, level)
+{
+  var str = '', type, msg;
+
+    // Start Input Validations
+    // Don't touch, we start iterating at level zero
+    if(level == null)  level = 0;
+
+    // At least you want to show the first level
+    if(maxLevels == null) maxLevels = 1;
+    if(maxLevels < 1)
+        return '<font color="red">Error: Levels number must be > 0</font>';
+
+    // We start with a non null object
+    if(obj == null)
+    return '<font color="red">Error: Object <b>NULL</b></font>';
+    // End Input Validations
+
+    // Each Iteration must be indented
+    str += '<ul>';
+
+    // Start iterations for all objects in obj
+    for(property in obj)
+    {
+      try
+      {
+          // Show "property" and "type property"
+          type =  typeof(obj[property]);
+          str += '<li>(' + type + ') ' + property +
+                 ( (obj[property]==null)?(': <b>null</b>'):('')) + '</li>';
+
+          // We keep iterating if this property is an Object, non null
+          // and we are inside the required number of levels
+          if((type == 'object') && (obj[property] != null) && (level+1 < maxLevels))
+          str += inspect(obj[property], maxLevels, level+1);
+      }
+      catch(err)
+      {
+        // Is there some properties in obj we can't access? Print it red.
+        if(typeof(err) == 'string') msg = err;
+        else if(err.message)        msg = err.message;
+        else if(err.description)    msg = err.description;
+        else                        msg = 'Unknown';
+
+        str += '<li><font color="red">(Error) ' + property + ': ' + msg +'</font></li>';
+      }
+    }
+
+      // Close indent
+      str += '</ul>';
+
+    return str;
+}
+
+// Run xulrunner application.ini -jsconsole -console, to see messages.
+function log(str)
+{
+  Components.classes['@mozilla.org/consoleservice;1']
+    .getService(Components.interfaces.nsIConsoleService)
+    .logStringMessage(str);
+}
+
+function makeTempFile(prefix)
+{
+  var file = Components.classes["@mozilla.org/file/directory_service;1"]
+                       .getService(Components.interfaces.nsIProperties)
+                       .get("TmpD", Components.interfaces.nsIFile);
+  file.append(prefix || "xulrunner");
+  file.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0664);
+  return file;
+}
+
+function writeToFile(file, data)
+{
+  // file is nsIFile, data is a string
+  var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
+                           .createInstance(Components.interfaces.nsIFileOutputStream);
+
+  // use 0x02 | 0x10 to open file for appending.
+  foStream.init(file, 0x02 | 0x08 | 0x20, 0664, 0); // write, create, truncate
+  foStream.write(data, data.length);
+  foStream.close();
+}
+
+function readFromFile(file)
+{
+  // |file| is nsIFile
+  var data = "";
+  var fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]
+                          .createInstance(Components.interfaces.nsIFileInputStream);
+  var sstream = Components.classes["@mozilla.org/scriptableinputstream;1"]
+                          .createInstance(Components.interfaces.nsIScriptableInputStream);
+  fstream.init(file, -1, 0, 0);
+  sstream.init(fstream);
+
+  var str = sstream.read(4096);
+  while (str.length > 0) {
+    data += str;
+    str = sstream.read(4096);
+  }
+
+  sstream.close();
+  fstream.close();
+
+  return data;
+}
+
+function runProgram(commandLine)
+{
+  // create an nsILocalFile for the executable
+  var file = Components.classes["@mozilla.org/file/local;1"]
+                       .createInstance(Components.interfaces.nsILocalFile);
+  file.initWithPath("/bin/sh");
+
+  // create an nsIProcess
+  var process = Components.classes["@mozilla.org/process/util;1"]
+                          .createInstance(Components.interfaces.nsIProcess);
+  process.init(file);
+
+  // Run the process.
+  // If first param is true, calling thread will be blocked until
+  // called process terminates.
+  // Second and third params are used to pass command-line arguments
+  // to the process.
+  var args = ["-c", commandLine];
+  process.run(true, args, args.length);
+}
+
+// only for testing...
+function testIO()
+{
+  var f = makeTempFile();
+  writeToFile(f, "essai\ntest");
+  alert(readFromFile(f));
+  runProgram("zenity --info");
+}
diff --git a/nixos/gui/chrome/content/main.js b/nixos/gui/chrome/content/main.js
new file mode 100644
index 000000000000..ecfc5a8c5c99
--- /dev/null
+++ b/nixos/gui/chrome/content/main.js
@@ -0,0 +1,70 @@
+// global variables.
+var gNixOS;
+var gOptionView;
+
+/*
+var gProgressBar;
+function setProgress(current, max)
+{
+  if (gProgressBar) {
+    gProgressBar.value = 100 * current / max;
+    log("progress: " + gProgressBar.value + "%");
+  }
+  else
+    log("unknow progress bar");
+}
+*/
+
+function updateTextbox(id, value)
+{
+  // setting the height cause an overflow which resize the textbox to its
+  // content due to its onoverflow attribute.
+  $(id).attr("value", value).attr("height", 1);
+};
+
+function updatePanel(options)
+{
+  log("updatePanel: " + options.length);
+  if (options.length == 0)
+    return;
+  // FIXME: ignore the rest of the selection for now.
+  var o = options[0];
+  $("#name").attr("label", o.path);
+
+  if (o.typename != null)
+    $("#typename").attr("label", o.typename);
+  else
+    $("#typename").attr("label", "");
+
+  $("#desc").text(o.description);
+
+  if (o.value != null)
+    updateTextbox("#val", o.value);
+  else
+    updateTextbox("#val", "");
+
+  if (o.defaultValue != null)
+    updateTextbox("#def", o.defaultValue);
+  else
+    updateTextbox("#def", "");
+
+  if (o.example != null)
+    updateTextbox("#exp", o.example);
+  else
+    updateTextbox("#exp", "");
+
+  updateTextbox("#decls", o.declarations.join("\n"));
+  updateTextbox("#defs", o.definitions.join("\n"));
+}
+
+
+function onload()
+{
+  var optionTree = document.getElementById("option-tree");
+  // gProgressBar = document.getElementById("progress-bar");
+  // setProgress(0, 1);
+
+  gNixOS = new NixOS();
+  gOptionView = new OptionView(gNixOS.option, updatePanel);
+  optionTree.view = gOptionView;
+}
diff --git a/nixos/gui/chrome/content/myviewer.xul b/nixos/gui/chrome/content/myviewer.xul
new file mode 100644
index 000000000000..2aeb9391d076
--- /dev/null
+++ b/nixos/gui/chrome/content/myviewer.xul
@@ -0,0 +1,63 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<!DOCTYPE window>
+
+<!-- To edit this file I recommend you to use:
+     http://xulfr.org/outils/xulediteur.xul
+  -->
+
+<window
+  id     = "nixos-gui"
+  title  = "NixOS gui"
+  width  = "800"
+  height = "600"
+  xmlns  = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <script src="jquery-1.5.2.js"/>
+  <script src="io.js"/>
+  <script src="nixos.js"/>
+  <script src="optionView.js"/>
+  <script src="main.js"/>
+  <hbox flex="1">
+    <vbox width="250">
+      <tree flex="1" id="option-tree" persist="height" onselect="gOptionView.selectionChanged()">
+        <treecols>
+          <treecol persist="hidden width" flex="9" id="opt-name"
+                   label="Option" primary="true"/>
+          <!-- Uncomment the following column to see the number of option
+               printed below each options. -->
+          <!--
+          <treecol persist="hidden width" flex="1" id="dbg-size"
+                   label="sz"/>
+          -->
+        </treecols>
+        <treechildren id="first-child" flex="1"/>
+      </tree>
+    </vbox>
+    <vbox flex="3" style="overflow: auto">
+      <caption id="name" label=""/>
+      <caption id="typename" label=""/>
+      <separator/>
+      <description id="desc" hidden="false"></description>
+      <separator/>
+      <caption label="Value:"/>
+      <textbox id="val" readonly="true" multiline="true" value=""
+      class="plain" hidden="false" onoverflow="this.height =
+      this.inputField.scrollHeight;" />
+      <separator/>
+      <caption label="Default:"/>
+      <textbox id="def" readonly="true" multiline="true" value="" class="plain" hidden="false" onoverflow="this.height = this.inputField.scrollHeight;" />
+      <separator/>
+      <caption label="Example:"/>
+      <textbox id="exp" readonly="true" multiline="true" value="" class="plain" hidden="false" onoverflow="this.height = this.inputField.scrollHeight;" />
+      <separator/>
+      <caption label="Declarations:"/>
+      <textbox id="decls" readonly="true" multiline="true" value="" class="plain" hidden="false" onoverflow="this.height = this.inputField.scrollHeight;" />
+      <separator/>
+      <caption label="Definitions:"/>
+      <textbox id="defs" readonly="true" multiline="true" value=""
+      class="plain" hidden="false" onoverflow="this.height = this.inputField.scrollHeight;" />
+    </vbox>
+  </hbox>
+<!--  <progressmeter id="progress-bar" value="0%"/> -->
+</window>
diff --git a/nixos/gui/chrome/content/nixos.js b/nixos/gui/chrome/content/nixos.js
new file mode 100644
index 000000000000..63a3c16a573f
--- /dev/null
+++ b/nixos/gui/chrome/content/nixos.js
@@ -0,0 +1,255 @@
+
+function NixOS () {
+  var env = Components.classes["@mozilla.org/process/environment;1"].
+    getService(Components.interfaces.nsIEnvironment);
+
+  if (env.exists("NIXOS"))
+    this.nixos = env.get("NIXOS");
+  if (env.exists("NIXOS_CONFIG"))
+    this.config = env.get("NIXOS_CONFIG");
+  if (env.exists("NIXPKGS"))
+    this.nixpkgs = env.get("NIXPKGS");
+  if (env.exists("mountPoint"))
+    this.root = env.get("mountPoint");
+  if (env.exists("NIXOS_OPTION"))
+    this.optionBin = env.get("NIXOS_OPTION");
+  this.option = new Option("options", this, null);
+};
+
+NixOS.prototype = {
+  root: "",
+  nixos: "/etc/nixos/nixos",
+  nixpkgs: "/etc/nixos/nixpkgs",
+  config: "/etc/nixos/configuration.nix",
+  instantiateBin: "/run/current-system/sw/bin/nix-instantiate",
+  optionBin: "/run/current-system/sw/bin/nixos-option",
+  tmpFile: "nixos-gui",
+  option: null
+};
+
+function Option (name, context, parent) {
+  this.name = name;
+  this.context_ = context;
+  if (parent == null)
+    this.path = "";
+  else if (parent.path == "")
+    this.path = name;
+  else
+    this.path = parent.path + "." + name;
+};
+
+Option.prototype = {
+  load: function () {
+    var env = "";
+    env += "'NIXOS=" + this.context_.root + this.context_.nixos + "' ";
+    env += "'NIXOS_PKGS=" + this.context_.root + this.context_.nixpkgs + "' ";
+    env += "'NIXOS_CONFIG=" + this.context_.config + "' ";
+    var out = makeTempFile(this.context_.tmpFile);
+    var prog = this.context_.optionBin + " 2>&1 >" + out.path + " ";
+    var args = " --xml " + this.path;
+
+    runProgram(/*env + */ prog + args);
+    var xml = readFromFile(out);
+    out.remove(false);
+
+    // jQuery does a stack overflow when converting a huge XML to a DOM.
+    var dom = DOMParser().parseFromString(xml, "text/xml");
+    var xmlAttrs = $("expr > attrs > attr", dom);
+
+    this.isOption = xmlAttrs.first().attr("name") == "_isOption";
+
+    if (!this.isOption)
+      this.loadSubOptions(xmlAttrs);
+    else
+      this.loadOption(xmlAttrs);
+    this.isLoaded = true;
+  },
+
+  loadSubOptions:  function (xmlAttrs) {
+    var cur = this;
+    var attrs = new Array();
+
+    xmlAttrs.each(
+      function (index) {
+        var name = $(this).attr("name");
+        var attr = new Option(name, cur.context_, cur);
+        attrs.push(attr);
+      }
+    );
+
+    this.subOptions = attrs;
+  },
+
+  optionAttributeMap: {
+    _isOption: function (cur, v) { },
+    value: function (cur, v) { cur.value = xml2nix($(v).children().first()); },
+    default: function (cur, v) { cur.defaultValue = xml2nix($(v).children().first()); },
+    example: function (cur, v) { cur.example = xml2nix($(v).children().first()); },
+    description: function (cur, v) { cur.description = this.string(v); },
+    typename: function (cur, v) { cur.typename = this.string(v); },
+    options: function (cur, v) { cur.loadSubOptions($("attrs", v).children()); },
+    declarations: function (cur, v) { cur.declarations = this.pathList(v); },
+    definitions: function (cur, v) { cur.definitions = this.pathList(v); },
+
+    string: function (v) {
+      return $(v).children("string").first().attr("value");
+    },
+
+    pathList: function (v) {
+      var list = [];
+      $(v).children("list").first().children().each(
+        function (idx) {
+          list.push($(this).attr("value"));
+        }
+      );
+      return list;
+    }
+  },
+
+
+  loadOption: function (attrs) {
+    var cur = this;
+
+    attrs.each(
+      function (index) {
+        var name = $(this).attr("name");
+        log("loadOption: " + name);
+        cur.optionAttributeMap[name](cur, this);
+      }
+    );
+  },
+
+  // keep the context under which this option has been used.
+  context_: null,
+  // name of the option.
+  name: "",
+  // result of nixos-option.
+  value: null,
+  typename: null,
+  defaultValue: null,
+  example: null,
+  description: "",
+  declarations: [],
+  definitions: [],
+  // path to reach this option
+  path: "",
+
+  // list of options accessible from here.
+  isLoaded: false,
+  isOption: false,
+  subOptions: []
+};
+
+var xml2nix_pptable = {
+  attrs: function (node, depth, pp) {
+    var children = node.children().not(
+      function () {
+        var name = $(this).attr("name");
+        return name.charAt(0) == "_";
+      }
+    );
+    var c = 0;
+    var out = "";
+    out += "{";
+    depth += 1;
+    children.each(
+      function (idx) {
+        c += 1;
+        out += pp.indent(depth);
+        out += pp.dispatch($(this), depth, pp);
+      }
+    );
+    depth -= 1;
+    if (c > 0)
+      out += this.indent(depth);
+    else
+      out += " ";
+    out += "}";
+    return out;
+  },
+  list: function (node, depth, pp) {
+    var children = node.children();
+    var c = 0;
+    var out = "";
+    out += "[";
+    depth += 1;
+    children.each(
+      function (idx) {
+        c += 1;
+        out += pp.indent(depth);
+        out += pp.dispatch($(this), depth, pp);
+      }
+    );
+    depth -= 1;
+    if (c > 0)
+      out += this.indent(depth);
+    else
+      out += " ";
+    out += "]";
+    return out;
+  },
+  attr: function (node, depth, pp) {
+    var name = node.attr("name");
+    var out = "";
+    var val = "";
+    out += name + " = ";
+    depth += 1;
+    val = pp.dispatch(node.children().first(), depth, pp);
+    out += val;
+    depth -= 1;
+    out += ";";
+    return out;
+  },
+  string: function (node, depth, pp) {
+    return "\"" + node.attr("value") + "\"";
+  },
+  path: function (node, depth, pp) {
+    return node.attr("value");
+  },
+  bool: function (node, depth, pp) {
+    return node.attr("value");
+  },
+  "int": function (node, depth, pp) {
+    return node.attr("value");
+  },
+  null: function (node, depth, pp) {
+    return "null";
+  },
+  derivation: function (node, depth, pp) {
+    return "<derivation>";
+  },
+  function: function (node, depth, pp) {
+    return "<function>";
+  },
+  unevaluated: function (node, depth, pp) {
+    return "<unevaluated>";
+  },
+
+  dispatch: function (node, depth, pp) {
+    for (var key in pp)
+    {
+      if(node.is(key))
+      {
+        // log(this.indent(depth) + "dispatch: " + key);
+        var out = pp[key](node, depth, pp);
+        // log(this.indent(depth) + "dispatch: => " + out);
+        return out;
+      }
+    }
+    return "<dispatch-error>";
+  },
+  indent: function (depth) {
+    var ret = "\n";
+    while (depth--)
+      ret += "  ";
+    return ret;
+  }
+};
+
+function xml2nix(node) {
+  var depth = 0;
+  var pp = xml2nix_pptable;
+  var out = pp.dispatch(node, depth, pp);
+  // log("pretty:\n" + out);
+  return out;
+}
diff --git a/nixos/gui/chrome/content/optionView.js b/nixos/gui/chrome/content/optionView.js
new file mode 100644
index 000000000000..0d093740fe25
--- /dev/null
+++ b/nixos/gui/chrome/content/optionView.js
@@ -0,0 +1,242 @@
+// extend NixOS options to handle the Tree View.  Should be better to keep a
+// separation of concern here.
+
+Option.prototype.tv_opened = false;
+Option.prototype.tv_size = 1;
+
+Option.prototype.tv_open = function () {
+  this.tv_opened = true;
+  this.tv_size = 1;
+
+  // load an option if it is not loaded yet, and initialize them to be
+  // read by the Option view.
+  if (!this.isLoaded)
+    this.load();
+
+  // If this is not an option, then add it's lits of sub-options size.
+  if (!this.isOption)
+  {
+    for (var i = 0; i < this.subOptions.length; i++)
+      this.tv_size += this.subOptions[i].tv_size;
+  }
+};
+
+Option.prototype.tv_close = function () {
+  this.tv_opened = false;
+  this.tv_size = 1;
+};
+
+
+
+
+function OptionView (root, selCallback) {
+  root.tv_open();
+  this.rootOption = root;
+  this.selCallback = selCallback;
+}
+
+OptionView.prototype = {
+  rootOption: null,
+  selCallback: null,
+
+  // This function returns the path to option which is at the specified row.
+  reach_cache: null,
+  reachRow: function (row) {
+    var o = this.rootOption; // Current option.
+    var r = 0; // Number of rows traversed.
+    var c = 0; // Child index.
+    var path = [{ row: r, opt: o }]; // new Array();
+    // hypothesis: this.rootOption.tv_size is always open and bigger than
+
+    // Use the previous returned value to avoid making to many checks and to
+    // optimize for frequent access of near rows.
+    if (this.reach_cache != null)
+    {
+      for (var i = this.reach_cache.length - 2; i >= 0; i--) {
+        var p = this.reach_cache[i];
+        // If we will have to go the same path.
+        if (row >= p.row && row < p.row + p.opt.tv_size)
+        {
+          path.unshift(p);
+          r = path[0].row;
+          o = path[0].opt;
+        }
+        else
+          break;
+      };
+    }
+
+    while (r != row)
+    {
+      // Go deeper in the child which contains the requested row.  The
+      // tv_size contains the size of the tree starting from each option.
+      c = 0;
+      while (c < o.subOptions.length && r + o.subOptions[c].tv_size < row)
+      {
+        r += o.subOptions[c].tv_size;
+        c += 1;
+      }
+      if (c < o.subOptions.length && r + o.subOptions[c].tv_size >= row)
+      {
+        // Count the current option as a row.
+        o = o.subOptions[c];
+        r += 1;
+      }
+      else
+        alert("WTF: " + o.name + " ask: " + row + " children: " + o.subOptions + " c: " + c);
+      path.unshift({ row: r, opt: o });
+    }
+
+    this.reach_cache = path;
+    return path;
+  },
+
+  // needs to return true if there is a /row/ at the same level /after/ a
+  // given row.
+  hasNextSibling: function(row, after) {
+    log("sibling " + row + " after " + after);
+    var path = reachRow(row);
+    if (path.length > 1)
+    {
+      var last = path[1].row + path[1].opt.tv_size;
+      // Has a next sibling if the row is not over the size of the
+      // parent and if the current one is not the last child.
+      return after + 1 < last && path[0].row + path[0].opt.tv_size < last;
+    }
+    else
+      // The top-level option has no sibling.
+      return false;
+  },
+
+  // Does the current row contain any sub-options?
+  isContainer: function(row) {
+    return !this.reachRow(row)[0].opt.isOption;
+  },
+  isContainerEmpty: function(row) {
+    return this.reachRow(row)[0].opt.subOptions.length == 0;
+  },
+  isContainerOpen: function(row) {
+    return this.reachRow(row)[0].opt.tv_opened;
+  },
+
+  // Open or close an option.
+  toggleOpenState: function (row) {
+    var path = this.reachRow(row);
+    var delta = -path[0].opt.tv_size;
+    if (path[0].opt.tv_opened)
+      path[0].opt.tv_close();
+    else
+      path[0].opt.tv_open();
+    delta += path[0].opt.tv_size;
+
+    // Parents are alreay opened, but we need to update the tv_size
+    // counters.  Thus we have to invalidate the reach cache.
+    this.reach_cache = null;
+    for (var i = 1; i < path.length; i++)
+      path[i].opt.tv_open();
+
+    this.tree.rowCountChanged(row + 1, delta);
+  },
+
+  // Return the identation level of the option at the line /row/.  The
+  // top-level level is 0.
+  getLevel: function(row) {
+    return this.reachRow(row).length - 1;
+  },
+
+  // Obtain the index of a parent row. If there is no parent row,
+  // returns -1.
+  getParentIndex: function(row) {
+    var path = this.reachRow(row);
+    if (path.length > 1)
+      return path[1].row;
+    else
+      return -1;
+  },
+
+
+  // Return the content of each row base on the column name.
+  getCellText: function(row, column) {
+    if (column.id == "opt-name")
+      return this.reachRow(row)[0].opt.name;
+    if (column.id == "dbg-size")
+      return this.reachRow(row)[0].opt.tv_size;
+    return "";
+  },
+
+  // We have no column with images.
+  getCellValue: function(row, column) { },
+
+
+  isSelectable: function(row, column) { return true; },
+
+  // Get the selection out of the tree and give options to the call back
+  // function.
+  selectionChanged: function() {
+    if (this.selCallback == null)
+      return;
+    var opts = [];
+    var start = new Object();
+    var end = new Object();
+    var numRanges = this.tree.view.selection.getRangeCount();
+
+    for (var t = 0; t < numRanges; t++) {
+      this.tree.view.selection.getRangeAt(t,start,end);
+      for (var v = start.value; v <= end.value; v++) {
+        var opt = this.reachRow(v)[0].opt;
+        if (!opt.isLoaded)
+          opt.load();
+        if (opt.isOption)
+          opts.push(opt);
+
+        // FIXME: no need to make things slowing down, because our current
+        // callback do not handle multiple option display.
+        if (!opts.empty)
+          break;
+      }
+      // FIXME: no need to make things slowing down, because our current
+      // callback do not handle multiple option display.
+      if (!opts.empty)
+        break;
+    }
+
+    if (!opts.empty)
+      this.selCallback(opts);
+  },
+
+  set rowCount(c) { throw "rowCount is a readonly property"; },
+  get rowCount() { return this.rootOption.tv_size; },
+
+  // refuse drag-n-drop of options.
+  canDrop: function (index, orientation, dataTransfer) { return false; },
+  drop: function (index, orientation, dataTransfer) { },
+
+  // ?
+  getCellProperties: function(row, column, prop) { },
+  getColumnProperties: function(column, prop) { },
+  getRowProperties: function(row, prop) { },
+  getImageSrc: function(row, column) { },
+
+  // No progress columns are used.
+  getProgressMode: function(row, column) { },
+
+  // Do not add options yet.
+  isEditable: function(row, column) { return false; },
+  setCellValue: function(row, column, value) { },
+  setCellText: function(row, column, value) { },
+
+  // ...
+  isSeparator: function(index) { return false; },
+  isSorted: function() { return false; },
+  performAction: function(action) { },
+  performActionOnCell: function(action, row, column) { },
+  performActionOnRow: function(action, row) { }, // ??
+
+  // ??
+  cycleCell: function (row, col) { },
+  cycleHeader: function(col) { },
+
+  selection: null,
+  tree: null,
+  setTree: function(tree) { this.tree = tree; }
+};