diff options
author | Eelco Dolstra <eelco.dolstra@logicblox.com> | 2013-10-10 13:28:20 +0200 |
---|---|---|
committer | Eelco Dolstra <eelco.dolstra@logicblox.com> | 2013-10-10 13:28:20 +0200 |
commit | 5c1f8cbc70cd5e6867ef6a2a06d27a40daa07010 (patch) | |
tree | a6c0f605be6de3f372ae69905b331f9f75452da7 /nixos/gui/chrome | |
parent | 6070bc016bd2fd945b04347e25cfd3738622d2ac (diff) | |
download | nixlib-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.manifest | 1 | ||||
-rw-r--r-- | nixos/gui/chrome/content/io.js | 137 | ||||
-rw-r--r-- | nixos/gui/chrome/content/main.js | 70 | ||||
-rw-r--r-- | nixos/gui/chrome/content/myviewer.xul | 63 | ||||
-rw-r--r-- | nixos/gui/chrome/content/nixos.js | 255 | ||||
-rw-r--r-- | nixos/gui/chrome/content/optionView.js | 242 |
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; } +}; |