Gox语言——支持跨平台原生GUI开发的轻量级全功能脚本语言 - GX1

Gox语言(官网)是以Go语言(Golang)为基础的解释型/脚本语言,它除了具有一般脚本语言所具有的编写快捷、语言简洁、易于理解等特点外,还支持其他语言所不具备的跨平台原生图形界面(GUI)开发,并且代码写起来非常舒畅。文档参看这里。循序渐进学习Gox语言编程请参看这里Gox语言入门

用Gox语言编程

Gox语言的主要特点包括:

  • 跨平台,目前支持Windows、Mac和Linux等主流平台;
  • 完全免费和开源,使用MIT授权协议;
  • 代码基于Go语言(Golang),但做了一些优化,因此Go语言、C语言、C++、C#、Java及类似语言的开发者编写起来几乎没有任何压力,稍加了解就可以开始编写代码;
  • 相较于Go语言严格的语法书写要求和静态数据类型限制,Gox语言实现了动态类型,并做了许多更接近主流高级语言的改进,去除了一些语法限制,使得代码书写上方便了很多;
  • 基本支持所有Go语言主要的标准库,并加以扩充,并且理论上可以支持任意多的第三方扩展库,依托Go语言社区已有的海量代码库,Gox语言具备成为全栈语言的潜力;
  • 提供大量内置高效的网络服务、数据库访问等常用编程能力,可以直接提供完整的Web服务器、API服务器,免驱动访问SQLite、MySQL、SQL Server等数据库,也可以操作Oracle数据库;
  • 具备嵌套执行脚本的能力,支持模块化编程,支持比Go语言更方便的面向对象编程;
  • 与一般的解释性/脚本语言不同,Gox语言自带代码加密功能,支持对发布的代码进行加密,以及对加密代码的解密执行功能,可以有效地保护开发人员的工作;
  • 支持各操作系统下的编译,可以单文件发布最终应用程序,同时起到代码保护作用;
  • 语法基于Qlang这种与Go语言能够紧密互操作的语言,天然继承了很多Go语言的优秀特性,例如Go语言特色的并发线程goroutine和chan等;
  • 多脚本之间可以共享全局数据;
  • 支持跨平台的原生图形界面(GUI)的开发,并且界面布局代码书写简单易懂,使用通用的HTML/CSS/Script的形式制作界面;
  • 绿色,无安装文件或安装包,无需安装和任何环境配置过程,没有任何依赖,只需下载一个可执行文件,即可实现所有Gox代码开发和程序执行的任务(注:如果使用Sciter编写界面也只需要附带一个动态链接库文件即可,Windows版的压缩包中已经自带);
  • 经过实际项目检验,已经应用在很多中小型项目中,可用性与稳定性经受了足够的考验;
  • 更为神奇的是,这一个主程序文件竟然能够同时支持命令行模式的开发和图形界面(GUI)的开发,并且还自带交互式编程环境(REPL),甚至还内置了一个图形化的、支持代码高亮和语法检查的代码编辑器!
Gox语言内置的代码编辑器

下面用一个例子来介绍一下用Gox编写代码的方便程度,这是一个实现了简易计算器功能的代码:

expression = getInput("Please enter the expression: ")

result = eval(expression)

println("result:", result)


仅仅4行代码而已,实现的效果如下:

Gox语言实现的计算器

可以看到,4行语言就实现了一个命令行版本的简易表达式计算器。

也可以直接在内置的代码编辑器中编辑执行,直接运行带参数的gox -edit命令即可,效果如下:

image.png

如果用GUI图形界面来实现怎么样呢?同样的,只需要相对很简单的代码就能实现:

sciter = github_scitersdk_gosciter
window = github_scitersdk_gosciter_window

initGUI()

htmlT := `
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title>Calculator</title>
</head>
<body>
    <div style="margin-top: 10px; margin-bottom: 10px;">
        <span>Please enter the expression:</span>
    </div>
    <div style="margin-top: 10px; margin-bottom: 10px;">
        <input id="mainInputID" type=text />
    </div>
    <div>
        <button id="btnCal">Calculate!</button>
        <button id="btnClose">Close</button>
    </div>

    <script type="text/tiscript">
        $(#btnCal).on("click", function() {
        var result = eval($(#mainInputID).value);

        view.prints(String.printf("%v", result));

        $(#mainInputID).value = result;
        });
 
        $(#btnClose).on("click", function() {
            view.close();
        });
 
    </script>
</body>
</html>

`

runtime.LockOSThread()

w, err := window.New(sciter.DefaultWindowCreateFlag, sciter.DefaultRect)

checkError(err)

w.SetOption(sciter.SCITER_SET_SCRIPT_RUNTIME_FEATURES, sciter.ALLOW_EVAL|sciter.ALLOW_SYSINFO)

w.LoadHtml(htmlT, "")

w.SetTitle("Calculator")

w.DefineFunction("prints", func(args) {
    tk.Pl("%v", args[0].String())
    return sciter.NewValue("")
})

w.Show()

w.Run()

除去注释和用于制作GUI界面的HTML代码,有效代码不超过20行(其中还包括用于输出调试信息的代码),就能够实现一个界面相当不错的图形化计算器了,而且可以实现跨平台!实际效果如下图所示:

Gox实现的图形化计算器

那么,更为令人叹为观止地,一个支持中文、跨平台、功能齐全而又简洁的代码编辑器也不需要多少Gox代码就可以实现,运行效果如下图所示,有效代码仅需不到200行(请参见本文末尾所附代码)。

简易全功能代码编辑器

由于仅是示例,篇幅所限,没有实现代码高亮和自动完成功能,但除此以外,一个代码编辑器所需的所有主要功能都已经实现了,甚至还包括代码加密、解密和运行功能。


下面看看Gox语言的主要使用方式。

  • 首先,直接去Gox语言官网(gox.topget.org)或其Github/Gitee的release页面(topxeq/gox)下载Gox语言的单一可执行文件,将其放在一个目录下(如果是下载的zip压缩包,需要进行解压缩);该目录最好在操作系统可找到的目录下(即加入PATH环境变量中),以便执行;


    Gox语言官网
  • 直接在命令行模式下(例如Windows的命令提示符)运行gox命令(或gox -shell)即可进入交互式编程环境,如下图所示:


    Gox的交互式编程环境
  • 使用类似 gox -edit basic.gox 的命令可以启动Gox的内置编辑器编辑名为basic.gox的Gox代码文件,而如果直接用 gox -edit 不带文件名,则将打开一个新文件进行编辑;

  • 也可以在Gox交互式编程环境中直接用edit函数来启动图形化代码编辑器:


    REPL中启动Gox代码编辑器
  • 运行Gox代码,只需要执行类似 gox basic.gox 的命令即可。

  • 使用类似 gox -encrypt=mycode basic.gox 的命令来对源代码进行加密(mycode是密码,将会生成一个名为basic.goxe的加密代码文件),然后可以使用类似 gox -decrypt=mycode basic.goxe 的命令来对代码进行解密,或者直接用 gox -decrun=mycode basic.goxe 命令来解密执行源代码,也可以直接 gox basic.goxe 执行源代码,此时将会要求先输入密码后才能执行;

  • 除使用内置的代码编辑器外,也可以使用功能更为丰富的其他编辑器,例如Visual Studio Code就是一个很好的选择,将.gox类型文件的文件的代码高亮方案设置成与Go语言或C语言相同即可;

  • 可以用类似gox -compile basic.gox -output=basic.exe这样的命令来将Gox代码文件编译,可以起到单文件发布和代码加密的效果;

  • 进行图形化编程时,需要使用Sciter库,这也是经过业界检验的一个第三方图形库,采用HTML/CSS/Script的方式进行页面开发,运行时只需要附带一个动态链接库文件即可,非常方便;如果不希望出现命令行窗口(例如Windows下的CMD),可以用Gox的窗口版本goxw.exe即可;

  • 更多的说明和代码示例可以参考Github上Gox语言的代码库,github.com/topxeq/gox。


当然,Gox语言目前也存在不足,我们要心里有数,以便在使用Gox语言时清楚它更适合做什么和不太适合做什么。

目前,Gox语言最主要的不足就是:作为解释型/脚本语言,Gox也具有这类语言的通病,就是执行代码的速度相对比较慢,比一些具有多年优化积累的解释型语言如Python、Java等也要更慢,但在大多数的应用环境中已经足够使用,包括一些中型规模的网络应用和微服务,这也是经过实践检验的。

另外,Gox语言虽然只有一个主程序,但相对来说比较大(有几十M之多)。但这也比较好理解,毕竟还需要支持图形化编程并自带代码编辑器。考虑到这一点,Gox语言也提供一个去除了图形化编程功能的纯命令行版本,文件大小会小一些,效率也会高一点。

第三,Gox语言如果要支持更多的第三方类库,需要手动编译源码,但这点对于程序员来说应该不是大问题。


总的来说,Gox语言虽然具备开发各种各种应用场景的全栈开发语言的潜力,相对来说不太适合开发高密度计算类型和需要极高速响应的程序和系统;但它非常适合作为“胶水语言”来作为大型系统之间的粘合剂,也非常适合快速开发一些无需太高速度要求的功能系统(例如可以取代shell脚本的开发、进行各种复杂的文本处理、实现各种网络编程、进行数据库访问、编写一些图形化操控界面)以及做一些快速原型演示等。


附——全功能简易代码编辑器源码(该版本使用Sciter GUI图形库,使用时需要Sciter的动态链接库,Windows下为sciter.dll,在Gox官网上的主程序压缩包中已经提供,使用gox -initgui命令也可以直接下载):

sciter = github_scitersdk_gosciter
window = github_scitersdk_gosciter_window

initGUI()

htmlT := `
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title>Gox Editor</title>
    <style>
    
    plaintext {
      padding:0;
      flow:vertical;
      behavior:plaintext;
      background:#333; border:1px solid #333;
      color:white;
      overflow:scroll-indicator;
      font-rendering-mode:snap-pixel;
      size:*; 
      tab-size: 4;
    }
    plaintext > text {
      font-family:monospace;
      white-space: pre-wrap;
      background:white;
      color:black;
      margin-left: 3em;
      padding-left: 4dip;
      cursor:text;
      display:list-item;
      list-style-type: index;
      list-marker-color:#aaa;
    }
    plaintext > text:last-child {
      padding-bottom:*;
    }    
    
    plaintext > text:nth-child(10n) {
      list-marker-color:#fff;
    }
    
    
    </style>


    <script type="text/tiscript">
        function colorize() 
        {
            const apply = Selection.applyMark; 
            const isEditor = this.tag == "plaintext";
            
            // forward declarations:
            var doStyle;
            var doScript;

            // markup colorizer  
            function doMarkup(tz) 
            {
                    var bnTagStart = null;
                    var tagScript = false;
                    var tagScriptType = false;
                    var tagStyle = false;
                    var textElement;
                
                while(var tt = tz.token()) {
                if( isEditor && tz.element != textElement )       
                {
                    textElement = tz.element;
                    textElement.attributes["type"] = "markup";
                }
                switch(tt) {
                    case #TAG-START: {    
                        bnTagStart = tz.tokenStart; 
                        const tag = tz.tag;
                        tagScript = tag == "script";
                        tagStyle  = tag == "style";
                    } break;
                    case #TAG-HEAD-END: {
                        apply(bnTagStart,tz.tokenEnd,"tag"); 
                        if( tagScript ) { tz.push(#source,"</sc"+"ript>"); doScript(tz, tagScriptType, true); }
                        else if( tagStyle ) { tz.push(#source,"</style>"); doStyle(tz, true); }
                    } break;
                    case #TAG-END:      apply(tz.tokenStart,tz.tokenEnd,"tag"); break;  
                    case #TAG-ATTR:     if( tagScript && tz.attr == "type") tagScriptType = tz.value; 
                                        if( tz.attr == "id" ) apply(tz.tokenStart,tz.tokenEnd,"tag-id"); 
                                        break;
                }
                }
            }
            
            // script colorizer
            doScript = function(tz, typ, embedded = false) 
            {
                const KEYWORDS = 
                {
                "type"    :true, "function":true, "var"       :true,"if"       :true,
                "else"    :true, "while"   :true, "return"    :true,"for"      :true,
                "break"   :true, "continue":true, "do"        :true,"switch"   :true,
                "case"    :true, "default" :true, "null"      :true,"super"    :true,
                "new"     :true, "try"     :true, "catch"     :true,"finally"  :true,
                "throw"   :true, "typeof"  :true, "instanceof":true,"in"       :true,
                "property":true, "const"   :true, "get"       :true,"set"      :true,
                "include" :true, "like"    :true, "class"     :true,"namespace":true,
                "this"    :true, "assert"  :true, "delete"    :true,"otherwise":true,
                "with"    :true, "__FILE__":true, "__LINE__"  :true,"__TRACE__":true,
                "debug"   :true, "await"   :true 
                };
                
                const LITERALS = { "true": true, "false": true, "null": true, "undefined": true };
                
                var firstElement;
                var lastElement;
            
                while:loop(var tt = tz.token()) {
                var el = tz.element;
                if( !firstElement ) firstElement = el;
                lastElement = el;
                switch(tt) 
                {
                    case #NUMBER:       apply(tz.tokenStart,tz.tokenEnd,"number"); break; 
                    case #NUMBER-UNIT:  apply(tz.tokenStart,tz.tokenEnd,"number-unit"); break; 
                    case #STRING:       apply(tz.tokenStart,tz.tokenEnd,"string"); break;
                    case #NAME:         
                    {
                    var val = tz.value;
                    if( val[0] == '#' )
                        apply(tz.tokenStart,tz.tokenEnd, "symbol"); 
                    else if(KEYWORDS[val]) 
                        apply(tz.tokenStart,tz.tokenEnd, "keyword"); 
                    else if(LITERALS[val]) 
                        apply(tz.tokenStart,tz.tokenEnd, "literal"); 
                    break;
                    }
                    case #COMMENT:      apply(tz.tokenStart,tz.tokenEnd,"comment"); break;
                    case #END-OF-ISLAND:  
                    tz.pop(); //pop tokenizer layer
                    break loop;
                }
                }
                if(isEditor && embedded) {
                for( var el = firstElement; el; el = el.next ) {
                    el.attributes["type"] = "script";
                    if( el == lastElement )
                    break;
                }
                }
            };
            
            doStyle = function(tz, embedded = false) 
            {
                const KEYWORDS = 
                {
                "rgb":true, "rgba":true, "url":true, 
                "@import":true, "@media":true, "@set":true, "@const":true
                };
                
                const LITERALS = { "inherit": true };
                
                var firstElement;
                var lastElement;
                
                while:loop(var tt = tz.token()) {
                var el = tz.element;
                if( !firstElement ) firstElement = el;
                lastElement = el;
                switch(tt) 
                {
                    case #NUMBER:       apply(tz.tokenStart,tz.tokenEnd,"number"); break; 
                    case #NUMBER-UNIT:  apply(tz.tokenStart,tz.tokenEnd,"number-unit"); break; 
                    case #STRING:       apply(tz.tokenStart,tz.tokenEnd,"string"); break;
                    case #NAME:         
                    {
                    var val = tz.value;
                    if( val[0] == '#' )
                        apply(tz.tokenStart,tz.tokenEnd, "symbol"); 
                    else if(KEYWORDS[val]) 
                        apply(tz.tokenStart,tz.tokenEnd, "keyword"); 
                    else if(LITERALS[val]) 
                        apply(tz.tokenStart,tz.tokenEnd, "literal"); 
                    break;
                    }
                    case #COMMENT:      apply(tz.tokenStart,tz.tokenEnd,"comment"); break;
                    case #END-OF-ISLAND:  
                    // got </sc ript>
                    tz.pop(); //pop tokenizer layer
                    break loop;
                }
                }
                if(isEditor && embedded) {
                for( var el = firstElement; el; el = el.next ) {
                    el.attributes["type"] = "style";
                    if( el == lastElement )
                    break;
                }
                }
            };
            
            var me = this;
            
            function doIt() { 
            
                var typ = me.attributes["type"];

                var syntaxKind = typ like "*html" || typ like "*xml" ? #markup : #source;
                var syntax = typ like "*css"? #style : #script;
                
                var tz = new Tokenizer( me, syntaxKind );
            
                if( syntaxKind == #markup )
                doMarkup(tz);
                else if( syntax == #style )
                doStyle(tz);
                else 
                doScript(tz,typ);
            }
            
            doIt();
            
            // redefine value property
            this[#value] = property(v) {
                get { return this.state.value; }
                set { this.state.value = v; doIt(); }
            };
            
            this.load = function(text,sourceType) 
            {
                this.attributes["type"] = sourceType;
                if( !isEditor )
                text = text.replace(/\r\n/g,"\n"); 
                this.state.value = text; 
                doIt();
            };
            
            this.sourceType = property(v) {
                get { return this.attributes["type"]; }
                set { this.attributes["type"] = v; doIt(); }
            };
            if (isEditor)
                    this.on("change", function() {
                        this.timer(40ms,doIt);
                    });
            

        }
    </script>
    <style>

        @set colorizer < std-plaintext 
        {
            :root { aspect: colorize; }
            
            text { white-space:pre;  display:list-item; list-style-type: index; list-marker-color:#aaa; }
            /*markup*/  
            text::mark(tag) { color: olive; } /*background-color: #f0f0fa;*/
            text::mark(tag-id) { color: red; } /*background-color: #f0f0fa;*/

            /*source*/  
            text::mark(number) { color: brown; }
            text::mark(number-unit) { color: brown; }
            text::mark(string) { color: teal; }
            text::mark(keyword) { color: blue; }
            text::mark(symbol) { color: brown; }
            text::mark(literal) { color: brown; }
            text::mark(comment) { color: green; }
            
            text[type=script] {  background-color: #FFFAF0; }
            text[type=markup] {  background-color: #FFF;  }
            text[type=style]  {  background-color: #FAFFF0; }
        }

        plaintext[type] {
            style-set: colorizer;
        }

        @set element-colorizer 
        {
            :root { 
                aspect: colorize; 
                background-color: #fafaff;
                    padding:4dip;
                    border:1dip dashed #bbb;
                }
            
            /*markup*/  
            :root::mark(tag) { color: olive; } 
            :root::mark(tag-id) { color: red; }

            /*source*/  
            :root::mark(number) { color: brown; }
            :root::mark(number-unit) { color: brown; }
            :root::mark(string) { color: teal; }
            :root::mark(keyword) { color: blue; }
            :root::mark(symbol) { color: brown; }
            :root::mark(literal) { color: brown; }
            :root::mark(comment) { color: green; }
            }

            pre[type] {
            style-set: element-colorizer;
        }

    </style>
    <script type="text/tiscript">
        // if (view.connectToInspector) {
        //  view.connectToInspector(rootElement, inspectorIpAddress);
        // }

        function isErrStr(strA) {
            if (strA.substr(0, 6) == "TXERROR:") {
                return true;
            }

            return false;
        }

        function getErrStr(strA) {
            if (strA.substr(0, 6) == "TXERROR:") {
                return strA.substr(6);
            }

            return strA;
        }

        function getConfirm(titelA, msgA) {
            var result = view.msgbox { 
                type:#question,
                title: titelA,
                content: msgA, 
                buttons: [
                    {id:#yes,text:"Ok",role:"default-button"},
                    {id:#cancel,text:"Cancel",role:"cancel-button"}]                               
                };

            return result;
        }

        function showInfo(titelA, msgA) {
            var result = view.msgbox { 
                type:#information,
                title: titelA,
                content: msgA, 
                buttons: [
                    {id:#cancel,text:"Close",role:"cancel-button"}]                               
                };

            return result;
        }

        function showError(titelA, msgA) {
            var result = view.msgbox { 
                type:#alert,
                title: titelA,
                content: msgA, 
                buttons: [
                    {id:#cancel,text:"Close",role:"cancel-button"}]                               
                };

            return result;
        }

        function getScreenWH() {
            var (w, h) = view.screenBox(#frame, #dimension)

            view.move((w-800)/2, (h-600)/2, 800, 600);

            return String.printf("%v|%v", w, h);
        }

        var editFileNameG = "";
        var editFileCleanFlagG = "";

        function updateFileName() {
            $(#fileNameLabelID).html = (editFileNameG + editFileCleanFlagG);
        }

        function selectFileJS() {
            //var fn = view.selectFile(#open, "Gotx Files (*.gt,*.go)|*.gt;*.go|All Files (*.*)|*.*" , "gotx" );
            var fn = view.selectFile(#open);
            view.prints(String.printf("fn: %v", fn));

            if (fn == undefined) {
                return;
            }

            var fileNameT = URL.toPath(fn);

            var rs = view.loadStringFromFile(fileNameT);

            if (isErrStr(rs)) {
                showError("Error", String.printf("Failed to load file content: %v", getErrStr(rs)));
                return;
            }

            $(plaintext).attributes["type"] = "text/script";

            $(plaintext).value = rs;

            editFileNameG = fileNameT;

            editFileCleanFlagG = "";

            updateFileName();
        }

        function editFileLoadClick() {
            if (editFileCleanFlagG != "") {
            
                var rs = getConfirm("Please confirm", "File modified, load another file anyway?");

                if (rs != #yes) {
                    return;
                }

            }

            selectFileJS();
        }

        function editFileSaveAsClick() {
            var fn = view.selectFile(#save);
            view.prints(String.printf("fn: %v", fn));

            if (fn == undefined) {
                return;
            }

            var fileNameT = URL.toPath(fn);

            var textT = $(plaintext).value;

            var rs = view.saveStringToFile(textT, fileNameT);

            if (isErrStr(rs)) {
                showError("Error", String.printf("Failed to save file content: %v", getErrStr(rs)));
                return;
            }

            editFileNameG = fileNameT;
            editFileCleanFlagG = "";
            updateFileName();

            showInfo("Info", "Saved.");

        }

        function editFileSaveClick() {
            if (editFileNameG == "") {
                editFileSaveAsClick();

                return;
            }

            var textT = $(plaintext).value;

            var rs = view.saveStringToFile(textT, editFileNameG);

            if (isErrStr(rs)) {
                showError("Error", String.printf("Failed to save file content: %v", getErrStr(rs)));
                return;
            }

            editFileCleanFlagG = "";
            updateFileName();

            showInfo("Info", "Saved.");
        }

        function editRunClick() {
            view.close();
        }

        function getInput(msgA) {
            var res = view.dialog({ 
                html: `+"`"+`
                <html>
                <body>
                  <center>
                      <div style="margin-top: 10px; margin-bottom: 10px;">
                          <span`+"`"+`msgA`+"`"+`span>
                      </div>
                      <div style="margin-top: 10px; margin-bottom: 10px;">
                          <input id="mainTextID" type="text" />
                      </div>
                      <div style="margin-top: 10px; margin-bottom: 10px;">
                          <input id="submitButtonID" type="button" value="Ok" />
                          <input id="cancelButtonID" type="button" value="Cancel" />
                      </div>
                  </center>
                  <script type="text/tiscript">
                      $(#submitButtonID).onClick = function() {
                          view.close($(#mainTextID).value);
                      };
  
                      $(#cancelButtonID).onClick = function() {
                          view.close();
                      };
                  </scr`+"`"+`+`+"`"+`ipt>
                </body>
                </html>
                `+"`"+`
              });
  
              return res;
          }

        event click $(#btnEncrypt)
        {
            var res = getInput("Secure Code");

            if (res == undefined) {
                return;
            }

            var sourceT = $(plaintext).value;

            var encStrT = view.encryptText(sourceT, res);
        
            if (isErrStr(encStrT)) {
                showError("Error", String.printf("failed to encrypt content: %v",getErrStr(encStrT)));
                return;
            }
        
            $(plaintext).value = "\/\/TXDEF#" + encStrT;
            editFileCleanFlagG = "*";
        }
    
        event click $(#btnDecrypt)
        {
            var res = getInput("Secure Code");

            if (res == undefined) {
                return;
            }

            var sourceT = $(plaintext).value;

            var encStrT = view.decryptText(sourceT, res);
        
            if (isErrStr(encStrT)) {
                showError("Error", String.printf("failed to decrypt content: %v",getErrStr(encStrT)));
                return;
            }
        
            $(plaintext).value = encStrT;
            editFileCleanFlagG = "*";
        }
    
        event click $(#btnRun)
        {
            var res = getInput("Arguments to pass to script")

            if (res == undefined) {
                return;
            }

            var rs = view.runScript($(plaintext).value, res);

            showInfo("Result", rs)
        }
    

        function editCloseClick() {
            view.close();
        }

        function editFile(fileNameA) {
            var fcT string;

            if (fileNameA == "") {
                editFileNameG = "";

                fcT = "";

                editFileCleanFlagG = "*";
            } else {
                editFileNameG = fileNameA;

                fcT = view.loadStringFromFile(fileNameA);

                editFileCleanFlagG = "";
            }

            $(plaintext).attributes["type"] = "text/script";

            $(plaintext).value = fcT;

            updateFileName();

        }

        function self.ready() {
            $(#btnLoad).onClick = editFileLoadClick;
            $(#btnSave).onClick = editFileSaveClick;
            $(#btnSaveAs).onClick = editFileSaveAsClick;
            // $(#btnEncrypt).onClick = editFEncryptClick;
            // $(#btnDecrypt).onClick = editDecryptClick;
            // $(#btnRun).onClick = editRunClick;
            $(#btnClose).onClick = editCloseClick;

            $(plaintext#source).onControlEvent = function(evt) {
                switch (evt.type) {
                    case Event.EDIT_VALUE_CHANGED:      
                        editFileCleanFlagG = "*";
                        updateFileName();
                        return true;
                }
            };

        }
    </script>

</head>
<body>
    <div style="margin-top: 10px; margin-bottom: 10px;"><span id="fileNameLabelID"></span></div>
    <div style="margin-top: 10px; margin-bottom: 10px;">
        <button id="btn1" style="display: none">Load...</button>
        <button id="btnLoad">Load</button>
        <button id="btnSave">Save</button>
        <button id="btnSaveAs">SaveAs</button>
        <button id="btnEncrypt">Encrypt</button>
        <button id="btnDecrypt">Decrypt</button>
        <button id="btnRun">Run</button>
        <button id="btnClose">Close</button>
    </div>
    <plaintext#source type="text/html" style="font-size: 1.2em;"></plaintext>

</body>
</html>
`

runtime.LockOSThread()

w, err := window.New(sciter.DefaultWindowCreateFlag, sciter.DefaultRect)

checkError(err)

w.SetOption(sciter.SCITER_SET_SCRIPT_RUNTIME_FEATURES, sciter.ALLOW_FILE_IO | sciter.ALLOW_SOCKET_IO | sciter.ALLOW_EVAL | sciter.ALLOW_SYSINFO)

w.LoadHtml(htmlT, "")

w.SetTitle("Gox Editor")

w.DefineFunction("prints", func(args) {
    tk.Pl("%v", args[0].String())
    return sciter.NewValue("")
})

w.DefineFunction("loadStringFromFile", func(args) {
    rs := tk.LoadStringFromFile(args[0].String())
    return sciter.NewValue(rs)
})

w.DefineFunction("saveStringToFile", func(args) {
    rs := tk.SaveStringToFile(args[0].String(), args[1].String())
    return sciter.NewValue(rs)
})

w.DefineFunction("encryptText", func(args) {
    rs := tk.EncryptStringByTXDEF(args[0].String(), args[1].String())
    return sciter.NewValue(rs)
})

w.DefineFunction("decryptText", func(args) {
    rs := tk.DecryptStringByTXDEF(args[0].String(), args[1].String())
    return sciter.NewValue(rs)
})

w.DefineFunction("runScript", func(args) {
    rs := runScript(args[0].String(), "", args[1].String())
    return sciter.NewValue(spr("%v", rs))
})

w.DefineFunction("exit", func(args) {
    os.Exit(1);
})

data, _ := w.Call("getScreenWH")

fileNameT := getParam(os.Args, 2, "")

w.Call("editFile", sciter.NewValue(fileNameT))

w.Show()

w.Run()


另外,Ubuntu也需要安装GTK3的环境,执行下述命令即可安装:

apt install libgtk-3-dev
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,757评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,478评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,540评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,593评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,903评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,329评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,659评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,383评论 0 195
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,055评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,337评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,864评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,227评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,820评论 3 231
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,999评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,750评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,365评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,260评论 2 258

推荐阅读更多精彩内容