基於pygtk的linux有道詞典

1、桌面詞典設計
想把Linux用做桌面系統,其中一部分障礙就是Linux上沒有像有道同樣簡單易用的詞典。其實咱們徹底能夠本身開發一款桌面詞典, 並且開發一款桌面詞典也沒用咱們想象的那麼難。在這門項目課中,咱們就將開發一款很是簡單的桌面詞典,其功能就是:當咱們選中一個單詞時,詞典會將該單詞 的中文(英文)含義而後顯示在新的窗口中。
1. 查詢
那咱們到哪兒去查詢該單詞呢?這裏有兩種方法:javascript

  1. {
  2.     translation: [
  3.         "The lab building"
  4.     ],
  5.     basic: {
  6.         phonetic: "shí yàn lóu",
  7.         explains: [
  8.             "laboratory building",
  9.             "laboratory block"
  10.         ]
  11.     },
  12.     query: "實驗樓",
  13.     errorCode: 0,
  14.     web: [
  15.         {
  16.             value: [
  17.                 "Laboratory Building",
  18.                 "Experimental building"
  19.             ],
  20.             key: "實驗樓"
  21.         },
  22.         {
  23.             value: [
  24.                 "A experimental building",
  25.                 "A laboratory building"
  26.             ],
  27.             key: "一座實驗樓"
  28.         },
  29.         {
  30.             value: [
  31.                 "Three laboratory building",
  32.                 "Three experimental building"
  33.             ],
  34.             key: "三座實驗樓"
  35.         }
  36.     ]
  37. }


雖然經過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

參考:

相關文章
相關標籤/搜索