3 # GTK oriented packages
13 # Process oriented packages
14 from subprocess
import Popen
, PIPE
16 from Queue
import Queue
26 pipeline_executor
= None
27 source_lang_manager
= None
28 source_style_manager
= None
33 class PipelineExecutor(threading
.Thread
):
35 threading
.Thread
.__init
__(self
)
38 self
.last_f
= lambda: None
42 self
.last_f
= self
.queue
.get()
44 while self
.queue
.qsize() > 0:
45 self
.last_f
= self
.queue
.get()
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'
62 >>> c_b = c_a.set_next(Cell(b))
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.
71 def __init__(self
, func
):
73 self
.next
= lambda x
: x
75 def __call__(self
, val
):
80 # Widget convenience functions
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)
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)
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")
117 def close(widget
, data
= None):
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
})
132 # GTK Handlers which are automatically connected to the Glade
133 # events with the same names
136 def on_wndMain_destroy(widget
, data
= None):
141 def on_btnQuit_clicked(widget
, data
= None):
146 def on_wndMain_delete_event(widget
, event
, data
= None):
151 def on_chkMarkUnknown_toggled(widget
, data
= None):
152 Globals
.marcar
= not Globals
.marcar
153 Globals
.pipeline_executor
.reexec()
157 def on_comboPair_changed(widget
, data
= None):
158 setup_pair(widget
.get_model().get_value((widget
.get_active_iter()), 0))
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.
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];
185 out
, err
= call(list(process_cmd_line(cmd
)), str(val
))
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.
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
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
)
209 return Cell(observer
)
212 def update(widget
, runner
):
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 __________/\___________ _________________/\__________________ _____________________/\_____________________
237 [runner: apertium-destxt]->[runner: lt-proc]->[observer]->[update]->[runner: apertium-tagger]->[observer]->[update]->...
240 changed set_text changed set_text changed
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.
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
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
)
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)
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',
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")
339 if __name__
== "__main__":
340 gtk
.gdk
.threads_init()
342 logging
.debug('Completed init phase')
344 logging
.debug('Graceful shutdown')