1、桌面词典设计
想把Linux用做桌面系统,其中一部分障碍就是Linux上没有像有道同样简单易用的词典。其实咱们彻底能够本身开发一款桌面词典, 并且开发一款桌面词典也没用咱们想象的那么难。在这门项目课中,咱们就将开发一款很是简单的桌面词典,其功能就是:当咱们选中一个单词时,词典会将该单词 的中文(英文)含义而后显示在新的窗口中。
1. 查询
那咱们到哪儿去查询该单词呢?这里有两种方法:javascript
虽然经过API查询的结果没有在首页上查询的结果丰富,可是对于解决一些阅读英文文档的需求彻底足够了。感谢有道词典,提供了这么方便的API。
2. 图解界面设计
Linux上开发图形界面程序有不少选择,在这里咱们选择使用GTK进行,使用webview来显示查询结果。下一章中,咱们将学习一些简单的GTK和WEBVIEW的知识。
二. GTK 和 WEBVIEW
GTK最初是GIMP的专用开发库(GIMP Toolkit),后来发展为Unix-like系统下开发图形界面的应用程序的主流开发工具之一。GTK是自由软件,而且是GNU计划的一部分。GTK 的许可协议是LGPL。GTK使用C语言开发,可是其设计者使用面向对象技术。 也提供了C++(gtkmm)、Perl、Ruby、Java和Python(PyGTK)绑定。在这门课程中,咱们将使用pygtk进行开发。
1. GTK中的布局
GTK图形界面也像其余图形程序同样,由窗口,容器,控件,以及各类事件处理函数组成。其中窗口布局管理是很重要的一部份内容,由于这决定了咱们的图形程 序长什么样子。所谓布局管理就是在窗口中布置各类控件。各类控件能够放在一个“包”中进行统一显示处理,这种包就是GTK中的容器,其实它也是一个控件, 只是否是可见的,它的做用就是用于包含其各类控件。
GTK中有各类各样的容器控件,为了更好理解GTK中的布局,咱们建立一个计算器界面来学习下GTK中的容器,建立源文件calculator.py,输入如下源代码:php
import pygtk pygtk.require('2.0') import gtk class Calculator(gtk.Window): def __init__(self): super(Calculator, self).__init__() self.set_title("Calculator") self.set_size_request(250, 230) self.set_position(gtk.WIN_POS_CENTER) vbox = gtk.VBox(False, 2) table = gtk.Table(5, 4, True) table.attach(gtk.Button("Cls"), 0, 1, 0, 1) table.attach(gtk.Button("Bck"), 1, 2, 0, 1) table.attach(gtk.Label(), 2, 3, 0, 1) table.attach(gtk.Button("Close"), 3, 4, 0, 1) table.attach(gtk.Button("7"), 0, 1, 1, 2) table.attach(gtk.Button("8"), 1, 2, 1, 2) table.attach(gtk.Button("9"), 2, 3, 1, 2) table.attach(gtk.Button("/"), 3, 4, 1, 2) table.attach(gtk.Button("4"), 0, 1, 2, 3) table.attach(gtk.Button("5"), 1, 2, 2, 3) table.attach(gtk.Button("6"), 2, 3, 2, 3) table.attach(gtk.Button("*"), 3, 4, 2, 3) table.attach(gtk.Button("1"), 0, 1, 3, 4) table.attach(gtk.Button("2"), 1, 2, 3, 4) table.attach(gtk.Button("3"), 2, 3, 3, 4) table.attach(gtk.Button("-"), 3, 4, 3, 4) table.attach(gtk.Button("0"), 0, 1, 4, 5) table.attach(gtk.Button("."), 1, 2, 4, 5) table.attach(gtk.Button("="), 2, 3, 4, 5) table.attach(gtk.Button("+"), 3, 4, 4, 5) vbox.pack_start(gtk.Entry(), False, False, 0) vbox.pack_end(table, True, True, 0) self.add(vbox) self.connect("destroy", gtk.main_quit) self.show_all() Calculator() gtk.main()
以上程序中,咱们首先设置了窗口的一些属性:title,大小和位置。而后咱们使用vbox = gtk.VBox(False, 2) 咱们建立了一个垂直的容器( vertical container box),其中参数False指明了该容器中的控件不会是均匀大小的,参数2指明了该容器子部件之间的距离,单位是像素。
而后咱们使用 Table 容器部件建立了一个计算器的框架。table = gtk.Table(5, 4, True)咱们建立了一个 5 行 4 列的 table 容器部件。第三个参数是同质参数,若是被设置为 ture,table 中全部的部件将是相同的尺寸。而全部部件的尺寸与 table 容器中最大部件的尺寸相同。
table.attach(gtk.Button("Cls"), 0, 1, 0, 1)咱们附加了一个按钮到 table 容器中,其位置在表格的左上单元(cell)。前面两个参数表明这个单元的左侧和右侧,后两个参数表明这个单元的上部和下部。Table中的单元是依靠这 个单元的四个点的位置来肯定的。
vbox.pack_end(table, True, True, 0)咱们将table 容器部件放置到垂直箱子容器中。最后咱们使用窗口的shwo_all()方法,显示了全部的控件。使用如下命令执行该代码:html
$ python calculator.py
能够看到以上程序输出了如下画面:java
2. GTK中的事件
GTK中有各类各样的事件,好比按钮点击事件,选择事件等。又因为GTK中的控件没有X window,因此这些控件自己不具备接收事件的功能。在GTK中若是要让控件接收到事件,必需要先生成一个事件容器控件,而后让控件附加到这个事件容器 中。咱们开发的词典程序,会翻译咱们选择到的单词,那程序是如何检测到选择到的单词的呢?这就须要selection_received事件了,同时获取 选择事件是一个异步过程,因此要获取选择事件,须要先执行widget.selection_convert()方法。下面让咱们练下,建立源文件 selection_received.py,输入如下代码:python
#-*- coding: utf-8 -*- import pygtk pygtk.require('2.0') import gtk class GetSelectionExample(object): def __init__(self): # 建立窗口 window = gtk.Window(gtk.WINDOW_TOPLEVEL) window.set_title("Get Selection") window.set_border_width(10) window.connect("destroy", lambda w: gtk.main_quit()) # 建立一个垂直容器 vbox = gtk.VBox(False, 0) window.add(vbox) vbox.show() # 建立了一个按钮,当用点击按钮的时候,触发self.get_stringtarget函数 button = gtk.Button(u"输出选择字符串") eventbox = gtk.EventBox() eventbox.add(button) button.connect_object("clicked", self.get_stringtarget, eventbox) eventbox.connect("selection_received", self.selection_received) vbox.pack_start(eventbox) eventbox.show() button.show() window.show() def get_stringtarget(self, widget): # 开始获取选择的字符串 widget.selection_convert("PRIMARY", "STRING") return def selection_received(self, widget, selection_data, data): # 开始解析出获取到的字符串 if str(selection_data.type) == "STRING": # 打印获取到的字符串 print u"被选择的字符串: " + selection_data.get_text() elif str(selection_data.type) == "ATOM": # Print out the target list we received targets = selection_data.get_targets() for target in targets: name = str(target) if name is not None: print "%s" % name else: print "(bad target)" else: print "Selection was not returned as \"STRING\" or \"ATOM\"!" return False def main(): gtk.main() return 0 if __name__ == "__main__": GetSelectionExample() main()
以上代码中的逻辑很是清晰,咱们一次建立了窗口,垂直容器,事件容器以及按钮,并将get_stringtarget()函数注册到了按钮的 clicked事件上,而后将selection_received()函数注册打了事件容器的selection_received事件上。在这个例子 中,必定要注意是clicked事件,触发了selection_convert函数,而后该函数检查成功后触发了selection_received 事件。
让咱们来执行以上代码:linux
$ python selection_received.py
要测试该程序,咱们首先应该在任意界面选择字符串,而后点击程序界面上的按钮,这个时候在console就能够看到被选择的字符串了。以下图:
3. WEBVIEW
webview其实就是浏览器控件,所谓浏览器控件是指这个控件能够用来解析html字符串,就像网页同样显示。仍是直接从练习学习吧,建立文件webview.py,输入如下代码:git
#-*- coding:utf-8 -*- import gtk import webkit view = webkit.WebView() sw = gtk.ScrolledWindow() sw.add(view) win = gtk.Window(gtk.WINDOW_TOPLEVEL) win.add(sw) win.set_title("shiyanlou") win.show_all() view.open("http://www.shiyanlou.com") gtk.main()
以上代码中,咱们建立了一个webview,并在具备滚动条的窗口中显示,而后该veiw直接打开了http://www.shiyanlou.com网站。使用如下命令执行该程序:github
python webview.py
能够看到如下输出:web
三.词典程序的实现
到这里程序的整个逻辑已经很是清晰啦。咱们可让selection_convert()方法周期性的执行检查选择事件,而后促发 selection_received事件,接着执行相应的查询函数,将选择到的单词的含义查询显示到webview上。那么还有最后一个问题,咱们怎么 样周期性的执行selection_convert函数呢?在GTK中,咱们能够方便的使用gobject.timeout_add(interval, function, ...)函数注册须要周期性执行的函数,其中interval为周期,单位是毫秒。整个程序的源代码至关清晰,就再也不详细描述了。建立源文件 pyoudao.py,输入如下源码:json
#-*- coding: utf-8 -*- import os import re import time import fcntl import logging import pygtk pygtk.require('2.0') import gtk import gobject import webkit import requests import json HOME = os.getenv("HOME") + '/.youdao-dict/' LOG = HOME + '/pyoudao.log' LOCK = HOME + '/pyoudao.lock' QUERY_URL = 'http://fanyi.youdao.com/openapi.do?keyfrom=tinxing&key=1312427901&type=data&doctype=json&version=1.1&q=' if not os.path.exists(HOME): os.mkdir(HOME) logging.basicConfig(filename=LOG, level=logging.DEBUG) class Dict: def __init__(self): self.mouse_in = False self.popuptime = 0 self.last_selection = '' # 初始化窗口 self.window = gtk.Window(gtk.WINDOW_POPUP) self.window.set_title("pyoudao") self.window.set_border_width(3) self.window.connect("destroy", lambda w: gtk.main_quit()) self.window.resize(360, 200) # 初始化垂直容器 vbox = gtk.VBox(False, 0) vbox.show() # 建立一个事件容器, 并注册selection_recevied事件函数 eventbox = gtk.EventBox() eventbox.connect("selection_received", self._on_selection_received) eventbox.connect('enter-notify-event', self._on_mouse_enter) eventbox.connect('leave-notify-event', self._on_mouse_leave) # 注册周期函数_on_timer,每隔500毫秒执行一次 gobject.timeout_add(500, self._on_timer, eventbox) eventbox.show() # 建立一个webview self.view = webkit.WebView() def title_changed(widget, frame, title): logging.debug('title_changed to %s, will open webbrowser ' % title) import webbrowser webbrowser.open('http://dict.youdao.com/search?le=eng&q=' + title ) self.view.connect('title-changed', title_changed) self.view.show() # 打包各类控件 self.window.add(vbox) vbox.pack_start(eventbox) eventbox.add(self.view) def _on_timer(self, widget): # 开始检查选择事件 widget.selection_convert("PRIMARY", "STRING") if self.window.get_property('visible') and not self.mouse_in: x, y = self.window.get_position() px, py, mods = self.window.get_screen().get_root_window().get_pointer() if (px-x)*(px-x) + (py-y)*(py-y) > 400: logging.debug('distance big enough, hide window') self.window.hide(); if(time.time() - self.popuptime > 3): logging.debug('time long enough, hide window') self.window.hide(); return True # 若是有字符串被选择,则执行该函数 def _on_selection_received(self, widget, selection_data, data): if str(selection_data.type) == "STRING": text = selection_data.get_text() if not text: return False text = text.decode('raw-unicode-escape') if(len(text) > 20): return False if (not text) or (text == self.last_selection): return False logging.info("======== Selected String : %s" % text) self.last_selection = text m = re.search(r'[a-zA-Z-]+', text.encode('utf8')) if not m: logging.info("Query nothing") return False word = m.group(0).lower() if self.ignore(word): logging.info('Ignore Word: ' + word) return False logging.info('QueryWord: ' + word) self.query_word(word) return False # 查询单词 def query_word(self, word): query_url = QUERY_URL + word # 使用requests模块获取json字符串 js= json.loads(requests.get(query_url).text) if 'basic' not in js: logging.info('IgnoreWord: ' + word) return x, y, mods = self.window.get_screen().get_root_window().get_pointer() self.window.move(x+15, y+10) self.window.present() translation = '<br/>'.join(js['translation']) if 'phonetic' in js['basic']: phonetic = js['basic']['phonetic'] else: phonetic = '' explains = '<br/>'.join(js['basic']['explains']) web = '<br/>'.join( ['<a href="javascript:void(0);">%s</a>: %s'%(i['key'], ' '.join(i['value'])) for i in js['web'][:3] ] ) html = ''' <style> .add_to_wordbook { background: url(http://bs.baidu.com/yanglin/add.png) no-repeat; vertical-align: middle; overflow: hidden; display: inline-block; vertical-align: top; width: 24px; padding-top: 26px; height: 0; margin-left: .5em; } </style> <h2> %(translation)s <span style="color: #0B6121; font-size: 12px">< %(phonetic)s > </span> <a href="javascript:void(0);" id="wordbook" class="add_to_wordbook" title="点击在浏览器中打开" onclick="document.title='%(word)s'"></a> <br/> </h2> <span style="color: #A0A0A0; font-size: 15px">[ %(word)s ] </span> <b>基本翻译:</b> <p> %(explains)s </p> <span style="color: #A0A0A0; font-size: 15px">[ %(word)s ] </span> <b>网络释意:</b> <p> %(web)s </p> ''' % locals() # 经过webview显示html字符串 self.view.load_html_string(html, '') self.view.reload() self.popuptime = time.time() def ignore(self, word): if len(word)<=3: return True return False def _on_mouse_enter(self, wid, event): logging.debug('_on_mouse_enter') self.mouse_in = True def _on_mouse_leave(self, *args): logging.debug('_on_mouse_leave') self.mouse_in = False self.window.hide() def main(): Dict() gtk.main() if __name__ == "__main__": f=open(LOCK, 'w') try: fcntl.flock(f.fileno(), fcntl.LOCK_EX|fcntl.LOCK_NB) except: print 'a process is already running!!!' exit(0) main()
执行程序:
$ python pyoudao.py
下面是该程序的效果图:
总的来讲这门项目课相对简单,咱们只用不到300行的代码就实现了一个有道桌面词典,虽然其功能很是简陋。更进一步,咱们能够实现单词白名单、查询缓存、多个源查询等功能,更多的功能还须要你更进一步努力哦。
最后的最后~小编祝你们新年快乐~你们来年再见哈~还有,每天开心,笑口常开,啦啦啦~
若是还有疑问或者不解的地方,欢迎登录实验楼官方网站http://www.shiyanlou.com
查看该项目课的详细步骤和内容:http://www.shiyanlou.com/courses/47
与你们交流分享学习心得:http://forum.shiyanlou.com/forum.php?mod=guide&view=newthread
参考: