Updating makefil
[apertium.git] / apertium-tools / apertium-view / apertium-view.py
blob36967b721175dd71260fac53f8bbef9938d84a25
1 #!/usr/bin/env python
3 # GTK oriented packages
4 import gobject
5 import gtk, sys
6 import pygtk
8 pygtk.require('2.0')
10 # Logging
11 import logging
13 # Process oriented packages
14 from subprocess import Popen, PIPE
15 import threading
16 from Queue import Queue
18 import dbus
20 from widget import *
21 import TextWidget
23 # Global variables
24 class Globals:
25 marcar = False
26 pipeline_executor = None
27 source_lang_manager = None
28 source_style_manager = None
29 handlers = {}
30 wTree = None
33 class PipelineExecutor(threading.Thread):
34 def __init__(self):
35 threading.Thread.__init__(self)
36 self.queue = Queue()
37 self.setDaemon(True)
38 self.last_f = lambda: None
40 def run(self):
41 while True:
42 self.last_f = self.queue.get()
44 while self.queue.qsize() > 0:
45 self.last_f = self.queue.get()
47 self.last_f()
49 def add(self, stage):
50 self.queue.put(stage)
52 def reexec(self):
53 self.add(self.last_f)
56 class Cell(object):
57 """A really simple dataflow class. It only supports building dataflow chains.
59 >>> a = lambda x: 'a' + x + 'a'
60 >>> b = lambda x: 'b' + x + 'b'
61 >>> c_a = Cell(a)
62 >>> c_b = c_a.set_next(Cell(b))
63 >>> c_a('?')
64 'ba?ab'
66 It just like a chain of lazy function invocations. This class supports the
67 execution of the Apertium pipeline. For each Apertium module, there will be
68 a cell. After every such cell, there is a cell which contains a function to
69 update one of our text views.
70 """
71 def __init__(self, func):
72 self.func = func
73 self.next = lambda x: x
75 def __call__(self, val):
76 out = self.func(val)
77 return self.next(out)
80 # Widget convenience functions
82 def show(widget):
83 widget.show()
84 return widget
87 def configure_combo(combo):
88 combo.set_model(gtk.ListStore(gobject.TYPE_STRING))
89 cell = gtk.CellRendererText()
90 combo.pack_start(cell, True)
91 combo.add_attribute(cell, 'text', 0)
92 return combo
95 def make_text_buffer():
96 buf = sourceview.Buffer()
97 buf.set_language(Globals.source_lang_manager.get_language('apertium'))
98 buf.set_style_scheme(Globals.source_style_manager.get_scheme('tango'))
99 buf.set_highlight_syntax(True)
100 buf.set_highlight_matching_brackets(False)
102 return buf
105 def make_text_widget(cmd):
106 text_buffer = make_text_buffer()
107 src_view = show(make_source_view(text_buffer))
108 return text_buffer, TextWidget.make(" ".join(cmd), src_view)
111 def text_window(title, text_buffer):
112 wTree = gtk.glade.XML("TextWindow.glade")
114 wnd = wTree.get_widget("text_window")
115 wnd.set_title(title)
117 def close(widget, data = None):
118 wnd.hide()
119 wnd.destroy()
121 wnd.connect("destroy", close)
123 text_view = make_source_view(text_buffer)
124 scrolled_window = wTree.get_widget("scrolled_window")
125 scrolled_window.add_with_viewport(text_view)
127 wTree.signal_autoconnect({'on_btn_close_clicked': close})
129 wnd.show()
132 # GTK Handlers which are automatically connected to the Glade
133 # events with the same names
135 @gtk_handler
136 def on_wndMain_destroy(widget, data = None):
137 gtk.main_quit()
140 @gtk_handler
141 def on_btnQuit_clicked(widget, data = None):
142 gtk.main_quit()
145 @gtk_handler
146 def on_wndMain_delete_event(widget, event, data = None):
147 return False
150 @gtk_handler
151 def on_chkMarkUnknown_toggled(widget, data = None):
152 Globals.marcar = not Globals.marcar
153 Globals.pipeline_executor.reexec()
156 @gtk_handler
157 def on_comboPair_changed(widget, data = None):
158 setup_pair(widget.get_model().get_value((widget.get_active_iter()), 0))
161 @gtk_handler
162 def on_scrollMain_size_allocate(widget, allocation, data = None):
163 #print "x=%d, y=%d, width=%d, height=%d" % (allocation.x, allocation.y, allocation.width, allocation.height)
164 #TODO: Add code to resize the contents of the main scroll window.
165 pass
167 def call(params, _input):
168 from subprocess import Popen, PIPE
170 proc = Popen(params, stdin=PIPE, stdout=PIPE, stderr=PIPE)
171 return proc.communicate(_input)
174 def make_runner(cmd):
175 def process_cmd_line(cmd):
176 if len(cmd) > 1 and cmd[1] == '$1' and Globals.marcar:
177 return cmd[0], '-g', cmd[2];
178 elif len(cmd) > 1 and cmd[1] == '$1' and not Globals.marcar:
179 return cmd[0], '-n', cmd[2];
180 else:
181 return cmd
184 def runner(val):
185 out, err = call(list(process_cmd_line(cmd)), str(val))
186 return out
188 return Cell(runner)
191 def make_observer(text_buffer, update_handler):
192 """An observer cell is used to update the contents of a GTK text buffer. See the doc for setup_pair to
193 understand where observers fit in.
196 def observer(val):
197 def post_update():
198 # If we don't block the onchange event, then we'll invoke two updates
199 # because and update will already occur due to data flowing through
200 # cell pipeline
201 text_buffer.handler_block(update_handler)
202 text_buffer.set_text(val)
203 text_buffer.handler_unblock(update_handler)
205 # This will be executed in the GTK main loop
206 gobject.idle_add(post_update)
207 return val
209 return Cell(observer)
212 def update(widget, runner):
213 def get_text(buf):
214 return buf.get_text(buf.get_start_iter(), buf.get_end_iter())
216 Globals.pipeline_executor.add(lambda: runner(get_text(widget)))
219 def replace_child(container, new_child):
220 child = container.get_children()[0] # we must keep a reference (child) before removing it from portMain
221 container.remove(child)
222 container.add(new_child)
225 def setup_pair(name):
226 """setup_pair is responsible for:
227 1.) Creating the GTK TextViews for each of the stages used in the processing of the pair named by 'name'
228 2.) Creating a dataflow pipeline which will:
229 2.1) be used to determine in which TextView the user made any changes,
230 2.2) updating the TextViews as data flows through the pipeline.
232 Currently, we build a pipeline which looks something like the following (for en-af):
234 Filter first TextView related to the TextView for lt-proc related to the TextView for apertium-tagger
235 __________/\___________ _________________/\__________________ _____________________/\_____________________
236 / \ / \ / \
237 [runner: apertium-destxt]->[runner: lt-proc]->[observer]->[update]->[runner: apertium-tagger]->[observer]->[update]->...
238 /\ \ /\ \ /\
239 / \ / \ /
240 changed set_text changed set_text changed
241 / \ / \ /
242 / \ / \ /
243 / \/ / \/ /
244 GTK TextView GTK TextView GTK TextView
247 Consider the cells related to a the TextView for lt-proc (this TextView should show up as the second TextView from the
248 top in apertium-view for the en-af mode). In order to create what we see in that TextView,
249 lt-proc /usr/local/share/apertium/apertium-en-af/en-af.automorf.bin
250 must be executed on the input text (from the very first TextView). But the first cell (marked as '[runner: lt-proc]')
251 only invokes lt-proc in a subprocess and pushes the result to the next cell. We would like to display the output of
252 lt-proc in the TextView, so the next cell in the pipeline is an 'observer' which sets the text of the TextView.
254 Suppose now that the user modifies the text in the lt-proc TextView we are considering. We would like this change to
255 be reflected in the *rest* of the TextViews, but the current TextView and all preceding it must remain unchanged.
256 So we insert an update cell *after* the other cells for the lt-proc stage. We set the onchange event of the lt-proc
257 TextView to invoke this update cell.
259 Thus, when the user modifies the text in the lt-proc TextView, you can see from the cell pipeline above that the
260 change will flow into the cell which executes apertium-tagger; afterwards the observer cell for the apertium-tagger
261 window will update the contents of the TextView corresponding to the apertium-tagger phase. The data will continue
262 flowing down the pipeline updating the remaining TextViews.
265 def next(cell, obj):
266 cell.next = obj
267 return cell.next
269 pack_opts = {'expand': False, 'fill': True}
271 view_box = show(gtk.VBox(homogeneous = False))
273 in_filter, out_filter = Globals.info.get_filters('txt') # this will likely be apertium-destxt and apertium-retxt
274 cell = make_runner([in_filter]) # Add the deformatter
276 text_widgets = []
277 def _make_text_widget(*args):
278 text_buffer, text_widget = make_text_widget(*args)
279 text_widgets.append(text_widget)
280 return text_buffer, text_widget
282 text_buffer, text_widget = _make_text_widget(['input text'])
283 text_buffer.connect("changed", update, cell)
284 view_box.pack_start(text_widget, **pack_opts)
286 pipeline = Globals.info.get_pipeline(name)
287 for i, cmd in enumerate(pipeline):
288 cell = next(cell, make_runner([str(c) for c in cmd]))
290 text_buffer, text_widget = _make_text_widget(cmd)
292 update_cell = Cell(lambda x: x)
293 update_handler = text_buffer.connect("changed", update, update_cell)
295 if i == len(pipeline) - 1: # Before our last TextView, insert an apertium-retxt phase
296 cell = next(cell, make_runner([out_filter]))
298 cell = next(cell, make_observer(text_buffer, update_handler)) # Corresponds to [observer] in description above
299 cell = next(cell, update_cell) # Corresponds to [update] in description above
301 view_box.pack_start(text_widget, **pack_opts) # Add a TextView to our window
303 text_widgets[0].wTree.get_widget("btnCollapsed").set_active(False) # Uncollapse the first and last
304 text_widgets[-1].wTree.get_widget("btnCollapsed").set_active(False) # TextViews
306 replace_child(Globals.wTree.get_widget("portMain"), view_box)
309 def main_window():
310 Globals.wTree = glade_load_and_connect("MainWindow.glade")
311 comboPair = configure_combo(Globals.wTree.get_widget("comboPair"))
313 for mode in Globals.info.modes():
314 comboPair.append_text(str(mode))
316 comboPair.set_active(0)
319 def setup_logging():
320 logging.basicConfig(level=logging.DEBUG,
321 format='%(asctime)s %(levelname)-8s %(message)s',
322 datefmt='%a, %d %b %Y %H:%M:%S',
323 filename='apertium.log',
324 filemode='w')
327 def init():
328 Globals.pipeline_executor = PipelineExecutor()
329 Globals.pipeline_executor.start()
331 Globals.source_lang_manager = sourceview.language_manager_get_default()
332 Globals.source_style_manager = sourceview.style_scheme_manager_get_default()
333 Globals.info = dbus.Interface(dbus.SessionBus().get_object("org.apertium.info", "/"), "org.apertium.Info")
335 setup_logging()
336 main_window()
339 if __name__ == "__main__":
340 gtk.gdk.threads_init()
341 init()
342 logging.debug('Completed init phase')
343 gtk.main()
344 logging.debug('Graceful shutdown')