diff options
author | Dongxiao Xu <dongxiao.xu@intel.com> | 2011-11-28 14:32:40 +0800 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2012-02-24 18:04:27 +0000 |
commit | 656f9a07588cc00704825a78de9649ca4a1552b8 (patch) | |
tree | 653c7941689599994d5876162c540fb7ee22736e /bitbake/lib/bb/ui/hob.py | |
parent | 14df6d53b6856ec78322b9c0ef01e26c0406fe28 (diff) | |
download | poky-656f9a07588cc00704825a78de9649ca4a1552b8.tar.gz |
Hob: A new implemetation (v2)
This commit implements a new design for hob
Some of the new features:
- Friendly new designed GUI. Quick response to user actions.
- Two step builds support package generation and image generation.
- Support running GUI seprarately from bitbake server.
- Recipe/package selection and deselection.
- Accurate customization for image contents and size.
- Progress bars showing the parsing and build status.
- Load/save user configurations from/into templates.
(Bitbake rev: 4dacd29f9c957d20f4583330b51e5420f9c3338d)
Signed-off-by: Dongxiao Xu <dongxiao.xu@intel.com>
Signed-off-by: Shane Wang <shane.wang@intel.com>
Signed-off-by: Liming An <limingx.l.an@intel.com>
Signed-off-by: Fengxia Hua <fengxia.hua@intel.com>
Designed-by: Belen Barros Pena <belen.barros.pena@intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'bitbake/lib/bb/ui/hob.py')
-rwxr-xr-x[-rw-r--r--] | bitbake/lib/bb/ui/hob.py | 1115 |
1 files changed, 48 insertions, 1067 deletions
diff --git a/bitbake/lib/bb/ui/hob.py b/bitbake/lib/bb/ui/hob.py index 0fcaad54a7..429bb750dd 100644..100755 --- a/bitbake/lib/bb/ui/hob.py +++ b/bitbake/lib/bb/ui/hob.py | |||
@@ -1,9 +1,11 @@ | |||
1 | #!/usr/bin/env python | ||
1 | # | 2 | # |
2 | # BitBake Graphical GTK User Interface | 3 | # BitBake Graphical GTK User Interface |
3 | # | 4 | # |
4 | # Copyright (C) 2011 Intel Corporation | 5 | # Copyright (C) 2011 Intel Corporation |
5 | # | 6 | # |
6 | # Authored by Joshua Lock <josh@linux.intel.com> | 7 | # Authored by Joshua Lock <josh@linux.intel.com> |
8 | # Authored by Dongxiao Xu <dongxiao.xu@intel.com> | ||
7 | # | 9 | # |
8 | # This program is free software; you can redistribute it and/or modify | 10 | # This program is free software; you can redistribute it and/or modify |
9 | # it under the terms of the GNU General Public License version 2 as | 11 | # it under the terms of the GNU General Public License version 2 as |
@@ -18,1087 +20,58 @@ | |||
18 | # with this program; if not, write to the Free Software Foundation, Inc., | 20 | # with this program; if not, write to the Free Software Foundation, Inc., |
19 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | 21 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
20 | 22 | ||
21 | import glib | ||
22 | import gobject | 23 | import gobject |
23 | import gtk | 24 | import gtk |
24 | from bb.ui.crumbs.tasklistmodel import TaskListModel, BuildRep | 25 | import sys |
26 | import os | ||
27 | sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) | ||
28 | try: | ||
29 | import bb | ||
30 | except RuntimeError as exc: | ||
31 | sys.exit(str(exc)) | ||
32 | from bb.ui import uihelper | ||
33 | from bb.ui.crumbs.hoblistmodel import RecipeListModel, PackageListModel | ||
25 | from bb.ui.crumbs.hobeventhandler import HobHandler | 34 | from bb.ui.crumbs.hobeventhandler import HobHandler |
26 | from bb.ui.crumbs.configurator import Configurator | 35 | from bb.ui.crumbs.builder import Builder |
27 | from bb.ui.crumbs.hobprefs import HobPrefs | ||
28 | from bb.ui.crumbs.layereditor import LayerEditor | ||
29 | from bb.ui.crumbs.runningbuild import RunningBuildTreeView, RunningBuild | ||
30 | from bb.ui.crumbs.hig import CrumbsDialog | ||
31 | import xmlrpclib | ||
32 | import logging | ||
33 | import Queue | ||
34 | 36 | ||
35 | extraCaches = ['bb.cache_extra:HobRecipeInfo'] | 37 | extraCaches = ['bb.cache_extra:HobRecipeInfo'] |
36 | 38 | ||
37 | class MainWindow (gtk.Window): | 39 | def event_handle_idle_func(eventHandler, hobHandler): |
38 | 40 | # Consume as many messages as we can in the time available to us | |
39 | def __init__(self, taskmodel, handler, configurator, prefs, layers, mach): | 41 | if not eventHandler: |
40 | gtk.Window.__init__(self) | ||
41 | # global state | ||
42 | self.curr_mach = mach | ||
43 | self.machine_handler_id = None | ||
44 | self.image_combo_id = None | ||
45 | self.generating = False | ||
46 | self.files_to_clean = [] | ||
47 | self.selected_image = None | ||
48 | self.selected_packages = None | ||
49 | self.stopping = False | ||
50 | |||
51 | self.model = taskmodel | ||
52 | self.model.connect("tasklist-populated", self.update_model) | ||
53 | self.model.connect("image-changed", self.image_changed_string_cb) | ||
54 | self.handler = handler | ||
55 | self.configurator = configurator | ||
56 | self.prefs = prefs | ||
57 | self.layers = layers | ||
58 | self.save_path = None | ||
59 | self.dirty = False | ||
60 | self.build_succeeded = False | ||
61 | |||
62 | self.connect("delete-event", self.destroy_window) | ||
63 | self.set_title("Image Creator") | ||
64 | self.set_icon_name("applications-development") | ||
65 | self.set_default_size(1000, 650) | ||
66 | |||
67 | self.build = RunningBuild(sequential=True) | ||
68 | self.build.connect("build-failed", self.running_build_failed_cb) | ||
69 | self.build.connect("build-succeeded", self.running_build_succeeded_cb) | ||
70 | self.build.connect("build-started", self.build_started_cb) | ||
71 | self.build.connect("build-complete", self.build_complete_cb) | ||
72 | |||
73 | vbox = gtk.VBox(False, 0) | ||
74 | vbox.set_border_width(0) | ||
75 | vbox.show() | ||
76 | self.add(vbox) | ||
77 | self.menu = self.create_menu() | ||
78 | vbox.pack_start(self.menu, False) | ||
79 | createview = self.create_build_gui() | ||
80 | self.back = None | ||
81 | self.cancel = None | ||
82 | buildview = self.view_build_gui() | ||
83 | self.nb = gtk.Notebook() | ||
84 | self.nb.append_page(createview) | ||
85 | self.nb.append_page(buildview) | ||
86 | self.nb.set_current_page(0) | ||
87 | self.nb.set_show_tabs(False) | ||
88 | vbox.pack_start(self.nb, expand=True, fill=True) | ||
89 | |||
90 | def destroy_window(self, widget, event): | ||
91 | self.quit() | ||
92 | |||
93 | def menu_quit(self, action): | ||
94 | self.quit() | ||
95 | |||
96 | def quit(self): | ||
97 | if self.dirty and len(self.model.contents): | ||
98 | question = "Would you like to save your customisations?" | ||
99 | dialog = CrumbsDialog(self, question, gtk.STOCK_DIALOG_WARNING) | ||
100 | dialog.add_buttons(gtk.STOCK_NO, gtk.RESPONSE_NO, | ||
101 | gtk.STOCK_YES, gtk.RESPONSE_YES) | ||
102 | resp = dialog.run() | ||
103 | dialog.destroy() | ||
104 | if resp == gtk.RESPONSE_YES: | ||
105 | if not self.save_path: | ||
106 | self.get_save_path() | ||
107 | |||
108 | if self.save_path: | ||
109 | self.save_recipe_file() | ||
110 | rep = self.model.get_build_rep() | ||
111 | rep.writeRecipe(self.save_path, self.model) | ||
112 | |||
113 | # Prevent the busy cursor being shown after hob exits if quit is called | ||
114 | # whilst the busy cursor is set | ||
115 | self.set_busy_cursor(False) | ||
116 | |||
117 | self.handler.remove_temp_dir() | ||
118 | |||
119 | gtk.main_quit() | ||
120 | |||
121 | """ | ||
122 | In the case of a fatal error give the user as much information as possible | ||
123 | and then exit. | ||
124 | """ | ||
125 | def fatal_error_cb(self, handler, errormsg, phase): | ||
126 | lbl = "<b>Error!</b>\nThere was an unrecoverable error during the" | ||
127 | lbl = lbl + " <i>%s</i> phase of BitBake. This must be" % phase | ||
128 | lbl = lbl + " rectified before the GUI will function. The error" | ||
129 | lbl = lbl + " message which which caused this is:\n\n\"%s\"" % errormsg | ||
130 | dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_ERROR) | ||
131 | dialog.add_button("Exit", gtk.RESPONSE_OK) | ||
132 | response = dialog.run() | ||
133 | dialog.destroy() | ||
134 | self.set_busy_cursor(False) | ||
135 | gtk.main_quit() | ||
136 | |||
137 | def scroll_tv_cb(self, model, path, it, view): | ||
138 | view.scroll_to_cell(path) | ||
139 | |||
140 | def running_build_succeeded_cb(self, running_build): | ||
141 | self.build_succeeded = True | ||
142 | |||
143 | def running_build_failed_cb(self, running_build): | ||
144 | self.build_succeeded = False | ||
145 | |||
146 | def image_changed_string_cb(self, model, new_image): | ||
147 | self.selected_image = new_image | ||
148 | # disconnect the image combo's signal handler | ||
149 | if self.image_combo_id: | ||
150 | self.image_combo.disconnect(self.image_combo_id) | ||
151 | self.image_combo_id = None | ||
152 | cnt = 0 | ||
153 | it = self.model.images.get_iter_first() | ||
154 | while it: | ||
155 | path = self.model.images.get_path(it) | ||
156 | if self.model.images[path][self.model.COL_NAME] == new_image: | ||
157 | self.image_combo.set_active(cnt) | ||
158 | break | ||
159 | it = self.model.images.iter_next(it) | ||
160 | cnt = cnt + 1 | ||
161 | # Reconnect the signal handler | ||
162 | if not self.image_combo_id: | ||
163 | self.image_combo_id = self.image_combo.connect("changed", self.image_changed_cb) | ||
164 | |||
165 | def image_changed_cb(self, combo): | ||
166 | model = self.image_combo.get_model() | ||
167 | it = self.image_combo.get_active_iter() | ||
168 | if it: | ||
169 | path = model.get_path(it) | ||
170 | # Firstly, deselect the previous image | ||
171 | userp, _ = self.model.get_selected_packages() | ||
172 | self.model.reset() | ||
173 | # Now select the new image and save its path in case we | ||
174 | # change the image later | ||
175 | self.toggle_package(path, model, image=True) | ||
176 | if len(userp): | ||
177 | self.model.set_selected_packages(userp) | ||
178 | self.selected_image = model[path][self.model.COL_NAME] | ||
179 | |||
180 | def reload_triggered_cb(self, handler, image, packages): | ||
181 | if image: | ||
182 | self.selected_image = image | ||
183 | if len(packages): | ||
184 | self.selected_packages = packages.split() | ||
185 | |||
186 | def data_generated(self, handler): | ||
187 | self.generating = False | ||
188 | self.enable_widgets() | ||
189 | |||
190 | def machine_combo_changed_cb(self, combo, handler): | ||
191 | mach = combo.get_active_text() | ||
192 | if mach != self.curr_mach: | ||
193 | self.curr_mach = mach | ||
194 | # Flush this straight to the file as MACHINE is changed | ||
195 | # independently of other 'Preferences' | ||
196 | self.configurator.setConfVar('MACHINE', mach) | ||
197 | self.configurator.writeConf() | ||
198 | handler.set_machine(mach) | ||
199 | handler.reload_data() | ||
200 | |||
201 | def update_machines(self, handler, machines): | ||
202 | active = 0 | ||
203 | # disconnect the signal handler before updating the combo model | ||
204 | if self.machine_handler_id: | ||
205 | self.machine_combo.disconnect(self.machine_handler_id) | ||
206 | self.machine_handler_id = None | ||
207 | |||
208 | model = self.machine_combo.get_model() | ||
209 | if model: | ||
210 | model.clear() | ||
211 | |||
212 | for machine in machines: | ||
213 | self.machine_combo.append_text(machine) | ||
214 | if machine == self.curr_mach: | ||
215 | self.machine_combo.set_active(active) | ||
216 | active = active + 1 | ||
217 | |||
218 | self.machine_handler_id = self.machine_combo.connect("changed", self.machine_combo_changed_cb, handler) | ||
219 | |||
220 | def set_busy_cursor(self, busy=True): | ||
221 | """ | ||
222 | Convenience method to set the cursor to a spinner when executing | ||
223 | a potentially lengthy process. | ||
224 | A busy value of False will set the cursor back to the default | ||
225 | left pointer. | ||
226 | """ | ||
227 | if busy: | ||
228 | cursor = gtk.gdk.Cursor(gtk.gdk.WATCH) | ||
229 | else: | ||
230 | # TODO: presumably the default cursor is different on RTL | ||
231 | # systems. Can we determine the default cursor? Or at least | ||
232 | # the cursor which is set before we change it? | ||
233 | cursor = gtk.gdk.Cursor(gtk.gdk.LEFT_PTR) | ||
234 | window = self.get_root_window() | ||
235 | window.set_cursor(cursor) | ||
236 | |||
237 | def busy_idle_func(self): | ||
238 | if self.generating: | ||
239 | self.progress.pulse() | ||
240 | return True | ||
241 | else: | ||
242 | if not self.image_combo_id: | ||
243 | self.image_combo_id = self.image_combo.connect("changed", self.image_changed_cb) | ||
244 | self.progress.set_text("Loaded") | ||
245 | self.progress.set_fraction(0.0) | ||
246 | self.set_busy_cursor(False) | ||
247 | return False | ||
248 | |||
249 | def busy(self, handler): | ||
250 | self.generating = True | ||
251 | self.progress.set_text("Loading...") | ||
252 | self.set_busy_cursor() | ||
253 | if self.image_combo_id: | ||
254 | self.image_combo.disconnect(self.image_combo_id) | ||
255 | self.image_combo_id = None | ||
256 | self.progress.pulse() | ||
257 | gobject.timeout_add (100, self.busy_idle_func) | ||
258 | self.disable_widgets() | ||
259 | |||
260 | def enable_widgets(self): | ||
261 | self.menu.set_sensitive(True) | ||
262 | self.machine_combo.set_sensitive(True) | ||
263 | self.image_combo.set_sensitive(True) | ||
264 | self.nb.set_sensitive(True) | ||
265 | self.contents_tree.set_sensitive(True) | ||
266 | |||
267 | def disable_widgets(self): | ||
268 | self.menu.set_sensitive(False) | ||
269 | self.machine_combo.set_sensitive(False) | ||
270 | self.image_combo.set_sensitive(False) | ||
271 | self.nb.set_sensitive(False) | ||
272 | self.contents_tree.set_sensitive(False) | ||
273 | |||
274 | def update_model(self, model): | ||
275 | # We want the packages model to be alphabetised and sortable so create | ||
276 | # a TreeModelSort to use in the view | ||
277 | pkgsaz_model = gtk.TreeModelSort(self.model.packages_model()) | ||
278 | pkgsaz_model.set_sort_column_id(self.model.COL_NAME, gtk.SORT_ASCENDING) | ||
279 | # Unset default sort func so that we only toggle between A-Z and | ||
280 | # Z-A sorting | ||
281 | pkgsaz_model.set_default_sort_func(None) | ||
282 | self.pkgsaz_tree.set_model(pkgsaz_model) | ||
283 | |||
284 | self.image_combo.set_model(self.model.images_model()) | ||
285 | # Without this the image combo is incorrectly sized on first load of the GUI | ||
286 | self.image_combo.set_active(0) | ||
287 | self.image_combo.set_active(-1) | ||
288 | |||
289 | if not self.image_combo_id: | ||
290 | self.image_combo_id = self.image_combo.connect("changed", self.image_changed_cb) | ||
291 | |||
292 | # We want the contents to be alphabetised so create a TreeModelSort to | ||
293 | # use in the view | ||
294 | contents_model = gtk.TreeModelSort(self.model.contents_model()) | ||
295 | contents_model.set_sort_column_id(self.model.COL_NAME, gtk.SORT_ASCENDING) | ||
296 | # Unset default sort func so that we only toggle between A-Z and | ||
297 | # Z-A sorting | ||
298 | contents_model.set_default_sort_func(None) | ||
299 | self.contents_tree.set_model(contents_model) | ||
300 | self.tasks_tree.set_model(self.model.tasks_model()) | ||
301 | |||
302 | if self.selected_image: | ||
303 | if self.image_combo_id: | ||
304 | self.image_combo.disconnect(self.image_combo_id) | ||
305 | self.image_combo_id = None | ||
306 | self.model.set_selected_image(self.selected_image) | ||
307 | if not self.image_combo_id: | ||
308 | self.image_combo_id = self.image_combo.connect("changed", self.image_changed_cb) | ||
309 | |||
310 | if self.selected_packages: | ||
311 | self.model.set_selected_packages(self.selected_packages) | ||
312 | |||
313 | def reset_clicked_cb(self, button): | ||
314 | lbl = "<b>Reset your selections?</b>\n\nAny new changes you have made will be lost" | ||
315 | dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING) | ||
316 | dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) | ||
317 | dialog.add_button("Reset", gtk.RESPONSE_OK) | ||
318 | response = dialog.run() | ||
319 | dialog.destroy() | ||
320 | if response == gtk.RESPONSE_OK: | ||
321 | self.reset_build() | ||
322 | self.search.set_text("") | ||
323 | self.selected_image = None | ||
324 | return | ||
325 | |||
326 | def reset_build(self): | ||
327 | if self.image_combo_id: | ||
328 | self.image_combo.disconnect(self.image_combo_id) | ||
329 | self.image_combo_id = None | ||
330 | self.image_combo.set_active(-1) | ||
331 | self.model.reset() | ||
332 | if not self.image_combo_id: | ||
333 | self.image_combo_id = self.image_combo.connect("changed", self.image_changed_cb) | ||
334 | |||
335 | def layers_cb(self, action): | ||
336 | resp = self.layers.run() | ||
337 | self.layers.save_current_layers() | ||
338 | self.layers.hide() | ||
339 | |||
340 | def add_layer_cb(self, action): | ||
341 | self.layers.find_layer(self) | ||
342 | self.layers.save_current_layers() | ||
343 | |||
344 | def preferences_cb(self, action): | ||
345 | resp = self.prefs.run() | ||
346 | self.prefs.write_changes() | ||
347 | self.prefs.hide() | ||
348 | |||
349 | def about_cb(self, action): | ||
350 | about = gtk.AboutDialog() | ||
351 | about.set_name("Image Creator") | ||
352 | about.set_copyright("Copyright (C) 2011 Intel Corporation") | ||
353 | about.set_authors(["Joshua Lock <josh@linux.intel.com>"]) | ||
354 | about.set_logo_icon_name("applications-development") | ||
355 | about.run() | ||
356 | about.destroy() | ||
357 | |||
358 | def save_recipe_file(self): | ||
359 | rep = self.model.get_build_rep() | ||
360 | rep.writeRecipe(self.save_path, self.model) | ||
361 | self.dirty = False | ||
362 | |||
363 | def get_save_path(self): | ||
364 | chooser = gtk.FileChooserDialog(title=None, parent=self, | ||
365 | action=gtk.FILE_CHOOSER_ACTION_SAVE, | ||
366 | buttons=(gtk.STOCK_CANCEL, | ||
367 | gtk.RESPONSE_CANCEL, | ||
368 | gtk.STOCK_SAVE, | ||
369 | gtk.RESPONSE_OK,)) | ||
370 | chooser.set_current_name("myimage.bb") | ||
371 | response = chooser.run() | ||
372 | if response == gtk.RESPONSE_OK: | ||
373 | save_path = chooser.get_filename() | ||
374 | else: | ||
375 | save_path = None | ||
376 | chooser.destroy() | ||
377 | self.save_path = save_path | ||
378 | |||
379 | def save_cb(self, action): | ||
380 | if not self.save_path: | ||
381 | self.get_save_path() | ||
382 | if self.save_path: | ||
383 | self.save_recipe_file() | ||
384 | |||
385 | def save_as_cb(self, action): | ||
386 | self.get_save_path() | ||
387 | if self.save_path: | ||
388 | self.save_recipe_file() | ||
389 | |||
390 | def open_cb(self, action): | ||
391 | chooser = gtk.FileChooserDialog(title=None, parent=self, | ||
392 | action=gtk.FILE_CHOOSER_ACTION_OPEN, | ||
393 | buttons=(gtk.STOCK_CANCEL, | ||
394 | gtk.RESPONSE_CANCEL, | ||
395 | gtk.STOCK_OPEN, | ||
396 | gtk.RESPONSE_OK)) | ||
397 | response = chooser.run() | ||
398 | rep = BuildRep(None, None, None) | ||
399 | recipe = chooser.get_filename() | ||
400 | if response == gtk.RESPONSE_OK: | ||
401 | rep.loadRecipe(recipe) | ||
402 | self.save_path = recipe | ||
403 | self.model.load_image_rep(rep) | ||
404 | self.dirty = False | ||
405 | chooser.destroy() | ||
406 | |||
407 | def bake_clicked_cb(self, button): | ||
408 | build_image = True | ||
409 | |||
410 | rep = self.model.get_build_rep() | ||
411 | |||
412 | # If no base image and no user selected packages don't build anything | ||
413 | if not self.selected_image and not len(rep.userpkgs): | ||
414 | lbl = "<b>No selections made</b>\nYou have not made any selections" | ||
415 | lbl = lbl + " so there isn't anything to bake at this time." | ||
416 | dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_INFO) | ||
417 | dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK) | ||
418 | dialog.run() | ||
419 | dialog.destroy() | ||
420 | return | ||
421 | # Else if no base image, ask whether to just build packages or whether | ||
422 | # to build a rootfs with the selected packages in | ||
423 | elif not self.selected_image: | ||
424 | lbl = "<b>Build empty image or only packages?</b>\nA base image" | ||
425 | lbl = lbl + " has not been selected.\n\'Empty image' will build" | ||
426 | lbl = lbl + " an image with only the selected packages as its" | ||
427 | lbl = lbl + " contents.\n'Packages Only' will build only the" | ||
428 | lbl = lbl + " selected packages, no image will be created" | ||
429 | dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING) | ||
430 | dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) | ||
431 | dialog.add_button("Empty Image", gtk.RESPONSE_OK) | ||
432 | dialog.add_button("Packages Only", gtk.RESPONSE_YES) | ||
433 | response = dialog.run() | ||
434 | dialog.destroy() | ||
435 | if response == gtk.RESPONSE_CANCEL: | ||
436 | return | ||
437 | elif response == gtk.RESPONSE_YES: | ||
438 | build_image = False | ||
439 | elif response == gtk.RESPONSE_OK: | ||
440 | rep.base_image = "empty" | ||
441 | |||
442 | # Ensure at least one value is set in IMAGE_FSTYPES. | ||
443 | have_selected_fstype = False | ||
444 | if (len(self.prefs.selected_image_types) and | ||
445 | len(self.prefs.selected_image_types[0])): | ||
446 | have_selected_fstype = True | ||
447 | |||
448 | if build_image and not have_selected_fstype: | ||
449 | lbl = "<b>No image output type selected</b>\nThere is no image output" | ||
450 | lbl = lbl + " selected for the build. Please set an output image type" | ||
451 | lbl = lbl + " in the preferences (Edit -> Preferences)." | ||
452 | dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_INFO) | ||
453 | dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK) | ||
454 | dialog.run() | ||
455 | dialog.destroy() | ||
456 | return | ||
457 | elif build_image: | ||
458 | self.handler.make_temp_dir() | ||
459 | recipepath = self.handler.get_temp_recipe_path(rep.base_image) | ||
460 | image_name = recipepath.rstrip(".bb") | ||
461 | path, sep, image_name = image_name.rpartition("/") | ||
462 | |||
463 | image = [] | ||
464 | image.append(image_name) | ||
465 | |||
466 | rep.writeRecipe(recipepath, self.model) | ||
467 | # In the case where we saved the file for the purpose of building | ||
468 | # it we should then delete it so that the users workspace doesn't | ||
469 | # contain files they haven't explicitly saved there. | ||
470 | if not self.save_path: | ||
471 | self.files_to_clean.append(recipepath) | ||
472 | |||
473 | self.handler.build_targets(image, self.configurator) | ||
474 | else: | ||
475 | self.handler.build_targets(self.model.get_selected_pn(), self.configurator, "packages") | ||
476 | |||
477 | # Disable parts of the menu which shouldn't be used whilst building | ||
478 | self.set_menus_sensitive(False) | ||
479 | self.nb.set_current_page(1) | ||
480 | |||
481 | def set_menus_sensitive(self, sensitive): | ||
482 | self.add_layers_action.set_sensitive(sensitive) | ||
483 | self.layers_action.set_sensitive(sensitive) | ||
484 | self.prefs_action.set_sensitive(sensitive) | ||
485 | self.open_action.set_sensitive(sensitive) | ||
486 | |||
487 | def back_button_clicked_cb(self, button): | ||
488 | self.toggle_createview() | ||
489 | |||
490 | def toggle_createview(self): | ||
491 | self.set_menus_sensitive(True) | ||
492 | self.build.reset() | ||
493 | self.nb.set_current_page(0) | ||
494 | |||
495 | def build_complete_cb(self, running_build): | ||
496 | # Have the handler process BB events again | ||
497 | self.handler.building = False | ||
498 | self.stopping = False | ||
499 | self.back.connect("clicked", self.back_button_clicked_cb) | ||
500 | self.back.set_sensitive(True) | ||
501 | self.cancel.set_sensitive(False) | ||
502 | for f in self.files_to_clean: | ||
503 | try: | ||
504 | os.remove(f) | ||
505 | except OSError: | ||
506 | pass | ||
507 | self.files_to_clean.remove(f) | ||
508 | self.files_to_clean = [] | ||
509 | |||
510 | lbl = "<b>Build completed</b>\n\nClick 'Edit Image' to start another build or 'View Messages' to view the messages output during the build." | ||
511 | if self.handler.build_type == "image" and self.build_succeeded: | ||
512 | deploy = self.handler.get_image_deploy_dir() | ||
513 | lbl = lbl + "\n<a href=\"file://%s\" title=\"%s\">Browse folder of built images</a>." % (deploy, deploy) | ||
514 | |||
515 | dialog = CrumbsDialog(self, lbl) | ||
516 | dialog.add_button("View Messages", gtk.RESPONSE_CANCEL) | ||
517 | dialog.add_button("Edit Image", gtk.RESPONSE_OK) | ||
518 | response = dialog.run() | ||
519 | dialog.destroy() | ||
520 | if response == gtk.RESPONSE_OK: | ||
521 | self.toggle_createview() | ||
522 | |||
523 | def build_started_cb(self, running_build): | ||
524 | self.back.set_sensitive(False) | ||
525 | self.cancel.set_sensitive(True) | ||
526 | |||
527 | def include_gplv3_cb(self, toggle): | ||
528 | excluded = toggle.get_active() | ||
529 | self.handler.toggle_gplv3(excluded) | ||
530 | |||
531 | def change_bb_threads(self, spinner): | ||
532 | val = spinner.get_value_as_int() | ||
533 | self.handler.set_bbthreads(val) | ||
534 | |||
535 | def change_make_threads(self, spinner): | ||
536 | val = spinner.get_value_as_int() | ||
537 | self.handler.set_pmake(val) | ||
538 | |||
539 | def toggle_toolchain(self, check): | ||
540 | enabled = check.get_active() | ||
541 | self.handler.toggle_toolchain(enabled) | ||
542 | |||
543 | def toggle_headers(self, check): | ||
544 | enabled = check.get_active() | ||
545 | self.handler.toggle_toolchain_headers(enabled) | ||
546 | |||
547 | def toggle_package_idle_cb(self, opath, image): | ||
548 | """ | ||
549 | As the operations which we're calling on the model can take | ||
550 | a significant amount of time (in the order of seconds) during which | ||
551 | the GUI is unresponsive as the main loop is blocked perform them in | ||
552 | an idle function which at least enables us to set the busy cursor | ||
553 | before the UI is blocked giving the appearance of being responsive. | ||
554 | """ | ||
555 | # Whether the item is currently included | ||
556 | inc = self.model[opath][self.model.COL_INC] | ||
557 | # FIXME: due to inpredictability of the removal of packages we are | ||
558 | # temporarily disabling this feature | ||
559 | # If the item is already included, mark it for removal then | ||
560 | # the sweep_up() method finds affected items and marks them | ||
561 | # appropriately | ||
562 | # if inc: | ||
563 | # self.model.mark(opath) | ||
564 | # self.model.sweep_up() | ||
565 | # # If the item isn't included, mark it for inclusion | ||
566 | # else: | ||
567 | if not inc: | ||
568 | self.model.include_item(item_path=opath, | ||
569 | binb="User Selected", | ||
570 | image_contents=image) | ||
571 | |||
572 | self.set_busy_cursor(False) | ||
573 | return False | 42 | return False |
43 | event = eventHandler.getEvent() | ||
44 | while event: | ||
45 | hobHandler.handle_event(event) | ||
46 | event = eventHandler.getEvent() | ||
47 | return True | ||
48 | |||
49 | def main (server = None, eventHandler = None): | ||
50 | bitbake_server = None | ||
51 | client_addr = None | ||
52 | server_addr = None | ||
53 | |||
54 | if not eventHandler: | ||
55 | helper = uihelper.BBUIHelper() | ||
56 | server, eventHandler, server_addr, client_addr = helper.findServerDetails() | ||
57 | bitbake_server = server | ||
574 | 58 | ||
575 | def toggle_package(self, path, model, image=False): | ||
576 | inc = model[path][self.model.COL_INC] | ||
577 | # Warn user before removing included packages | ||
578 | if inc: | ||
579 | # FIXME: due to inpredictability of the removal of packages we are | ||
580 | # temporarily disabling this feature | ||
581 | return | ||
582 | # pn = model[path][self.model.COL_NAME] | ||
583 | # revdeps = self.model.find_reverse_depends(pn) | ||
584 | # if len(revdeps): | ||
585 | # lbl = "<b>Remove %s?</b>\n\nThis action cannot be undone and all packages which depend on this will be removed\nPackages which depend on %s include %s." % (pn, pn, ", ".join(revdeps).rstrip(",")) | ||
586 | # else: | ||
587 | # lbl = "<b>Remove %s?</b>\n\nThis action cannot be undone." % pn | ||
588 | # dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING) | ||
589 | # dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) | ||
590 | # dialog.add_button("Remove", gtk.RESPONSE_OK) | ||
591 | # response = dialog.run() | ||
592 | # dialog.destroy() | ||
593 | # if response == gtk.RESPONSE_CANCEL: | ||
594 | # return | ||
595 | |||
596 | self.set_busy_cursor() | ||
597 | # Convert path to path in original model | ||
598 | opath = model.convert_path_to_child_path(path) | ||
599 | # This is a potentially length call which can block the | ||
600 | # main loop, therefore do the work in an idle func to keep | ||
601 | # the UI responsive | ||
602 | glib.idle_add(self.toggle_package_idle_cb, opath, image) | ||
603 | |||
604 | self.dirty = True | ||
605 | |||
606 | def toggle_include_cb(self, cell, path, tv): | ||
607 | model = tv.get_model() | ||
608 | self.toggle_package(path, model) | ||
609 | |||
610 | def toggle_pkg_include_cb(self, cell, path, tv): | ||
611 | # there's an extra layer of models in the packages case. | ||
612 | sort_model = tv.get_model() | ||
613 | cpath = sort_model.convert_path_to_child_path(path) | ||
614 | self.toggle_package(cpath, sort_model.get_model()) | ||
615 | |||
616 | def pkgsaz(self): | ||
617 | vbox = gtk.VBox(False, 6) | ||
618 | vbox.show() | ||
619 | self.pkgsaz_tree = gtk.TreeView() | ||
620 | self.pkgsaz_tree.set_headers_visible(True) | ||
621 | self.pkgsaz_tree.set_headers_clickable(True) | ||
622 | self.pkgsaz_tree.set_enable_search(True) | ||
623 | self.pkgsaz_tree.set_search_column(0) | ||
624 | self.pkgsaz_tree.get_selection().set_mode(gtk.SELECTION_SINGLE) | ||
625 | |||
626 | col = gtk.TreeViewColumn('Package') | ||
627 | col.set_clickable(True) | ||
628 | col.set_sort_column_id(self.model.COL_NAME) | ||
629 | col.set_min_width(220) | ||
630 | col1 = gtk.TreeViewColumn('Description') | ||
631 | col1.set_resizable(True) | ||
632 | col1.set_min_width(360) | ||
633 | col2 = gtk.TreeViewColumn('License') | ||
634 | col2.set_resizable(True) | ||
635 | col2.set_clickable(True) | ||
636 | col2.set_sort_column_id(self.model.COL_LIC) | ||
637 | col2.set_min_width(170) | ||
638 | col3 = gtk.TreeViewColumn('Group') | ||
639 | col3.set_clickable(True) | ||
640 | col3.set_sort_column_id(self.model.COL_GROUP) | ||
641 | col4 = gtk.TreeViewColumn('Included') | ||
642 | col4.set_min_width(80) | ||
643 | col4.set_max_width(90) | ||
644 | col4.set_sort_column_id(self.model.COL_INC) | ||
645 | |||
646 | self.pkgsaz_tree.append_column(col) | ||
647 | self.pkgsaz_tree.append_column(col1) | ||
648 | self.pkgsaz_tree.append_column(col2) | ||
649 | self.pkgsaz_tree.append_column(col3) | ||
650 | self.pkgsaz_tree.append_column(col4) | ||
651 | |||
652 | cell = gtk.CellRendererText() | ||
653 | cell1 = gtk.CellRendererText() | ||
654 | cell1.set_property('width-chars', 20) | ||
655 | cell2 = gtk.CellRendererText() | ||
656 | cell2.set_property('width-chars', 20) | ||
657 | cell3 = gtk.CellRendererText() | ||
658 | cell4 = gtk.CellRendererToggle() | ||
659 | cell4.set_property('activatable', True) | ||
660 | cell4.connect("toggled", self.toggle_pkg_include_cb, self.pkgsaz_tree) | ||
661 | |||
662 | col.pack_start(cell, True) | ||
663 | col1.pack_start(cell1, True) | ||
664 | col2.pack_start(cell2, True) | ||
665 | col3.pack_start(cell3, True) | ||
666 | col4.pack_end(cell4, True) | ||
667 | |||
668 | col.set_attributes(cell, text=self.model.COL_NAME) | ||
669 | col1.set_attributes(cell1, text=self.model.COL_DESC) | ||
670 | col2.set_attributes(cell2, text=self.model.COL_LIC) | ||
671 | col3.set_attributes(cell3, text=self.model.COL_GROUP) | ||
672 | col4.set_attributes(cell4, active=self.model.COL_INC) | ||
673 | |||
674 | self.pkgsaz_tree.show() | ||
675 | |||
676 | scroll = gtk.ScrolledWindow() | ||
677 | scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS) | ||
678 | scroll.set_shadow_type(gtk.SHADOW_IN) | ||
679 | scroll.add(self.pkgsaz_tree) | ||
680 | vbox.pack_start(scroll, True, True, 0) | ||
681 | |||
682 | hb = gtk.HBox(False, 0) | ||
683 | hb.show() | ||
684 | self.search = gtk.Entry() | ||
685 | self.search.set_icon_from_stock(gtk.ENTRY_ICON_SECONDARY, "gtk-clear") | ||
686 | self.search.connect("icon-release", self.search_entry_clear_cb) | ||
687 | self.search.show() | ||
688 | self.pkgsaz_tree.set_search_entry(self.search) | ||
689 | hb.pack_end(self.search, False, False, 0) | ||
690 | label = gtk.Label("Search packages:") | ||
691 | label.show() | ||
692 | hb.pack_end(label, False, False, 6) | ||
693 | vbox.pack_start(hb, False, False, 0) | ||
694 | |||
695 | return vbox | ||
696 | |||
697 | def search_entry_clear_cb(self, entry, icon_pos, event): | ||
698 | entry.set_text("") | ||
699 | |||
700 | def tasks(self): | ||
701 | vbox = gtk.VBox(False, 6) | ||
702 | vbox.show() | ||
703 | self.tasks_tree = gtk.TreeView() | ||
704 | self.tasks_tree.set_headers_visible(True) | ||
705 | self.tasks_tree.set_headers_clickable(False) | ||
706 | self.tasks_tree.set_enable_search(True) | ||
707 | self.tasks_tree.set_search_column(0) | ||
708 | self.tasks_tree.get_selection().set_mode(gtk.SELECTION_SINGLE) | ||
709 | |||
710 | col = gtk.TreeViewColumn('Package Collection') | ||
711 | col.set_min_width(430) | ||
712 | col1 = gtk.TreeViewColumn('Description') | ||
713 | col1.set_min_width(430) | ||
714 | col2 = gtk.TreeViewColumn('Include') | ||
715 | col2.set_min_width(70) | ||
716 | col2.set_max_width(80) | ||
717 | |||
718 | self.tasks_tree.append_column(col) | ||
719 | self.tasks_tree.append_column(col1) | ||
720 | self.tasks_tree.append_column(col2) | ||
721 | |||
722 | cell = gtk.CellRendererText() | ||
723 | cell1 = gtk.CellRendererText() | ||
724 | cell2 = gtk.CellRendererToggle() | ||
725 | cell2.set_property('activatable', True) | ||
726 | cell2.connect("toggled", self.toggle_include_cb, self.tasks_tree) | ||
727 | |||
728 | col.pack_start(cell, True) | ||
729 | col1.pack_start(cell1, True) | ||
730 | col2.pack_end(cell2, True) | ||
731 | |||
732 | col.set_attributes(cell, text=self.model.COL_NAME) | ||
733 | col1.set_attributes(cell1, text=self.model.COL_DESC) | ||
734 | col2.set_attributes(cell2, active=self.model.COL_INC) | ||
735 | |||
736 | self.tasks_tree.show() | ||
737 | |||
738 | scroll = gtk.ScrolledWindow() | ||
739 | scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS) | ||
740 | scroll.set_shadow_type(gtk.SHADOW_IN) | ||
741 | scroll.add(self.tasks_tree) | ||
742 | vbox.pack_start(scroll, True, True, 0) | ||
743 | |||
744 | hb = gtk.HBox(False, 0) | ||
745 | hb.show() | ||
746 | search = gtk.Entry() | ||
747 | search.show() | ||
748 | self.tasks_tree.set_search_entry(search) | ||
749 | hb.pack_end(search, False, False, 0) | ||
750 | label = gtk.Label("Search collections:") | ||
751 | label.show() | ||
752 | hb.pack_end(label, False, False, 6) | ||
753 | vbox.pack_start(hb, False, False, 0) | ||
754 | |||
755 | return vbox | ||
756 | |||
757 | def cancel_build(self, button): | ||
758 | if self.stopping: | ||
759 | lbl = "<b>Force Stop build?</b>\nYou've already selected Stop once," | ||
760 | lbl = lbl + " would you like to 'Force Stop' the build?\n\n" | ||
761 | lbl = lbl + "This will stop the build as quickly as possible but may" | ||
762 | lbl = lbl + " well leave your build directory in an unusable state" | ||
763 | lbl = lbl + " that requires manual steps to fix.\n" | ||
764 | dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING) | ||
765 | dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) | ||
766 | dialog.add_button("Force Stop", gtk.RESPONSE_YES) | ||
767 | else: | ||
768 | lbl = "<b>Stop build?</b>\n\nAre you sure you want to stop this" | ||
769 | lbl = lbl + " build?\n\n'Force Stop' will stop the build as quickly as" | ||
770 | lbl = lbl + " possible but may well leave your build directory in an" | ||
771 | lbl = lbl + " unusable state that requires manual steps to fix.\n\n" | ||
772 | lbl = lbl + "'Stop' will stop the build as soon as all in" | ||
773 | lbl = lbl + " progress build tasks are finished. However if a" | ||
774 | lbl = lbl + " lengthy compilation phase is in progress this may take" | ||
775 | lbl = lbl + " some time." | ||
776 | dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING) | ||
777 | dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) | ||
778 | dialog.add_button("Stop", gtk.RESPONSE_OK) | ||
779 | dialog.add_button("Force Stop", gtk.RESPONSE_YES) | ||
780 | response = dialog.run() | ||
781 | dialog.destroy() | ||
782 | if response != gtk.RESPONSE_CANCEL: | ||
783 | self.stopping = True | ||
784 | if response == gtk.RESPONSE_OK: | ||
785 | self.handler.cancel_build() | ||
786 | elif response == gtk.RESPONSE_YES: | ||
787 | self.handler.cancel_build(True) | ||
788 | |||
789 | def view_build_gui(self): | ||
790 | vbox = gtk.VBox(False, 12) | ||
791 | vbox.set_border_width(6) | ||
792 | vbox.show() | ||
793 | build_tv = RunningBuildTreeView(readonly=True) | ||
794 | build_tv.show() | ||
795 | build_tv.set_model(self.build.model) | ||
796 | self.build.model.connect("row-inserted", self.scroll_tv_cb, build_tv) | ||
797 | scrolled_view = gtk.ScrolledWindow () | ||
798 | scrolled_view.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) | ||
799 | scrolled_view.add(build_tv) | ||
800 | scrolled_view.show() | ||
801 | vbox.pack_start(scrolled_view, expand=True, fill=True) | ||
802 | hbox = gtk.HBox(False, 12) | ||
803 | hbox.show() | ||
804 | vbox.pack_start(hbox, expand=False, fill=False) | ||
805 | self.back = gtk.Button("Back") | ||
806 | self.back.show() | ||
807 | self.back.set_sensitive(False) | ||
808 | hbox.pack_start(self.back, expand=False, fill=False) | ||
809 | self.cancel = gtk.Button("Stop Build") | ||
810 | self.cancel.connect("clicked", self.cancel_build) | ||
811 | self.cancel.show() | ||
812 | hbox.pack_end(self.cancel, expand=False, fill=False) | ||
813 | |||
814 | return vbox | ||
815 | |||
816 | def create_menu(self): | ||
817 | menu_items = '''<ui> | ||
818 | <menubar name="MenuBar"> | ||
819 | <menu action="File"> | ||
820 | <menuitem action="Save"/> | ||
821 | <menuitem action="Save As"/> | ||
822 | <menuitem action="Open"/> | ||
823 | <separator/> | ||
824 | <menuitem action="AddLayer" label="Add Layer"/> | ||
825 | <separator/> | ||
826 | <menuitem action="Quit"/> | ||
827 | </menu> | ||
828 | <menu action="Edit"> | ||
829 | <menuitem action="Layers" label="Layers"/> | ||
830 | <menuitem action="Preferences"/> | ||
831 | </menu> | ||
832 | <menu action="Help"> | ||
833 | <menuitem action="About"/> | ||
834 | </menu> | ||
835 | </menubar> | ||
836 | </ui>''' | ||
837 | |||
838 | uimanager = gtk.UIManager() | ||
839 | accel = uimanager.get_accel_group() | ||
840 | self.add_accel_group(accel) | ||
841 | |||
842 | actions = gtk.ActionGroup('ImageCreator') | ||
843 | self.actions = actions | ||
844 | actions.add_actions([('Quit', gtk.STOCK_QUIT, None, None, None, self.menu_quit,), | ||
845 | ('File', None, '_File'), | ||
846 | ('Save', gtk.STOCK_SAVE, None, None, None, self.save_cb), | ||
847 | ('Save As', gtk.STOCK_SAVE_AS, None, None, None, self.save_as_cb), | ||
848 | ('Edit', None, '_Edit'), | ||
849 | ('Help', None, '_Help'), | ||
850 | ('About', gtk.STOCK_ABOUT, None, None, None, self.about_cb)]) | ||
851 | |||
852 | self.add_layers_action = gtk.Action('AddLayer', 'Add Layer', None, None) | ||
853 | self.add_layers_action.connect("activate", self.add_layer_cb) | ||
854 | self.actions.add_action(self.add_layers_action) | ||
855 | self.layers_action = gtk.Action('Layers', 'Layers', None, None) | ||
856 | self.layers_action.connect("activate", self.layers_cb) | ||
857 | self.actions.add_action(self.layers_action) | ||
858 | self.prefs_action = gtk.Action('Preferences', 'Preferences', None, None) | ||
859 | self.prefs_action.connect("activate", self.preferences_cb) | ||
860 | self.actions.add_action(self.prefs_action) | ||
861 | self.open_action = gtk.Action('Open', 'Open', None, None) | ||
862 | self.open_action.connect("activate", self.open_cb) | ||
863 | self.actions.add_action(self.open_action) | ||
864 | |||
865 | uimanager.insert_action_group(actions, 0) | ||
866 | uimanager.add_ui_from_string(menu_items) | ||
867 | |||
868 | menubar = uimanager.get_widget('/MenuBar') | ||
869 | menubar.show_all() | ||
870 | |||
871 | return menubar | ||
872 | |||
873 | def info_button_clicked_cb(self, button): | ||
874 | info = "We cannot accurately predict the image contents before they are built so instead a best" | ||
875 | info = info + " attempt at estimating what the image will contain is listed." | ||
876 | dialog = CrumbsDialog(self, info, gtk.STOCK_DIALOG_INFO) | ||
877 | dialog.add_buttons(gtk.STOCK_CLOSE, gtk.RESPONSE_OK) | ||
878 | resp = dialog.run() | ||
879 | dialog.destroy() | ||
880 | |||
881 | def create_build_gui(self): | ||
882 | vbox = gtk.VBox(False, 12) | ||
883 | vbox.set_border_width(6) | ||
884 | vbox.show() | ||
885 | |||
886 | hbox = gtk.HBox(False, 12) | ||
887 | hbox.show() | ||
888 | vbox.pack_start(hbox, expand=False, fill=False) | ||
889 | |||
890 | label = gtk.Label("Machine:") | ||
891 | label.show() | ||
892 | hbox.pack_start(label, expand=False, fill=False, padding=6) | ||
893 | self.machine_combo = gtk.combo_box_new_text() | ||
894 | self.machine_combo.show() | ||
895 | self.machine_combo.set_tooltip_text("Selects the architecture of the target board for which you would like to build an image.") | ||
896 | hbox.pack_start(self.machine_combo, expand=False, fill=False, padding=6) | ||
897 | label = gtk.Label("Base image:") | ||
898 | label.show() | ||
899 | hbox.pack_start(label, expand=False, fill=False, padding=6) | ||
900 | self.image_combo = gtk.ComboBox() | ||
901 | self.image_combo.show() | ||
902 | self.image_combo.set_tooltip_text("Selects the image on which to base the created image") | ||
903 | image_combo_cell = gtk.CellRendererText() | ||
904 | self.image_combo.pack_start(image_combo_cell, True) | ||
905 | self.image_combo.add_attribute(image_combo_cell, 'text', self.model.COL_NAME) | ||
906 | hbox.pack_start(self.image_combo, expand=False, fill=False, padding=6) | ||
907 | self.progress = gtk.ProgressBar() | ||
908 | self.progress.set_size_request(250, -1) | ||
909 | hbox.pack_end(self.progress, expand=False, fill=False, padding=6) | ||
910 | |||
911 | ins = gtk.Notebook() | ||
912 | vbox.pack_start(ins, expand=True, fill=True) | ||
913 | ins.set_show_tabs(True) | ||
914 | label = gtk.Label("Packages") | ||
915 | label.show() | ||
916 | ins.append_page(self.pkgsaz(), tab_label=label) | ||
917 | label = gtk.Label("Package Collections") | ||
918 | label.show() | ||
919 | ins.append_page(self.tasks(), tab_label=label) | ||
920 | ins.set_current_page(0) | ||
921 | ins.show_all() | ||
922 | |||
923 | hbox = gtk.HBox(False, 1) | ||
924 | hbox.show() | ||
925 | label = gtk.Label("Estimated image contents:") | ||
926 | self.model.connect("contents-changed", self.update_package_count_cb, label) | ||
927 | label.set_property("xalign", 0.00) | ||
928 | label.show() | ||
929 | hbox.pack_start(label, expand=False, fill=False, padding=6) | ||
930 | info = gtk.Button("?") | ||
931 | info.set_tooltip_text("What does this mean?") | ||
932 | info.show() | ||
933 | info.connect("clicked", self.info_button_clicked_cb) | ||
934 | hbox.pack_start(info, expand=False, fill=False, padding=6) | ||
935 | vbox.pack_start(hbox, expand=False, fill=False, padding=6) | ||
936 | con = self.contents() | ||
937 | con.show() | ||
938 | vbox.pack_start(con, expand=True, fill=True) | ||
939 | |||
940 | bbox = gtk.HButtonBox() | ||
941 | bbox.set_spacing(12) | ||
942 | bbox.set_layout(gtk.BUTTONBOX_END) | ||
943 | bbox.show() | ||
944 | vbox.pack_start(bbox, expand=False, fill=False) | ||
945 | reset = gtk.Button("Reset") | ||
946 | reset.connect("clicked", self.reset_clicked_cb) | ||
947 | reset.show() | ||
948 | bbox.add(reset) | ||
949 | bake = gtk.Button("Bake") | ||
950 | bake.connect("clicked", self.bake_clicked_cb) | ||
951 | bake.show() | ||
952 | bbox.add(bake) | ||
953 | |||
954 | return vbox | ||
955 | |||
956 | def update_package_count_cb(self, model, count, label): | ||
957 | lbl = "Estimated image contents (%s packages):" % count | ||
958 | label.set_text(lbl) | ||
959 | |||
960 | def contents(self): | ||
961 | self.contents_tree = gtk.TreeView() | ||
962 | self.contents_tree.set_headers_visible(True) | ||
963 | self.contents_tree.get_selection().set_mode(gtk.SELECTION_SINGLE) | ||
964 | |||
965 | # allow searching in the package column | ||
966 | self.contents_tree.set_search_column(0) | ||
967 | self.contents_tree.set_enable_search(True) | ||
968 | |||
969 | col = gtk.TreeViewColumn('Package') | ||
970 | col.set_sort_column_id(0) | ||
971 | col.set_min_width(430) | ||
972 | col1 = gtk.TreeViewColumn('Brought in by') | ||
973 | col1.set_resizable(True) | ||
974 | col1.set_min_width(430) | ||
975 | |||
976 | self.contents_tree.append_column(col) | ||
977 | self.contents_tree.append_column(col1) | ||
978 | |||
979 | cell = gtk.CellRendererText() | ||
980 | cell1 = gtk.CellRendererText() | ||
981 | cell1.set_property('width-chars', 20) | ||
982 | |||
983 | col.pack_start(cell, True) | ||
984 | col1.pack_start(cell1, True) | ||
985 | |||
986 | col.set_attributes(cell, text=self.model.COL_NAME) | ||
987 | col1.set_attributes(cell1, text=self.model.COL_BINB) | ||
988 | |||
989 | self.contents_tree.show() | ||
990 | |||
991 | scroll = gtk.ScrolledWindow() | ||
992 | scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS) | ||
993 | scroll.set_shadow_type(gtk.SHADOW_IN) | ||
994 | scroll.add(self.contents_tree) | ||
995 | |||
996 | return scroll | ||
997 | |||
998 | def main (server, eventHandler): | ||
999 | gobject.threads_init() | 59 | gobject.threads_init() |
1000 | 60 | ||
1001 | # NOTE: For now we require that the user run with pre and post files to | 61 | # That indicates whether the Hob and the bitbake server are |
1002 | # read and store configuration set in the GUI. | 62 | # running on different machines |
1003 | # We hope to adjust this long term as tracked in Yocto Bugzilla #1441 | 63 | # recipe model and package model |
1004 | # http://bugzilla.pokylinux.org/show_bug.cgi?id=1441 | 64 | recipe_model = RecipeListModel() |
1005 | reqfiles = 0 | 65 | package_model = PackageListModel() |
1006 | dep_files = server.runCommand(["getVariable", "__depends"]) or set() | ||
1007 | dep_files.union(server.runCommand(["getVariable", "__base_depends"]) or set()) | ||
1008 | for f in dep_files: | ||
1009 | if f[0].endswith("hob-pre.conf"): | ||
1010 | reqfiles = reqfiles + 1 | ||
1011 | elif f[0].endswith("hob-post.conf"): | ||
1012 | reqfiles = reqfiles + 1 | ||
1013 | if reqfiles == 2: | ||
1014 | break | ||
1015 | if reqfiles < 2: | ||
1016 | print("""The hob UI requires a pre file named hob-pre.conf and a post | ||
1017 | file named hob-post.conf to store and read its configuration from. Please run | ||
1018 | hob with these files, i.e.\n | ||
1019 | \bitbake -u hob -r conf/hob-pre.conf -R conf/hob-post.conf""") | ||
1020 | return | ||
1021 | 66 | ||
1022 | taskmodel = TaskListModel() | 67 | hobHandler = HobHandler(bitbake_server, server_addr, client_addr, recipe_model, package_model) |
1023 | configurator = Configurator() | 68 | if hobHandler.kick() == False: |
1024 | handler = HobHandler(taskmodel, server) | ||
1025 | mach = server.runCommand(["getVariable", "MACHINE"]) | ||
1026 | sdk_mach = server.runCommand(["getVariable", "SDKMACHINE"]) | ||
1027 | # If SDKMACHINE not set the default SDK_ARCH is used so we | ||
1028 | # should represent that in the GUI | ||
1029 | if not sdk_mach: | ||
1030 | sdk_mach = server.runCommand(["getVariable", "SDK_ARCH"]) | ||
1031 | distro = server.runCommand(["getVariable", "DISTRO"]) | ||
1032 | if not distro: | ||
1033 | distro = "defaultsetup" | ||
1034 | bbthread = server.runCommand(["getVariable", "BB_NUMBER_THREADS"]) | ||
1035 | if not bbthread: | ||
1036 | bbthread = 1 | ||
1037 | else: | ||
1038 | bbthread = int(bbthread) | ||
1039 | pmake = server.runCommand(["getVariable", "PARALLEL_MAKE"]) | ||
1040 | if not pmake: | ||
1041 | pmake = 1 | ||
1042 | else: | ||
1043 | # The PARALLEL_MAKE variable will be of the format: "-j 3" and we only | ||
1044 | # want a number for the spinner, so strip everything from the variable | ||
1045 | # up to and including the space | ||
1046 | pmake = int(pmake.lstrip("-j ")) | ||
1047 | |||
1048 | selected_image_types = server.runCommand(["getVariable", "IMAGE_FSTYPES"]) | ||
1049 | all_image_types = server.runCommand(["getVariable", "IMAGE_TYPES"]) | ||
1050 | |||
1051 | pclasses = server.runCommand(["getVariable", "PACKAGE_CLASSES"]).split(" ") | ||
1052 | # NOTE: we're only supporting one value for PACKAGE_CLASSES being set | ||
1053 | # this seems OK because we're using the first package format set in | ||
1054 | # PACKAGE_CLASSES and that's the package manager used for the rootfs | ||
1055 | pkg, sep, pclass = pclasses[0].rpartition("_") | ||
1056 | |||
1057 | incompatible = server.runCommand(["getVariable", "INCOMPATIBLE_LICENSE"]) | ||
1058 | gplv3disabled = False | ||
1059 | if incompatible and incompatible.lower().find("gplv3") != -1: | ||
1060 | gplv3disabled = True | ||
1061 | |||
1062 | build_toolchain = bool(server.runCommand(["getVariable", "HOB_BUILD_TOOLCHAIN"])) | ||
1063 | handler.toggle_toolchain(build_toolchain) | ||
1064 | build_headers = bool(server.runCommand(["getVariable", "HOB_BUILD_TOOLCHAIN_HEADERS"])) | ||
1065 | handler.toggle_toolchain_headers(build_headers) | ||
1066 | |||
1067 | prefs = HobPrefs(configurator, handler, sdk_mach, distro, pclass, | ||
1068 | pmake, bbthread, selected_image_types, all_image_types, | ||
1069 | gplv3disabled, build_toolchain, build_headers) | ||
1070 | layers = LayerEditor(configurator, None) | ||
1071 | window = MainWindow(taskmodel, handler, configurator, prefs, layers, mach) | ||
1072 | prefs.set_parent_window(window) | ||
1073 | layers.set_parent_window(window) | ||
1074 | window.show_all () | ||
1075 | handler.connect("machines-updated", window.update_machines) | ||
1076 | handler.connect("sdk-machines-updated", prefs.update_sdk_machines) | ||
1077 | handler.connect("distros-updated", prefs.update_distros) | ||
1078 | handler.connect("package-formats-found", prefs.update_package_formats) | ||
1079 | handler.connect("generating-data", window.busy) | ||
1080 | handler.connect("data-generated", window.data_generated) | ||
1081 | handler.connect("reload-triggered", window.reload_triggered_cb) | ||
1082 | configurator.connect("layers-loaded", layers.load_current_layers) | ||
1083 | configurator.connect("layers-changed", handler.reload_data) | ||
1084 | handler.connect("config-found", configurator.configFound) | ||
1085 | handler.connect("fatal-error", window.fatal_error_cb) | ||
1086 | |||
1087 | try: | ||
1088 | # kick the while thing off | ||
1089 | handler.current_command = handler.CFG_PATH_LOCAL | ||
1090 | server.runCommand(["findConfigFilePath", "local.conf"]) | ||
1091 | except xmlrpclib.Fault: | ||
1092 | print("XMLRPC Fault getting commandline:\n %s" % x) | ||
1093 | return 1 | 69 | return 1 |
70 | builder = Builder(hobHandler, recipe_model, package_model) | ||
1094 | 71 | ||
1095 | # This timeout function regularly probes the event queue to find out if we | 72 | # This timeout function regularly probes the event queue to find out if we |
1096 | # have any messages waiting for us. | 73 | # have any messages waiting for us. |
1097 | gobject.timeout_add (100, | 74 | gobject.timeout_add(10, event_handle_idle_func, eventHandler, hobHandler) |
1098 | handler.event_handle_idle_func, | ||
1099 | eventHandler, | ||
1100 | window.build, | ||
1101 | window.progress) | ||
1102 | 75 | ||
1103 | try: | 76 | try: |
1104 | gtk.main() | 77 | gtk.main() |
@@ -1107,5 +80,13 @@ hob with these files, i.e.\n | |||
1107 | if ioerror.args[0] == 4: | 80 | if ioerror.args[0] == 4: |
1108 | pass | 81 | pass |
1109 | finally: | 82 | finally: |
1110 | server.runCommand(["stateStop"]) | 83 | hobHandler.cancel_build(force = True) |
1111 | 84 | ||
85 | if __name__ == "__main__": | ||
86 | try: | ||
87 | ret = main() | ||
88 | except Exception: | ||
89 | ret = 1 | ||
90 | import traceback | ||
91 | traceback.print_exc(15) | ||
92 | sys.exit(ret) | ||