Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Added web gui

  • Loading branch information...
commit f116b2d656a29acc5de7fb0cf347d9f5f744e9d5 1 parent f3193c7
Zsolt Keszthelyi authored

Showing 25 changed files with 1,708 additions and 0 deletions. Show diff stats Hide diff stats

  1. +34 0 priv/www/cadamgui/Manifest.json
  2. +80 0 priv/www/cadamgui/config.json
  3. +72 0 priv/www/cadamgui/generate.py
  4. +10 0 priv/www/cadamgui/readme.txt
  5. +242 0 priv/www/cadamgui/source/class/cadamgui/Application.js
  6. +241 0 priv/www/cadamgui/source/class/cadamgui/Application_withWindows.js
  7. +309 0 priv/www/cadamgui/source/class/cadamgui/QxSmoothie.js
  8. +48 0 priv/www/cadamgui/source/class/cadamgui/WebSocket.js
  9. +59 0 priv/www/cadamgui/source/class/cadamgui/WebSocket2.js
  10. +55 0 priv/www/cadamgui/source/class/cadamgui/test/DemoTest.js
  11. +18 0 priv/www/cadamgui/source/class/cadamgui/theme/Appearance.js
  12. +18 0 priv/www/cadamgui/source/class/cadamgui/theme/Color.js
  13. +18 0 priv/www/cadamgui/source/class/cadamgui/theme/Decoration.js
  14. +18 0 priv/www/cadamgui/source/class/cadamgui/theme/Font.js
  15. +21 0 priv/www/cadamgui/source/class/cadamgui/theme/Theme.js
  16. +9 0 priv/www/cadamgui/source/index.html
  17. BIN  priv/www/cadamgui/source/resource/cadamgui/close-button-hovered.png
  18. BIN  priv/www/cadamgui/source/resource/cadamgui/close-button-pressed.png
  19. BIN  priv/www/cadamgui/source/resource/cadamgui/close-button.png
  20. BIN  priv/www/cadamgui/source/resource/cadamgui/close-button1.png
  21. BIN  priv/www/cadamgui/source/resource/cadamgui/test.png
  22. +164 0 priv/www/cadamgui/source/script/cadamgui.js
  23. +3 0  priv/www/cadamgui/source/translation/readme.txt
  24. +29 0 priv/www/metrics_ws_server_connector.yaws
  25. +260 0 priv/www/smoothie.js
34 priv/www/cadamgui/Manifest.json
... ... @@ -0,0 +1,34 @@
  1 +{
  2 + "info" :
  3 + {
  4 + "name" : "cadamgui",
  5 +
  6 + "summary" : "Custom Application",
  7 + "description" : "This is a skeleton for a custom application with qooxdoo.",
  8 +
  9 + "homepage" : "http://some.homepage.url/",
  10 +
  11 + "license" : "SomeLicense",
  12 + "authors" :
  13 + [
  14 + {
  15 + "name" : "First Author (uid)",
  16 + "email" : "first.author@some.domain"
  17 + }
  18 + ],
  19 +
  20 + "version" : "trunk",
  21 + "qooxdoo-versions": ["1.2"]
  22 + },
  23 +
  24 + "provides" :
  25 + {
  26 + "namespace" : "cadamgui",
  27 + "encoding" : "utf-8",
  28 + "class" : "source/class",
  29 + "resource" : "source/resource",
  30 + "translation" : "source/translation",
  31 + "type" : "application"
  32 + }
  33 +}
  34 +
80 priv/www/cadamgui/config.json
... ... @@ -0,0 +1,80 @@
  1 +{
  2 + "name" : "cadamgui",
  3 +
  4 + "include" :
  5 + [
  6 + {
  7 + "path" : "${QOOXDOO_PATH}/tool/data/config/application.json"
  8 + }
  9 + ],
  10 +
  11 + "export" :
  12 + [
  13 + "api",
  14 + "api-data",
  15 + "build",
  16 + "clean",
  17 + "distclean",
  18 + "fix",
  19 + "info",
  20 + "inspector",
  21 + "lint",
  22 + "migration",
  23 + "pretty",
  24 + "profiling",
  25 + "source",
  26 + "source-all",
  27 + "test",
  28 + "test-source",
  29 + "translation"
  30 + ],
  31 +
  32 + "let" :
  33 + {
  34 + "APPLICATION" : "cadamgui",
  35 + "QOOXDOO_PATH" : "../../../../qoo12",
  36 +// "QXTHEME" : "cadamgui.theme.Theme",
  37 + "QXTHEME" : "darktheme.DarkTheme",
  38 +// "QXICONTHEME" : ["Tango"],
  39 + "API_EXCLUDE" : ["qx.test.*", "${APPLICATION}.theme.*", "${APPLICATION}.test.*"],
  40 + "LOCALES" : [ "en" ],
  41 + "CACHE" : "${TMPDIR}/cache",
  42 + "ROOT" : "."
  43 + },
  44 +
  45 + // You only need to edit the remainder of this file, if you want to customize
  46 + // specific jobs, or add own job definitions.
  47 +
  48 +
  49 + "jobs" :
  50 + {
  51 + // Uncomment the following entry to add a contrib or library to your
  52 + // project; make sure to adapt the path to the Manifest.json; if you are
  53 + // using a contrib: library, it will be downloaded into the path specified
  54 + // by the 'cache/downloads' config key
  55 + "libraries" :
  56 + {
  57 + "library" :
  58 + [
  59 + {
  60 + "manifest" : "DarkTheme/trunk/Manifest.json"
  61 + }
  62 + ]
  63 + },
  64 +
  65 + // If you want to tweak a job setting, see the following sample where
  66 + // the "format" feature of the "build-script" job is overridden.
  67 + // To see a list of available jobs, invoke 'generate.py x'.
  68 + "build-script" :
  69 + {
  70 + "compile-options" :
  71 + {
  72 + "code" :
  73 + {
  74 + "format" : false
  75 + }
  76 + }
  77 + }
  78 + }
  79 +
  80 +}
72 priv/www/cadamgui/generate.py
... ... @@ -0,0 +1,72 @@
  1 +#!/usr/bin/env python
  2 +################################################################################
  3 +#
  4 +# qooxdoo - the new era of web development
  5 +#
  6 +# http://qooxdoo.org
  7 +#
  8 +# Copyright:
  9 +# 2008 - 2009 1&1 Internet AG, Germany, http://www.1und1.de
  10 +#
  11 +# License:
  12 +# LGPL: http://www.gnu.org/licenses/lgpl.html
  13 +# EPL: http://www.eclipse.org/org/documents/epl-v10.php
  14 +# See the LICENSE file in the project's top-level directory for details.
  15 +#
  16 +# Authors:
  17 +# * Thomas Herchenroeder (thron7)
  18 +#
  19 +################################################################################
  20 +
  21 +##
  22 +# This is a stub proxy for the real generator.py
  23 +##
  24 +
  25 +import sys, os, re, subprocess
  26 +
  27 +CMD_PYTHON = 'python'
  28 +QOOXDOO_PATH = '../../../../qoo12'
  29 +
  30 +def getQxPath():
  31 + path = QOOXDOO_PATH
  32 + # try updating from config file
  33 + if os.path.exists('config.json'):
  34 + # "using QOOXDOO_PATH from config.json"
  35 + qpathr=re.compile(r'"QOOXDOO_PATH"\s*:\s*"([^"]*)"\s*,?')
  36 + conffile = open('config.json')
  37 + aconffile = conffile.readlines()
  38 + for line in aconffile:
  39 + mo = qpathr.search(line)
  40 + if mo:
  41 + path = mo.group(1)
  42 + break # assume first occurrence is ok
  43 + path = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), path))
  44 +
  45 + return path
  46 +
  47 +os.chdir(os.path.dirname(os.path.abspath(sys.argv[0]))) # switch to skeleton dir
  48 +qxpath = getQxPath()
  49 +REAL_GENERATOR = os.path.join(qxpath, 'tool', 'bin', 'generator.py')
  50 +
  51 +if not os.path.exists(REAL_GENERATOR):
  52 + print "Cannot find real generator script under: \"%s\"; aborting" % REAL_GENERATOR
  53 + sys.exit(1)
  54 +
  55 +argList = []
  56 +argList.append(CMD_PYTHON)
  57 +argList.append(REAL_GENERATOR)
  58 +argList.extend(sys.argv[1:])
  59 +if sys.platform == "win32":
  60 + argList1=[]
  61 + for arg in argList:
  62 + if arg.find(' ')>-1:
  63 + argList1.append('"%s"' % arg)
  64 + else:
  65 + argList1.append(arg)
  66 + argList = argList1
  67 +else:
  68 + argList = ['"%s"' % x for x in argList] # quote argv elements
  69 +
  70 +cmd = " ".join(argList)
  71 +retval = subprocess.call(cmd, shell=True)
  72 +sys.exit(retval)
10 priv/www/cadamgui/readme.txt
... ... @@ -0,0 +1,10 @@
  1 +GUI Skeleton - A qooxdoo Application Template
  2 +=============================================
  3 +
  4 +This is a qooxdoo application skeleton which is used as a template. The
  5 +'create-application.py' script (usually under tool/bin/create-application.py)
  6 +will use this and expand it into a self-contained qooxdoo application which
  7 +can then be further extended. Please refer to the script and other documentation
  8 +for further information.
  9 +
  10 +short:: is a standard qooxdoo GUI application
242 priv/www/cadamgui/source/class/cadamgui/Application.js
... ... @@ -0,0 +1,242 @@
  1 +/* ************************************************************************
  2 +
  3 + Copyright:
  4 +
  5 + License:
  6 +
  7 + Authors: Zsolt Keszthelyi <zsolt.erl@gmail.com>
  8 +
  9 +************************************************************************ */
  10 +
  11 +/* ************************************************************************
  12 +
  13 +#asset(cadamgui/*)
  14 +
  15 +************************************************************************ */
  16 +
  17 +qx.Class.define("cadamgui.Application",
  18 +{
  19 + extend : qx.application.Standalone,
  20 +
  21 +
  22 +
  23 + /*
  24 + *****************************************************************************
  25 + MEMBERS
  26 + *****************************************************************************
  27 + */
  28 +
  29 + members :
  30 + {
  31 + timeseries : [],
  32 + metric_routes : [],
  33 + ws : {},
  34 +
  35 + /**
  36 + * This method contains the initial application code and gets called
  37 + * during startup of the application
  38 + *
  39 + * @lint ignoreDeprecated(alert)
  40 + */
  41 + main : function()
  42 + {
  43 + // Call super class
  44 + this.base(arguments);
  45 +
  46 + // Enable logging in debug variant
  47 + if (qx.core.Variant.isSet("qx.debug", "on"))
  48 + {
  49 + // support native logging capabilities, e.g. Firebug for Firefox
  50 + qx.log.appender.Native;
  51 + // support additional cross-browser console. Press F7 to toggle visibility
  52 + qx.log.appender.Console;
  53 + }
  54 +
  55 + var root = this.getRoot();
  56 + var timeseries = this.timeseries;
  57 + var metric_routes = this.metric_routes;
  58 +
  59 + /*
  60 + * ---------------------------------------------------------------------------
  61 + * CREATE UI
  62 + * ---------------------------------------------------------------------------
  63 + */
  64 +
  65 + // set up background
  66 + // var decorator=new qx.ui.decoration.Single();
  67 + // decorator.setBackgroundImage("cadam/bl1280.jpg");
  68 + // root.set({decorator:decorator});
  69 +
  70 + var mainContainer = new qx.ui.container.Composite( new qx.ui.layout.VBox(10) );
  71 + var headerContainer = new qx.ui.container.Composite( new qx.ui.layout.VBox(10) );
  72 + var workContainer = new qx.ui.container.Composite( new qx.ui.layout.HBox() );
  73 +
  74 + root.add(mainContainer);
  75 + mainContainer.add(headerContainer);
  76 + mainContainer.add(workContainer);
  77 +
  78 + var menubar = new qx.ui.toolbar.ToolBar();
  79 + menubar.setWidth(2500);
  80 + menubar.setFont( new qx.bom.Font(16, ["Verdana", "Courier"]).set({bold: true}) );
  81 +
  82 + menubar.add( new qx.ui.basic.Label("CADAM v0.1").set({padding: 7}) );
  83 + menubar.add( new qx.ui.toolbar.Separator() );
  84 + var toolsMenu = new qx.ui.toolbar.MenuButton("Tools");
  85 + menubar.add( toolsMenu );
  86 + menubar.add( new qx.ui.toolbar.Separator() );
  87 + var usageLabel = new qx.ui.basic.Label().set(
  88 + {
  89 + value: "Drag and Drop a metric from the list onto one of the empty gray containers!",
  90 + marginLeft: 20,
  91 + alignY: "middle"
  92 + });
  93 + menubar.add( usageLabel );
  94 +
  95 + headerContainer.add(menubar);
  96 +
  97 +
  98 + var metricsList = new qx.ui.form.List().set(
  99 + {
  100 + textColor: "black",
  101 + font: new qx.bom.Font(12, ["Verdana", "Courier"]),
  102 + margin: 20,
  103 + width: 350,
  104 + height: 530,
  105 + draggable: true
  106 + });
  107 + workContainer.add(metricsList);
  108 +
  109 + metricsList.addListener("dragstart", function(e)
  110 + {
  111 + e.addAction("move");
  112 + e.addType("metricid");
  113 + });
  114 +
  115 + metricsList.addListener("droprequest", function(e)
  116 + {
  117 + var type = e.getCurrentType();
  118 + var action = e.getCurrentAction();
  119 + if ((type=="metricid") && (action=="move")) {
  120 + var metricid = this.getSelection()[0].getModel();
  121 + e.addData(type, metricid);
  122 + }
  123 + });
  124 +
  125 + // create grid
  126 + var dashboardGrid = new qx.ui.container.Composite( new qx.ui.layout.Grid(10,10) );
  127 + dashboardGrid.set(
  128 + {
  129 + marginTop: 20
  130 + });
  131 + workContainer.add( dashboardGrid );
  132 +
  133 + // populate grid with empty containers
  134 + var widgets = this.widgets = [];
  135 + for (var i=0; i<2; i++) {
  136 + widgets[i] = [];
  137 + for (var j=0; j<3; j++) {
  138 + widgets[i][j] = new qx.ui.container.Composite( new qx.ui.layout.VBox() ).set(
  139 + {
  140 + backgroundColor: "#3a3a3a",
  141 + width: 400,
  142 + height: 170,
  143 + droppable: true
  144 + });
  145 + widgets[i][j].addListener("drop", this.showChart, this);
  146 + dashboardGrid.add(widgets[i][j], {column: i, row: j});
  147 + };
  148 + };
  149 +
  150 +
  151 + /*
  152 + * ---------------------------------------------------------------------------
  153 + * COMMUNICATE WITH SERVER
  154 + * ---------------------------------------------------------------------------
  155 + */
  156 +
  157 + // create a function to handle messages from server
  158 + var wsonmessage = function(m) {
  159 + if (m.data){
  160 + var message = qx.util.Json.parse(m.data);
  161 + console.log(message);
  162 + switch (message.response_for) {
  163 + case 'get_all_metrics' :
  164 + var metrics = message.response;
  165 + var listItem;
  166 + metricsList.removeAll();
  167 + for (var i=0; i<metrics.length; i++) {
  168 + listItem = new qx.ui.form.ListItem(metrics[i][0]+" - "+metrics[i][1], null, metrics[i]);
  169 + metricsList.add(listItem);
  170 + }
  171 + break;
  172 + case 'subscription' :
  173 + var metricid = message.response.metricid;
  174 + var value = message.response.value;
  175 + var ts = metric_routes[ metricid ];
  176 + // append new value to timeseries
  177 + ts.append(new Date().getTime(), value);
  178 + break;
  179 + default :
  180 + this.parent.debug("unexpected response:"+message.response_for);
  181 + }
  182 + }
  183 + };
  184 +
  185 + // create websocket object
  186 + var ws = this.ws = new cadamgui.WebSocket2();
  187 + // overwrite onmessage call of the websocket
  188 + ws.onmessage = wsonmessage;
  189 + ws.connect("ws://localhost:9000/metrics_ws_server_connector.yaws");
  190 + ws.addListener("socket-ready", function()
  191 + {
  192 + ws.sendJSON({cmd: "get_all_metrics"});
  193 + });
  194 + },
  195 +
  196 + // e is the drop event, 'this' is the qx.Application
  197 + showChart : function(e)
  198 + {
  199 + var ContainerWidget = e.getTarget();
  200 + var metricID = e.getData("metricid");
  201 +
  202 + var Header = new qx.ui.container.Composite( new qx.ui.layout.HBox(0, "right"));
  203 + ContainerWidget.add(Header);
  204 +
  205 + // add label
  206 + var Label = new qx.ui.basic.Label( metricID[0]+' - '+metricID[1]).set(
  207 + {
  208 + textColor: "yellow",
  209 + font: new qx.bom.Font(12, ["Verdana", "Courier"]),
  210 + marginTop: 2,
  211 + marginRight: 20
  212 + });
  213 + Header.add(Label);
  214 +
  215 + var CloseButton = new qx.ui.basic.Image("cadamgui/close-button.png");
  216 + CloseButton.addListener("click", function(e){
  217 + this.ws.sendJSON({cmd: "unsubscribe_from_queue", metricid: metricID, queuenum: 1});
  218 + // take timeline out of metric_routes array
  219 + delete this.metric_routes[metricID];
  220 + ContainerWidget.removeAll();
  221 + }, this);
  222 +
  223 +
  224 + Header.add(CloseButton);
  225 +
  226 + // create/add smoothie chart
  227 + var chartOptions = {grid: {verticalSections : 2, millisPerLine : 10000}, maxValueScale: 1.1,
  228 + fps : 20, millisPerPixel : 100};
  229 + var chart = new cadamgui.QxSmoothie(chartOptions, 4000).set(
  230 + {
  231 + canvasWidth: 400,
  232 + maxWidth: 400,
  233 + canvasHeight: 150,
  234 + height: 150
  235 + });
  236 + ContainerWidget.add(chart);
  237 + var ts = chart.getTimeseries();
  238 + this.metric_routes[metricID] = ts;
  239 + this.ws.sendJSON({cmd: "subscribe_to_queue", metricid: metricID, queuenum: 1});
  240 + }
  241 + }
  242 +});
241 priv/www/cadamgui/source/class/cadamgui/Application_withWindows.js
... ... @@ -0,0 +1,241 @@
  1 +/* ************************************************************************
  2 +
  3 + Copyright:
  4 +
  5 + License:
  6 +
  7 + Authors: Zsolt Keszthelyi <zsolt.erl@gmail.com>
  8 +
  9 +************************************************************************ */
  10 +
  11 +/* ************************************************************************
  12 +
  13 +#asset(cadamgui/*)
  14 +
  15 +************************************************************************ */
  16 +
  17 +qx.Class.define("cadamgui.Application_withWindows",
  18 +{
  19 + extend : qx.application.Standalone,
  20 +
  21 +
  22 +
  23 + /*
  24 + *****************************************************************************
  25 + MEMBERS
  26 + *****************************************************************************
  27 + */
  28 +
  29 + members :
  30 + {
  31 + timeseries : [],
  32 + metric_routes : [],
  33 + ws : {},
  34 +
  35 + gridTop : 120,
  36 + gridLeft : 400,
  37 + gridSpaceX : 20,
  38 + gridSpaceY : 20,
  39 + windowWidth : 404,
  40 + windowHeight : 154,
  41 +
  42 +
  43 +
  44 + /**
  45 + * This method contains the initial application code and gets called
  46 + * during startup of the application
  47 + *
  48 + * @lint ignoreDeprecated(alert)
  49 + */
  50 + main : function()
  51 + {
  52 + // Call super class
  53 + this.base(arguments);
  54 +
  55 + // Enable logging in debug variant
  56 + if (qx.core.Variant.isSet("qx.debug", "on"))
  57 + {
  58 + // support native logging capabilities, e.g. Firebug for Firefox
  59 + qx.log.appender.Native;
  60 + // support additional cross-browser console. Press F7 to toggle visibility
  61 + qx.log.appender.Console;
  62 + }
  63 +
  64 + var root = this.getRoot();
  65 + var timeseries = this.timeseries;
  66 + var metric_routes = this.metric_routes;
  67 +
  68 + /*
  69 + * ---------------------------------------------------------------------------
  70 + * CREATE UI
  71 + * ---------------------------------------------------------------------------
  72 + */
  73 +
  74 + // set up background
  75 + // var decorator=new qx.ui.decoration.Single();
  76 + // decorator.setBackgroundImage("mtagui/bl1280.jpg");
  77 + // root.set({decorator:decorator});
  78 +
  79 + var mainContainer = new qx.ui.container.Composite( new qx.ui.layout.VBox(10) );
  80 + var headerContainer = new qx.ui.container.Composite( new qx.ui.layout.VBox(10) );
  81 + var workContainer = new qx.ui.container.Composite( new qx.ui.layout.Canvas() );
  82 +
  83 + root.add(mainContainer);
  84 + mainContainer.add(headerContainer);
  85 + mainContainer.add(workContainer);
  86 +
  87 + var menubar = new qx.ui.toolbar.ToolBar();
  88 + menubar.setWidth(2500);
  89 + var toolsMenu = new qx.ui.toolbar.MenuButton("Tools");
  90 +
  91 + menubar.add( new qx.ui.basic.Label("CADAM v0.1").set({padding: 7}) );
  92 + menubar.add( new qx.ui.toolbar.Separator() );
  93 + // menubar.add( toolsMenu );
  94 + headerContainer.add(menubar);
  95 +
  96 + var usageLabel = new qx.ui.basic.Label( "Drag and Drop a metric from the list onto the gray area!" );
  97 + usageLabel.set(
  98 + {
  99 +// font: new qx.bom.Font(18, ["Verdana", "Courier"]),
  100 + textColor: "white",
  101 + marginLeft: 50
  102 + });
  103 + menubar.add(usageLabel);
  104 +
  105 + var metricsList = new qx.ui.form.List().set(
  106 + {
  107 + textColor: "black",
  108 + width: 300,
  109 + maxHeight: 400,
  110 + draggable: true
  111 + });
  112 + workContainer.add(metricsList, {left: 50, top: this.gridTop});
  113 +
  114 + metricsList.addListener("dragstart", function(e)
  115 + {
  116 + e.addAction("move");
  117 + e.addType("metricid");
  118 + });
  119 +
  120 + metricsList.addListener("droprequest", function(e)
  121 + {
  122 + var type = e.getCurrentType();
  123 + var action = e.getCurrentAction();
  124 + if ((type=="metricid") && (action=="move")) {
  125 + var metricid = this.getSelection()[0].getModel();
  126 + e.addData(type, metricid);
  127 + }
  128 + });
  129 +
  130 + var desktop = this.desktop = new qx.ui.window.Desktop( new qx.ui.window.Manager() ).set(
  131 + {
  132 + width: (this.windowWidth + 20) * 2 + this.gridSpaceX,
  133 + height: this.windowHeight * 3 + this.gridSpaceY * 2,
  134 + backgroundColor: "#3a3a3a",
  135 + droppable: true
  136 + });
  137 +
  138 + workContainer.add( desktop, {left: this.gridLeft, top: this.gridTop});
  139 + desktop.addListener("drop", this.showChartW, this);
  140 +
  141 +
  142 + /*
  143 + * ---------------------------------------------------------------------------
  144 + * COMMUNICATE WITH SERVER
  145 + * ---------------------------------------------------------------------------
  146 + */
  147 +
  148 + // create a function to handle messages from server
  149 + var wsonmessage = function(m) {
  150 + if (m.data){
  151 + var message = qx.util.Json.parse(m.data);
  152 + console.log(message);
  153 + switch (message.response_for) {
  154 + case 'get_all_metrics' :
  155 + var metrics = message.response;
  156 + var listItem;
  157 + metricsList.removeAll();
  158 + for (var i=0; i<metrics.length; i++) {
  159 + listItem = new qx.ui.form.ListItem(metrics[i][0]+" - "+metrics[i][1], null, metrics[i]);
  160 + metricsList.add(listItem);
  161 + }
  162 + break;
  163 + case 'subscription' :
  164 + var metricid = message.response.metricid;
  165 + var value = message.response.value;
  166 + var ts = metric_routes[ metricid ];
  167 + // append new value to timeseries
  168 + ts.append(new Date().getTime(), value);
  169 + break;
  170 + default :
  171 + this.parent.debug("unexpected response:"+message.response_for);
  172 + }
  173 + }
  174 + };
  175 +
  176 + // create websocket object
  177 + var ws = this.ws = new cadamgui.WebSocket2();
  178 + // overwrite onmessage call of the websocket
  179 + ws.onmessage = wsonmessage;
  180 + ws.connect("ws://localhost:9000/metrics_ws_server_connector.yaws");
  181 + ws.addListener("socket-ready", function()
  182 + {
  183 + ws.sendJSON({cmd: "get_all_metrics"});
  184 + });
  185 + },
  186 +
  187 +
  188 +
  189 + // e is the drop event, 'this' is the qx.Application
  190 + showChartW : function(e)
  191 + {
  192 + var windowRealHeight = this.windowHeight + 20; // adjust for title bar
  193 +
  194 + var ContainerWidget = e.getTarget();
  195 + var metricID = e.getData("metricid");
  196 +
  197 + var cursorX = e.getDocumentLeft();
  198 + var cursorY = e.getDocumentTop();
  199 +
  200 + Window = new qx.ui.window.Window( metricID[0]+' - '+metricID[1] ).set(
  201 + {
  202 + width: this.windowWidth,
  203 + height: this.windowHeight,
  204 + contentPadding: 2,
  205 +
  206 + movable: true,
  207 + resizable: false,
  208 + showMaximize: false,
  209 + showMinimize: false
  210 + });
  211 + Window.setLayout(new qx.ui.layout.VBox());
  212 + var windowX = Math.floor((cursorX - this.gridLeft) / (this.windowWidth + this.gridSpaceX)) *
  213 + (this.windowWidth + this.gridSpaceX);// + this.gridLeft;
  214 +
  215 + var windowY = Math.floor((cursorY - this.gridTop) / (windowRealHeight + this.gridSpaceY)) *
  216 + (windowRealHeight + this.gridSpaceY);// + this.gridTop;
  217 +
  218 + this.desktop.add(Window, {left: windowX, top: windowY});
  219 +
  220 + // create/add smoothie chart
  221 + var chartOptions = {grid: {verticalSections : 2, millisPerLine : 10000}, maxValueScale: 1.1,
  222 + fps : 20, millisPerPixel : 100};
  223 + var chart = new cadamgui.QxSmoothie(chartOptions, 4000).set(
  224 + {
  225 + canvasWidth: 400,
  226 + maxWidth: 400,
  227 + canvasHeight: 150,
  228 + height: 150
  229 + });
  230 + Window.add(chart);
  231 + Window.open();
  232 + var ts = chart.getTimeseries();
  233 + this.metric_routes[metricID] = ts;
  234 + this.ws.sendJSON({cmd: "subscribe_to_queue", metricid: metricID, queuenum: 1});
  235 + console.log('drop listener - metric_routes='+qx.util.Json.stringify(this.metric_routes));
  236 + }
  237 +
  238 +
  239 +
  240 + }
  241 +});
309 priv/www/cadamgui/source/class/cadamgui/QxSmoothie.js
... ... @@ -0,0 +1,309 @@
  1 +qx.Class.define("cadamgui.QxSmoothie",{
  2 + extend : qx.ui.embed.Canvas,
  3 + construct : function(chartOptions, delay) {
  4 + this.base(arguments);
  5 +
  6 + if (document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1")){
  7 + this.setTimeseries(new TimeSeries());
  8 + this.setSmoothiechart(new SmoothieChart(chartOptions));
  9 + if (delay) {
  10 + this.setDelay(delay);
  11 + } else {
  12 + this.setDelay(1000);
  13 + };
  14 + this.addListenerOnce('appear',this._setCanvas,this);
  15 + this.addListener('resize',this._setSize,this);
  16 + this.getSmoothiechart().addTimeSeries( this.getTimeseries(),
  17 + {strokeStyle: 'rgba(0, 255, 0, 1)', fillStyle: 'rgba(0, 255, 0, 0.2)', lineWidth: 2});
  18 + }
  19 + else {
  20 + this._setLayout(new qx.ui.layout.Grow());
  21 + this._add(new qx.ui.basic.Atom('Your browser does not seem to have support for SVG.').set({
  22 + rich: true,
  23 + alignX: 'center',
  24 + alignY: 'middle',
  25 + padding: 20
  26 + }));
  27 + this.setTimeseries(null);
  28 + this.setSmoothiechart(null);
  29 + }
  30 + },
  31 + properties: {
  32 + timeseries: {},
  33 + smoothiechart: {},
  34 + delay: 0
  35 + },
  36 + members: {
  37 + _setCanvas: function(e){
  38 + var el = this.getContentElement().getDomElement();
  39 + this.getSmoothiechart().streamTo(el, 1000);
  40 + },
  41 + _setSize: function(e){
  42 + qx.html.Element.flush();
  43 + }
  44 + }
  45 +});
  46 +
  47 +
  48 +
  49 +
  50 +// MIT License:
  51 +//
  52 +// Copyright (c) 2010, Joe Walnes
  53 +//
  54 +// Permission is hereby granted, free of charge, to any person obtaining a copy
  55 +// of this software and associated documentation files (the "Software"), to deal
  56 +// in the Software without restriction, including without limitation the rights
  57 +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  58 +// copies of the Software, and to permit persons to whom the Software is
  59 +// furnished to do so, subject to the following conditions:
  60 +//
  61 +// The above copyright notice and this permission notice shall be included in
  62 +// all copies or substantial portions of the Software.
  63 +//
  64 +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  65 +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  66 +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  67 +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  68 +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  69 +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  70 +// THE SOFTWARE.
  71 +
  72 +/**
  73 + * Smoothie Charts - http://smoothiecharts.org/
  74 + * (c) 2010, Joe Walnes
  75 + *
  76 + * v1.0: Main charting library, by Joe Walnes
  77 + * v1.1: Auto scaling of axis, by Neil Dunn
  78 + * v1.2: fps (frames per second) option, by Mathias Petterson
  79 + * v1.3: Fix for divide by zero, by Paul Nikitochkin
  80 + * v1.4: Set minimum, top-scale padding, remove timeseries, add optional timer to reset bounds, by Kelley Reynolds
  81 + */
  82 +
  83 +function TimeSeries(options) {
  84 + options = options || {};
  85 + options.resetBoundsInterval = options.resetBoundsInterval || 3000; // Reset the max/min bounds after this many milliseconds
  86 + options.resetBounds = options.resetBounds || true; // Enable or disable the resetBounds timer
  87 + this.options = options;
  88 + this.data = [];
  89 +
  90 + this.maxValue = Number.NaN; // The maximum value ever seen in this time series.
  91 + this.minValue = Number.NaN; // The minimum value ever seen in this time series.
  92 +
  93 + // Start a resetBounds Interval timer desired
  94 + if (options.resetBounds) {
  95 + this.boundsTimer = setInterval(function(thisObj) { thisObj.resetBounds(); }, options.resetBoundsInterval, this);
  96 + }
  97 +}
  98 +
  99 +// Reset the min and max for this timeseries so the graph rescales itself
  100 +TimeSeries.prototype.resetBounds = function() {
  101 + this.maxValue = Number.NaN;
  102 + this.minValue = Number.NaN;
  103 + for (var i = 0; i < this.data.length; i++) {
  104 + this.maxValue = !isNaN(this.maxValue) ? Math.max(this.maxValue, this.data[i][1]) : this.data[i][1];
  105 + this.minValue = !isNaN(this.minValue) ? Math.min(this.minValue, this.data[i][1]) : this.data[i][1];
  106 + }
  107 +};
  108 +
  109 +TimeSeries.prototype.append = function(timestamp, value) {
  110 + this.data.push([timestamp, value]);
  111 + this.maxValue = !isNaN(this.maxValue) ? Math.max(this.maxValue, value) : value;
  112 + this.minValue = !isNaN(this.minValue) ? Math.min(this.minValue, value) : value;
  113 +};
  114 +
  115 +function SmoothieChart(options) {
  116 + // Defaults
  117 + options = options || {};
  118 + options.grid = options.grid || { fillStyle:'#000000', strokeStyle: '#777777', lineWidth: 1, millisPerLine: 1000, verticalSections: 2 };
  119 + options.millisPerPixel = options.millisPerPixel || 20;
  120 + options.fps = options.fps || 20;
  121 + options.maxValueScale = options.maxValueScale || 1;
  122 + options.minValue = options.minValue;
  123 + options.labels = options.labels || { fillStyle:'#ffffff' }
  124 + this.options = options;
  125 + this.seriesSet = [];
  126 +}
  127 +
  128 +SmoothieChart.prototype.addTimeSeries = function(timeSeries, options) {
  129 + this.seriesSet.push({timeSeries: timeSeries, options: options || {}});
  130 +};
  131 +
  132 +SmoothieChart.prototype.removeTimeSeries = function(timeSeries) {
  133 + this.seriesSet.splice(this.seriesSet.indexOf(timeSeries), 1);
  134 +};
  135 +
  136 +SmoothieChart.prototype.streamTo = function(canvas, delay) {
  137 + var self = this;
  138 + (function render() {
  139 + self.render(canvas, new Date().getTime() - (delay || 0));
  140 + setTimeout(render, 1000/self.options.fps);
  141 + })()
  142 +};
  143 +
  144 +SmoothieChart.prototype.render = function(canvas, time) {
  145 + var canvasContext = canvas.getContext("2d");
  146 + var options = this.options;
  147 + var dimensions = {top: 0, left: 0, width: canvas.clientWidth, height: canvas.clientHeight};
  148 +
  149 + // Save the state of the canvas context, any transformations applied in this method
  150 + // will get removed from the stack at the end of this method when .restore() is called.
  151 + canvasContext.save();
  152 +
  153 + // Round time down to pixel granularity, so motion appears smoother.
  154 + time = time - time % options.millisPerPixel;
  155 +
  156 + // Move the origin.
  157 + canvasContext.translate(dimensions.left, dimensions.top);
  158 +
  159 + // Create a clipped rectangle - anything we draw will be constrained to this rectangle.
  160 + // This prevents the occasional pixels from curves near the edges overrunning and creating
  161 + // screen cheese (that phrase should neeed no explanation).
  162 + canvasContext.beginPath();
  163 + canvasContext.rect(0, 0, dimensions.width, dimensions.height);
  164 + canvasContext.clip();
  165 +
  166 + // Clear the working area.
  167 + canvasContext.save();
  168 + canvasContext.fillStyle = options.grid.fillStyle;
  169 + canvasContext.fillRect(0, 0, dimensions.width, dimensions.height);
  170 + canvasContext.restore();
  171 +
  172 + // Grid lines....
  173 + canvasContext.save();
  174 + canvasContext.lineWidth = options.grid.lineWidth || 1;
  175 + canvasContext.strokeStyle = options.grid.strokeStyle || '#ffffff';
  176 + // Vertical (time) dividers.
  177 + if (options.grid.millisPerLine > 0) {
  178 + for (var t = time - (time % options.grid.millisPerLine); t >= time - (dimensions.width * options.millisPerPixel); t -= options.grid.millisPerLine) {
  179 + canvasContext.beginPath();
  180 + var gx = Math.round(dimensions.width - ((time - t) / options.millisPerPixel));
  181 + canvasContext.moveTo(gx, 0);
  182 + canvasContext.lineTo(gx, dimensions.height);
  183 + canvasContext.stroke();
  184 + canvasContext.closePath();
  185 + }
  186 + }
  187 +
  188 + // Horizontal (value) dividers.
  189 + for (var v = 1; v < options.grid.verticalSections; v++) {
  190 + var gy = Math.round(v * dimensions.height / options.grid.verticalSections);
  191 + canvasContext.beginPath();
  192 + canvasContext.moveTo(0, gy);
  193 + canvasContext.lineTo(dimensions.width, gy);
  194 + canvasContext.stroke();
  195 + canvasContext.closePath();
  196 + }
  197 + // Bounding rectangle.
  198 + canvasContext.beginPath();
  199 + canvasContext.strokeRect(0, 0, dimensions.width, dimensions.height);
  200 + canvasContext.closePath();
  201 + canvasContext.restore();
  202 +
  203 + // Calculate the current scale of the chart, from all time series.
  204 + var maxValue = Number.NaN;
  205 + var minValue = Number.NaN;
  206 +
  207 + for (var d = 0; d < this.seriesSet.length; d++) {
  208 + // TODO(ndunn): We could calculate / track these values as they stream in.
  209 + var timeSeries = this.seriesSet[d].timeSeries;
  210 + if (!isNaN(timeSeries.maxValue)) {
  211 + maxValue = !isNaN(maxValue) ? Math.max(maxValue, timeSeries.maxValue) : timeSeries.maxValue;
  212 + }
  213 +
  214 + if (!isNaN(timeSeries.minValue)) {
  215 + minValue = !isNaN(minValue) ? Math.min(minValue, timeSeries.minValue) : timeSeries.minValue;
  216 + }
  217 + }
  218 +
  219 + if (isNaN(maxValue) && isNaN(minValue)) {
  220 + return;
  221 + }
  222 +
  223 + // Scale the maxValue to add padding at the top if required
  224 + maxValue = maxValue * options.maxValueScale;
  225 + // Set the minimum if we've specified one
  226 + if (options.minValue != null)
  227 + minValue = options.minValue;
  228 + var valueRange = maxValue - minValue;
  229 +
  230 + // For each data set...
  231 + for (var d = 0; d < this.seriesSet.length; d++) {
  232 + canvasContext.save();
  233 + var timeSeries = this.seriesSet[d].timeSeries;
  234 + var dataSet = timeSeries.data;
  235 + var seriesOptions = this.seriesSet[d].options;
  236 +
  237 + // Delete old data that's moved off the left of the chart.
  238 + // We must always keep the last expired data point as we need this to draw the
  239 + // line that comes into the chart, but any points prior to that can be removed.
  240 + while (dataSet.length >= 2 && dataSet[1][0] < time - (dimensions.width * options.millisPerPixel)) {
  241 + dataSet.splice(0, 1);
  242 + }
  243 +
  244 + // Set style for this dataSet.
  245 + canvasContext.lineWidth = seriesOptions.lineWidth || 1;
  246 + canvasContext.fillStyle = seriesOptions.fillStyle;
  247 + canvasContext.strokeStyle = seriesOptions.strokeStyle || '#ffffff';
  248 + // Draw the line...
  249 + canvasContext.beginPath();
  250 + // Retain lastX, lastY for calculating the control points of bezier curves.
  251 + var firstX = 0, lastX = 0, lastY = 0;
  252 + for (var i = 0; i < dataSet.length; i++) {
  253 + // TODO: Deal with dataSet.length < 2.
  254 + var x = Math.round(dimensions.width - ((time - dataSet[i][0]) / options.millisPerPixel));
  255 + var value = dataSet[i][1];
  256 + var offset = maxValue - value;
  257 + var scaledValue = valueRange ? Math.round((offset / valueRange) * dimensions.height) : 0;
  258 + var y = Math.max(Math.min(scaledValue, dimensions.height - 1), 1); // Ensure line is always on chart.
  259 +
  260 + if (i == 0) {
  261 + firstX = x;
  262 + canvasContext.moveTo(x, y);
  263 + }
  264 + // Great explanation of Bezier curves: http://en.wikipedia.org/wiki/B�zier_curve#Quadratic_curves
  265 + //
  266 + // Assuming A was the last point in the line plotted and B is the new point,
  267 + // we draw a curve with control points P and Q as below.
  268 + //
  269 + // A---P
  270 + // |
  271 + // |
  272 + // |
  273 + // Q---B
  274 + //
  275 + // Importantly, A and P are at the same y coordinate, as are B and Q. This is
  276 + // so adjacent curves appear to flow as one.
  277 + //
  278 + else {
  279 + canvasContext.bezierCurveTo( // startPoint (A) is implicit from last iteration of loop
  280 + Math.round((lastX + x) / 2), lastY, // controlPoint1 (P)
  281 + Math.round((lastX + x)) / 2, y, // controlPoint2 (Q)
  282 + x, y); // endPoint (B)
  283 + }
  284 +
  285 + lastX = x, lastY = y;
  286 + }
  287 + if (dataSet.length > 0 && seriesOptions.fillStyle) {
  288 + // Close up the fill region.
  289 + canvasContext.lineTo(dimensions.width + seriesOptions.lineWidth + 1, lastY);
  290 + canvasContext.lineTo(dimensions.width + seriesOptions.lineWidth + 1, dimensions.height + seriesOptions.lineWidth + 1);
  291 + canvasContext.lineTo(firstX, dimensions.height + seriesOptions.lineWidth);
  292 + canvasContext.fill();
  293 + }
  294 + canvasContext.stroke();
  295 + canvasContext.closePath();
  296 + canvasContext.restore();
  297 + }
  298 +
  299 + // Draw the axis values on the chart.
  300 + if (!options.labels.disabled) {
  301 + canvasContext.fillStyle = options.labels.fillStyle;
  302 + var maxValueString = maxValue.toFixed(2);
  303 + var minValueString = minValue.toFixed(2);
  304 + canvasContext.fillText(maxValueString, dimensions.width - canvasContext.measureText(maxValueString).width - 2, 10);
  305 + canvasContext.fillText(minValueString, dimensions.width - canvasContext.measureText(minValueString).width - 2, dimensions.height - 2);
  306 + }
  307 +
  308 + canvasContext.restore(); // See .save() above.
  309 +}
48 priv/www/cadamgui/source/class/cadamgui/WebSocket.js
... ... @@ -0,0 +1,48 @@
  1 +qx.Class.define("cadamgui.WebSocket",
  2 +{
  3 + extend : qx.core.Object,
  4 +
  5 + properties:
  6 + {
  7 + ws : {nullable : true}
  8 + },
  9 +
  10 + members :
  11 + {
  12 +// ws : {},
  13 +
  14 + _onopen: function(){
  15 + this.parent.debug('websocket connecting');
  16 + this.send('client-connected');
  17 + },
  18 + _onclose: function(m) {
  19 + this.parent.debug('websocket closing');
  20 + //this._ws=null;
  21 + },
  22 + _send: function(msg) {
  23 + this.send(msg);
  24 + },
  25 + send: function(msg){
  26 + if (this.getWs()) this.getWs().send(msg);
  27 + },
  28 +
  29 + // example endpoint: "ws://localhost:9000/metrics_ws_server_connector.yaws"
  30 + connect: function(endpoint){
  31 + this.ws = new WebSocket(endpoint);
  32 + this.ws.onopen = this._onopen;
  33 + this.ws.onmessage = this.onmessage;
  34 + this.ws.onclose = this._onclose;
  35 + this.ws.parent = this;
  36 + },
  37 +// sendMsg: function(message){
  38 +// this._ws.send(message);
  39 +// if (this._ws) this._ws.send(message);
  40 +// },
  41 + onmessage: function(m) {
  42 + if (m.data){
  43 + var text = m.data;
  44 + this.parent.debug('got data:'+text);
  45 + }
  46 + }
  47 + }
  48 +});
59 priv/www/cadamgui/source/class/cadamgui/WebSocket2.js
... ... @@ -0,0 +1,59 @@
  1 +qx.Class.define("cadamgui.WebSocket2",
  2 +{
  3 + extend : qx.core.Object,
  4 +
  5 + events :
  6 + {
  7 + "socket-ready" : "qx.event.type.Data"
  8 + },
  9 +
  10 + members :
  11 + {
  12 + _wsock : {},
  13 + _connected : false,
  14 +
  15 + _onopen: function(){
  16 + console.log('websocket connecting');
  17 + this.send('client-connected');
  18 + this.parent._connected = true;
  19 + this.parent.fireDataEvent("socket-ready", true);
  20 + },
  21 +
  22 + _onclose: function(m) {
  23 + console.log('websocket closing');
  24 + this._wsock = null;
  25 + this._connected = false;
  26 + },
  27 +
  28 + sendTxt: function(msg){
  29 + if (this._connected) {
  30 + console.log('sending:'+msg);
  31 + this._wsock.send(msg);
  32 + }
  33 + },
  34 +
  35 + sendJSON: function(msg){
  36 + if (this._connected) {
  37 + var msgString = qx.util.Json.stringify(msg);
  38 + console.log('sending:'+msgString);