diff options
Diffstat (limited to 'qx08')
-rw-r--r-- | qx08/Manifest.json | 34 | ||||
-rw-r--r-- | qx08/config.json | 20 | ||||
-rwxr-xr-x | qx08/generate.py | 42 | ||||
-rw-r--r-- | qx08/readme.txt | 9 | ||||
-rw-r--r-- | qx08/source/class/tr/Application.js | 84 | ||||
-rw-r--r-- | qx08/source/class/tr/Server.js | 72 | ||||
-rw-r--r-- | qx08/source/class/tr/test/DemoTest.js | 55 | ||||
-rw-r--r-- | qx08/source/class/tr/ui/ActionButton.js | 196 | ||||
-rw-r--r-- | qx08/source/class/tr/ui/Cellrenderer.js | 46 | ||||
-rw-r--r-- | qx08/source/class/tr/ui/Footer.js | 45 | ||||
-rw-r--r-- | qx08/source/class/tr/ui/TraceTable.js | 214 | ||||
-rw-r--r-- | qx08/source/index.html | 8 | ||||
-rw-r--r-- | qx08/source/resource/tr/test.png | bin | 0 -> 2478 bytes |
13 files changed, 825 insertions, 0 deletions
diff --git a/qx08/Manifest.json b/qx08/Manifest.json new file mode 100644 index 0000000..dc58c7f --- /dev/null +++ b/qx08/Manifest.json @@ -0,0 +1,34 @@ +{ + "info" : + { + "name" : "qx08", + + "summary" : "Custom Application", + "description" : "This is a skeleton for a custom application with qooxdoo.", + + "homepage" : "http://some.homepage.url/", + + "license" : "SomeLicense", + "authors" : + [ + { + "name" : "First Author (uid)", + "email" : "first.author@some.domain" + } + ], + + "version" : "trunk", + "qooxdoo-versions": ["0.8"] + }, + + "provides" : + { + "namespace" : "tr", + "encoding" : "utf-8", + "class" : "source/class", + "resource" : "source/resource", + "translation" : "source/translation", + "type" : "application" + } +} + diff --git a/qx08/config.json b/qx08/config.json new file mode 100644 index 0000000..4bc2a4c --- /dev/null +++ b/qx08/config.json @@ -0,0 +1,20 @@ +{ + "name" : "qx08", + + "include" : + [ + { + "path" : "../../../../../../../usr/pack/qooxdoo-0.8svn-to/frontend/tool/data/config/application.json" + } + ], + + "let" : + { + "APPLICATION" : "tr", + "QOOXDOO_PATH" : "../../../../../../../usr/pack/qooxdoo-0.8svn-to/frontend/framework", + "QOOXDOO_URI" : "../${QOOXDOO_PATH}", + "QXTHEME" : "qx.theme.Modern", + "LOCALES" : [ "en" ], + "ROOT" : "." + } +} diff --git a/qx08/generate.py b/qx08/generate.py new file mode 100755 index 0000000..5dfb446 --- /dev/null +++ b/qx08/generate.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +################################################################################ +# +# qooxdoo - the new era of web development +# +# http://qooxdoo.org +# +# Copyright: +# 2008 1&1 Internet AG, Germany, http://www.1und1.de +# +# License: +# LGPL: http://www.gnu.org/licenses/lgpl.html +# EPL: http://www.eclipse.org/org/documents/epl-v10.php +# See the LICENSE file in the project's top-level directory for details. +# +# Authors: +# * Thomas Herchenroeder (thron7) +# +################################################################################ + +## +# This is a stub proxy for the real generator.py +## + +import sys, os, subprocess + +CMD_PYTHON = 'python' +QOOXDOO_PATH = '../../../../../../../usr/pack/qooxdoo-0.8svn-to/frontend' +REAL_GENERATOR = os.path.normpath( + os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), + QOOXDOO_PATH, 'tool', 'bin', 'generator.py')) + +os.chdir(os.path.dirname(sys.argv[0])) # switch to skeleton dir + +argList = [] +argList.append(CMD_PYTHON) +argList.append(REAL_GENERATOR) +argList.extend(sys.argv[1:]) +if sys.platform != "win32": + argList = ['"%s"' % x for x in argList] # quote argv elements +cmd = " ".join(argList) +subprocess.call(cmd, shell=True) diff --git a/qx08/readme.txt b/qx08/readme.txt new file mode 100644 index 0000000..74e2f0a --- /dev/null +++ b/qx08/readme.txt @@ -0,0 +1,9 @@ +Skeleton - A qooxdoo Application Template +========================================= + +This is a qooxdoo application skeleton which is used as a template. The +'create-application.py' script (usually under tool/bin/create-application.py) +will use this and expand it into a self-contained qooxdoo application which +can then be further extended. Please refer to the script and other documentation +for further information. + diff --git a/qx08/source/class/tr/Application.js b/qx08/source/class/tr/Application.js new file mode 100644 index 0000000..7113b14 --- /dev/null +++ b/qx08/source/class/tr/Application.js @@ -0,0 +1,84 @@ +/* ************************************************************************ + + Copyright: OETIKER+PARTNER AG + + License: Gnu GPL Verrsion 3 + + Authors: Tobias Oetiker <tobi@oetiker.ch> + +************************************************************************ */ + +/* ************************************************************************ + +#asset(tr/*) + +************************************************************************ */ + +/** + * This is the main application class of your custom application "qx08" + */ +qx.Class.define("tr.Application", +{ + extend : qx.application.Standalone, + + /* + ***************************************************************************** + MEMBERS + ***************************************************************************** + */ + + members : + { + /** + * This method contains the initial application code and gets called + * during startup of the application + */ + main : function() + { + var self=this; + // Call super class + this.base(arguments); + + // Enable logging in debug variant + if (qx.core.Variant.isSet("qx.debug", "on")) + { + // support native logging capabilities, e.g. Firebug for Firefox + qx.log.appender.Native; + // support additional cross-browser console. Press F7 to toggle visibility + qx.log.appender.Console; + } + + /* + ------------------------------------------------------------------------- + Below is your actual application code... + ------------------------------------------------------------------------- + */ + + + // if we run with a file:// url make sure + // the app finds the Tr service (Tr.cgi) + Tr.Server.getInstance().setLocalUrl( + 'http://johan.oetiker.ch/~oetiker/tr/' + ); + var root=this.getRoot(); + // Document is the application root + var root = new qx.ui.container.Composite(new qx.ui.layout.VBox()); + this.getRoot().add(root, { left : 0, top: 0}); + + + + var top = new qx.ui.container.Composite(new qx.ui.layout.HBox()); + var title = new qx.ui.basic.Atom('SmokeTrace 2.4.2'); + with(title){ + setTextColor('#b0b0b0'); + setFont(qx.bom.Font.fromString('20px bold sans-serif')); + } + top.add(title); + top.add(new qx.ui.basic.HorizontalSpacer()); + top.add(new Tr.ui.ActionButton()); + root.add(top); + var trace = new Tr.ui.TraceTable(); + root.add(trace); + root.add(new Tr.ui.Footer(this.tr("SmokeTrace is part of the of the SmokePing suite created by Tobi Oetiker, Copyright 2008."),'http://oss.oetiker.ch/smokeping')); + } +}); diff --git a/qx08/source/class/tr/Server.js b/qx08/source/class/tr/Server.js new file mode 100644 index 0000000..89496f1 --- /dev/null +++ b/qx08/source/class/tr/Server.js @@ -0,0 +1,72 @@ +/* ************************************************************************ +#module(Tr) +************************************************************************ */ + +/** + * A Tr specific rpc call which works + */ + +qx.Class.define('Tr.Server', { + extend: qx.io.remote.Rpc, + type: "singleton", + + /* + ***************************************************************************** + CONSTRUCTOR + ***************************************************************************** + */ + + /** + * @param local_url {String} When running the application in file:// mode. + * where will we find our RPC server. + */ + construct: function (local_url) { + this.base(arguments); + this.set({ + timeout: 7000000, + url: 'tr.cgi', + serviceName: 'Tr', + crossDomain: true + }); + return this; + }, + + /* + ***************************************************************************** + MEMBERS + ***************************************************************************** + */ + + members : + { + + /* + --------------------------------------------------------------------------- + CORE METHODS + --------------------------------------------------------------------------- + */ + + /** + * Tell about the BaseUrl we found. + * + * @type member + * + * @param {void} + * + * @return BaseUrl {Strings} + */ + + getBaseUrl: function(){ + return this.__base_url; + }, + + setLocalUrl: function(local_url){ + if ( document.location.host === '' ) { + with(this){ + setUrl(local_url+'tr.cgi'); + } + } + } + + } +}); diff --git a/qx08/source/class/tr/test/DemoTest.js b/qx08/source/class/tr/test/DemoTest.js new file mode 100644 index 0000000..76ce685 --- /dev/null +++ b/qx08/source/class/tr/test/DemoTest.js @@ -0,0 +1,55 @@ +/* ************************************************************************ + + Copyright: + + License: + + Authors: + +************************************************************************ */ + +/** + * This class demonstrates how to define unit tests for your application. + * + * Execute <code>generate.py test</code> to generate a testrunner application + * and open it from <tt>test/index.html</tt> + * + * The methods that contain the tests are instance methods with a + * <code>test</code> prefix. You can create an arbitrary number of test + * classes like this one. They can be organized in a regular class hierarchy, + * i.e. using deeper namespaces and a corresponding file structure within the + * <tt>test</tt> folder. + */ +qx.Class.define("tr.test.DemoTest", +{ + extend : qx.dev.unit.TestCase, + + members : + { + /* + --------------------------------------------------------------------------- + TESTS + --------------------------------------------------------------------------- + */ + + /** + * Here are some simple tests + */ + testSimple : function() + { + this.assertEquals(4, 3+1, "This should never fail!"); + this.assertFalse(false, "Can false be true?!"); + }, + + /** + * Here are some more advanced tests + */ + testAdvanced: function () + { + var a = 3; + var b = a; + this.assertIdentical(a, b, "A rose by any other name is still a rose"); + this.assertInRange(3, 1, 10, "You must be kidding, 3 can never be outside [1,10]!"); + } + } +}); diff --git a/qx08/source/class/tr/ui/ActionButton.js b/qx08/source/class/tr/ui/ActionButton.js new file mode 100644 index 0000000..5d6eb5d --- /dev/null +++ b/qx08/source/class/tr/ui/ActionButton.js @@ -0,0 +1,196 @@ +/* ************************************************************************ +#module(Tr) +************************************************************************ */ + +/** + * a widget showing the Tr graph overview + */ + +qx.Class.define('Tr.ui.ActionButton', +{ + extend: qx.ui.Containter. + + /* + ***************************************************************************** + CONSTRUCTOR + ***************************************************************************** + */ + + construct: function () { + this.base(arguments); + + this.set({ + height: 'auto', + width: 'auto', + horizontalChildrenAlign: 'left' + }); + var hbox = new qx.ui.layout.HorizontalBoxLayout; + hbox.set({ + height: 'auto', + width: 'auto', + verticalChildrenAlign: 'middle' + }); + var lab1 = new qx.ui.basic.Label(this.tr("Host")); + lab1.set({ + paddingRight: 6 + }); + hbox.add(lab1); + var host = new qx.ui.form.TextField(); + host.set({ + width: 200, + height: 'auto', + border: 'dark-shadow', + padding: 1 + }); + hbox.add(host); + this.__host = host; + var lab2 = new qx.ui.basic.Label(this.tr("Delay")); + lab2.set({ + paddingRight: 6, + paddingLeft: 12 + }); + hbox.add(lab2); + var delay = new qx.ui.form.Spinner(1,2,60); + delay.set({ + border: 'dark-shadow', + width: 45 + }); + hbox.add(delay); + this.__delay = delay; + + var lab3 = new qx.ui.basic.Label(this.tr("Rounds")); + lab3.set({ + paddingRight: 6, + paddingLeft: 12 + }); + hbox.add(lab3); + var rounds = new qx.ui.form.Spinner(1,20,200); + rounds.set({ + border: 'dark-shadow', + width: 45 + }); + hbox.add(rounds); + this.__rounds = rounds; + + var button = new qx.ui.form.Button(''); + this.__button = button; + button.set({ + marginLeft: 10, + width: 60, + height: 'auto', + border: 'dark-shadow', + padding: 2 + }); + hbox.add(button); + this.add(hbox); + var info = new qx.ui.basic.Atom(); + info.set({ + marginTop: 3, + padding: 3, + textColor: 'red', + width: '100%', + height: 'auto', + backgroundColor: '#f0f0f0', + visibility: false + }); + qx.event.message.Bus.subscribe('tr.info',this.__set_info,this); + this.add(info); + this.__info = info; + + qx.event.message.Bus.subscribe('tr.status',this.__set_status,this); + qx.event.message.Bus.dispatch('tr.status','stopped'); + + var start_trace = function(event) { + qx.event.message.Bus.dispatch('tr.cmd',{ + action: button.getUserData('action'), + host: host.getValue(), + delay: delay.getValue(), + rounds: rounds.getValue() + }); + }; + +// var self=this; +// host.addEventListener('keyup',function(e){if(e.getKeyIdentifier() == 'Enter'){start_trace()}}); + host.addEventListener('execute',start_trace); + button.addEventListener('execute', start_trace ); + + var history = qx.client.History.getInstance(); + var history_action = function(event){ + var targ = event.getData(); + host.setValue(targ); + history.addToHistory(targ,'SmokeTrace to '+targ); + start_trace(); + } + history.addEventListener('request', history_action); + + // if we got called with a host on the commandline + var initial_host = qx.client.History.getInstance().getState(); + if (initial_host){ + host.setValue(initial_host); + history.addToHistory(initial_host,'SmokeTrace to '+initial_host); + // dispatch this task once all the initializations are done + qx.client.Timer.once(start_trace,this,0); + } + }, + + members: { + __set_info: function(e){ + this.__info.set({ + label: e.getData(), + visibility: true + }); + }, + __set_status: function(m){ + var host = this.__host; + var rounds = this.__rounds; + var delay = this.__delay; + with(this.__button){ + // this.debug(m.getData()); + switch(m.getData()){ + case 'starting': + if (getUserData('action') == 'go') { + setLabel(this.tr("Starting")); + this.__info.setVisibility(false); + border: 'dark-shadow' + setEnabled(false); + host.setEnabled(false); + rounds.setEnabled(false); + delay.setEnabled(false); + } + break; + case 'stopping': + if (getUserData('action') == 'stop') { + setLabel(this.tr("Stopping")); + setEnabled(false); + host.setEnabled(false); + rounds.setEnabled(false); + delay.setEnabled(false); + } + break; + case 'stopped': + setUserData('action','go'); + setLabel(this.tr("Go")); + setEnabled(true); + host.setEnabled(true); + rounds.setEnabled(true); + delay.setEnabled(true); + break; + case 'started': + setUserData('action','stop'); + setLabel(this.tr("Stop")); + setEnabled(true); + host.setEnabled(false); + rounds.setEnabled(false); + delay.setEnabled(false); + break; + default: + alert('Unknown Status Message: '+m.getData()); + } + } + } + } + + +}); + + diff --git a/qx08/source/class/tr/ui/Cellrenderer.js b/qx08/source/class/tr/ui/Cellrenderer.js new file mode 100644 index 0000000..f18672d --- /dev/null +++ b/qx08/source/class/tr/ui/Cellrenderer.js @@ -0,0 +1,46 @@ +/* ************************************************************************ + + Tr Frontend + + Author: + * Tobias Oetiker + +************************************************************************ */ +/* ************************************************************************ +#module(Tr) +************************************************************************ */ + +/** + * A configurable cell renderre + */ + +qx.Class.define('Tr.ui.Cellrenderer', +{ + extend: qx.ui.table.cellrenderer.Number, + /** + * Format a number with a configurable number of fraction digits + * and add optional pre and postfix. + * @param digits {Integer} how many digits should there be. Default is 0. + * @param prefix {String} optional prefix. + * @param postfix {String} optional postfix. + */ + + construct: function (digits,postfix,prefix) { + if (digits == undefined){ + digits = 0; + } + this.base(arguments) + var format = new qx.util.format.NumberFormat(); + format.set({ + maximumFractionDigits: digits, + minimumFractionDigits: digits + }); + if (postfix != undefined){ + format.setPostfix(postfix); + } + if (prefix != undefined){ + format.setPrefix(prefix); + } + this.setNumberFormat(format); + } +}); diff --git a/qx08/source/class/tr/ui/Footer.js b/qx08/source/class/tr/ui/Footer.js new file mode 100644 index 0000000..1882043 --- /dev/null +++ b/qx08/source/class/tr/ui/Footer.js @@ -0,0 +1,45 @@ +/* ************************************************************************ +#module(Tr) +************************************************************************ */ + +/** + * a widget showing the footer + */ + +qx.Class.define('Tr.ui.Footer', +{ + extend: qx.ui.layout.HorizontalBoxLayout, + + /* + ***************************************************************************** + CONSTRUCTOR + ***************************************************************************** + */ + + construct: function (text,url) { + this.base(arguments); + this.set({ + horizontalChildrenAlign: 'right', + height: 'auto' + }); + var logo = new qx.ui.form.Button(text); + logo.set({ + textColor: '#b0b0b0', + backgroundColor: null, + font: qx.ui.core.Font.fromString('10px sans-serif'), + border: null + }); + + logo.addEventListener('execute', function(e){ + var w = new qx.client.NativeWindow(url); + w.set({ + width: 1000, + height: 800 + }); + w.open() + }); + this.add(logo); + } +}); + + diff --git a/qx08/source/class/tr/ui/TraceTable.js b/qx08/source/class/tr/ui/TraceTable.js new file mode 100644 index 0000000..446cbad --- /dev/null +++ b/qx08/source/class/tr/ui/TraceTable.js @@ -0,0 +1,214 @@ +/* ************************************************************************ +#module(Tr) +************************************************************************ */ + +/** + * a widget showing the Tr target tree + */ + +qx.Class.define('Tr.ui.TraceTable', +{ + extend: qx.ui.table.Table, + + /* + ***************************************************************************** + CONSTRUCTOR + ***************************************************************************** + */ + + + construct: function () { + + var tableModel = new qx.ui.table.model.Simple(); + this.__tableModel = tableModel; + tableModel.setColumns([ this.tr("Hop"), this.tr("Host"),this.tr("Ip"), + this.tr("Loss [%]"), this.tr("Sent"), this.tr("Last [ms]"), //"; help syntax highliter + this.tr("Avg [ms]"), this.tr("Best [ms]"), this.tr("Worst [ms]"), this.tr("StDev [ms]") ]); + var custom = { + tableColumnModel: function(obj) { + return new qx.ui.table.columnmodel.Resize(obj); + } + }; + with(this){ + base(arguments,tableModel,custom); + set({ + width: '100%', + height: '1*', + border: 'dark-shadow', + showCellFocusIndicator: false, + statusBarVisible: false + }); + }; + var tcm = this.getTableColumnModel(); + this.__tcm = tcm; + + tcm.setDataCellRenderer(0, new Tr.ui.Cellrenderer(1)); + tcm.setDataCellRenderer(3, new Tr.ui.Cellrenderer(0,' %')); + tcm.setDataCellRenderer(4, new Tr.ui.Cellrenderer(0)); + + var render_ms = new Tr.ui.Cellrenderer(1); + + for (var i=5;i<10;i++){ + tcm.setDataCellRenderer(i, render_ms); + } + + + // Obtain the behavior object to manipulate + var resizeBehavior = tcm.getBehavior(); + // This uses the set() method to set all attriutes at once; uses flex + resizeBehavior.set(0, { width:"2*"}); + resizeBehavior.set(1, { width:"9*"}); + resizeBehavior.set(2, { width:"5*"}); + + for (var i=3;i<10;i++){ + resizeBehavior.set(i, { width:"3*"}); + } + qx.event.message.Bus.subscribe('tr.cmd',this.__handle_tr,this); + }, + + /* + ***************************************************************************** + Statics + ***************************************************************************** + */ + members: { + __make_empty_row: function (){ + return ([undefined,undefined,undefined,0,0,undefined,undefined,undefined,undefined,undefined,0,0,0]); + }, + __stop_table: function (){ + var tableModel = this.__tableModel; + for (var i=0;i<10;i++){ + tableModel.setColumnSortable(i,true); + } + qx.event.message.Bus.dispatch('tr.status','stopped'); + this.__handle = undefined; + }, + __handle_tr: function(m){ + var self = this; + var f_hop = 0,f_host=1,f_ip=2,f_loss=3,f_snt=4,f_last=5,f_avg=6,f_best=7,f_worst=8,f_stdev=9,f_cnt=10,f_sum=11,f_sqsum=12; + var fill_table; + fill_table = function(retval,exc,id){ + if (exc == null){ + if ( self.__handle == undefined ) { + qx.event.message.Bus.dispatch('tr.status','started'); + } + self.__handle = retval['handle']; + var tableModel = self.__tableModel; + var lines = retval['output'].length; + var data = self.__data; + var sleep = 0; + for(var i=0;i<lines;i++){ + sleep = 0; + var hop = retval['output'][i][0]; + if (hop == 'SLEEP'){ + sleep = retval['output'][i][1]; + continue; + } + else if (hop == 'INFO'){ + qx.event.message.Bus.dispatch('tr.info',retval['output'][i][1]); + continue; + } + var host = retval['output'][i][1]; + var ip = retval['output'][i][2]; + var value = retval['output'][i][3]; + var ii = 0; + var max = data.length; + while (true){ + if ( ii == max ) break; + if ( Math.floor(data[ii][0]) > hop) break; + if ( Math.floor(data[ii][0]) == hop ){ + if ( ip == undefined ) break; + if ( ip == data[ii][2] ) break; + } + ii++; + } + if (ii == max || Math.floor(data[ii][0]) > hop ){ + if (ii > 0 && Math.floor(data[ii-1][0]) == hop ){ + hop = data[ii-1][0] + 0.1; + } + data.splice(ii,0,self.__make_empty_row()); + data[ii][0] = hop; + } + + var drow = data[ii]; + if (drow[f_host] == undefined && host != undefined){ + drow[f_host] = host; + } + if (drow[f_ip] == undefined && ip != undefined){ + drow[f_ip] = ip; + } + drow[f_snt]++; + drow[f_last] = value; + + + if (value != undefined){ + var best = drow[f_best]; + if (best == undefined || best > value){ + drow[f_best] = value; + } + var worst = drow[f_worst]; + if (worst == undefined || worst < value){ + drow[f_worst] = value; + } + drow[f_sum] += value; + var sum = drow[f_sum]; + drow[f_cnt] ++; + var cnt = drow[f_cnt]; + var sqsum = drow[f_sqsum]+value*value; + drow[f_sqsum] = sqsum; + drow[f_avg] = drow[f_sum]/drow[f_cnt]; + drow[f_stdev] = Math.sqrt((cnt*sqsum-sum*sum)/(cnt*(cnt-1))) + } + drow[f_loss] = ((drow[f_snt]-drow[f_cnt])/drow[f_snt])*100; + } + + tableModel.setData(data); + if (retval['again']){ + var next_round = function (){Tr.Server.getInstance().callAsync( + fill_table,'run_tr',{ handle: retval['handle'], + point: retval['point']})}; + qx.client.Timer.once(next_round,self,sleep*1000); + } + else { + self.__stop_table(); + } + } + else { + alert(exc); + self.__stop_table(); + } + }; + + var stop_handler = function (data,exc,id){ + if (exc == null){ + qx.event.message.Bus.dispatch('tr.status','stopped'); + } + else { + alert(exc); + } + }; + + var cmd = m.getData(); + switch(cmd['action']){ + case 'stop': + qx.event.message.Bus.dispatch('tr.status','stopping'); + Tr.Server.getInstance().callAsync(stop_handler,'stop_tr',this.__handle); + break; + case 'go': + this.__data = []; + this.__tableModel.setData(this.__data); + this.__delay = cmd['delay']; + for (var i=0;i<10;i++){ + this.__tableModel.setColumnSortable(i,false); + } + qx.event.message.Bus.dispatch('tr.status','starting'); + Tr.Server.getInstance().callAsync(fill_table,'run_tr',{host: cmd['host'], rounds: cmd['rounds'], delay: cmd['delay']}); + break; + default: + alert('Unknown Command '+cmd['action']); + } + } + } +}); + + diff --git a/qx08/source/index.html b/qx08/source/index.html new file mode 100644 index 0000000..c935564 --- /dev/null +++ b/qx08/source/index.html @@ -0,0 +1,8 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <title>qx08</title> + <script type="text/javascript" src="script/tr.js"></script> +</head> +</html> diff --git a/qx08/source/resource/tr/test.png b/qx08/source/resource/tr/test.png Binary files differnew file mode 100644 index 0000000..ef360cd --- /dev/null +++ b/qx08/source/resource/tr/test.png |