diff options
author | Joshua Lock <josh@linux.intel.com> | 2011-07-01 15:58:50 -0700 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2011-07-05 14:40:30 +0100 |
commit | 4cc291c007103c19779f995e852b37dbad122293 (patch) | |
tree | 3447d62ef1ba2eca08137b8e13df58f8a337a453 /bitbake/lib/bb/ui/hob.py | |
parent | 7fc9c3488f7865111ec052d1cab213d216d2b414 (diff) | |
download | poky-4cc291c007103c19779f995e852b37dbad122293.tar.gz |
hob: re-designed interaction and implementation
Highlights include:
* Atempted GNOME HIG compliance
* Simplified UI and interaction model
* Sorting and type to find in tree views
* Preferences dialog to modify local settings
* Dialog to add and remove layers
* Search in packages list
* Save/Load image recipes
The build model has been changed, hob will attempt to build all dependent
packages of an image and then use the buildFile server method to build the
created image.
(Bitbake rev: 48e64acaae4a741b9f5630f426fb4e6142755c2c)
Signed-off-by: Joshua Lock <josh@linux.intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'bitbake/lib/bb/ui/hob.py')
-rw-r--r-- | bitbake/lib/bb/ui/hob.py | 924 |
1 files changed, 625 insertions, 299 deletions
diff --git a/bitbake/lib/bb/ui/hob.py b/bitbake/lib/bb/ui/hob.py index 175e5bdf6c..fca41e44dc 100644 --- a/bitbake/lib/bb/ui/hob.py +++ b/bitbake/lib/bb/ui/hob.py | |||
@@ -18,12 +18,16 @@ | |||
18 | # with this program; if not, write to the Free Software Foundation, Inc., | 18 | # with this program; if not, write to the Free Software Foundation, Inc., |
19 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | 19 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
20 | 20 | ||
21 | import glib | ||
21 | import gobject | 22 | import gobject |
22 | import gtk | 23 | import gtk |
23 | from bb.ui.crumbs.progress import ProgressBar | 24 | from bb.ui.crumbs.tasklistmodel import TaskListModel, BuildRep |
24 | from bb.ui.crumbs.tasklistmodel import TaskListModel | ||
25 | from bb.ui.crumbs.hobeventhandler import HobHandler | 25 | from bb.ui.crumbs.hobeventhandler import HobHandler |
26 | from bb.ui.crumbs.configurator import Configurator | ||
27 | from bb.ui.crumbs.hobprefs import HobPrefs | ||
28 | from bb.ui.crumbs.layereditor import LayerEditor | ||
26 | from bb.ui.crumbs.runningbuild import RunningBuildTreeView, RunningBuild | 29 | from bb.ui.crumbs.runningbuild import RunningBuildTreeView, RunningBuild |
30 | from bb.ui.crumbs.hig import CrumbsDialog | ||
27 | import xmlrpclib | 31 | import xmlrpclib |
28 | import logging | 32 | import logging |
29 | import Queue | 33 | import Queue |
@@ -32,226 +36,459 @@ extraCaches = ['bb.cache_extra:HobRecipeInfo'] | |||
32 | 36 | ||
33 | class MainWindow (gtk.Window): | 37 | class MainWindow (gtk.Window): |
34 | 38 | ||
35 | def __init__(self, taskmodel, handler, curr_mach=None, curr_distro=None): | 39 | def __init__(self, taskmodel, handler, configurator, prefs, layers, mach): |
36 | gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL) | 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 | |||
37 | self.model = taskmodel | 50 | self.model = taskmodel |
38 | self.model.connect("tasklist-populated", self.update_model) | 51 | self.model.connect("tasklist-populated", self.update_model) |
39 | self.curr_mach = curr_mach | 52 | self.model.connect("image-changed", self.image_changed_string_cb) |
40 | self.curr_distro = curr_distro | 53 | self.curr_image_path = None |
41 | self.handler = handler | 54 | self.handler = handler |
42 | self.set_border_width(10) | 55 | self.configurator = configurator |
43 | self.connect("delete-event", gtk.main_quit) | 56 | self.prefs = prefs |
44 | self.set_title("BitBake Image Creator") | 57 | self.layers = layers |
45 | self.set_default_size(700, 600) | 58 | self.save_path = None |
59 | self.dirty = False | ||
60 | |||
61 | self.connect("delete-event", self.destroy_window) | ||
62 | self.set_title("Image Creator") | ||
63 | self.set_icon_name("applications-development") | ||
64 | self.set_default_size(1000, 650) | ||
46 | 65 | ||
47 | self.build = RunningBuild() | 66 | self.build = RunningBuild() |
48 | self.build.connect("build-succeeded", self.running_build_succeeded_cb) | ||
49 | self.build.connect("build-failed", self.running_build_failed_cb) | 67 | self.build.connect("build-failed", self.running_build_failed_cb) |
68 | self.build.connect("build-complete", self.handler.build_complete_cb) | ||
69 | self.build.connect("build-started", self.build_started_cb) | ||
50 | 70 | ||
51 | createview = self.create_build_gui() | 71 | self.handler.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 | ||
52 | buildview = self.view_build_gui() | 82 | buildview = self.view_build_gui() |
53 | self.nb = gtk.Notebook() | 83 | self.nb = gtk.Notebook() |
54 | self.nb.append_page(createview) | 84 | self.nb.append_page(createview) |
55 | self.nb.append_page(buildview) | 85 | self.nb.append_page(buildview) |
56 | self.nb.set_current_page(0) | 86 | self.nb.set_current_page(0) |
57 | self.nb.set_show_tabs(False) | 87 | self.nb.set_show_tabs(False) |
58 | self.add(self.nb) | 88 | vbox.pack_start(self.nb, expand=True, fill=True) |
59 | self.generating = False | 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 | if resp == gtk.RESPONSE_YES: | ||
104 | if not self.save_path: | ||
105 | self.get_save_path() | ||
106 | self.save_recipe_file() | ||
107 | rep = self.model.get_build_rep() | ||
108 | rep.writeRecipe(self.save_path, self.model) | ||
109 | |||
110 | gtk.main_quit() | ||
60 | 111 | ||
61 | def scroll_tv_cb(self, model, path, it, view): | 112 | def scroll_tv_cb(self, model, path, it, view): |
62 | view.scroll_to_cell(path) | 113 | view.scroll_to_cell(path) |
63 | 114 | ||
64 | def running_build_failed_cb(self, running_build): | 115 | def running_build_failed_cb(self, running_build): |
65 | # FIXME: handle this | 116 | # FIXME: handle this |
66 | return | 117 | print("Build failed") |
118 | |||
119 | def image_changed_string_cb(self, model, new_image): | ||
120 | cnt = 0 | ||
121 | it = self.model.images.get_iter_first() | ||
122 | while it: | ||
123 | path = self.model.images.get_path(it) | ||
124 | if self.model.images[path][self.model.COL_NAME] == new_image: | ||
125 | self.image_combo.set_active(cnt) | ||
126 | break | ||
127 | it = self.model.images.iter_next(it) | ||
128 | cnt = cnt + 1 | ||
129 | |||
130 | def image_changed_cb(self, combo): | ||
131 | model = self.image_combo.get_model() | ||
132 | it = self.image_combo.get_active_iter() | ||
133 | if it: | ||
134 | path = model.get_path(it) | ||
135 | # Firstly, deselect the previous image | ||
136 | if self.curr_image_path: | ||
137 | self.toggle_package(self.curr_image_path, model) | ||
138 | # Now select the new image and save its path in case we | ||
139 | # change the image later | ||
140 | self.curr_image_path = path | ||
141 | self.toggle_package(path, model, image=True) | ||
142 | |||
143 | def reload_triggered_cb(self, handler, image, packages): | ||
144 | if image: | ||
145 | self.selected_image = image | ||
146 | if len(packages): | ||
147 | self.selected_packages = packages.split() | ||
67 | 148 | ||
68 | def running_build_succeeded_cb(self, running_build): | 149 | def data_generated(self, handler): |
69 | label = gtk.Label("Build completed, start another build?") | 150 | self.generating = False |
70 | dialog = gtk.Dialog("Build complete", | 151 | self.image_combo.set_model(self.model.images_model()) |
71 | self, | 152 | if not self.image_combo_id: |
72 | gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, | 153 | self.image_combo_id = self.image_combo.connect("changed", self.image_changed_cb) |
73 | (gtk.STOCK_NO, gtk.RESPONSE_NO, | 154 | self.enable_widgets() |
74 | gtk.STOCK_YES, gtk.RESPONSE_YES)) | ||
75 | dialog.vbox.pack_start(label) | ||
76 | label.show() | ||
77 | response = dialog.run() | ||
78 | dialog.destroy() | ||
79 | if response == gtk.RESPONSE_YES: | ||
80 | self.model.reset() # NOTE: really? | ||
81 | self.nb.set_current_page(0) | ||
82 | return | ||
83 | 155 | ||
84 | def machine_combo_changed_cb(self, combo, handler): | 156 | def machine_combo_changed_cb(self, combo, handler): |
85 | mach = combo.get_active_text() | 157 | mach = combo.get_active_text() |
86 | if mach != self.curr_mach: | 158 | if mach != self.curr_mach: |
87 | self.curr_mach = mach | 159 | self.curr_mach = mach |
160 | # Flush this straight to the file as MACHINE is changed | ||
161 | # independently of other 'Preferences' | ||
162 | self.configurator.setLocalConfVar('MACHINE', mach) | ||
163 | self.configurator.writeLocalConf() | ||
88 | handler.set_machine(mach) | 164 | handler.set_machine(mach) |
165 | handler.reload_data() | ||
89 | 166 | ||
90 | def update_machines(self, handler, machines): | 167 | def update_machines(self, handler, machines): |
91 | active = 0 | 168 | active = 0 |
92 | for machine in machines: | 169 | # disconnect the signal handler before updating the combo model |
93 | self.machine_combo.append_text(machine) | 170 | if self.machine_handler_id: |
94 | if machine == self.curr_mach: | 171 | self.machine_combo.disconnect(self.machine_handler_id) |
172 | self.machine_handler_id = None | ||
173 | |||
174 | model = self.machine_combo.get_model() | ||
175 | if model: | ||
176 | model.clear() | ||
177 | |||
178 | for machine in machines: | ||
179 | self.machine_combo.append_text(machine) | ||
180 | if machine == self.curr_mach: | ||
95 | self.machine_combo.set_active(active) | 181 | self.machine_combo.set_active(active) |
96 | active = active + 1 | 182 | active = active + 1 |
97 | self.machine_combo.connect("changed", self.machine_combo_changed_cb, handler) | 183 | |
98 | 184 | self.machine_handler_id = self.machine_combo.connect("changed", self.machine_combo_changed_cb, handler) | |
99 | def update_distros(self, handler, distros): | 185 | |
100 | # FIXME: when we add UI for changing distro this will be used | 186 | def set_busy_cursor(self, busy=True): |
101 | return | 187 | """ |
102 | 188 | Convenience method to set the cursor to a spinner when executing | |
103 | def data_generated(self, handler): | 189 | a potentially lengthy process. |
104 | self.generating = False | 190 | A busy value of False will set the cursor back to the default |
105 | 191 | left pointer. | |
106 | def spin_idle_func(self, pbar): | 192 | """ |
193 | if busy: | ||
194 | cursor = gtk.gdk.Cursor(gtk.gdk.WATCH) | ||
195 | else: | ||
196 | # TODO: presumably the default cursor is different on RTL | ||
197 | # systems. Can we determine the default cursor? Or at least | ||
198 | # the cursor which is set before we change it? | ||
199 | cursor = gtk.gdk.Cursor(gtk.gdk.LEFT_PTR) | ||
200 | window = self.get_root_window() | ||
201 | window.set_cursor(cursor) | ||
202 | |||
203 | def busy_idle_func(self): | ||
107 | if self.generating: | 204 | if self.generating: |
108 | pbar.pulse() | 205 | self.progress.set_text("Loading...") |
206 | self.progress.pulse() | ||
109 | return True | 207 | return True |
110 | else: | 208 | else: |
111 | pbar.hide() | 209 | if not self.image_combo_id: |
210 | self.image_combo_id = self.image_combo.connect("changed", self.image_changed_cb) | ||
211 | self.progress.set_text("Loaded") | ||
212 | self.progress.set_fraction(0.0) | ||
213 | self.set_busy_cursor(False) | ||
112 | return False | 214 | return False |
113 | 215 | ||
114 | def busy(self, handler): | 216 | def busy(self, handler): |
115 | self.generating = True | 217 | self.generating = True |
116 | pbar = ProgressBar(self) | 218 | self.set_busy_cursor() |
117 | pbar.connect("delete-event", gtk.main_quit) # NOTE: questionable... | 219 | if self.image_combo_id: |
118 | pbar.pulse() | 220 | self.image_combo.disconnect(self.image_combo_id) |
119 | gobject.timeout_add (200, | 221 | self.image_combo_id = None |
120 | self.spin_idle_func, | 222 | self.progress.pulse() |
121 | pbar) | 223 | gobject.timeout_add (200, self.busy_idle_func) |
224 | self.disable_widgets() | ||
225 | |||
226 | def enable_widgets(self): | ||
227 | self.menu.set_sensitive(True) | ||
228 | self.machine_combo.set_sensitive(True) | ||
229 | self.image_combo.set_sensitive(True) | ||
230 | self.nb.set_sensitive(True) | ||
231 | self.contents_tree.set_sensitive(True) | ||
232 | |||
233 | def disable_widgets(self): | ||
234 | self.menu.set_sensitive(False) | ||
235 | self.machine_combo.set_sensitive(False) | ||
236 | self.image_combo.set_sensitive(False) | ||
237 | self.nb.set_sensitive(False) | ||
238 | self.contents_tree.set_sensitive(False) | ||
122 | 239 | ||
123 | def update_model(self, model): | 240 | def update_model(self, model): |
124 | pkgsaz_model = gtk.TreeModelSort(self.model.packages_model()) | 241 | # We want the packages model to be alphabetised and sortable so create |
242 | # a TreeModelSort to use in the view | ||
243 | pkgsaz_model = gtk.TreeModelSort(self.model.packages_model()) | ||
125 | pkgsaz_model.set_sort_column_id(self.model.COL_NAME, gtk.SORT_ASCENDING) | 244 | pkgsaz_model.set_sort_column_id(self.model.COL_NAME, gtk.SORT_ASCENDING) |
245 | # Unset default sort func so that we only toggle between A-Z and | ||
246 | # Z-A sorting | ||
247 | pkgsaz_model.set_default_sort_func(None) | ||
126 | self.pkgsaz_tree.set_model(pkgsaz_model) | 248 | self.pkgsaz_tree.set_model(pkgsaz_model) |
127 | 249 | ||
128 | # FIXME: need to implement a custom sort function, as otherwise the column | 250 | # We want the contents to be alphabetised so create a TreeModelSort to |
129 | # is re-ordered when toggling the inclusion state (COL_INC) | 251 | # use in the view |
130 | pkgsgrp_model = gtk.TreeModelSort(self.model.packages_model()) | 252 | contents_model = gtk.TreeModelSort(self.model.contents_model()) |
131 | pkgsgrp_model.set_sort_column_id(self.model.COL_GROUP, gtk.SORT_ASCENDING) | 253 | contents_model.set_sort_column_id(self.model.COL_NAME, gtk.SORT_ASCENDING) |
132 | self.pkgsgrp_tree.set_model(pkgsgrp_model) | 254 | # Unset default sort func so that we only toggle between A-Z and |
133 | 255 | # Z-A sorting | |
134 | self.contents_tree.set_model(self.model.contents_model()) | 256 | contents_model.set_default_sort_func(None) |
135 | self.images_tree.set_model(self.model.images_model()) | 257 | self.contents_tree.set_model(contents_model) |
136 | self.tasks_tree.set_model(self.model.tasks_model()) | 258 | self.tasks_tree.set_model(self.model.tasks_model()) |
259 | |||
260 | if self.selected_image: | ||
261 | if self.image_combo_id: | ||
262 | self.image_combo.disconnect(self.image_combo_id) | ||
263 | self.image_combo_id = None | ||
264 | self.model.set_selected_image(self.selected_image) | ||
265 | self.selected_image = None | ||
266 | if not self.image_combo_id: | ||
267 | self.image_combo_id = self.image_combo.connect("changed", self.image_changed_cb) | ||
268 | |||
269 | if self.selected_packages: | ||
270 | self.model.set_selected_packages(self.selected_packages) | ||
271 | self.selected_packages = None | ||
137 | 272 | ||
138 | def reset_clicked_cb(self, button): | 273 | def reset_clicked_cb(self, button): |
139 | label = gtk.Label("Are you sure you want to reset the image contents?") | 274 | lbl = "<b>Reset your selections?</b>\n\nAny new changes you have made will be lost" |
140 | dialog = gtk.Dialog("Confirm reset", self, | 275 | dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING) |
141 | gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, | 276 | dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) |
142 | (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, | 277 | dialog.add_button("Reset", gtk.RESPONSE_OK) |
143 | gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) | ||
144 | dialog.vbox.pack_start(label) | ||
145 | label.show() | ||
146 | response = dialog.run() | 278 | response = dialog.run() |
147 | dialog.destroy() | 279 | dialog.destroy() |
148 | if (response == gtk.RESPONSE_ACCEPT): | 280 | if response == gtk.RESPONSE_OK: |
149 | self.model.reset() | 281 | self.reset_build() |
150 | return | 282 | return |
151 | 283 | ||
284 | def reset_build(self): | ||
285 | self.image_combo.disconnect(self.image_combo_id) | ||
286 | self.image_combo_id = None | ||
287 | self.image_combo.set_active(-1) | ||
288 | self.image_combo_id = self.image_combo.connect("changed", self.image_changed_cb) | ||
289 | self.model.reset() | ||
290 | |||
291 | def layers_cb(self, action): | ||
292 | resp = self.layers.run() | ||
293 | self.layers.save_current_layers() | ||
294 | self.layers.hide() | ||
295 | |||
296 | def add_layer_cb(self, action): | ||
297 | self.layers.find_layer(self) | ||
298 | |||
299 | def preferences_cb(self, action): | ||
300 | resp = self.prefs.run() | ||
301 | self.prefs.write_changes() | ||
302 | self.prefs.hide() | ||
303 | |||
304 | def about_cb(self, action): | ||
305 | about = gtk.AboutDialog() | ||
306 | about.set_name("Image Creator") | ||
307 | about.set_copyright("Copyright (C) 2011 Intel Corporation") | ||
308 | about.set_authors(["Joshua Lock <josh@linux.intel.com>"]) | ||
309 | about.set_logo_icon_name("applications-development") | ||
310 | about.run() | ||
311 | about.destroy() | ||
312 | |||
313 | def save_recipe_file(self): | ||
314 | rep = self.model.get_build_rep() | ||
315 | rep.writeRecipe(self.save_path, self.model) | ||
316 | self.dirty = False | ||
317 | |||
318 | def get_save_path(self): | ||
319 | chooser = gtk.FileChooserDialog(title=None, parent=self, | ||
320 | action=gtk.FILE_CHOOSER_ACTION_SAVE, | ||
321 | buttons=(gtk.STOCK_CANCEL, | ||
322 | gtk.RESPONSE_CANCEL, | ||
323 | gtk.STOCK_SAVE, | ||
324 | gtk.RESPONSE_OK,)) | ||
325 | chooser.set_current_name("myimage.bb") | ||
326 | response = chooser.run() | ||
327 | if response == gtk.RESPONSE_OK: | ||
328 | self.save_path = chooser.get_filename() | ||
329 | chooser.destroy() | ||
330 | |||
331 | def save_cb(self, action): | ||
332 | if not self.save_path: | ||
333 | self.get_save_path() | ||
334 | self.save_recipe_file() | ||
335 | |||
336 | def save_as_cb(self, action): | ||
337 | self.get_save_path() | ||
338 | self.save_recipe_file() | ||
339 | |||
340 | def open_cb(self, action): | ||
341 | chooser = gtk.FileChooserDialog(title=None, parent=self, | ||
342 | action=gtk.FILE_CHOOSER_ACTION_OPEN, | ||
343 | buttons=(gtk.STOCK_CANCEL, | ||
344 | gtk.RESPONSE_CANCEL, | ||
345 | gtk.STOCK_OPEN, | ||
346 | gtk.RESPONSE_OK)) | ||
347 | response = chooser.run() | ||
348 | rep = BuildRep(None, None, None) | ||
349 | if response == gtk.RESPONSE_OK: | ||
350 | rep.loadRecipe(chooser.get_filename()) | ||
351 | chooser.destroy() | ||
352 | self.model.load_image_rep(rep) | ||
353 | self.dirty = False | ||
354 | |||
152 | def bake_clicked_cb(self, button): | 355 | def bake_clicked_cb(self, button): |
153 | if not self.model.targets_contains_image(): | 356 | rep = self.model.get_build_rep() |
154 | label = gtk.Label("No image was selected. Just build the selected packages?") | 357 | if not rep.base_image: |
155 | dialog = gtk.Dialog("Warning, no image selected", | 358 | lbl = "<b>Build only packages?</b>\n\nAn image has not been selected, so only the selected packages will be built." |
156 | self, | 359 | dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING) |
157 | gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, | 360 | dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) |
158 | (gtk.STOCK_NO, gtk.RESPONSE_NO, | 361 | dialog.add_button("Build", gtk.RESPONSE_YES) |
159 | gtk.STOCK_YES, gtk.RESPONSE_YES)) | ||
160 | dialog.vbox.pack_start(label) | ||
161 | label.show() | ||
162 | response = dialog.run() | 362 | response = dialog.run() |
163 | dialog.destroy() | 363 | dialog.destroy() |
164 | if not response == gtk.RESPONSE_YES: | 364 | if response == gtk.RESPONSE_CANCEL: |
165 | return | 365 | return |
166 | |||
167 | # Note: We could "squash" the targets list to only include things not brought in by an image | ||
168 | task_list = self.model.get_targets() | ||
169 | if len(task_list): | ||
170 | tasks = " ".join(task_list) | ||
171 | # TODO: show a confirmation dialog | ||
172 | print("Including these extra tasks in IMAGE_INSTALL: %s" % tasks) | ||
173 | else: | 366 | else: |
174 | return | 367 | # TODO: show a confirmation dialog ? |
175 | 368 | if not self.save_path: | |
369 | import tempfile, datetime | ||
370 | image_name = "hob-%s-variant-%s.bb" % (rep.base_image, datetime.date.today().isoformat()) | ||
371 | image_dir = os.path.join(tempfile.gettempdir(), 'hob-images') | ||
372 | bb.utils.mkdirhier(image_dir) | ||
373 | recipepath = os.path.join(image_dir, image_name) | ||
374 | else: | ||
375 | recipepath = self.save_path | ||
376 | |||
377 | rep.writeRecipe(recipepath, self.model) | ||
378 | # In the case where we saved the file for the purpose of building | ||
379 | # it we should then delete it so that the users workspace doesn't | ||
380 | # contain files they haven't explicitly saved there. | ||
381 | if not self.save_path: | ||
382 | self.files_to_clean.append(recipepath) | ||
383 | |||
384 | self.handler.queue_image_recipe_path(recipepath) | ||
385 | |||
386 | self.handler.build_packages(rep.allpkgs.split(" ")) | ||
176 | self.nb.set_current_page(1) | 387 | self.nb.set_current_page(1) |
177 | self.handler.run_build(task_list) | ||
178 | 388 | ||
179 | return | 389 | def back_button_clicked_cb(self, button): |
390 | self.toggle_createview() | ||
180 | 391 | ||
181 | def advanced_expander_cb(self, expander, param): | 392 | def toggle_createview(self): |
182 | return | 393 | self.build.model.clear() |
183 | 394 | self.nb.set_current_page(0) | |
184 | def images(self): | ||
185 | self.images_tree = gtk.TreeView() | ||
186 | self.images_tree.set_headers_visible(True) | ||
187 | self.images_tree.set_headers_clickable(False) | ||
188 | self.images_tree.set_enable_search(True) | ||
189 | self.images_tree.set_search_column(0) | ||
190 | self.images_tree.get_selection().set_mode(gtk.SELECTION_NONE) | ||
191 | 395 | ||
192 | col = gtk.TreeViewColumn('Package') | 396 | def build_complete_cb(self, running_build): |
193 | col1 = gtk.TreeViewColumn('Description') | 397 | self.back.connect("clicked", self.back_button_clicked_cb) |
194 | col2 = gtk.TreeViewColumn('License') | 398 | self.back.set_sensitive(True) |
195 | col3 = gtk.TreeViewColumn('Include') | 399 | self.cancel.set_sensitive(False) |
196 | col3.set_resizable(False) | 400 | for f in self.files_to_clean: |
401 | os.remove(f) | ||
197 | 402 | ||
198 | self.images_tree.append_column(col) | 403 | lbl = "<b>Build completed</b>\n\nClick 'Edit Image' to start another build or 'View Log' to view the build log." |
199 | self.images_tree.append_column(col1) | 404 | if self.handler.building == "image": |
200 | self.images_tree.append_column(col2) | 405 | deploy = self.handler.get_image_deploy_dir() |
201 | self.images_tree.append_column(col3) | 406 | lbl = lbl + "\n<a href=\"file://%s\" title=\"%s\">Browse folder of built images</a>." % (deploy, deploy) |
202 | 407 | ||
203 | cell = gtk.CellRendererText() | 408 | dialog = CrumbsDialog(self, lbl) |
204 | cell1 = gtk.CellRendererText() | 409 | dialog.add_button("View Log", gtk.RESPONSE_CANCEL) |
205 | cell2 = gtk.CellRendererText() | 410 | dialog.add_button("Edit Image", gtk.RESPONSE_OK) |
206 | cell3 = gtk.CellRendererToggle() | 411 | response = dialog.run() |
207 | cell3.set_property('activatable', True) | 412 | dialog.destroy() |
208 | cell3.connect("toggled", self.toggle_include_cb, self.images_tree) | 413 | if response == gtk.RESPONSE_OK: |
209 | 414 | self.toggle_createview() | |
210 | col.pack_start(cell, True) | 415 | |
211 | col1.pack_start(cell1, True) | 416 | def build_started_cb(self, running_build): |
212 | col2.pack_start(cell2, True) | 417 | self.back.set_sensitive(False) |
213 | col3.pack_start(cell3, True) | 418 | self.cancel.set_sensitive(True) |
214 | 419 | ||
215 | col.set_attributes(cell, text=self.model.COL_NAME) | 420 | def include_gplv3_cb(self, toggle): |
216 | col1.set_attributes(cell1, text=self.model.COL_DESC) | 421 | excluded = toggle.get_active() |
217 | col2.set_attributes(cell2, text=self.model.COL_LIC) | 422 | self.handler.toggle_gplv3(excluded) |
218 | col3.set_attributes(cell3, active=self.model.COL_INC) | 423 | |
219 | 424 | def change_bb_threads(self, spinner): | |
220 | self.images_tree.show() | 425 | val = spinner.get_value_as_int() |
221 | 426 | self.handler.set_bbthreads(val) | |
222 | scroll = gtk.ScrolledWindow() | 427 | |
223 | scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS) | 428 | def change_make_threads(self, spinner): |
224 | scroll.set_shadow_type(gtk.SHADOW_IN) | 429 | val = spinner.get_value_as_int() |
225 | scroll.add(self.images_tree) | 430 | self.handler.set_pmake(val) |
226 | 431 | ||
227 | return scroll | 432 | def toggle_toolchain(self, check): |
433 | enabled = check.get_active() | ||
434 | self.handler.toggle_toolchain(enabled) | ||
435 | |||
436 | def toggle_headers(self, check): | ||
437 | enabled = check.get_active() | ||
438 | self.handler.toggle_toolchain_headers(enabled) | ||
439 | |||
440 | def toggle_package_idle_cb(self, opath, image): | ||
441 | """ | ||
442 | As the operations which we're calling on the model can take | ||
443 | a significant amount of time (in the order of seconds) during which | ||
444 | the GUI is unresponsive as the main loop is blocked perform them in | ||
445 | an idle function which at least enables us to set the busy cursor | ||
446 | before the UI is blocked giving the appearance of being responsive. | ||
447 | """ | ||
448 | # Whether the item is currently included | ||
449 | inc = self.model[opath][self.model.COL_INC] | ||
450 | # If the item is already included, mark it for removal then | ||
451 | # the sweep_up() method finds affected items and marks them | ||
452 | # appropriately | ||
453 | if inc: | ||
454 | self.model.mark(opath) | ||
455 | self.model.sweep_up() | ||
456 | # If the item isn't included, mark it for inclusion | ||
457 | else: | ||
458 | self.model.include_item(item_path=opath, | ||
459 | binb="User Selected", | ||
460 | image_contents=image) | ||
461 | |||
462 | self.set_busy_cursor(False) | ||
463 | return False | ||
464 | |||
465 | def toggle_package(self, path, model, image=False): | ||
466 | # Warn user before removing packages | ||
467 | inc = model[path][self.model.COL_INC] | ||
468 | if inc: | ||
469 | pn = model[path][self.model.COL_NAME] | ||
470 | revdeps = self.model.find_reverse_depends(pn) | ||
471 | if len(revdeps): | ||
472 | 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(",")) | ||
473 | else: | ||
474 | lbl = "<b>Remove %s?</b>\n\nThis action cannot be undone." % pn | ||
475 | dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING) | ||
476 | dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) | ||
477 | dialog.add_button("Remove", gtk.RESPONSE_OK) | ||
478 | response = dialog.run() | ||
479 | dialog.destroy() | ||
480 | if response == gtk.RESPONSE_CANCEL: | ||
481 | return | ||
228 | 482 | ||
229 | def toggle_package(self, path, model): | 483 | self.set_busy_cursor() |
230 | # Convert path to path in original model | 484 | # Convert path to path in original model |
231 | opath = model.convert_path_to_child_path(path) | 485 | opath = model.convert_path_to_child_path(path) |
232 | # current include status | 486 | # This is a potentially length call which can block the |
233 | inc = self.model[opath][self.model.COL_INC] | 487 | # main loop, therefore do the work in an idle func to keep |
234 | if inc: | 488 | # the UI responsive |
235 | self.model.mark(opath) | 489 | glib.idle_add(self.toggle_package_idle_cb, opath, image) |
236 | self.model.sweep_up() | ||
237 | #self.model.remove_package_full(cpath) | ||
238 | else: | ||
239 | self.model.include_item(opath) | ||
240 | return | ||
241 | 490 | ||
242 | def remove_package_cb(self, cell, path): | 491 | self.dirty = True |
243 | model = self.model.contents_model() | ||
244 | label = gtk.Label("Are you sure you want to remove this item?") | ||
245 | dialog = gtk.Dialog("Confirm removal", self, | ||
246 | gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, | ||
247 | (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, | ||
248 | gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) | ||
249 | dialog.vbox.pack_start(label) | ||
250 | label.show() | ||
251 | response = dialog.run() | ||
252 | dialog.destroy() | ||
253 | if (response == gtk.RESPONSE_ACCEPT): | ||
254 | self.toggle_package(path, model) | ||
255 | 492 | ||
256 | def toggle_include_cb(self, cell, path, tv): | 493 | def toggle_include_cb(self, cell, path, tv): |
257 | model = tv.get_model() | 494 | model = tv.get_model() |
@@ -262,23 +499,36 @@ class MainWindow (gtk.Window): | |||
262 | sort_model = tv.get_model() | 499 | sort_model = tv.get_model() |
263 | cpath = sort_model.convert_path_to_child_path(path) | 500 | cpath = sort_model.convert_path_to_child_path(path) |
264 | self.toggle_package(cpath, sort_model.get_model()) | 501 | self.toggle_package(cpath, sort_model.get_model()) |
265 | 502 | ||
266 | def pkgsaz(self): | 503 | def pkgsaz(self): |
504 | vbox = gtk.VBox(False, 6) | ||
505 | vbox.show() | ||
267 | self.pkgsaz_tree = gtk.TreeView() | 506 | self.pkgsaz_tree = gtk.TreeView() |
268 | self.pkgsaz_tree.set_headers_visible(True) | 507 | self.pkgsaz_tree.set_headers_visible(True) |
269 | self.pkgsaz_tree.set_headers_clickable(True) | 508 | self.pkgsaz_tree.set_headers_clickable(True) |
270 | self.pkgsaz_tree.set_enable_search(True) | 509 | self.pkgsaz_tree.set_enable_search(True) |
271 | self.pkgsaz_tree.set_search_column(0) | 510 | self.pkgsaz_tree.set_search_column(0) |
272 | self.pkgsaz_tree.get_selection().set_mode(gtk.SELECTION_NONE) | 511 | self.pkgsaz_tree.get_selection().set_mode(gtk.SELECTION_SINGLE) |
273 | 512 | ||
274 | col = gtk.TreeViewColumn('Package') | 513 | col = gtk.TreeViewColumn('Package') |
514 | col.set_clickable(True) | ||
515 | col.set_sort_column_id(self.model.COL_NAME) | ||
516 | col.set_min_width(220) | ||
275 | col1 = gtk.TreeViewColumn('Description') | 517 | col1 = gtk.TreeViewColumn('Description') |
276 | col1.set_resizable(True) | 518 | col1.set_resizable(True) |
519 | col1.set_min_width(360) | ||
277 | col2 = gtk.TreeViewColumn('License') | 520 | col2 = gtk.TreeViewColumn('License') |
278 | col2.set_resizable(True) | 521 | col2.set_resizable(True) |
522 | col2.set_clickable(True) | ||
523 | col2.set_sort_column_id(self.model.COL_LIC) | ||
524 | col2.set_min_width(170) | ||
279 | col3 = gtk.TreeViewColumn('Group') | 525 | col3 = gtk.TreeViewColumn('Group') |
280 | col4 = gtk.TreeViewColumn('Include') | 526 | col3.set_clickable(True) |
281 | col4.set_resizable(False) | 527 | col3.set_sort_column_id(self.model.COL_GROUP) |
528 | col4 = gtk.TreeViewColumn('Included') | ||
529 | col4.set_min_width(80) | ||
530 | col4.set_max_width(90) | ||
531 | col4.set_sort_column_id(self.model.COL_INC) | ||
282 | 532 | ||
283 | self.pkgsaz_tree.append_column(col) | 533 | self.pkgsaz_tree.append_column(col) |
284 | self.pkgsaz_tree.append_column(col1) | 534 | self.pkgsaz_tree.append_column(col1) |
@@ -288,9 +538,9 @@ class MainWindow (gtk.Window): | |||
288 | 538 | ||
289 | cell = gtk.CellRendererText() | 539 | cell = gtk.CellRendererText() |
290 | cell1 = gtk.CellRendererText() | 540 | cell1 = gtk.CellRendererText() |
291 | cell1.set_property('width-chars', 20) | 541 | cell1.set_property('width-chars', 20) |
292 | cell2 = gtk.CellRendererText() | 542 | cell2 = gtk.CellRendererText() |
293 | cell2.set_property('width-chars', 20) | 543 | cell2.set_property('width-chars', 20) |
294 | cell3 = gtk.CellRendererText() | 544 | cell3 = gtk.CellRendererText() |
295 | cell4 = gtk.CellRendererToggle() | 545 | cell4 = gtk.CellRendererToggle() |
296 | cell4.set_property('activatable', True) | 546 | cell4.set_property('activatable', True) |
@@ -300,7 +550,7 @@ class MainWindow (gtk.Window): | |||
300 | col1.pack_start(cell1, True) | 550 | col1.pack_start(cell1, True) |
301 | col2.pack_start(cell2, True) | 551 | col2.pack_start(cell2, True) |
302 | col3.pack_start(cell3, True) | 552 | col3.pack_start(cell3, True) |
303 | col4.pack_start(cell4, True) | 553 | col4.pack_end(cell4, True) |
304 | 554 | ||
305 | col.set_attributes(cell, text=self.model.COL_NAME) | 555 | col.set_attributes(cell, text=self.model.COL_NAME) |
306 | col1.set_attributes(cell1, text=self.model.COL_DESC) | 556 | col1.set_attributes(cell1, text=self.model.COL_DESC) |
@@ -314,75 +564,43 @@ class MainWindow (gtk.Window): | |||
314 | scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS) | 564 | scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS) |
315 | scroll.set_shadow_type(gtk.SHADOW_IN) | 565 | scroll.set_shadow_type(gtk.SHADOW_IN) |
316 | scroll.add(self.pkgsaz_tree) | 566 | scroll.add(self.pkgsaz_tree) |
567 | vbox.pack_start(scroll, True, True, 0) | ||
568 | |||
569 | hb = gtk.HBox(False, 0) | ||
570 | hb.show() | ||
571 | search = gtk.Entry() | ||
572 | search.set_icon_from_stock(gtk.ENTRY_ICON_SECONDARY, "gtk-clear") | ||
573 | search.connect("icon-release", self.search_entry_clear_cb) | ||
574 | search.show() | ||
575 | self.pkgsaz_tree.set_search_entry(search) | ||
576 | hb.pack_end(search, False, False, 0) | ||
577 | label = gtk.Label("Search packages:") | ||
578 | label.show() | ||
579 | hb.pack_end(label, False, False, 6) | ||
580 | vbox.pack_start(hb, False, False, 0) | ||
317 | 581 | ||
318 | return scroll | 582 | return vbox |
319 | |||
320 | def pkgsgrp(self): | ||
321 | self.pkgsgrp_tree = gtk.TreeView() | ||
322 | self.pkgsgrp_tree.set_headers_visible(True) | ||
323 | self.pkgsgrp_tree.set_headers_clickable(False) | ||
324 | self.pkgsgrp_tree.set_enable_search(True) | ||
325 | self.pkgsgrp_tree.set_search_column(0) | ||
326 | self.pkgsgrp_tree.get_selection().set_mode(gtk.SELECTION_NONE) | ||
327 | |||
328 | col = gtk.TreeViewColumn('Package') | ||
329 | col1 = gtk.TreeViewColumn('Description') | ||
330 | col1.set_resizable(True) | ||
331 | col2 = gtk.TreeViewColumn('License') | ||
332 | col2.set_resizable(True) | ||
333 | col3 = gtk.TreeViewColumn('Group') | ||
334 | col4 = gtk.TreeViewColumn('Include') | ||
335 | col4.set_resizable(False) | ||
336 | |||
337 | self.pkgsgrp_tree.append_column(col) | ||
338 | self.pkgsgrp_tree.append_column(col1) | ||
339 | self.pkgsgrp_tree.append_column(col2) | ||
340 | self.pkgsgrp_tree.append_column(col3) | ||
341 | self.pkgsgrp_tree.append_column(col4) | ||
342 | |||
343 | cell = gtk.CellRendererText() | ||
344 | cell1 = gtk.CellRendererText() | ||
345 | cell1.set_property('width-chars', 20) | ||
346 | cell2 = gtk.CellRendererText() | ||
347 | cell2.set_property('width-chars', 20) | ||
348 | cell3 = gtk.CellRendererText() | ||
349 | cell4 = gtk.CellRendererToggle() | ||
350 | cell4.set_property("activatable", True) | ||
351 | cell4.connect("toggled", self.toggle_pkg_include_cb, self.pkgsgrp_tree) | ||
352 | |||
353 | col.pack_start(cell, True) | ||
354 | col1.pack_start(cell1, True) | ||
355 | col2.pack_start(cell2, True) | ||
356 | col3.pack_start(cell3, True) | ||
357 | col4.pack_start(cell4, True) | ||
358 | |||
359 | col.set_attributes(cell, text=self.model.COL_NAME) | ||
360 | col1.set_attributes(cell1, text=self.model.COL_DESC) | ||
361 | col2.set_attributes(cell2, text=self.model.COL_LIC) | ||
362 | col3.set_attributes(cell3, text=self.model.COL_GROUP) | ||
363 | col4.set_attributes(cell4, active=self.model.COL_INC) | ||
364 | |||
365 | self.pkgsgrp_tree.show() | ||
366 | |||
367 | scroll = gtk.ScrolledWindow() | ||
368 | scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS) | ||
369 | scroll.set_shadow_type(gtk.SHADOW_IN) | ||
370 | scroll.add(self.pkgsgrp_tree) | ||
371 | 583 | ||
372 | return scroll | 584 | def search_entry_clear_cb(self, entry, icon_pos, event): |
585 | entry.set_text("") | ||
373 | 586 | ||
374 | def tasks(self): | 587 | def tasks(self): |
588 | vbox = gtk.VBox(False, 6) | ||
589 | vbox.show() | ||
375 | self.tasks_tree = gtk.TreeView() | 590 | self.tasks_tree = gtk.TreeView() |
376 | self.tasks_tree.set_headers_visible(True) | 591 | self.tasks_tree.set_headers_visible(True) |
377 | self.tasks_tree.set_headers_clickable(False) | 592 | self.tasks_tree.set_headers_clickable(False) |
378 | self.tasks_tree.set_enable_search(True) | 593 | self.tasks_tree.set_enable_search(True) |
379 | self.tasks_tree.set_search_column(0) | 594 | self.tasks_tree.set_search_column(0) |
380 | self.tasks_tree.get_selection().set_mode(gtk.SELECTION_NONE) | 595 | self.tasks_tree.get_selection().set_mode(gtk.SELECTION_SINGLE) |
381 | 596 | ||
382 | col = gtk.TreeViewColumn('Package') | 597 | col = gtk.TreeViewColumn('Package') |
598 | col.set_min_width(430) | ||
383 | col1 = gtk.TreeViewColumn('Description') | 599 | col1 = gtk.TreeViewColumn('Description') |
600 | col1.set_min_width(430) | ||
384 | col2 = gtk.TreeViewColumn('Include') | 601 | col2 = gtk.TreeViewColumn('Include') |
385 | col2.set_resizable(False) | 602 | col2.set_min_width(70) |
603 | col2.set_max_width(80) | ||
386 | 604 | ||
387 | self.tasks_tree.append_column(col) | 605 | self.tasks_tree.append_column(col) |
388 | self.tasks_tree.append_column(col1) | 606 | self.tasks_tree.append_column(col1) |
@@ -396,7 +614,7 @@ class MainWindow (gtk.Window): | |||
396 | 614 | ||
397 | col.pack_start(cell, True) | 615 | col.pack_start(cell, True) |
398 | col1.pack_start(cell1, True) | 616 | col1.pack_start(cell1, True) |
399 | col2.pack_start(cell2, True) | 617 | col2.pack_end(cell2, True) |
400 | 618 | ||
401 | col.set_attributes(cell, text=self.model.COL_NAME) | 619 | col.set_attributes(cell, text=self.model.COL_NAME) |
402 | col1.set_attributes(cell1, text=self.model.COL_DESC) | 620 | col1.set_attributes(cell1, text=self.model.COL_DESC) |
@@ -408,26 +626,37 @@ class MainWindow (gtk.Window): | |||
408 | scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS) | 626 | scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS) |
409 | scroll.set_shadow_type(gtk.SHADOW_IN) | 627 | scroll.set_shadow_type(gtk.SHADOW_IN) |
410 | scroll.add(self.tasks_tree) | 628 | scroll.add(self.tasks_tree) |
629 | vbox.pack_start(scroll, True, True, 0) | ||
630 | |||
631 | hb = gtk.HBox(False, 0) | ||
632 | hb.show() | ||
633 | search = gtk.Entry() | ||
634 | search.show() | ||
635 | self.tasks_tree.set_search_entry(search) | ||
636 | hb.pack_end(search, False, False, 0) | ||
637 | label = gtk.Label("Search collections:") | ||
638 | label.show() | ||
639 | hb.pack_end(label, False, False, 6) | ||
640 | vbox.pack_start(hb, False, False, 0) | ||
411 | 641 | ||
412 | return scroll | 642 | return vbox |
413 | 643 | ||
414 | def cancel_build(self, button): | 644 | def cancel_build(self, button): |
415 | label = gtk.Label("Do you really want to stop this build?") | 645 | lbl = "<b>Stop build?</b>\n\nAre you sure you want to stop this build?" |
416 | dialog = gtk.Dialog("Cancel build", | 646 | dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING) |
417 | self, | 647 | dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) |
418 | gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, | 648 | dialog.add_button("Stop", gtk.RESPONSE_OK) |
419 | (gtk.STOCK_NO, gtk.RESPONSE_NO, | 649 | dialog.add_button("Force Stop", gtk.RESPONSE_YES) |
420 | gtk.STOCK_YES, gtk.RESPONSE_YES)) | ||
421 | dialog.vbox.pack_start(label) | ||
422 | label.show() | ||
423 | response = dialog.run() | 650 | response = dialog.run() |
424 | dialog.destroy() | 651 | dialog.destroy() |
425 | if response == gtk.RESPONSE_YES: | 652 | if response == gtk.RESPONSE_OK: |
426 | self.handler.cancel_build() | 653 | self.handler.cancel_build() |
427 | return | 654 | elif response == gtk.RESPONSE_YES: |
655 | self.handler.cancel_build(True) | ||
428 | 656 | ||
429 | def view_build_gui(self): | 657 | def view_build_gui(self): |
430 | vbox = gtk.VBox(False, 6) | 658 | vbox = gtk.VBox(False, 12) |
659 | vbox.set_border_width(6) | ||
431 | vbox.show() | 660 | vbox.show() |
432 | build_tv = RunningBuildTreeView() | 661 | build_tv = RunningBuildTreeView() |
433 | build_tv.show() | 662 | build_tv.show() |
@@ -438,20 +667,74 @@ class MainWindow (gtk.Window): | |||
438 | scrolled_view.add(build_tv) | 667 | scrolled_view.add(build_tv) |
439 | scrolled_view.show() | 668 | scrolled_view.show() |
440 | vbox.pack_start(scrolled_view, expand=True, fill=True) | 669 | vbox.pack_start(scrolled_view, expand=True, fill=True) |
441 | hbox = gtk.HBox(False, 6) | 670 | hbox = gtk.HBox(False, 12) |
442 | hbox.show() | 671 | hbox.show() |
443 | vbox.pack_start(hbox, expand=False, fill=False) | 672 | vbox.pack_start(hbox, expand=False, fill=False) |
444 | cancel = gtk.Button(stock=gtk.STOCK_CANCEL) | 673 | self.back = gtk.Button("Back") |
445 | cancel.connect("clicked", self.cancel_build) | 674 | self.back.show() |
446 | cancel.show() | 675 | self.back.set_sensitive(False) |
447 | hbox.pack_end(cancel, expand=False, fill=False) | 676 | hbox.pack_start(self.back, expand=False, fill=False) |
677 | self.cancel = gtk.Button("Stop Build") | ||
678 | self.cancel.connect("clicked", self.cancel_build) | ||
679 | self.cancel.show() | ||
680 | hbox.pack_end(self.cancel, expand=False, fill=False) | ||
448 | 681 | ||
449 | return vbox | 682 | return vbox |
683 | |||
684 | def create_menu(self): | ||
685 | menu_items = '''<ui> | ||
686 | <menubar name="MenuBar"> | ||
687 | <menu action="File"> | ||
688 | <menuitem action="Save"/> | ||
689 | <menuitem action="Save As"/> | ||
690 | <menuitem action="Open"/> | ||
691 | <separator/> | ||
692 | <menuitem action="AddLayer" label="Add Layer"/> | ||
693 | <separator/> | ||
694 | <menuitem action="Quit"/> | ||
695 | </menu> | ||
696 | <menu action="Edit"> | ||
697 | <menuitem action="Layers" label="Layers"/> | ||
698 | <menuitem action="Preferences"/> | ||
699 | </menu> | ||
700 | <menu action="Help"> | ||
701 | <menuitem action="About"/> | ||
702 | </menu> | ||
703 | </menubar> | ||
704 | </ui>''' | ||
705 | |||
706 | uimanager = gtk.UIManager() | ||
707 | accel = uimanager.get_accel_group() | ||
708 | self.add_accel_group(accel) | ||
709 | |||
710 | actions = gtk.ActionGroup('ImageCreator') | ||
711 | self.actions = actions | ||
712 | actions.add_actions([('Quit', gtk.STOCK_QUIT, None, None, | ||
713 | None, self.menu_quit,), | ||
714 | ('File', None, '_File'), | ||
715 | ('Save', gtk.STOCK_SAVE, None, None, None, self.save_cb), | ||
716 | ('Save As', gtk.STOCK_SAVE_AS, None, None, None, self.save_as_cb), | ||
717 | ('Open', gtk.STOCK_OPEN, None, None, None, self.open_cb), | ||
718 | ('AddLayer', None, 'Add Layer', None, None, self.add_layer_cb), | ||
719 | ('Edit', None, '_Edit'), | ||
720 | ('Help', None, '_Help'), | ||
721 | ('Layers', None, 'Layers', None, None, self.layers_cb), | ||
722 | ('Preferences', gtk.STOCK_PREFERENCES, None, None, None, self.preferences_cb), | ||
723 | ('About', gtk.STOCK_ABOUT, None, None, None, self.about_cb)]) | ||
724 | uimanager.insert_action_group(actions, 0) | ||
725 | uimanager.add_ui_from_string(menu_items) | ||
726 | |||
727 | menubar = uimanager.get_widget('/MenuBar') | ||
728 | menubar.show_all() | ||
729 | |||
730 | return menubar | ||
450 | 731 | ||
451 | def create_build_gui(self): | 732 | def create_build_gui(self): |
452 | vbox = gtk.VBox(False, 6) | 733 | vbox = gtk.VBox(False, 12) |
734 | vbox.set_border_width(6) | ||
453 | vbox.show() | 735 | vbox.show() |
454 | hbox = gtk.HBox(False, 6) | 736 | |
737 | hbox = gtk.HBox(False, 12) | ||
455 | hbox.show() | 738 | hbox.show() |
456 | vbox.pack_start(hbox, expand=False, fill=False) | 739 | vbox.pack_start(hbox, expand=False, fill=False) |
457 | 740 | ||
@@ -459,90 +742,92 @@ class MainWindow (gtk.Window): | |||
459 | label.show() | 742 | label.show() |
460 | hbox.pack_start(label, expand=False, fill=False, padding=6) | 743 | hbox.pack_start(label, expand=False, fill=False, padding=6) |
461 | self.machine_combo = gtk.combo_box_new_text() | 744 | self.machine_combo = gtk.combo_box_new_text() |
462 | self.machine_combo.set_active(0) | ||
463 | self.machine_combo.show() | 745 | self.machine_combo.show() |
464 | self.machine_combo.set_tooltip_text("Selects the architecture of the target board for which you would like to build an image.") | 746 | self.machine_combo.set_tooltip_text("Selects the architecture of the target board for which you would like to build an image.") |
465 | hbox.pack_start(self.machine_combo, expand=False, fill=False, padding=6) | 747 | hbox.pack_start(self.machine_combo, expand=False, fill=False, padding=6) |
748 | label = gtk.Label("Base image:") | ||
749 | label.show() | ||
750 | hbox.pack_start(label, expand=False, fill=False, padding=6) | ||
751 | self.image_combo = gtk.ComboBox() | ||
752 | self.image_combo.show() | ||
753 | self.image_combo.set_tooltip_text("Selects the image on which to base the created image") | ||
754 | image_combo_cell = gtk.CellRendererText() | ||
755 | self.image_combo.pack_start(image_combo_cell, True) | ||
756 | self.image_combo.add_attribute(image_combo_cell, 'text', self.model.COL_NAME) | ||
757 | hbox.pack_start(self.image_combo, expand=False, fill=False, padding=6) | ||
758 | self.progress = gtk.ProgressBar() | ||
759 | self.progress.set_size_request(250, -1) | ||
760 | hbox.pack_end(self.progress, expand=False, fill=False, padding=6) | ||
466 | 761 | ||
467 | ins = gtk.Notebook() | 762 | ins = gtk.Notebook() |
468 | vbox.pack_start(ins, expand=True, fill=True) | 763 | vbox.pack_start(ins, expand=True, fill=True) |
469 | ins.set_show_tabs(True) | 764 | ins.set_show_tabs(True) |
470 | label = gtk.Label("Images") | 765 | label = gtk.Label("Packages") |
471 | label.show() | 766 | label.show() |
472 | ins.append_page(self.images(), tab_label=label) | 767 | ins.append_page(self.pkgsaz(), tab_label=label) |
473 | label = gtk.Label("Tasks") | 768 | label = gtk.Label("Package Collections") |
474 | label.show() | 769 | label.show() |
475 | ins.append_page(self.tasks(), tab_label=label) | 770 | ins.append_page(self.tasks(), tab_label=label) |
476 | label = gtk.Label("Packages (by Group)") | ||
477 | label.show() | ||
478 | ins.append_page(self.pkgsgrp(), tab_label=label) | ||
479 | label = gtk.Label("Packages (by Name)") | ||
480 | label.show() | ||
481 | ins.append_page(self.pkgsaz(), tab_label=label) | ||
482 | ins.set_current_page(0) | 771 | ins.set_current_page(0) |
483 | ins.show_all() | 772 | ins.show_all() |
484 | 773 | ||
485 | hbox = gtk.HBox() | ||
486 | hbox.show() | ||
487 | vbox.pack_start(hbox, expand=False, fill=False) | ||
488 | label = gtk.Label("Image contents:") | 774 | label = gtk.Label("Image contents:") |
775 | self.model.connect("contents-changed", self.update_package_count_cb, label) | ||
776 | label.set_property("xalign", 0.00) | ||
489 | label.show() | 777 | label.show() |
490 | hbox.pack_start(label, expand=False, fill=False, padding=6) | 778 | vbox.pack_start(label, expand=False, fill=False, padding=6) |
491 | con = self.contents() | 779 | con = self.contents() |
492 | con.show() | 780 | con.show() |
493 | vbox.pack_start(con, expand=True, fill=True) | 781 | vbox.pack_start(con, expand=True, fill=True) |
494 | 782 | ||
495 | #advanced = gtk.Expander(label="Advanced") | 783 | bbox = gtk.HButtonBox() |
496 | #advanced.connect("notify::expanded", self.advanced_expander_cb) | 784 | bbox.set_spacing(12) |
497 | #advanced.show() | 785 | bbox.set_layout(gtk.BUTTONBOX_END) |
498 | #vbox.pack_start(advanced, expand=False, fill=False) | 786 | bbox.show() |
499 | 787 | vbox.pack_start(bbox, expand=False, fill=False) | |
500 | hbox = gtk.HBox() | ||
501 | hbox.show() | ||
502 | vbox.pack_start(hbox, expand=False, fill=False) | ||
503 | bake = gtk.Button("Bake") | ||
504 | bake.connect("clicked", self.bake_clicked_cb) | ||
505 | bake.show() | ||
506 | hbox.pack_end(bake, expand=False, fill=False, padding=6) | ||
507 | reset = gtk.Button("Reset") | 788 | reset = gtk.Button("Reset") |
508 | reset.connect("clicked", self.reset_clicked_cb) | 789 | reset.connect("clicked", self.reset_clicked_cb) |
509 | reset.show() | 790 | reset.show() |
510 | hbox.pack_end(reset, expand=False, fill=False, padding=6) | 791 | bbox.add(reset) |
792 | bake = gtk.Button("Bake") | ||
793 | bake.connect("clicked", self.bake_clicked_cb) | ||
794 | bake.show() | ||
795 | bbox.add(bake) | ||
511 | 796 | ||
512 | return vbox | 797 | return vbox |
513 | 798 | ||
799 | def update_package_count_cb(self, model, count, label): | ||
800 | lbl = "Image contents (%s packages):" % count | ||
801 | label.set_text(lbl) | ||
802 | |||
514 | def contents(self): | 803 | def contents(self): |
515 | self.contents_tree = gtk.TreeView() | 804 | self.contents_tree = gtk.TreeView() |
516 | self.contents_tree.set_headers_visible(True) | 805 | self.contents_tree.set_headers_visible(True) |
517 | self.contents_tree.get_selection().set_mode(gtk.SELECTION_NONE) | 806 | self.contents_tree.get_selection().set_mode(gtk.SELECTION_SINGLE) |
518 | 807 | ||
519 | # allow searching in the package column | 808 | # allow searching in the package column |
520 | self.contents_tree.set_search_column(0) | 809 | self.contents_tree.set_search_column(0) |
810 | self.contents_tree.set_enable_search(True) | ||
521 | 811 | ||
522 | col = gtk.TreeViewColumn('Package') | 812 | col = gtk.TreeViewColumn('Package') |
523 | col.set_sort_column_id(0) | 813 | col.set_sort_column_id(0) |
814 | col.set_min_width(430) | ||
524 | col1 = gtk.TreeViewColumn('Brought in by') | 815 | col1 = gtk.TreeViewColumn('Brought in by') |
525 | col1.set_resizable(True) | 816 | col1.set_resizable(True) |
526 | col2 = gtk.TreeViewColumn('Remove') | 817 | col1.set_min_width(430) |
527 | col2.set_expand(False) | ||
528 | 818 | ||
529 | self.contents_tree.append_column(col) | 819 | self.contents_tree.append_column(col) |
530 | self.contents_tree.append_column(col1) | 820 | self.contents_tree.append_column(col1) |
531 | self.contents_tree.append_column(col2) | ||
532 | 821 | ||
533 | cell = gtk.CellRendererText() | 822 | cell = gtk.CellRendererText() |
534 | cell1 = gtk.CellRendererText() | 823 | cell1 = gtk.CellRendererText() |
535 | cell1.set_property('width-chars', 20) | 824 | cell1.set_property('width-chars', 20) |
536 | cell2 = gtk.CellRendererToggle() | ||
537 | cell2.connect("toggled", self.remove_package_cb) | ||
538 | 825 | ||
539 | col.pack_start(cell, True) | 826 | col.pack_start(cell, True) |
540 | col1.pack_start(cell1, True) | 827 | col1.pack_start(cell1, True) |
541 | col2.pack_start(cell2, True) | ||
542 | 828 | ||
543 | col.set_attributes(cell, text=self.model.COL_NAME) | 829 | col.set_attributes(cell, text=self.model.COL_NAME) |
544 | col1.set_attributes(cell1, text=self.model.COL_BINB) | 830 | col1.set_attributes(cell1, text=self.model.COL_BINB) |
545 | col2.set_attributes(cell2, active=self.model.COL_INC) | ||
546 | 831 | ||
547 | self.contents_tree.show() | 832 | self.contents_tree.show() |
548 | 833 | ||
@@ -554,26 +839,67 @@ class MainWindow (gtk.Window): | |||
554 | return scroll | 839 | return scroll |
555 | 840 | ||
556 | def main (server, eventHandler): | 841 | def main (server, eventHandler): |
842 | import multiprocessing | ||
843 | cpu_cnt = multiprocessing.cpu_count() | ||
844 | |||
557 | gobject.threads_init() | 845 | gobject.threads_init() |
558 | 846 | ||
559 | taskmodel = TaskListModel() | 847 | taskmodel = TaskListModel() |
848 | configurator = Configurator() | ||
560 | handler = HobHandler(taskmodel, server) | 849 | handler = HobHandler(taskmodel, server) |
561 | mach = server.runCommand(["getVariable", "MACHINE"]) | 850 | mach = server.runCommand(["getVariable", "MACHINE"]) |
851 | sdk_mach = server.runCommand(["getVariable", "SDKMACHINE"]) | ||
852 | # If SDKMACHINE not set the default SDK_ARCH is used so we | ||
853 | # should represent that in the GUI | ||
854 | if not sdk_mach: | ||
855 | sdk_mach = server.runCommand(["getVariable", "SDK_ARCH"]) | ||
562 | distro = server.runCommand(["getVariable", "DISTRO"]) | 856 | distro = server.runCommand(["getVariable", "DISTRO"]) |
563 | 857 | bbthread = server.runCommand(["getVariable", "BB_NUMBER_THREADS"]) | |
564 | window = MainWindow(taskmodel, handler, mach, distro) | 858 | if not bbthread: |
859 | bbthread = cpu_cnt | ||
860 | handler.set_bbthreads(cpu_cnt) | ||
861 | else: | ||
862 | bbthread = int(bbthread) | ||
863 | pmake = server.runCommand(["getVariable", "PARALLEL_MAKE"]) | ||
864 | if not pmake: | ||
865 | pmake = cpu_cnt | ||
866 | handler.set_pmake(cpu_cnt) | ||
867 | else: | ||
868 | # The PARALLEL_MAKE variable will be of the format: "-j 3" and we only | ||
869 | # want a number for the spinner, so strip everything from the variable | ||
870 | # up to and including the space | ||
871 | pmake = int(pmake[pmake.find(" ")+1:]) | ||
872 | |||
873 | image_types = server.runCommand(["getVariable", "IMAGE_TYPES"]) | ||
874 | |||
875 | pclasses = server.runCommand(["getVariable", "PACKAGE_CLASSES"]).split(" ") | ||
876 | # NOTE: we're only supporting one value for PACKAGE_CLASSES being set | ||
877 | # this seems OK because we're using the first package format set in | ||
878 | # PACKAGE_CLASSES and that's the package manager used for the rootfs | ||
879 | pkg, sep, pclass = pclasses[0].rpartition("_") | ||
880 | |||
881 | prefs = HobPrefs(configurator, handler, sdk_mach, distro, pclass, cpu_cnt, | ||
882 | pmake, bbthread, image_types) | ||
883 | layers = LayerEditor(configurator, None) | ||
884 | window = MainWindow(taskmodel, handler, configurator, prefs, layers, mach) | ||
885 | prefs.set_parent_window(window) | ||
886 | layers.set_parent_window(window) | ||
565 | window.show_all () | 887 | window.show_all () |
566 | handler.connect("machines-updated", window.update_machines) | 888 | handler.connect("machines-updated", window.update_machines) |
567 | handler.connect("distros-updated", window.update_distros) | 889 | handler.connect("sdk-machines-updated", prefs.update_sdk_machines) |
890 | handler.connect("distros-updated", prefs.update_distros) | ||
891 | handler.connect("package-formats-found", prefs.update_package_formats) | ||
568 | handler.connect("generating-data", window.busy) | 892 | handler.connect("generating-data", window.busy) |
569 | handler.connect("data-generated", window.data_generated) | 893 | handler.connect("data-generated", window.data_generated) |
570 | pbar = ProgressBar(window) | 894 | handler.connect("reload-triggered", window.reload_triggered_cb) |
571 | pbar.connect("delete-event", gtk.main_quit) | 895 | configurator.connect("layers-loaded", layers.load_current_layers) |
896 | configurator.connect("layers-changed", handler.reload_data) | ||
897 | handler.connect("config-found", configurator.configFound) | ||
572 | 898 | ||
573 | try: | 899 | try: |
574 | # kick the while thing off | 900 | # kick the while thing off |
575 | handler.current_command = "findConfigFilesDistro" | 901 | handler.current_command = "findConfigFilePathLocal" |
576 | server.runCommand(["findConfigFiles", "DISTRO"]) | 902 | server.runCommand(["findConfigFilePath", "local.conf"]) |
577 | except xmlrpclib.Fault: | 903 | except xmlrpclib.Fault: |
578 | print("XMLRPC Fault getting commandline:\n %s" % x) | 904 | print("XMLRPC Fault getting commandline:\n %s" % x) |
579 | return 1 | 905 | return 1 |
@@ -584,7 +910,7 @@ def main (server, eventHandler): | |||
584 | handler.event_handle_idle_func, | 910 | handler.event_handle_idle_func, |
585 | eventHandler, | 911 | eventHandler, |
586 | window.build, | 912 | window.build, |
587 | pbar) | 913 | window.progress) |
588 | 914 | ||
589 | try: | 915 | try: |
590 | gtk.main() | 916 | gtk.main() |