hob: providing details about process state through porgress bar
[bitbake.git] / lib / bb / ui / crumbs / imageconfigurationpage.py
1 #!/usr/bin/env python
2 #
3 # BitBake Graphical GTK User Interface
4 #
5 # Copyright (C) 2012        Intel Corporation
6 #
7 # Authored by Dongxiao Xu <dongxiao.xu@intel.com>
8 # Authored by Shane Wang <shane.wang@intel.com>
9 #
10 # This program is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License version 2 as
12 # published by the Free Software Foundation.
13 #
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the GNU General Public License along
20 # with this program; if not, write to the Free Software Foundation, Inc.,
21 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22
23 import gtk
24 import glib
25 import re
26 from bb.ui.crumbs.progressbar import HobProgressBar
27 from bb.ui.crumbs.hobcolor import HobColors
28 from bb.ui.crumbs.hobwidget import hic, HobImageButton, HobInfoButton, HobAltButton, HobButton
29 from bb.ui.crumbs.hoblistmodel import RecipeListModel
30 from bb.ui.crumbs.hobpages import HobPage
31
32 #
33 # ImageConfigurationPage
34 #
35 class ImageConfigurationPage (HobPage):
36
37     __dummy_machine__ = "--select a machine--"
38     __dummy_image__   = "--select a base image--"
39
40     def __init__(self, builder):
41         super(ImageConfigurationPage, self).__init__(builder, "Image configuration")
42
43         self.image_combo_id = None
44         # we use machine_combo_changed_by_manual to identify the machine is changed by code
45         # or by manual. If by manual, all user's recipe selection and package selection are
46         # cleared.
47         self.machine_combo_changed_by_manual = True
48         self.stopping = False
49         self.create_visual_elements()
50
51     def create_visual_elements(self):
52         # create visual elements
53         self.toolbar = gtk.Toolbar()
54         self.toolbar.set_orientation(gtk.ORIENTATION_HORIZONTAL)
55         self.toolbar.set_style(gtk.TOOLBAR_BOTH)
56
57         template_button = self.append_toolbar_button(self.toolbar,
58             "Templates",
59             hic.ICON_TEMPLATES_DISPLAY_FILE,
60             hic.ICON_TEMPLATES_HOVER_FILE,
61             "Load a previously saved template",
62             self.template_button_clicked_cb)
63         my_images_button = self.append_toolbar_button(self.toolbar,
64             "Images",
65             hic.ICON_IMAGES_DISPLAY_FILE,
66             hic.ICON_IMAGES_HOVER_FILE,
67             "Open previously built images",
68             self.my_images_button_clicked_cb)
69         settings_button = self.append_toolbar_button(self.toolbar,
70             "Settings",
71             hic.ICON_SETTINGS_DISPLAY_FILE,
72             hic.ICON_SETTINGS_HOVER_FILE,
73             "View additional build settings",
74             self.settings_button_clicked_cb)
75
76         self.config_top_button = self.add_onto_top_bar(self.toolbar)
77
78         self.gtable = gtk.Table(40, 40, True)
79         self.create_config_machine()
80         self.create_config_baseimg()
81         self.config_build_button = self.create_config_build_button()
82
83     def _remove_all_widget(self):
84         children = self.gtable.get_children() or []
85         for child in children:
86             self.gtable.remove(child)
87         children = self.box_group_area.get_children() or []
88         for child in children:
89             self.box_group_area.remove(child)
90         children = self.get_children() or []
91         for child in children:
92             self.remove(child)
93
94     def _pack_components(self, pack_config_build_button = False):
95         self._remove_all_widget()
96         self.pack_start(self.config_top_button, expand=False, fill=False)
97         self.pack_start(self.group_align, expand=True, fill=True)
98
99         self.box_group_area.pack_start(self.gtable, expand=True, fill=True)
100         if pack_config_build_button:
101             self.box_group_area.pack_end(self.config_build_button, expand=False, fill=False)
102         else:
103             box = gtk.HBox(False, 6)
104             box.show()
105             subbox = gtk.HBox(False, 0)
106             subbox.set_size_request(205, 49)
107             subbox.show()
108             box.add(subbox)
109             self.box_group_area.pack_end(box, False, False)
110
111     def show_machine(self):
112         self.progress_bar.reset()
113         self._pack_components(pack_config_build_button = False)
114         self.set_config_machine_layout(show_progress_bar = False)
115         self.show_all()
116
117     def update_progress_bar(self, title, fraction, status=None):
118         if self.stopping == False:
119             self.progress_bar.update(fraction)
120             self.progress_bar.set_text(title)
121             self.progress_bar.set_rcstyle(status)
122
123     def show_info_populating(self):
124         self._pack_components(pack_config_build_button = False)
125         self.set_config_machine_layout(show_progress_bar = True)
126         self.show_all()
127
128     def show_info_populated(self):
129         self.progress_bar.reset()
130         self._pack_components(pack_config_build_button = False)
131         self.set_config_machine_layout(show_progress_bar = False)
132         self.set_config_baseimg_layout()
133         self.show_all()
134
135     def show_baseimg_selected(self):
136         self.progress_bar.reset()
137         self._pack_components(pack_config_build_button = True)
138         self.set_config_machine_layout(show_progress_bar = False)
139         self.set_config_baseimg_layout()
140         self.show_all()
141         if self.builder.recipe_model.get_selected_image() == self.builder.recipe_model.__custom_image__:
142             self.just_bake_button.hide()
143
144     def create_config_machine(self):
145         self.machine_title = gtk.Label()
146         self.machine_title.set_alignment(0.0, 0.5)
147         mark = "<span %s>Select a machine</span>" % self.span_tag('x-large', 'bold')
148         self.machine_title.set_markup(mark)
149
150         self.machine_title_desc = gtk.Label()
151         self.machine_title_desc.set_alignment(0.0, 0.5)
152         mark = ("<span %s>Your selection is the profile of the target machine for which you"
153         " are building the image.\n</span>") % (self.span_tag('medium'))
154         self.machine_title_desc.set_markup(mark)
155
156         self.machine_combo = gtk.combo_box_new_text()
157         self.machine_combo.connect("changed", self.machine_combo_changed_cb)
158
159         icon_file = hic.ICON_LAYERS_DISPLAY_FILE
160         hover_file = hic.ICON_LAYERS_HOVER_FILE
161         self.layer_button = HobImageButton("Layers", "Add support for machines, software, etc.",
162                                 icon_file, hover_file)
163         self.layer_button.connect("clicked", self.layer_button_clicked_cb)
164
165         markup = "Layers are a powerful mechanism to extend the Yocto Project "
166         markup += "with your own functionality.\n"
167         markup += "For more on layers, check the <a href=\""
168         markup += "http://www.yoctoproject.org/docs/current/dev-manual/"
169         markup += "dev-manual.html#understanding-and-using-layers\">reference manual</a>."
170         self.layer_info_icon = HobInfoButton(markup, self.get_parent())
171
172 #        self.progress_box = gtk.HBox(False, 6)
173         self.progress_bar = HobProgressBar()
174 #        self.progress_box.pack_start(self.progress_bar, expand=True, fill=True)
175         self.stop_button = HobAltButton("Stop")
176         self.stop_button.connect("clicked", self.stop_button_clicked_cb)
177 #        self.progress_box.pack_end(stop_button, expand=False, fill=False)
178         self.machine_separator = gtk.HSeparator()
179
180     def set_config_machine_layout(self, show_progress_bar = False):
181         self.gtable.attach(self.machine_title, 0, 40, 0, 4)
182         self.gtable.attach(self.machine_title_desc, 0, 40, 4, 6)
183         self.gtable.attach(self.machine_combo, 0, 12, 7, 10)
184         self.gtable.attach(self.layer_button, 14, 36, 7, 12)
185         self.gtable.attach(self.layer_info_icon, 36, 40, 7, 11)
186         if show_progress_bar:
187             #self.gtable.attach(self.progress_box, 0, 40, 15, 18)
188             self.gtable.attach(self.progress_bar, 0, 37, 15, 18)
189             self.gtable.attach(self.stop_button, 37, 40, 15, 18, 0, 0)
190         self.gtable.attach(self.machine_separator, 0, 40, 13, 14)
191
192     def create_config_baseimg(self):
193         self.image_title = gtk.Label()
194         self.image_title.set_alignment(0, 1.0)
195         mark = "<span %s>Select a base image</span>" % self.span_tag('x-large', 'bold')
196         self.image_title.set_markup(mark)
197
198         self.image_title_desc = gtk.Label()
199         self.image_title_desc.set_alignment(0, 0.5)
200         mark = ("<span %s>Base images are a starting point for the type of image you want. "
201                 "You can build them as \n"
202                 "they are or customize them to your specific needs.\n</span>") % self.span_tag('medium')
203         self.image_title_desc.set_markup(mark)
204
205         self.image_combo = gtk.combo_box_new_text()
206         self.image_combo_id = self.image_combo.connect("changed", self.image_combo_changed_cb)
207
208         self.image_desc = gtk.Label()
209         self.image_desc.set_alignment(0.0, 0.5)
210         self.image_desc.set_size_request(256, -1)
211         self.image_desc.set_justify(gtk.JUSTIFY_LEFT)
212         self.image_desc.set_line_wrap(True)
213
214         # button to view recipes
215         icon_file = hic.ICON_RCIPE_DISPLAY_FILE
216         hover_file = hic.ICON_RCIPE_HOVER_FILE
217         self.view_adv_configuration_button = HobImageButton("Advanced configuration",
218                                                                  "Select image types, package formats, etc",
219                                                                  icon_file, hover_file)        
220         self.view_adv_configuration_button.connect("clicked", self.view_adv_configuration_button_clicked_cb)
221
222         self.image_separator = gtk.HSeparator()
223
224     def set_config_baseimg_layout(self):
225         self.gtable.attach(self.image_title, 0, 40, 15, 17)
226         self.gtable.attach(self.image_title_desc, 0, 40, 18, 22)
227         self.gtable.attach(self.image_combo, 0, 12, 23, 26)
228         self.gtable.attach(self.image_desc, 0, 12, 27, 33)
229         self.gtable.attach(self.view_adv_configuration_button, 14, 36, 23, 28)
230         self.gtable.attach(self.image_separator, 0, 40, 35, 36)
231
232     def create_config_build_button(self):
233         # Create the "Build packages" and "Build image" buttons at the bottom
234         button_box = gtk.HBox(False, 6)
235
236         # create button "Build image"
237         self.just_bake_button = HobButton("Build image")
238         #self.just_bake_button.set_size_request(205, 49)
239         self.just_bake_button.set_tooltip_text("Build target image")
240         self.just_bake_button.connect("clicked", self.just_bake_button_clicked_cb)
241         button_box.pack_end(self.just_bake_button, expand=False, fill=False)
242
243         # create button "Edit Image"
244         self.edit_image_button = HobAltButton("Edit image")
245         #self.edit_image_button.set_size_request(205, 49)
246         self.edit_image_button.set_tooltip_text("Edit target image")
247         self.edit_image_button.connect("clicked", self.edit_image_button_clicked_cb)
248         button_box.pack_end(self.edit_image_button, expand=False, fill=False)
249
250         return button_box
251
252     def stop_button_clicked_cb(self, button):
253         self.stopping = True
254         self.progress_bar.set_text("Stopping recipe parsing")
255         self.progress_bar.set_rcstyle("stop")
256         self.builder.cancel_parse_sync()
257
258     def machine_combo_changed_cb(self, machine_combo):
259         self.stopping = False
260         combo_item = machine_combo.get_active_text()
261         if not combo_item or combo_item == self.__dummy_machine__:
262             return
263
264         # remove __dummy_machine__ item from the store list after first user selection
265         # because it is no longer valid
266         combo_store = machine_combo.get_model()
267         if len(combo_store) and (combo_store[0][0] == self.__dummy_machine__):
268             machine_combo.remove_text(0)
269
270         self.builder.configuration.curr_mach = combo_item
271         if self.machine_combo_changed_by_manual:
272             self.builder.configuration.clear_selection()
273         # reset machine_combo_changed_by_manual
274         self.machine_combo_changed_by_manual = True
275
276         # Do reparse recipes
277         self.builder.populate_recipe_package_info_async()
278
279     def update_machine_combo(self):
280         all_machines = [self.__dummy_machine__] + self.builder.parameters.all_machines
281
282         model = self.machine_combo.get_model()
283         model.clear()
284         for machine in all_machines:
285             self.machine_combo.append_text(machine)
286         self.machine_combo.set_active(0)
287
288     def switch_machine_combo(self):
289         self.machine_combo_changed_by_manual = False
290         model = self.machine_combo.get_model()
291         active = 0
292         while active < len(model):
293             if model[active][0] == self.builder.configuration.curr_mach:
294                 self.machine_combo.set_active(active)
295                 return
296             active += 1
297
298         if model[0][0] != self.__dummy_machine__:
299             self.machine_combo.insert_text(0, self.__dummy_machine__)
300
301         self.machine_combo.set_active(0)
302
303     def update_image_desc(self):
304         desc = ""
305         selected_image = self.image_combo.get_active_text()
306         if selected_image and selected_image in self.builder.recipe_model.pn_path.keys():
307             image_path = self.builder.recipe_model.pn_path[selected_image]
308             image_iter = self.builder.recipe_model.get_iter(image_path)
309             desc = self.builder.recipe_model.get_value(image_iter, self.builder.recipe_model.COL_DESC)
310
311         mark = ("<span %s>%s</span>\n") % (self.span_tag('small'), desc)
312         self.image_desc.set_markup(mark)
313
314     def image_combo_changed_idle_cb(self, selected_image, selected_recipes, selected_packages):
315         self.builder.update_recipe_model(selected_image, selected_recipes)
316         self.builder.update_package_model(selected_packages)
317         self.builder.window_sensitive(True)
318
319     def image_combo_changed_cb(self, combo):
320         self.builder.window_sensitive(False)
321         selected_image = self.image_combo.get_active_text()
322         if not selected_image or (selected_image == self.__dummy_image__):
323             return
324
325         # remove __dummy_image__ item from the store list after first user selection
326         # because it is no longer valid
327         combo_store = combo.get_model()
328         if len(combo_store) and (combo_store[0][0] == self.__dummy_image__):
329             combo.remove_text(0)
330
331         self.builder.customized = False
332
333         selected_recipes = []
334
335         image_path = self.builder.recipe_model.pn_path[selected_image]
336         image_iter = self.builder.recipe_model.get_iter(image_path)
337         selected_packages = self.builder.recipe_model.get_value(image_iter, self.builder.recipe_model.COL_INSTALL).split()
338         self.update_image_desc()
339
340         self.builder.recipe_model.reset()
341         self.builder.package_model.reset()
342
343         self.show_baseimg_selected()
344
345         if selected_image == self.builder.recipe_model.__custom_image__:
346             self.just_bake_button.hide()
347
348         glib.idle_add(self.image_combo_changed_idle_cb, selected_image, selected_recipes, selected_packages)
349
350     def _image_combo_connect_signal(self):
351         if not self.image_combo_id:
352             self.image_combo_id = self.image_combo.connect("changed", self.image_combo_changed_cb)
353
354     def _image_combo_disconnect_signal(self):
355         if self.image_combo_id:
356             self.image_combo.disconnect(self.image_combo_id)
357             self.image_combo_id = None
358
359     def update_image_combo(self, recipe_model, selected_image):
360         # Update the image combo according to the images in the recipe_model
361         # populate image combo
362         filter = {RecipeListModel.COL_TYPE : ['image']}
363         image_model = recipe_model.tree_model(filter)
364         image_model.set_sort_column_id(recipe_model.COL_NAME, gtk.SORT_ASCENDING)
365         active = 0
366         cnt = 1
367
368         white_pattern = []
369         if self.builder.parameters.image_white_pattern:
370             for i in self.builder.parameters.image_white_pattern.split():
371                 white_pattern.append(re.compile(i))
372
373         black_pattern = []
374         if self.builder.parameters.image_black_pattern:
375             for i in self.builder.parameters.image_black_pattern.split():
376                 black_pattern.append(re.compile(i))
377         black_pattern.append(re.compile("hob-image"))
378
379         it = image_model.get_iter_first()
380         self._image_combo_disconnect_signal()
381         model = self.image_combo.get_model()
382         model.clear()
383         # Set a indicator text to combo store when first open
384         self.image_combo.append_text(self.__dummy_image__)
385         # append and set active
386         while it:
387             path = image_model.get_path(it)
388             it = image_model.iter_next(it)
389             image_name = image_model[path][recipe_model.COL_NAME]
390             if image_name == self.builder.recipe_model.__custom_image__:
391                 continue
392
393             if black_pattern:
394                 allow = True
395                 for pattern in black_pattern:
396                     if pattern.search(image_name):
397                         allow = False
398                         break
399             elif white_pattern:
400                 allow = False
401                 for pattern in white_pattern:
402                     if pattern.search(image_name):
403                         allow = True
404                         break
405             else:
406                 allow = True
407
408             if allow:
409                 self.image_combo.append_text(image_name)
410                 if image_name == selected_image:
411                     active = cnt
412                 cnt = cnt + 1
413
414         self.image_combo.append_text(self.builder.recipe_model.__custom_image__)
415         if selected_image == self.builder.recipe_model.__custom_image__:
416             active = cnt
417
418         self.image_combo.set_active(active)
419
420         if active != 0:
421             self.show_baseimg_selected()
422
423         self._image_combo_connect_signal()
424
425     def layer_button_clicked_cb(self, button):
426         # Create a layer selection dialog
427         self.builder.show_layer_selection_dialog()
428         
429     def view_adv_configuration_button_clicked_cb(self, button):
430         # Create an advanced settings dialog
431         response, settings_changed = self.builder.show_adv_settings_dialog()
432         if not response:
433             return
434         if settings_changed:
435             self.builder.reparse_post_adv_settings()        
436
437     def just_bake_button_clicked_cb(self, button):
438         self.builder.just_bake()
439
440     def edit_image_button_clicked_cb(self, button):
441         self.builder.configuration.initial_selected_image = self.builder.configuration.selected_image
442         self.builder.show_recipes()
443
444     def template_button_clicked_cb(self, button):
445         response, path = self.builder.show_load_template_dialog()
446         if not response:
447             return
448         if path:
449             self.builder.load_template(path)
450
451     def my_images_button_clicked_cb(self, button):
452         self.builder.show_load_my_images_dialog()
453
454     def settings_button_clicked_cb(self, button):
455         # Create an advanced settings dialog
456         response, settings_changed = self.builder.show_simple_settings_dialog()
457         if not response:
458             return
459         if settings_changed:
460             self.builder.reparse_post_adv_settings()