diff options
Diffstat (limited to 'bitbake/lib/bb/ui/crumbs/runningbuild.py')
-rw-r--r-- | bitbake/lib/bb/ui/crumbs/runningbuild.py | 551 |
1 files changed, 551 insertions, 0 deletions
diff --git a/bitbake/lib/bb/ui/crumbs/runningbuild.py b/bitbake/lib/bb/ui/crumbs/runningbuild.py new file mode 100644 index 0000000000..16a955d2b1 --- /dev/null +++ b/bitbake/lib/bb/ui/crumbs/runningbuild.py | |||
@@ -0,0 +1,551 @@ | |||
1 | |||
2 | # | ||
3 | # BitBake Graphical GTK User Interface | ||
4 | # | ||
5 | # Copyright (C) 2008 Intel Corporation | ||
6 | # | ||
7 | # Authored by Rob Bradford <rob@linux.intel.com> | ||
8 | # | ||
9 | # This program is free software; you can redistribute it and/or modify | ||
10 | # it under the terms of the GNU General Public License version 2 as | ||
11 | # published by the Free Software Foundation. | ||
12 | # | ||
13 | # This program is distributed in the hope that it will be useful, | ||
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
16 | # GNU General Public License for more details. | ||
17 | # | ||
18 | # You should have received a copy of the GNU General Public License along | ||
19 | # with this program; if not, write to the Free Software Foundation, Inc., | ||
20 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
21 | |||
22 | import gtk | ||
23 | import gobject | ||
24 | import logging | ||
25 | import time | ||
26 | import urllib | ||
27 | import urllib2 | ||
28 | import pango | ||
29 | from bb.ui.crumbs.hobcolor import HobColors | ||
30 | from bb.ui.crumbs.hobwidget import HobWarpCellRendererText, HobCellRendererPixbuf | ||
31 | |||
32 | class RunningBuildModel (gtk.TreeStore): | ||
33 | (COL_LOG, COL_PACKAGE, COL_TASK, COL_MESSAGE, COL_ICON, COL_COLOR, COL_NUM_ACTIVE) = range(7) | ||
34 | |||
35 | def __init__ (self): | ||
36 | gtk.TreeStore.__init__ (self, | ||
37 | gobject.TYPE_STRING, | ||
38 | gobject.TYPE_STRING, | ||
39 | gobject.TYPE_STRING, | ||
40 | gobject.TYPE_STRING, | ||
41 | gobject.TYPE_STRING, | ||
42 | gobject.TYPE_STRING, | ||
43 | gobject.TYPE_INT) | ||
44 | |||
45 | def failure_model_filter(self, model, it): | ||
46 | color = model.get(it, self.COL_COLOR)[0] | ||
47 | if not color: | ||
48 | return False | ||
49 | if color == HobColors.ERROR or color == HobColors.WARNING: | ||
50 | return True | ||
51 | return False | ||
52 | |||
53 | def failure_model(self): | ||
54 | model = self.filter_new() | ||
55 | model.set_visible_func(self.failure_model_filter) | ||
56 | return model | ||
57 | |||
58 | def foreach_cell_func(self, model, path, iter, usr_data=None): | ||
59 | if model.get_value(iter, self.COL_ICON) == "gtk-execute": | ||
60 | model.set(iter, self.COL_ICON, "") | ||
61 | |||
62 | def close_task_refresh(self): | ||
63 | self.foreach(self.foreach_cell_func, None) | ||
64 | |||
65 | class RunningBuild (gobject.GObject): | ||
66 | __gsignals__ = { | ||
67 | 'build-started' : (gobject.SIGNAL_RUN_LAST, | ||
68 | gobject.TYPE_NONE, | ||
69 | ()), | ||
70 | 'build-succeeded' : (gobject.SIGNAL_RUN_LAST, | ||
71 | gobject.TYPE_NONE, | ||
72 | ()), | ||
73 | 'build-failed' : (gobject.SIGNAL_RUN_LAST, | ||
74 | gobject.TYPE_NONE, | ||
75 | ()), | ||
76 | 'build-complete' : (gobject.SIGNAL_RUN_LAST, | ||
77 | gobject.TYPE_NONE, | ||
78 | ()), | ||
79 | 'build-aborted' : (gobject.SIGNAL_RUN_LAST, | ||
80 | gobject.TYPE_NONE, | ||
81 | ()), | ||
82 | 'task-started' : (gobject.SIGNAL_RUN_LAST, | ||
83 | gobject.TYPE_NONE, | ||
84 | (gobject.TYPE_PYOBJECT,)), | ||
85 | 'log-error' : (gobject.SIGNAL_RUN_LAST, | ||
86 | gobject.TYPE_NONE, | ||
87 | ()), | ||
88 | 'log-warning' : (gobject.SIGNAL_RUN_LAST, | ||
89 | gobject.TYPE_NONE, | ||
90 | ()), | ||
91 | 'disk-full' : (gobject.SIGNAL_RUN_LAST, | ||
92 | gobject.TYPE_NONE, | ||
93 | ()), | ||
94 | 'no-provider' : (gobject.SIGNAL_RUN_LAST, | ||
95 | gobject.TYPE_NONE, | ||
96 | (gobject.TYPE_PYOBJECT,)), | ||
97 | 'log' : (gobject.SIGNAL_RUN_LAST, | ||
98 | gobject.TYPE_NONE, | ||
99 | (gobject.TYPE_STRING, gobject.TYPE_PYOBJECT,)), | ||
100 | } | ||
101 | pids_to_task = {} | ||
102 | tasks_to_iter = {} | ||
103 | |||
104 | def __init__ (self, sequential=False): | ||
105 | gobject.GObject.__init__ (self) | ||
106 | self.model = RunningBuildModel() | ||
107 | self.sequential = sequential | ||
108 | self.buildaborted = False | ||
109 | |||
110 | def reset (self): | ||
111 | self.pids_to_task.clear() | ||
112 | self.tasks_to_iter.clear() | ||
113 | self.model.clear() | ||
114 | |||
115 | def handle_event (self, event, pbar=None): | ||
116 | # Handle an event from the event queue, this may result in updating | ||
117 | # the model and thus the UI. Or it may be to tell us that the build | ||
118 | # has finished successfully (or not, as the case may be.) | ||
119 | |||
120 | parent = None | ||
121 | pid = 0 | ||
122 | package = None | ||
123 | task = None | ||
124 | |||
125 | # If we have a pid attached to this message/event try and get the | ||
126 | # (package, task) pair for it. If we get that then get the parent iter | ||
127 | # for the message. | ||
128 | if hasattr(event, 'pid'): | ||
129 | pid = event.pid | ||
130 | if hasattr(event, 'process'): | ||
131 | pid = event.process | ||
132 | |||
133 | if pid and pid in self.pids_to_task: | ||
134 | (package, task) = self.pids_to_task[pid] | ||
135 | parent = self.tasks_to_iter[(package, task)] | ||
136 | |||
137 | if(isinstance(event, logging.LogRecord)): | ||
138 | if event.taskpid == 0 or event.levelno > logging.INFO: | ||
139 | self.emit("log", "handle", event) | ||
140 | # FIXME: this is a hack! More info in Yocto #1433 | ||
141 | # http://bugzilla.pokylinux.org/show_bug.cgi?id=1433, temporarily | ||
142 | # mask the error message as it's not informative for the user. | ||
143 | if event.msg.startswith("Execution of event handler 'run_buildstats' failed"): | ||
144 | return | ||
145 | |||
146 | if (event.levelno < logging.INFO or | ||
147 | event.msg.startswith("Running task")): | ||
148 | return # don't add these to the list | ||
149 | |||
150 | if event.levelno >= logging.ERROR: | ||
151 | icon = "dialog-error" | ||
152 | color = HobColors.ERROR | ||
153 | self.emit("log-error") | ||
154 | elif event.levelno >= logging.WARNING: | ||
155 | icon = "dialog-warning" | ||
156 | color = HobColors.WARNING | ||
157 | self.emit("log-warning") | ||
158 | else: | ||
159 | icon = None | ||
160 | color = HobColors.OK | ||
161 | |||
162 | # if we know which package we belong to, we'll append onto its list. | ||
163 | # otherwise, we'll jump to the top of the master list | ||
164 | if self.sequential or not parent: | ||
165 | tree_add = self.model.append | ||
166 | else: | ||
167 | tree_add = self.model.prepend | ||
168 | tree_add(parent, | ||
169 | (None, | ||
170 | package, | ||
171 | task, | ||
172 | event.getMessage(), | ||
173 | icon, | ||
174 | color, | ||
175 | 0)) | ||
176 | |||
177 | # if there are warnings while processing a package | ||
178 | # (parent), mark the task with warning color; | ||
179 | # in case there are errors, the updates will be | ||
180 | # handled on TaskFailed. | ||
181 | if color == HobColors.WARNING and parent: | ||
182 | self.model.set(parent, self.model.COL_COLOR, color) | ||
183 | if task: #then we have a parent (package), and update it's color | ||
184 | self.model.set(self.tasks_to_iter[(package, None)], self.model.COL_COLOR, color) | ||
185 | |||
186 | elif isinstance(event, bb.build.TaskStarted): | ||
187 | (package, task) = (event._package, event._task) | ||
188 | |||
189 | # Save out this PID. | ||
190 | self.pids_to_task[pid] = (package, task) | ||
191 | |||
192 | # Check if we already have this package in our model. If so then | ||
193 | # that can be the parent for the task. Otherwise we create a new | ||
194 | # top level for the package. | ||
195 | if ((package, None) in self.tasks_to_iter): | ||
196 | parent = self.tasks_to_iter[(package, None)] | ||
197 | else: | ||
198 | if self.sequential: | ||
199 | add = self.model.append | ||
200 | else: | ||
201 | add = self.model.prepend | ||
202 | parent = add(None, (None, | ||
203 | package, | ||
204 | None, | ||
205 | "Package: %s" % (package), | ||
206 | None, | ||
207 | HobColors.OK, | ||
208 | 0)) | ||
209 | self.tasks_to_iter[(package, None)] = parent | ||
210 | |||
211 | # Because this parent package now has an active child mark it as | ||
212 | # such. | ||
213 | self.model.set(parent, self.model.COL_ICON, "gtk-execute") | ||
214 | parent_color = self.model.get(parent, self.model.COL_COLOR)[0] | ||
215 | if parent_color != HobColors.ERROR and parent_color != HobColors.WARNING: | ||
216 | self.model.set(parent, self.model.COL_COLOR, HobColors.RUNNING) | ||
217 | |||
218 | # Add an entry in the model for this task | ||
219 | i = self.model.append (parent, (None, | ||
220 | package, | ||
221 | task, | ||
222 | "Task: %s" % (task), | ||
223 | "gtk-execute", | ||
224 | HobColors.RUNNING, | ||
225 | 0)) | ||
226 | |||
227 | # update the parent's active task count | ||
228 | num_active = self.model.get(parent, self.model.COL_NUM_ACTIVE)[0] + 1 | ||
229 | self.model.set(parent, self.model.COL_NUM_ACTIVE, num_active) | ||
230 | |||
231 | # Save out the iter so that we can find it when we have a message | ||
232 | # that we need to attach to a task. | ||
233 | self.tasks_to_iter[(package, task)] = i | ||
234 | |||
235 | elif isinstance(event, bb.build.TaskBase): | ||
236 | self.emit("log", "info", event._message) | ||
237 | current = self.tasks_to_iter[(package, task)] | ||
238 | parent = self.tasks_to_iter[(package, None)] | ||
239 | |||
240 | # remove this task from the parent's active count | ||
241 | num_active = self.model.get(parent, self.model.COL_NUM_ACTIVE)[0] - 1 | ||
242 | self.model.set(parent, self.model.COL_NUM_ACTIVE, num_active) | ||
243 | |||
244 | if isinstance(event, bb.build.TaskFailed): | ||
245 | # Mark the task and parent as failed | ||
246 | icon = "dialog-error" | ||
247 | color = HobColors.ERROR | ||
248 | |||
249 | logfile = event.logfile | ||
250 | if logfile and os.path.exists(logfile): | ||
251 | with open(logfile) as f: | ||
252 | logdata = f.read() | ||
253 | self.model.append(current, ('pastebin', None, None, logdata, 'gtk-error', HobColors.OK, 0)) | ||
254 | |||
255 | for i in (current, parent): | ||
256 | self.model.set(i, self.model.COL_ICON, icon, | ||
257 | self.model.COL_COLOR, color) | ||
258 | else: | ||
259 | # Mark the parent package and the task as inactive, | ||
260 | # but make sure to preserve error, warnings and active | ||
261 | # states | ||
262 | parent_color = self.model.get(parent, self.model.COL_COLOR)[0] | ||
263 | task_color = self.model.get(current, self.model.COL_COLOR)[0] | ||
264 | |||
265 | # Mark the task as inactive | ||
266 | self.model.set(current, self.model.COL_ICON, None) | ||
267 | if task_color != HobColors.ERROR: | ||
268 | if task_color == HobColors.WARNING: | ||
269 | self.model.set(current, self.model.COL_ICON, 'dialog-warning') | ||
270 | else: | ||
271 | self.model.set(current, self.model.COL_COLOR, HobColors.OK) | ||
272 | |||
273 | # Mark the parent as inactive | ||
274 | if parent_color != HobColors.ERROR: | ||
275 | if parent_color == HobColors.WARNING: | ||
276 | self.model.set(parent, self.model.COL_ICON, "dialog-warning") | ||
277 | else: | ||
278 | self.model.set(parent, self.model.COL_ICON, None) | ||
279 | if num_active == 0: | ||
280 | self.model.set(parent, self.model.COL_COLOR, HobColors.OK) | ||
281 | |||
282 | # Clear the iters and the pids since when the task goes away the | ||
283 | # pid will no longer be used for messages | ||
284 | del self.tasks_to_iter[(package, task)] | ||
285 | del self.pids_to_task[pid] | ||
286 | |||
287 | elif isinstance(event, bb.event.BuildStarted): | ||
288 | |||
289 | self.emit("build-started") | ||
290 | self.model.prepend(None, (None, | ||
291 | None, | ||
292 | None, | ||
293 | "Build Started (%s)" % time.strftime('%m/%d/%Y %H:%M:%S'), | ||
294 | None, | ||
295 | HobColors.OK, | ||
296 | 0)) | ||
297 | if pbar: | ||
298 | pbar.update(0, self.progress_total) | ||
299 | pbar.set_title(bb.event.getName(event)) | ||
300 | |||
301 | elif isinstance(event, bb.event.BuildCompleted): | ||
302 | failures = int (event._failures) | ||
303 | self.model.prepend(None, (None, | ||
304 | None, | ||
305 | None, | ||
306 | "Build Completed (%s)" % time.strftime('%m/%d/%Y %H:%M:%S'), | ||
307 | None, | ||
308 | HobColors.OK, | ||
309 | 0)) | ||
310 | |||
311 | # Emit the appropriate signal depending on the number of failures | ||
312 | if self.buildaborted: | ||
313 | self.emit ("build-aborted") | ||
314 | self.buildaborted = False | ||
315 | elif (failures >= 1): | ||
316 | self.emit ("build-failed") | ||
317 | else: | ||
318 | self.emit ("build-succeeded") | ||
319 | # Emit a generic "build-complete" signal for things wishing to | ||
320 | # handle when the build is finished | ||
321 | self.emit("build-complete") | ||
322 | # reset the all cell's icon indicator | ||
323 | self.model.close_task_refresh() | ||
324 | if pbar: | ||
325 | pbar.set_text(event.msg) | ||
326 | |||
327 | elif isinstance(event, bb.event.DiskFull): | ||
328 | self.buildaborted = True | ||
329 | self.emit("disk-full") | ||
330 | |||
331 | elif isinstance(event, bb.command.CommandFailed): | ||
332 | self.emit("log", "error", "Command execution failed: %s" % (event.error)) | ||
333 | if event.error.startswith("Exited with"): | ||
334 | # If the command fails with an exit code we're done, emit the | ||
335 | # generic signal for the UI to notify the user | ||
336 | self.emit("build-complete") | ||
337 | # reset the all cell's icon indicator | ||
338 | self.model.close_task_refresh() | ||
339 | |||
340 | elif isinstance(event, bb.event.CacheLoadStarted) and pbar: | ||
341 | pbar.set_title("Loading cache") | ||
342 | self.progress_total = event.total | ||
343 | pbar.update(0, self.progress_total) | ||
344 | elif isinstance(event, bb.event.CacheLoadProgress) and pbar: | ||
345 | pbar.update(event.current, self.progress_total) | ||
346 | elif isinstance(event, bb.event.CacheLoadCompleted) and pbar: | ||
347 | pbar.update(self.progress_total, self.progress_total) | ||
348 | pbar.hide() | ||
349 | elif isinstance(event, bb.event.ParseStarted) and pbar: | ||
350 | if event.total == 0: | ||
351 | return | ||
352 | pbar.set_title("Processing recipes") | ||
353 | self.progress_total = event.total | ||
354 | pbar.update(0, self.progress_total) | ||
355 | elif isinstance(event, bb.event.ParseProgress) and pbar: | ||
356 | pbar.update(event.current, self.progress_total) | ||
357 | elif isinstance(event, bb.event.ParseCompleted) and pbar: | ||
358 | pbar.hide() | ||
359 | #using runqueue events as many as possible to update the progress bar | ||
360 | elif isinstance(event, bb.runqueue.runQueueTaskFailed): | ||
361 | self.emit("log", "error", "Task %s (%s) failed with exit code '%s'" % (event.taskid, event.taskstring, event.exitcode)) | ||
362 | elif isinstance(event, bb.runqueue.sceneQueueTaskFailed): | ||
363 | self.emit("log", "warn", "Setscene task %s (%s) failed with exit code '%s' - real task will be run instead" \ | ||
364 | % (event.taskid, event.taskstring, event.exitcode)) | ||
365 | elif isinstance(event, (bb.runqueue.runQueueTaskStarted, bb.runqueue.sceneQueueTaskStarted)): | ||
366 | if isinstance(event, bb.runqueue.sceneQueueTaskStarted): | ||
367 | self.emit("log", "info", "Running setscene task %d of %d (%s)" % \ | ||
368 | (event.stats.completed + event.stats.active + event.stats.failed + 1, | ||
369 | event.stats.total, event.taskstring)) | ||
370 | else: | ||
371 | if event.noexec: | ||
372 | tasktype = 'noexec task' | ||
373 | else: | ||
374 | tasktype = 'task' | ||
375 | self.emit("log", "info", "Running %s %s of %s (ID: %s, %s)" % \ | ||
376 | (tasktype, event.stats.completed + event.stats.active + event.stats.failed + 1, | ||
377 | event.stats.total, event.taskid, event.taskstring)) | ||
378 | message = {} | ||
379 | message["eventname"] = bb.event.getName(event) | ||
380 | num_of_completed = event.stats.completed + event.stats.failed | ||
381 | message["current"] = num_of_completed | ||
382 | message["total"] = event.stats.total | ||
383 | message["title"] = "" | ||
384 | message["task"] = event.taskstring | ||
385 | self.emit("task-started", message) | ||
386 | elif isinstance(event, bb.event.MultipleProviders): | ||
387 | self.emit("log", "info", "multiple providers are available for %s%s (%s)" \ | ||
388 | % (event._is_runtime and "runtime " or "", event._item, ", ".join(event._candidates))) | ||
389 | self.emit("log", "info", "consider defining a PREFERRED_PROVIDER entry to match %s" % (event._item)) | ||
390 | elif isinstance(event, bb.event.NoProvider): | ||
391 | msg = "" | ||
392 | if event._runtime: | ||
393 | r = "R" | ||
394 | else: | ||
395 | r = "" | ||
396 | |||
397 | extra = '' | ||
398 | if not event._reasons: | ||
399 | if event._close_matches: | ||
400 | extra = ". Close matches:\n %s" % '\n '.join(event._close_matches) | ||
401 | |||
402 | if event._dependees: | ||
403 | msg = "Nothing %sPROVIDES '%s' (but %s %sDEPENDS on or otherwise requires it)%s\n" % (r, event._item, ", ".join(event._dependees), r, extra) | ||
404 | else: | ||
405 | msg = "Nothing %sPROVIDES '%s'%s\n" % (r, event._item, extra) | ||
406 | if event._reasons: | ||
407 | for reason in event._reasons: | ||
408 | msg += ("%s\n" % reason) | ||
409 | self.emit("no-provider", msg) | ||
410 | self.emit("log", "error", msg) | ||
411 | elif isinstance(event, bb.event.LogExecTTY): | ||
412 | icon = "dialog-warning" | ||
413 | color = HobColors.WARNING | ||
414 | if self.sequential or not parent: | ||
415 | tree_add = self.model.append | ||
416 | else: | ||
417 | tree_add = self.model.prepend | ||
418 | tree_add(parent, | ||
419 | (None, | ||
420 | package, | ||
421 | task, | ||
422 | event.msg, | ||
423 | icon, | ||
424 | color, | ||
425 | 0)) | ||
426 | else: | ||
427 | if not isinstance(event, (bb.event.BuildBase, | ||
428 | bb.event.StampUpdate, | ||
429 | bb.event.ConfigParsed, | ||
430 | bb.event.RecipeParsed, | ||
431 | bb.event.RecipePreFinalise, | ||
432 | bb.runqueue.runQueueEvent, | ||
433 | bb.runqueue.runQueueExitWait, | ||
434 | bb.event.OperationStarted, | ||
435 | bb.event.OperationCompleted, | ||
436 | bb.event.OperationProgress)): | ||
437 | self.emit("log", "error", "Unknown event: %s" % (event.error if hasattr(event, 'error') else 'error')) | ||
438 | |||
439 | return | ||
440 | |||
441 | |||
442 | def do_pastebin(text): | ||
443 | url = 'http://pastebin.com/api_public.php' | ||
444 | params = {'paste_code': text, 'paste_format': 'text'} | ||
445 | |||
446 | req = urllib2.Request(url, urllib.urlencode(params)) | ||
447 | response = urllib2.urlopen(req) | ||
448 | paste_url = response.read() | ||
449 | |||
450 | return paste_url | ||
451 | |||
452 | |||
453 | class RunningBuildTreeView (gtk.TreeView): | ||
454 | __gsignals__ = { | ||
455 | "button_press_event" : "override" | ||
456 | } | ||
457 | def __init__ (self, readonly=False, hob=False): | ||
458 | gtk.TreeView.__init__ (self) | ||
459 | self.readonly = readonly | ||
460 | |||
461 | # The icon that indicates whether we're building or failed. | ||
462 | # add 'hob' flag because there has not only hob to share this code | ||
463 | if hob: | ||
464 | renderer = HobCellRendererPixbuf () | ||
465 | else: | ||
466 | renderer = gtk.CellRendererPixbuf() | ||
467 | col = gtk.TreeViewColumn ("Status", renderer) | ||
468 | col.add_attribute (renderer, "icon-name", 4) | ||
469 | self.append_column (col) | ||
470 | |||
471 | # The message of the build. | ||
472 | # add 'hob' flag because there has not only hob to share this code | ||
473 | if hob: | ||
474 | self.message_renderer = HobWarpCellRendererText (col_number=1) | ||
475 | else: | ||
476 | self.message_renderer = gtk.CellRendererText () | ||
477 | self.message_column = gtk.TreeViewColumn ("Message", self.message_renderer, text=3) | ||
478 | self.message_column.add_attribute(self.message_renderer, 'background', 5) | ||
479 | self.message_renderer.set_property('editable', (not self.readonly)) | ||
480 | self.append_column (self.message_column) | ||
481 | |||
482 | def do_button_press_event(self, event): | ||
483 | gtk.TreeView.do_button_press_event(self, event) | ||
484 | |||
485 | if event.button == 3: | ||
486 | selection = super(RunningBuildTreeView, self).get_selection() | ||
487 | (model, it) = selection.get_selected() | ||
488 | if it is not None: | ||
489 | can_paste = model.get(it, model.COL_LOG)[0] | ||
490 | if can_paste == 'pastebin': | ||
491 | # build a simple menu with a pastebin option | ||
492 | menu = gtk.Menu() | ||
493 | menuitem = gtk.MenuItem("Copy") | ||
494 | menu.append(menuitem) | ||
495 | menuitem.connect("activate", self.clipboard_handler, (model, it)) | ||
496 | menuitem.show() | ||
497 | menuitem = gtk.MenuItem("Send log to pastebin") | ||
498 | menu.append(menuitem) | ||
499 | menuitem.connect("activate", self.pastebin_handler, (model, it)) | ||
500 | menuitem.show() | ||
501 | menu.show() | ||
502 | menu.popup(None, None, None, event.button, event.time) | ||
503 | |||
504 | def _add_to_clipboard(self, clipping): | ||
505 | """ | ||
506 | Add the contents of clipping to the system clipboard. | ||
507 | """ | ||
508 | clipboard = gtk.clipboard_get() | ||
509 | clipboard.set_text(clipping) | ||
510 | clipboard.store() | ||
511 | |||
512 | def pastebin_handler(self, widget, data): | ||
513 | """ | ||
514 | Send the log data to pastebin, then add the new paste url to the | ||
515 | clipboard. | ||
516 | """ | ||
517 | (model, it) = data | ||
518 | paste_url = do_pastebin(model.get(it, model.COL_MESSAGE)[0]) | ||
519 | |||
520 | # @todo Provide visual feedback to the user that it is done and that | ||
521 | # it worked. | ||
522 | print paste_url | ||
523 | |||
524 | self._add_to_clipboard(paste_url) | ||
525 | |||
526 | def clipboard_handler(self, widget, data): | ||
527 | """ | ||
528 | """ | ||
529 | (model, it) = data | ||
530 | message = model.get(it, model.COL_MESSAGE)[0] | ||
531 | |||
532 | self._add_to_clipboard(message) | ||
533 | |||
534 | class BuildFailureTreeView(gtk.TreeView): | ||
535 | |||
536 | def __init__ (self): | ||
537 | gtk.TreeView.__init__(self) | ||
538 | self.set_rules_hint(False) | ||
539 | self.set_headers_visible(False) | ||
540 | self.get_selection().set_mode(gtk.SELECTION_SINGLE) | ||
541 | |||
542 | # The icon that indicates whether we're building or failed. | ||
543 | renderer = HobCellRendererPixbuf () | ||
544 | col = gtk.TreeViewColumn ("Status", renderer) | ||
545 | col.add_attribute (renderer, "icon-name", RunningBuildModel.COL_ICON) | ||
546 | self.append_column (col) | ||
547 | |||
548 | # The message of the build. | ||
549 | self.message_renderer = HobWarpCellRendererText (col_number=1) | ||
550 | self.message_column = gtk.TreeViewColumn ("Message", self.message_renderer, text=RunningBuildModel.COL_MESSAGE, background=RunningBuildModel.COL_COLOR) | ||
551 | self.append_column (self.message_column) | ||