Commit | Line | Data |
---|---|---|
e603e417 P |
1 | #!/usr/bin/env python2.5 |
2 | # -*- coding: utf-8 -*- | |
3 | """ | |
4 | PygTranslator - pas de doc cette fois-ci... :-P | |
5 | ||
6 | Copyright : Agence universitaire de la Francophonie | |
7 | Licence : GNU General Public Licence, version 2 | |
8 | Auteur : Jean Christophe André | |
9 | Date de création : 28 septembre 2009 | |
10 | """ | |
11 | ||
12 | from threading import Thread | |
13 | from Queue import Queue | |
14 | import xmpp | |
15 | ||
16 | import pygtk | |
17 | pygtk.require('2.0') | |
18 | import gtk, gobject | |
19 | ||
20 | def DEBUG(message): | |
69eefce0 | 21 | print "\x1b[31;1mDEBUG\x1b[m:", message |
e603e417 P |
22 | return |
23 | ||
24 | class XmppTranslator(Thread): | |
25 | ||
26 | def __init__(self, jid, password): | |
27 | Thread.__init__(self) | |
28 | self.setDaemon(True) | |
69eefce0 P |
29 | self._quit = False |
30 | ||
e603e417 P |
31 | self._input_queue = Queue(0) |
32 | self._output_queue = Queue(0) | |
69eefce0 | 33 | self._last_text_to_translate = {} |
e603e417 P |
34 | |
35 | self._jid = xmpp.JID(jid) | |
36 | self._password = password | |
37 | if self._jid.getResource() == '': | |
38 | self._jid.setResource('translator') | |
39 | ||
40 | self._client = xmpp.Client(self._jid.getDomain(), debug=[]) | |
41 | ||
42 | def __del__(self): | |
43 | if self._client and not self._client.isConnected(): | |
44 | return | |
45 | self._client.sendPresence(typ='unavailable') | |
46 | self._client.disconnect() | |
47 | ||
48 | def _connect(self): | |
49 | try: | |
50 | if not self._client.connect(): | |
51 | raise IOError | |
52 | except IOError: | |
53 | raise IOError, "can't connect to '%s'" % self._jid.getDomain() | |
54 | self._client.RegisterHandler('message', self._messageHandler) | |
55 | ||
56 | auth = self._client.auth(self._jid.getNode(), self._password, | |
57 | self._jid.getResource()) | |
58 | if not auth: | |
59 | raise IOError, "unable to authenticate '%s'" % self._jid.getNode() | |
60 | self._client.sendInitPresence(requestRoster=0) | |
61 | ||
62 | def _messageHandler(self, client, message): | |
63 | if message.getType() == 'error': | |
64 | return | |
65 | translation_path = message.getFrom().getNode() | |
66 | text = message.getBody() | |
67 | self._output_queue.put( (translation_path, text) ) | |
68 | raise xmpp.NodeProcessed | |
69 | ||
70 | def _process_input_queue(self): | |
71 | if self._input_queue.empty(): | |
72 | return | |
73 | # process pending translation job | |
74 | translation_path, text = self._input_queue.get() | |
75 | jid = '%s@bot.talk.google.com' % translation_path | |
76 | message = xmpp.Message(to=jid, typ='chat', body=text) | |
77 | self._client.send(message) | |
78 | DEBUG("sent [%s] to '%s'." % (text, jid)) | |
79 | self._input_queue.task_done() | |
80 | ||
81 | def run(self): | |
82 | self._connect() | |
83 | self._client.Process(1) | |
84 | while not self._quit: | |
85 | self._process_input_queue() | |
86 | self._client.Process(1) | |
87 | ||
88 | def request_translation(self, translation_path, text): | |
69eefce0 P |
89 | text = text.strip().replace('\r\n','\n') |
90 | # skip consecutive identical requests | |
91 | if text == self._last_text_to_translate.get(translation_path): | |
92 | return | |
93 | self._last_text_to_translate[translation_path] = text | |
e603e417 P |
94 | # remove any pending translation job |
95 | while not self._input_queue.empty(): | |
96 | self._input_queue.get() | |
97 | DEBUG("removed a pending translation job") | |
98 | self._input_queue.task_done() | |
99 | # put a new translation job | |
100 | self._input_queue.put( (translation_path, text) ) | |
101 | ||
102 | def translation_result(self): | |
103 | if self._output_queue.empty(): | |
104 | return None | |
105 | translation_path, text = self._output_queue.get() | |
106 | return translation_path, text | |
107 | ||
108 | def translation_done(self): | |
109 | self._output_queue.task_done() | |
110 | ||
111 | class GtkTranslator(object): | |
112 | ||
113 | def __init__(self, translator): | |
114 | self._translator = translator | |
115 | ||
116 | self._textbuf = {} | |
117 | ||
118 | self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) | |
119 | self.window.set_title(u"PygTranslator") | |
120 | self.window.set_border_width(0) | |
121 | self.window.connect("destroy", lambda w: gtk.main_quit()) | |
69eefce0 | 122 | self.window.connect("delete_event", self._delete_event) |
e603e417 P |
123 | |
124 | vbox = gtk.VBox(True, 0) | |
125 | vbox.show() | |
126 | ||
127 | textview = gtk.TextView() | |
128 | textview.set_size_request(400, 100) | |
129 | textview.set_wrap_mode(gtk.WRAP_WORD) | |
130 | textview.show() | |
131 | scroll = gtk.ScrolledWindow() | |
132 | scroll.add(textview) | |
133 | scroll.show() | |
134 | frame = gtk.Frame(u"Texte en français") | |
69eefce0 P |
135 | frame.connect("key_release_event", self._text_change, 'fr') |
136 | frame.connect("button_release_event", self._text_change, 'fr') | |
e603e417 P |
137 | frame.add(scroll) |
138 | frame.show() | |
139 | vbox.pack_start(frame, True, True, 0) | |
140 | self._textbuf['fr'] = textview.get_buffer() | |
141 | ||
142 | textview = gtk.TextView() | |
143 | textview.set_size_request(400, 100) | |
144 | textview.set_wrap_mode(gtk.WRAP_WORD) | |
145 | textview.show() | |
146 | scroll = gtk.ScrolledWindow() | |
147 | scroll.add(textview) | |
148 | scroll.show() | |
149 | frame = gtk.Frame(u"Text in English") | |
69eefce0 P |
150 | frame.connect("key_release_event", self._text_change, 'en') |
151 | frame.connect("button_release_event", self._text_change, 'en') | |
e603e417 P |
152 | frame.add(scroll) |
153 | frame.show() | |
154 | vbox.pack_start(frame, True, True, 0) | |
155 | self._textbuf['en'] = textview.get_buffer() | |
156 | ||
157 | self.window.add(vbox) | |
158 | self.window.show() | |
159 | ||
69eefce0 | 160 | def _delete_event(self, widget, event, data=None): |
e603e417 P |
161 | DEBUG("caught delete event") |
162 | gtk.main_quit() | |
163 | return False | |
164 | ||
69eefce0 | 165 | def _text_change(self, widget, event, lang): |
e603e417 P |
166 | start, end = self._textbuf[lang].get_bounds() |
167 | text = self._textbuf[lang].get_text(start, end) | |
168 | other_lang = (set(self._textbuf.keys()) - set((lang,))).pop() | |
169 | translation_path = lang + '2' + other_lang | |
170 | self._translator.request_translation(translation_path, text) | |
171 | ||
69eefce0 | 172 | def _process_translation_result(self): |
e603e417 P |
173 | result = {} |
174 | # FIXME: to transform into an iterator... | |
175 | while True: | |
176 | r = self._translator.translation_result() | |
177 | if r is None: | |
178 | break | |
179 | translation_path, text = r | |
180 | lang = translation_path[3:] | |
181 | if lang in self._textbuf: | |
182 | result[lang] = text | |
183 | else: | |
184 | DEBUG("unknown destination language '%s'" % lang) | |
185 | self._translator.translation_done() | |
186 | for lang, text in result.items(): | |
187 | self._textbuf[lang].set_text(text) | |
69eefce0 P |
188 | gobject.timeout_add(300, self._process_translation_result) |
189 | ||
190 | def run(self): | |
191 | DEBUG("running graphic interface") | |
192 | gobject.timeout_add(300, self._process_translation_result) | |
193 | gtk.gdk.threads_init() | |
194 | gtk.main() | |
e603e417 P |
195 | |
196 | if __name__ == "__main__": | |
197 | import sys | |
198 | if len(sys.argv) != 3: | |
199 | print "Usage: %s <jid> <password>" % sys.argv[0] | |
200 | sys.exit(1) | |
201 | ||
202 | translator = XmppTranslator(sys.argv[1], sys.argv[2]) | |
203 | translator.start() | |
204 | ||
205 | pygtranslator = GtkTranslator(translator) | |
206 | try: | |
207 | pygtranslator.run() | |
208 | except KeyboardInterrupt: | |
209 | pass | |
210 | ||
211 | translator._quit = True | |
212 | translator.join() | |
213 | ||
214 | sys.exit(0) | |
215 |