zoukankan      html  css  js  c++  java
  • 基于pygtk的linux有道词典

    一、桌面词典设计
    想把Linux用作桌面系统,其中一部分障碍就是Linux上没有像有道一样简单易用的词典。其实我们完全可以自己开发一款桌面词典, 而且开发一款桌面词典也没用我们想象的那么难。在这门项目课中,我们就将开发一款非常简单的桌面词典,其功能就是:当我们选中一个单词时,词典会将该单词 的中文(英文)含义然后显示在新的窗口中。
    1. 查询
    那我们到哪儿去查询该单词呢?这里有两种方法:

    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,输入以下源代码:

    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()方法,显示了所有的控件。使用以下命令执行该代码:

    $ python calculator.py



    可以看到以上程序输出了以下画面:



    2. GTK中的事件
    GTK中有各种各样的事件,比如按钮点击事件,选择事件等。又由于GTK中的控件没有X window,所以这些控件本身不具有接收事件的功能。在GTK中如果要让控件接收到事件,必须要先生成一个事件容器控件,然后让控件附加到这个事件容器 中。我们开发的词典程序,会翻译我们选择到的单词,那程序是如何检测到选择到的单词的呢?这就需要selection_received事件了,同时获取 选择事件是一个异步过程,所以要获取选择事件,需要先执行widget.selection_convert()方法。下面让我们练下,创建源文件 selection_received.py,输入以下代码:

    #-*- 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 事件。
    让我们来执行以上代码:

    $ python selection_received.py


    要测试该程序,我们首先应该在任意界面选择字符串,然后点击程序界面上的按钮,这个时候在console就可以看到被选择的字符串了。如下图:
    3. WEBVIEW
    webview其实就是浏览器控件,所谓浏览器控件是指这个控件可以用来解析html字符串,就像网页一样显示。还是直接从练习学习吧,创建文件webview.py,输入以下代码:

    #-*- 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网站。使用以下命令执行该程序:

    python webview.py


    可以看到以下输出:


    三.词典程序的实现
    到这里程序的整个逻辑已经非常清晰啦。我们可以让selection_convert()方法周期性的执行检查选择事件,然后促发 selection_received事件,接着执行相应的查询函数,将选择到的单词的含义查询显示到webview上。那么还有最后一个问题,我们怎么 样周期性的执行selection_convert函数呢?在GTK中,我们可以方便的使用gobject.timeout_add(interval, function, ...)函数注册需要周期性执行的函数,其中interval为周期,单位是毫秒。整个程序的源代码相当清晰,就不再详细描述了。创建源文件 pyoudao.py,输入以下源码:

    #-*- 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;
         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

    参考:

  • 相关阅读:
    机器学习作业12--朴素贝叶斯-垃圾邮件分类
    机器学习作业11--分类与监督学习,朴素贝叶斯分类算法
    机器学习作业9--主成分分析
    机器学习作业8--特征选择
    机器学习作业7--逻辑回归实践
    机器学习作业6--逻辑回归
    实验五 单元测试
    实验二 结对编程 第二阶段
    实验二 结对编程第一阶段
    实验一 GIT代码版本管理
  • 原文地址:https://www.cnblogs.com/shiyanlou/p/4195838.html
Copyright © 2011-2022 走看看