# aardio 范例: COCA 20000 词汇表



```aardio
import fonts.fontAwesome;
import win.ui;
/*DSG{{*/
winform = win.form(text="COCA 20000 词汇表";right=963;bottom=598;bgcolor=0xFFFFFF)
winform.add(
btnAskAi={cls="plus";text="问 AI";left=889;top=570;right=955;bottom=599;align="left";anchors={right=1;bottom=1};bgcolor=0xFFFFFF;color=0x3C3C3C;font=LOGFONT(h=-13);iconColor=0x5757FF;iconStyle={align="left";font=LOGFONT(h=-13;name='FontAwesome');padding={left=8}};iconText='\uF0AA';notify=1;textPadding={left=25};z=4};
btnPractice={cls="plus";text="练习";left=586;top=570;right=651;bottom=599;align="left";anchors={left=1;bottom=1};bgcolor=0xFFFFFF;color=0x3C3C3C;font=LOGFONT(h=-13);iconColor=0x5757FF;iconStyle={align="left";font=LOGFONT(h=-13;name='FontAwesome');padding={left=8}};iconText='\uF02D';notify=1;textPadding={left=25};z=17};
btnSearch={cls="plus";left=851;top=570;right=881;bottom=596;align="left";anchors={right=1;bottom=1};border={bottom=1;color=0x969696};color=0x3C3C3C;font=LOGFONT(h=-13);iconStyle={align="left";font=LOGFONT(h=-13;name='FontAwesome');padding={left=8}};iconText='\uF002';notify=1;textPadding={left=25};z=14};
btnVoice={cls="plus";text="朗读";left=652;top=570;right=717;bottom=599;align="left";anchors={left=1;bottom=1};bgcolor=0xFFFFFF;color=0x3C3C3C;font=LOGFONT(h=-13);iconColor=0x008000;iconStyle={align="left";font=LOGFONT(h=-13;name='FontAwesome');padding={left=8}};iconText='\uF028';notify=1;textPadding={left=25};z=5};
chkCet4={cls="checkbox";text="CET4";left=189;top=572;right=241;bottom=597;anchors={left=1;bottom=1};bgcolor=0xFFFFFF;z=9};
chkCet6={cls="checkbox";text="CET6";left=242;top=572;right=294;bottom=597;anchors={left=1;bottom=1};bgcolor=0xFFFFFF;z=10};
chkGk={cls="checkbox";text="高考";left=135;top=572;right=187;bottom=597;anchors={left=1;bottom=1};bgcolor=0xFFFFFF;z=8};
chkGsl={cls="checkbox";text="通用";left=456;top=572;right=508;bottom=597;anchors={left=1;bottom=1};bgcolor=0xFFFFFF;z=18};
chkIelts={cls="checkbox";text="雅思";left=296;top=572;right=348;bottom=597;anchors={left=1;bottom=1};bgcolor=0xFFFFFF;z=11};
chkKy={cls="checkbox";text="考研";left=402;top=572;right=454;bottom=597;anchors={left=1;bottom=1};bgcolor=0xFFFFFF;z=13};
chkMeaning={cls="checkbox";text="释义";left=20;top=572;right=72;bottom=597;anchors={left=1;bottom=1};bgcolor=0xFFFFFF;z=6};
chkNewWords={cls="checkbox";text="生词本";left=509;top=572;right=573;bottom=597;anchors={left=1;bottom=1};bgcolor=0xFFFFFF;z=16};
chkToefl={cls="checkbox";text="托福";left=349;top=572;right=401;bottom=597;anchors={left=1;bottom=1};bgcolor=0xFFFFFF;z=12};
chkZk={cls="checkbox";text="中考";left=82;top=572;right=134;bottom=597;anchors={left=1;bottom=1};bgcolor=0xFFFFFF;z=7};
custom={cls="custom";text="自定义控件";left=572;top=20;right=958;bottom=562;anchors={top=1;right=1;bottom=1};z=2};
editKeyword={cls="plus";left=706;top=570;right=851;bottom=596;align="right";anchors={left=1;right=1;bottom=1};border={bottom=1;color=0x969696};editable="edit";font=LOGFONT(h=-13);notify=1;textPadding={top=3;bottom=3};z=15};
listview={cls="vlistview";left=20;top=20;right=567;bottom=562;anchors={left=1;top=1;bottom=1};edge=1;fullRow=1;z=1};
splitter={cls="splitter";left=567;top=20;right=572;bottom=562;anchors={top=1;right=1;bottom=1};frame=1;z=3}
)
/*}}*/

import table.coca20000;
winform.listview.columns = [["词频",50],["单词",100],["音标",100],["释义",0],["分类",-1]]
var fields = ["word","phonetic","meaning","tag"]

//虚表
var dataset = table.coca20000;
winform.listview.onGetDispItem = function(item,row,col){ 
	if(col==1) return {text = tostring(row)};
	return{ text=dataset[row][fields[col-1]]}; 
}

import win.ui.grid;
win.ui.grid(winform.listview);//双击显示文本框

import web.form.chat;
var chatUi = web.form.chat(winform.custom);

chatUi.doScript("
document.onmouseup = document.onselectionchange = function() {
	
    var text = '';
    if (window.getSelection) {
        var selection = window.getSelection();
 
    	if (selection.rangeCount === 0) {
        	return null;
    	}

        text = selection.toString();
    } else if (document.selection && document.selection.type != 'Control') {
        text = document.selection.createRange().text;
    }
    
    if(text) external.onSelectedText(text) 
};")

winform.btnAskAi.skin({
	color={
		active=0xFF00FF00;
		default=0xFF3C3C3C;
		disabled=0xFF999999;
		hover=0xFFFF0000		
	}
	iconColor={
		active=0xFF00FF00;
		default=0xFFFF5757;
		hover=0xFFFF0000;
		disabled=0xFF999999; 	
	} 
})

winform.btnAskAi.oncommand = function(id,event){

	var word = winform.listview.selectedWord;
	if(!#word) return winform.msgboxErr("请先点选单词");
	
	winform.btnAskAi.disabledText = ['\uF254','\uF251','\uF252','\uF253','\uF250']
	
	 
	chatUi.chatMessages.clear();
	chatUi.chatMessages.system("
你是一位英语教授,
你的任务是根据输入单词完成一系列英语学习相关的任务。

以下是输入的单词：
<单词>
{{WORD}}
</单词>

请完成以下具体任务：

### 任务1: 要点
**用中文讲解此单词的主要用法、使用要点、需要注意的问题。这是所有任务中最重要的部分！**

### 任务2：词根词缀
用中文讲述该单词的**词根词缀起源故事**。

请按照以下 Markdown 格式排版输出你的结果：

### 要点

### 词根词缀");	

	//添加用户提示词,界面上不显示
	chatUi.chatMessages.prompt( word );

	chatUi.assistant('\n\n---\n\n')
	
	//创建 AI 对话线程
	..thread.invoke( 
		function(winform,chatUi,word){

			import web.rest.aiChat;
			import fsys.table;
			
			var config = ..fsys.table(..io.appData("aardio/std/winex/flashDict.table"),{
				key =   '\0\1\96';
				url = "https://ai.aardio.com/api/v1/";
				model = "flash-dict";
			})	
			
			config.userAgent = "Mozilla/5.0 (Windows NT "+ _WIN_VER_MAJOR +"."+_WIN_VER_MINOR+"; win.dlg.flashDict) like Gecko";
			var aiClient = ..web.rest.aiChat(config)

			//调用聊天接口。
			var ok,err = aiClient.messages(chatUi.chatMessages,
				function(deltaText,reasoning){

					//输出思考过程
					if(reasoning) { 
						chatUi.showThinking(reasoning);
					}

					//输出回复,自带高性能 Markdown 解析器。 
					chatUi.assistant(deltaText);
				}
			);

			if(err){
				chatUi.errorMessage(err)
			}
			else{
				var md = chatUi.getMarkdown()
				
				var path = io.appData("aardio/std/examples/coca20000/.ai.word.cache/"+word+".md");
				string.save(path,md )
			}

			winform.btnAskAi.disabledText = null;

		},winform,chatUi,word)
}

import com.wmPlayer;
var soundUrlCache = table.cache(); 
var pronounce = function(word,site){
	com.wmPlayer.url = "https://dict.youdao.com/dictvoice?audio="+word+"&type=2";
}

import fsys.table;
var config = fsys.table(io.appData("aardio/std/tools/coca2000.table"),{newWords={}});

import string.words;
winform.listview.onSelChanged = function(selected,item,subItem,nmListView){
	if(!selected) return;

	chatUi.clear();
	
	var word = winform.listview.getItemText(item,2); 
	if((winform.listview.selectedWord == winform.editKeyword.text) 
		|| !#winform.editKeyword.text){
		winform.editKeyword.text = word;
	}
	winform.listview.selectedWord = word;

	var cacheMarkdown = string.load(io.appData("aardio/std/examples/coca20000/.ai.word.cache/"+word+".md"));
	if(cacheMarkdown){
		chatUi.setMarkdown(cacheMarkdown,false);
		
		winform.btnAskAi.disabled = true;
		pronounce(word,"youdao") 
		return;
	}
	
	winform.btnAskAi.disabled = false;
	
	var meaning = table.coca20000(word,true);
	meaning = string.replace(meaning,"^%//","");
	
	meaning = '##### '+word + ' ' +  winform.listview.getItemText(item,3)
			+ '\r\n\r\n##### '  + meaning
		 
	var wordInfo = dataset[item]
	if(wordInfo && wordInfo.sentence){
		meaning = meaning 
				+ '\r\n\r\n<br>例句：\r\n\r\n '  + string.replace(wordInfo.sentence,'\n','  <a style="cursor:pointer;font-size:16px;" href="javascript:void(0)" onclick="javascript:external.tts()">🔊</a>\n<br>');
	}
	
	if(!winform.btnAskAi.disabled){
		var card = meaning ++ ( config.newWords[word] ? "":'\r\n\r\n---\r\n<a href="javascript:void(0)"  onclick="javascript:external.newWords();this.innerText=\'已添加到生词本\'">添加到生词本</a>')
		chatUi.setMarkdown(card)
	}
	
	pronounce(word,"youdao") 
}


winform.splitter.split(winform.listview,winform.custom);

winform.btnVoice.skin({
	color={
		active=0xFF00FF00;
		default=0xFF3C3C3C;
		disabled=0xFF999999;
		hover=0xFFFF0000		
	}
})

import web.edgeTextToSpeech;
winform.btnVoice.oncommand = function(id,event){
	
	var word = winform.listview.selectedWord;
	if(!word) return winform.msgboxErr("请先点选单词");
	
	winform.btnVoice.disabledText = ['\uF254','\uF251','\uF252','\uF253','\uF250'];
	pronounce(word,"webster");
	thread.delay(1500);
	
	winform.btnVoice.disabledText = null;
}

winform.chkMeaning.oncommand = function(){
	winform.listview.setRedraw(false);
	
	if(owner.checked){
		winform.listview.setColumnWidth(0,5);
		winform.listview.fillParent(4);
	}
	else{
		winform.listview.setColumnWidth(0,4);
		winform.listview.fillParent(5);
	}
	
	winform.listview.setRedraw(true);
}

var tip = /*
- COCA（Corpus of Contemporary American English, 美国当代英语语料库）20000 词汇。   
原表有 22000 个词，合并词性不同的重复条目并添加 NGSL 词汇后共计 17649 个词（区分大小写）。
按词频排序，单词使用频率越高则排序越是靠前。

- 点选单词调用有道词典发音，点选「朗读」按钮则调用韦氏词典加载真人发音。
- 点单词显示例句，点例句后的「🔊」可以仿真人语音朗读例句。 
在 「🔊」链接获得焦点时也可以按 <kbd>Enter</kbd> 键朗读例句。
- 在释义与例句页面内双击选中单词可显示中文释义。
- 双击列表项可切换到编辑框模式。
- 列表项右键菜单 ☑ 勾选「生词本」可添加单词到生词本，也可点击「根据生词本生成例句」菜单项。
- 可勾选下方中考、高考、CET4、CET6、雅思、托福、考研等分类标签。  
不勾选任何分类标签则显示全部词汇。
*/

chatUi.write(tip);

import win.ui.menu;
winform.listview.onRightClick = function(item,subItem,nmListView){

	var popmenu = win.ui.popmenu(winform);
	
	if(item){
		var word = winform.listview.getItemText(item,2); 
		
		var id = popmenu.add('生词本',function(id){
			config.newWords[word] = !popmenu.checked(1) ? true : null;
			config.save();
			
			winform.listview.redraw();
		});
		popmenu.check(1,!!config.newWords[word]);		
	}

	popmenu.add();
	
	popmenu.add('清空生词本',function(id){
		config.newWords = {};
		config.save();
		
		winform.chkNewWords.oncommand();
	});
	
	popmenu.add('复制生词本',function(id){
		var newWords = config.newWords;
		
		var words = []
		for(i=1;#dataset;1){
			if(newWords[dataset[i].word]){
				table.push(words,i + " " +dataset[i].word);
			} 
		}
		
		import win.clip;
		win.clip.write( string.join(words,'\r\n') )
	});
	
	popmenu.add();
	
	popmenu.add('生词本练习',function(id){
		winform.loadForm("/coca2000.practice.aardio")
	});
	 
	popmenu.popup(); 
}
	
var filter = function(){
 
	var p = []
	if(winform.chkZk.checked) table.push(p,"<zk>");
	if(winform.chkGk.checked) table.push(p,"<gk>");
	if(winform.chkCet4.checked) table.push(p,"<cet4>");
	if(winform.chkCet6.checked) table.push(p,"<cet6>");
	if(winform.chkIelts.checked) table.push(p,"<ielts>");
	if(winform.chkToefl.checked) table.push(p,"<toefl>");
	if(winform.chkKy.checked) table.push(p,"<ky>");
	if(winform.chkGsl.checked) table.push(p,"<gl>");
	
	if(!#p){
		dataset = table.coca20000;
	} 
	else{
		p = "!\w"+string.join(p,"|");
		
		dataset = []
		for(i,wordInfo in table.coca20000){
			if(wordInfo.tag && string.find(wordInfo.tag,p)){
				table.push(dataset,wordInfo);
			}
		}
	}
	
	if(winform.chkNewWords.checked){
		var newWords = config.newWords;
		
		var dataset2 = []
		for(i,v in dataset){
			if(newWords[v.word]){
				table.push(dataset2,v);
			}
		
		}
		
		dataset = dataset2;
	}
		
	winform.listview.count = #dataset;
	winform.listview.redraw();
}

winform.chkZk.oncommand = filter;
winform.chkGk.oncommand = filter;
winform.chkCet4.oncommand = filter;
winform.chkCet6.oncommand = filter;
winform.chkIelts.oncommand = filter;
winform.chkToefl.oncommand = filter;
winform.chkKy.oncommand = filter;
winform.chkGsl.oncommand = filter;
winform.chkNewWords.oncommand = filter;

winform.bindConfig( config,{
	checkbox = "checked";
} );

winform.chkMeaning.oncommand();
filter();

winform.listview.onnotify = function(id,code,ptr){ 
	if( code == 0xFFFFFFF4/*_NM_CUSTOMDRAW*/ ){
		var lvcd = winform.listview.getNotifyCustomDraw(code,ptr);
		if( lvcd.nmcd.dwDrawStage == 0x10001/*_CDDS_ITEMPREPAINT*/)
			return 0x20/*_CDRF_NOTIFYSUBITEMDRAW*/
		elseif( lvcd.nmcd.dwDrawStage == 1/*_CDDS_PREPAINT*/ ){
			return 0x20/*_CDRF_NOTIFYITEMDRAW*/;
		}
		elseif( lvcd.nmcd.dwDrawStage == ( 0x10001/*_CDDS_ITEMPREPAINT*/ | 0x20000/*_CDDS_SUBITEM*/) ){ 
			//注意这里 iSubItem 的索引自0开始( 其他函数通常自1开始 )
			
			lvcd.clrText = 0x000000;
			var word = dataset[lvcd.nmcd.dwItemSpec+1][["word"]]
			if(word && config.newWords[word]){
				lvcd.clrText = 0x0000FF; 
			}
			
			lvcd.update();
			
			
			return 0/*_CDRF_DODEFAULT*/
		}
	}
}

import win.ui.mask;
var mask = win.ui.mask(winform);
mask.add(
plus={cls="plus";marginBottom=20;marginLeft=550;width=370;height=400;color=0x008000;
font=LOGFONT(h=-96;name='FontAwesome');valign="bottom";
})

import win.debounce;
chatUi.external = {
   
    newWords = function(){
    	config.newWords[winform.listview.selectedWord] = true;
		config.save();	
    };
    onSelectedText = win.debounce(function(text,left,top,right,bottom){
        import winex.tooltip;
        if(#text){ 
            
        	var meaning,word = string.words(text);
        	
        	if(meaning){
        	    var x,y = win.getMessagePos();
        	    y = y  + winform.dpiScale(25)
        	    
        		winex.tooltip.balloon(meaning,x,y);
        		
        		if(!winform.btnVoice.disabled){
        			pronounce(word,"youdao");
        		}
        		return;
        	}
        } 
        
        winex.tooltip.balloon(false);
    },500);
    tts = function(){
        var selectedWord = winform.listview.selectedWord;
        if(!selectedWord) return;
        if(winform.btnVoice.disabledText) return;
        
        var mp3Path = io.appData("aardio/std/examples/coca20000/.ai.word.cache/"+selectedWord+".sentence.mp3");
        
		var playMp3 = function(){
			winform.btnVoice.disabledText = ['\uF026','\uF027','\uF028'];
			
			import com.wmPlayer
			com.wmPlayer.url = mp3Path;  
			com.wmPlayer.PlayStateChange = function(newState) { 
				if(newState==1){
					winform.btnVoice.disabledText = null;
				}
			}
				 
        	return;		
		}
		
		if( io.exist(mp3Path) ){
			return playMp3();
		}
		
		var wordInfo = table.coca20000(selectedWord)
		if(wordInfo && wordInfo.sentence){
			var sentence = string.match(wordInfo.sentence,"(.+?)<::>?=");
			
			if(sentence){
			 	
				mask.show();
				mask.plus.disabledText = ['\uF254','\uF251','\uF252','\uF253','\uF250'];
				winform.btnVoice.disabledText = ['\uF254','\uF251','\uF252','\uF253','\uF250'];
				
    			var partPath=mp3Path+".part";
				io.createDir(io.splitpath(partPath).dir);
				var file,err=io.file(partPath,"w+b");
				if(!file){
					mask.show(false);
					winform.btnVoice.disabledText=null;
					return winform.msgboxErr(err);
				}
				var finish=function(result,err){
					file.close();
					if(!err){
						var ok,e=io.rename(partPath,mp3Path);
						if(!ok) err={message=e};
					}
					if(err) io.remove(partPath);
					if(!::User32.IsWindow(winform.hwnd)) return;
					mask.plus.disabledText=null;
					mask.show(false);
					winform.btnVoice.disabledText=null;
					if(!err) playMp3();
				};
				var task,err=web.edgeTextToSpeech().synthesize({
					text=sentence;voice="en-US-AriaNeural";rate=0.9;
				},{
					onAudio=function(data){return file.write(data);};
					onFinish=finish;
				});
				winform.ttsTask=task;
				if(!task) finish(null,err);	
    			
    			return;
			}
		}
    };
}

winform.btnSearch.skin({
	color={
		active=0xFF00FF00;
		default=0xFF3C3C3C;
		disabled=0xFF6D6D6D;
		hover=0xFFFF0000
	}
})

winform.btnSearch.oncommand = function(id,event){
	
	var p = string.trim(winform.editKeyword.text);
	if(!#p) {
		winform.msgboxErr("请输入搜索词，支持模式匹配语法！");
		winform.editKeyword.setFocus();
		return;
	}
	
	var count = winform.listview.count;
	var selIndex = winform.listview.selIndex;
	if(!selIndex || selIndex >= count ){
		selIndex = 1;
	}
	else{
		selIndex++;
	}
	
	for(i=selIndex;count;1){
		var wordInfo = dataset[i]
		if(string.find(wordInfo.word,p)){
			winform.listview.selIndex = i;
			winform.listview.ensureVisible(i);
			return;
		} 
	}
	
	if(selIndex>2){
		for(i=1;selIndex-2;1){
			var wordInfo = dataset[i]
			if(string.find(wordInfo.word,p)){
				winform.listview.selIndex = i;
				winform.listview.ensureVisible(i);
				return;
			} 
		}		
	}

	winform.msgboxErr("找不到下一个匹配的单词！");	
	winform.editKeyword.setFocus();
}

winform.editKeyword.setCueBannerText("请输入搜索词，支持模式匹配语法");

winform.editKeyword.editBox.onOk = function(ctrl,alt,shift){ 
	winform.btnSearch.oncommand();
	return true; 	
}

winform.btnPractice.skin({
	color={
		active=0xFF00FF00;
		default=0xFF3C3C3C;
		disabled=0xFF6D6D6D;
		hover=0xFFFF0000
	}
})

winform.btnPractice.oncommand = function(id,event){
	winform.loadForm("/coca2000.practice.aardio")
}

winform.editKeyword.editBox.disableInputMethod();
winform.show(3/*_SW_MAXIMIZE*/);

import com.autoComplete;
var aco = com.autoComplete(winform.editKeyword);

var items = []
for(k,v in table.coca20000){
	table.push(items,v.word); 
}
aco.setStrings(items);

winform.onDestroy = function(){
	if(winform.ttsTask) winform.ttsTask.cancel();
};
win.loopMessage();
```

