summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/bb/ui/crumbs
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake/lib/bb/ui/crumbs')
-rw-r--r--bitbake/lib/bb/ui/crumbs/__init__.py17
-rwxr-xr-xbitbake/lib/bb/ui/crumbs/builddetailspage.py437
-rwxr-xr-xbitbake/lib/bb/ui/crumbs/builder.py1475
-rw-r--r--bitbake/lib/bb/ui/crumbs/buildmanager.py455
-rw-r--r--bitbake/lib/bb/ui/crumbs/hig/__init__.py0
-rw-r--r--bitbake/lib/bb/ui/crumbs/hig/advancedsettingsdialog.py341
-rw-r--r--bitbake/lib/bb/ui/crumbs/hig/crumbsdialog.py44
-rw-r--r--bitbake/lib/bb/ui/crumbs/hig/crumbsmessagedialog.py70
-rw-r--r--bitbake/lib/bb/ui/crumbs/hig/deployimagedialog.py219
-rw-r--r--bitbake/lib/bb/ui/crumbs/hig/imageselectiondialog.py172
-rw-r--r--bitbake/lib/bb/ui/crumbs/hig/layerselectiondialog.py298
-rw-r--r--bitbake/lib/bb/ui/crumbs/hig/parsingwarningsdialog.py163
-rw-r--r--bitbake/lib/bb/ui/crumbs/hig/propertydialog.py437
-rw-r--r--bitbake/lib/bb/ui/crumbs/hig/proxydetailsdialog.py90
-rw-r--r--bitbake/lib/bb/ui/crumbs/hig/retrieveimagedialog.py51
-rw-r--r--bitbake/lib/bb/ui/crumbs/hig/saveimagedialog.py159
-rw-r--r--bitbake/lib/bb/ui/crumbs/hig/settingsuihelper.py122
-rw-r--r--bitbake/lib/bb/ui/crumbs/hig/simplesettingsdialog.py894
-rw-r--r--bitbake/lib/bb/ui/crumbs/hobcolor.py38
-rw-r--r--bitbake/lib/bb/ui/crumbs/hobeventhandler.py639
-rw-r--r--bitbake/lib/bb/ui/crumbs/hoblistmodel.py903
-rwxr-xr-xbitbake/lib/bb/ui/crumbs/hobpages.py128
-rw-r--r--bitbake/lib/bb/ui/crumbs/hobwidget.py904
-rw-r--r--bitbake/lib/bb/ui/crumbs/imageconfigurationpage.py561
-rwxr-xr-xbitbake/lib/bb/ui/crumbs/imagedetailspage.py669
-rwxr-xr-xbitbake/lib/bb/ui/crumbs/packageselectionpage.py355
-rw-r--r--bitbake/lib/bb/ui/crumbs/persistenttooltip.py186
-rw-r--r--bitbake/lib/bb/ui/crumbs/progress.py23
-rw-r--r--bitbake/lib/bb/ui/crumbs/progressbar.py59
-rw-r--r--bitbake/lib/bb/ui/crumbs/puccho.glade606
-rwxr-xr-xbitbake/lib/bb/ui/crumbs/recipeselectionpage.py335
-rw-r--r--bitbake/lib/bb/ui/crumbs/runningbuild.py551
-rw-r--r--bitbake/lib/bb/ui/crumbs/sanitycheckpage.py85
-rw-r--r--bitbake/lib/bb/ui/crumbs/utils.py34
34 files changed, 11520 insertions, 0 deletions
diff --git a/bitbake/lib/bb/ui/crumbs/__init__.py b/bitbake/lib/bb/ui/crumbs/__init__.py
new file mode 100644
index 0000000000..b7cbe1a4f3
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/__init__.py
@@ -0,0 +1,17 @@
1#
2# Gtk+ UI pieces for BitBake
3#
4# Copyright (C) 2006-2007 Richard Purdie
5#
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License version 2 as
8# published by the Free Software Foundation.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License along
16# with this program; if not, write to the Free Software Foundation, Inc.,
17# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
diff --git a/bitbake/lib/bb/ui/crumbs/builddetailspage.py b/bitbake/lib/bb/ui/crumbs/builddetailspage.py
new file mode 100755
index 0000000000..7fc690e2fa
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/builddetailspage.py
@@ -0,0 +1,437 @@
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
23import gtk
24import pango
25import gobject
26import bb.process
27from bb.ui.crumbs.progressbar import HobProgressBar
28from bb.ui.crumbs.hobwidget import hic, HobNotebook, HobAltButton, HobWarpCellRendererText, HobButton, HobInfoButton
29from bb.ui.crumbs.runningbuild import RunningBuildTreeView
30from bb.ui.crumbs.runningbuild import BuildFailureTreeView
31from bb.ui.crumbs.hobpages import HobPage
32from bb.ui.crumbs.hobcolor import HobColors
33
34class BuildConfigurationTreeView(gtk.TreeView):
35 def __init__ (self):
36 gtk.TreeView.__init__(self)
37 self.set_rules_hint(False)
38 self.set_headers_visible(False)
39 self.set_property("hover-expand", True)
40 self.get_selection().set_mode(gtk.SELECTION_SINGLE)
41
42 # The icon that indicates whether we're building or failed.
43 renderer0 = gtk.CellRendererText()
44 renderer0.set_property('font-desc', pango.FontDescription('courier bold 12'))
45 col0 = gtk.TreeViewColumn ("Name", renderer0, text=0)
46 self.append_column (col0)
47
48 # The message of configuration.
49 renderer1 = HobWarpCellRendererText(col_number=1)
50 col1 = gtk.TreeViewColumn ("Values", renderer1, text=1)
51 self.append_column (col1)
52
53 def set_vars(self, key="", var=[""]):
54 d = {}
55 if type(var) == str:
56 d = {key: [var]}
57 elif type(var) == list and len(var) > 1:
58 #create the sub item line
59 l = []
60 text = ""
61 for item in var:
62 text = " - " + item
63 l.append(text)
64 d = {key: var}
65
66 return d
67
68 def set_config_model(self, show_vars):
69 listmodel = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
70 parent = None
71 for var in show_vars:
72 for subitem in var.items():
73 name = subitem[0]
74 is_parent = True
75 for value in subitem[1]:
76 if is_parent:
77 parent = listmodel.append(parent, (name, value))
78 is_parent = False
79 else:
80 listmodel.append(parent, (None, value))
81 name = " - "
82 parent = None
83 # renew the tree model after get the configuration messages
84 self.set_model(listmodel)
85
86 def show(self, src_config_info, src_params):
87 vars = []
88 vars.append(self.set_vars("BB version:", src_params.bb_version))
89 vars.append(self.set_vars("Target arch:", src_params.target_arch))
90 vars.append(self.set_vars("Target OS:", src_params.target_os))
91 vars.append(self.set_vars("Machine:", src_config_info.curr_mach))
92 vars.append(self.set_vars("Distro:", src_config_info.curr_distro))
93 vars.append(self.set_vars("Distro version:", src_params.distro_version))
94 vars.append(self.set_vars("SDK machine:", src_config_info.curr_sdk_machine))
95 vars.append(self.set_vars("Tune features:", src_params.tune_pkgarch))
96 vars.append(self.set_vars("Layers:", src_config_info.layers))
97
98 for path in src_config_info.layers:
99 import os, os.path
100 if os.path.exists(path):
101 branch = bb.process.run('cd %s; git branch | grep "^* " | tr -d "* "' % path)[0]
102 if branch.startswith("fatal:"):
103 branch = "(unknown)"
104 if branch:
105 branch = branch.strip('\n')
106 vars.append(self.set_vars("Branch:", branch))
107 break
108
109 self.set_config_model(vars)
110
111 def reset(self):
112 self.set_model(None)
113
114#
115# BuildDetailsPage
116#
117
118class BuildDetailsPage (HobPage):
119
120 def __init__(self, builder):
121 super(BuildDetailsPage, self).__init__(builder, "Building ...")
122
123 self.num_of_issues = 0
124 self.endpath = (0,)
125 # create visual elements
126 self.create_visual_elements()
127
128 def create_visual_elements(self):
129 # create visual elements
130 self.vbox = gtk.VBox(False, 12)
131
132 self.progress_box = gtk.VBox(False, 12)
133 self.task_status = gtk.Label("\n") # to ensure layout is correct
134 self.task_status.set_alignment(0.0, 0.5)
135 self.progress_box.pack_start(self.task_status, expand=False, fill=False)
136 self.progress_hbox = gtk.HBox(False, 6)
137 self.progress_box.pack_end(self.progress_hbox, expand=True, fill=True)
138 self.progress_bar = HobProgressBar()
139 self.progress_hbox.pack_start(self.progress_bar, expand=True, fill=True)
140 self.stop_button = HobAltButton("Stop")
141 self.stop_button.connect("clicked", self.stop_button_clicked_cb)
142 self.stop_button.set_sensitive(False)
143 self.progress_hbox.pack_end(self.stop_button, expand=False, fill=False)
144
145 self.notebook = HobNotebook()
146 self.config_tv = BuildConfigurationTreeView()
147 self.scrolled_view_config = gtk.ScrolledWindow ()
148 self.scrolled_view_config.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
149 self.scrolled_view_config.add(self.config_tv)
150 self.notebook.append_page(self.scrolled_view_config, "Build configuration")
151
152 self.failure_tv = BuildFailureTreeView()
153 self.failure_model = self.builder.handler.build.model.failure_model()
154 self.failure_tv.set_model(self.failure_model)
155 self.scrolled_view_failure = gtk.ScrolledWindow ()
156 self.scrolled_view_failure.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
157 self.scrolled_view_failure.add(self.failure_tv)
158 self.notebook.append_page(self.scrolled_view_failure, "Issues")
159
160 self.build_tv = RunningBuildTreeView(readonly=True, hob=True)
161 self.build_tv.set_model(self.builder.handler.build.model)
162 self.scrolled_view_build = gtk.ScrolledWindow ()
163 self.scrolled_view_build.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
164 self.scrolled_view_build.add(self.build_tv)
165 self.notebook.append_page(self.scrolled_view_build, "Log")
166
167 self.builder.handler.build.model.connect_after("row-changed", self.scroll_to_present_row, self.scrolled_view_build.get_vadjustment(), self.build_tv)
168
169 self.button_box = gtk.HBox(False, 6)
170 self.back_button = HobAltButton('&lt;&lt; Back')
171 self.back_button.connect("clicked", self.back_button_clicked_cb)
172 self.button_box.pack_start(self.back_button, expand=False, fill=False)
173
174 def update_build_status(self, current, total, task):
175 recipe_path, recipe_task = task.split(", ")
176 recipe = os.path.basename(recipe_path).rstrip(".bb")
177 tsk_msg = "<b>Running task %s of %s:</b> %s\n<b>Recipe:</b> %s" % (current, total, recipe_task, recipe)
178 self.task_status.set_markup(tsk_msg)
179 self.stop_button.set_sensitive(True)
180
181 def reset_build_status(self):
182 self.task_status.set_markup("\n") # to ensure layout is correct
183 self.endpath = (0,)
184
185 def show_issues(self):
186 self.num_of_issues += 1
187 self.notebook.show_indicator_icon("Issues", self.num_of_issues)
188 self.notebook.queue_draw()
189
190 def reset_issues(self):
191 self.num_of_issues = 0
192 self.notebook.hide_indicator_icon("Issues")
193
194 def _remove_all_widget(self):
195 children = self.vbox.get_children() or []
196 for child in children:
197 self.vbox.remove(child)
198 children = self.box_group_area.get_children() or []
199 for child in children:
200 self.box_group_area.remove(child)
201 children = self.get_children() or []
202 for child in children:
203 self.remove(child)
204
205 def add_build_fail_top_bar(self, actions, log_file=None):
206 primary_action = "Edit %s" % actions
207
208 color = HobColors.ERROR
209 build_fail_top = gtk.EventBox()
210 #build_fail_top.set_size_request(-1, 200)
211 build_fail_top.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(color))
212
213 build_fail_tab = gtk.Table(14, 46, True)
214 build_fail_top.add(build_fail_tab)
215
216 icon = gtk.Image()
217 icon_pix_buffer = gtk.gdk.pixbuf_new_from_file(hic.ICON_INDI_ERROR_FILE)
218 icon.set_from_pixbuf(icon_pix_buffer)
219 build_fail_tab.attach(icon, 1, 4, 0, 6)
220
221 label = gtk.Label()
222 label.set_alignment(0.0, 0.5)
223 label.set_markup("<span size='x-large'><b>%s</b></span>" % self.title)
224 build_fail_tab.attach(label, 4, 26, 0, 6)
225
226 label = gtk.Label()
227 label.set_alignment(0.0, 0.5)
228 # Ensure variable disk_full is defined
229 if not hasattr(self.builder, 'disk_full'):
230 self.builder.disk_full = False
231
232 if self.builder.disk_full:
233 markup = "<span size='medium'>There is no disk space left, so Hob cannot finish building your image. Free up some disk space\n"
234 markup += "and restart the build. Check the \"Issues\" tab for more details</span>"
235 label.set_markup(markup)
236 else:
237 label.set_markup("<span size='medium'>Check the \"Issues\" information for more details</span>")
238 build_fail_tab.attach(label, 4, 40, 4, 9)
239
240 # create button 'Edit packages'
241 action_button = HobButton(primary_action)
242 #action_button.set_size_request(-1, 40)
243 action_button.set_tooltip_text("Edit the %s parameters" % actions)
244 action_button.connect('clicked', self.failure_primary_action_button_clicked_cb, primary_action)
245
246 if log_file:
247 open_log_button = HobAltButton("Open log")
248 open_log_button.set_relief(gtk.RELIEF_HALF)
249 open_log_button.set_tooltip_text("Open the build's log file")
250 open_log_button.connect('clicked', self.open_log_button_clicked_cb, log_file)
251
252 attach_pos = (24 if log_file else 14)
253 file_bug_button = HobAltButton('File a bug')
254 file_bug_button.set_relief(gtk.RELIEF_HALF)
255 file_bug_button.set_tooltip_text("Open the Yocto Project bug tracking website")
256 file_bug_button.connect('clicked', self.failure_activate_file_bug_link_cb)
257
258 if not self.builder.disk_full:
259 build_fail_tab.attach(action_button, 4, 13, 9, 12)
260 if log_file:
261 build_fail_tab.attach(open_log_button, 14, 23, 9, 12)
262 build_fail_tab.attach(file_bug_button, attach_pos, attach_pos + 9, 9, 12)
263
264 else:
265 restart_build = HobButton("Restart the build")
266 restart_build.set_tooltip_text("Restart the build")
267 restart_build.connect('clicked', self.restart_build_button_clicked_cb)
268
269 build_fail_tab.attach(restart_build, 4, 13, 9, 12)
270 build_fail_tab.attach(action_button, 14, 23, 9, 12)
271 if log_file:
272 build_fail_tab.attach(open_log_button, attach_pos, attach_pos + 9, 9, 12)
273
274 self.builder.disk_full = False
275 return build_fail_top
276
277 def show_fail_page(self, title):
278 self._remove_all_widget()
279 self.title = "Hob cannot build your %s" % title
280
281 self.build_fail_bar = self.add_build_fail_top_bar(title, self.builder.current_logfile)
282
283 self.pack_start(self.group_align, expand=True, fill=True)
284 self.box_group_area.pack_start(self.build_fail_bar, expand=False, fill=False)
285 self.box_group_area.pack_start(self.vbox, expand=True, fill=True)
286
287 self.vbox.pack_start(self.notebook, expand=True, fill=True)
288 self.show_all()
289 self.notebook.set_page("Issues")
290 self.back_button.hide()
291
292 def add_build_stop_top_bar(self, action, log_file=None):
293 color = HobColors.LIGHT_GRAY
294 build_stop_top = gtk.EventBox()
295 #build_stop_top.set_size_request(-1, 200)
296 build_stop_top.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(color))
297 build_stop_top.set_flags(gtk.CAN_DEFAULT)
298 build_stop_top.grab_default()
299
300 build_stop_tab = gtk.Table(11, 46, True)
301 build_stop_top.add(build_stop_tab)
302
303 icon = gtk.Image()
304 icon_pix_buffer = gtk.gdk.pixbuf_new_from_file(hic.ICON_INFO_HOVER_FILE)
305 icon.set_from_pixbuf(icon_pix_buffer)
306 build_stop_tab.attach(icon, 1, 4, 0, 6)
307
308 label = gtk.Label()
309 label.set_alignment(0.0, 0.5)
310 label.set_markup("<span size='x-large'><b>%s</b></span>" % self.title)
311 build_stop_tab.attach(label, 4, 26, 0, 6)
312
313 action_button = HobButton("Edit %s" % action)
314 action_button.set_size_request(-1, 40)
315 if action == "image":
316 action_button.set_tooltip_text("Edit the image parameters")
317 elif action == "recipes":
318 action_button.set_tooltip_text("Edit the included recipes")
319 elif action == "packages":
320 action_button.set_tooltip_text("Edit the included packages")
321 action_button.connect('clicked', self.stop_primary_action_button_clicked_cb, action)
322 build_stop_tab.attach(action_button, 4, 13, 6, 9)
323
324 if log_file:
325 open_log_button = HobAltButton("Open log")
326 open_log_button.set_relief(gtk.RELIEF_HALF)
327 open_log_button.set_tooltip_text("Open the build's log file")
328 open_log_button.connect('clicked', self.open_log_button_clicked_cb, log_file)
329 build_stop_tab.attach(open_log_button, 14, 23, 6, 9)
330
331 attach_pos = (24 if log_file else 14)
332 build_button = HobAltButton("Build new image")
333 #build_button.set_size_request(-1, 40)
334 build_button.set_tooltip_text("Create a new image from scratch")
335 build_button.connect('clicked', self.new_image_button_clicked_cb)
336 build_stop_tab.attach(build_button, attach_pos, attach_pos + 9, 6, 9)
337
338 return build_stop_top, action_button
339
340 def show_stop_page(self, action):
341 self._remove_all_widget()
342 self.title = "Build stopped"
343 self.build_stop_bar, action_button = self.add_build_stop_top_bar(action, self.builder.current_logfile)
344
345 self.pack_start(self.group_align, expand=True, fill=True)
346 self.box_group_area.pack_start(self.build_stop_bar, expand=False, fill=False)
347 self.box_group_area.pack_start(self.vbox, expand=True, fill=True)
348
349 self.vbox.pack_start(self.notebook, expand=True, fill=True)
350 self.show_all()
351 self.back_button.hide()
352 return action_button
353
354 def show_page(self, step):
355 self._remove_all_widget()
356 if step == self.builder.PACKAGE_GENERATING or step == self.builder.FAST_IMAGE_GENERATING:
357 self.title = "Building packages ..."
358 else:
359 self.title = "Building image ..."
360 self.build_details_top = self.add_onto_top_bar(None)
361 self.pack_start(self.build_details_top, expand=False, fill=False)
362 self.pack_start(self.group_align, expand=True, fill=True)
363
364 self.box_group_area.pack_start(self.vbox, expand=True, fill=True)
365
366 self.progress_bar.reset()
367 self.config_tv.reset()
368 self.vbox.pack_start(self.progress_box, expand=False, fill=False)
369
370 self.vbox.pack_start(self.notebook, expand=True, fill=True)
371
372 self.box_group_area.pack_end(self.button_box, expand=False, fill=False)
373 self.show_all()
374 self.notebook.set_page("Log")
375 self.back_button.hide()
376
377 self.reset_build_status()
378 self.reset_issues()
379
380 def update_progress_bar(self, title, fraction, status=None):
381 self.progress_bar.update(fraction)
382 self.progress_bar.set_title(title)
383 self.progress_bar.set_rcstyle(status)
384
385 def back_button_clicked_cb(self, button):
386 self.builder.show_configuration()
387
388 def new_image_button_clicked_cb(self, button):
389 self.builder.reset()
390
391 def show_back_button(self):
392 self.back_button.show()
393
394 def stop_button_clicked_cb(self, button):
395 self.builder.stop_build()
396
397 def hide_stop_button(self):
398 self.stop_button.set_sensitive(False)
399 self.stop_button.hide()
400
401 def scroll_to_present_row(self, model, path, iter, v_adj, treeview):
402 if treeview and v_adj:
403 if path[0] > self.endpath[0]: # check the event is a new row append or not
404 self.endpath = path
405 # check the gtk.adjustment position is at end boundary or not
406 if (v_adj.upper <= v_adj.page_size) or (v_adj.value == v_adj.upper - v_adj.page_size):
407 treeview.scroll_to_cell(path)
408
409 def show_configurations(self, configurations, params):
410 self.config_tv.show(configurations, params)
411
412 def failure_primary_action_button_clicked_cb(self, button, action):
413 if "Edit recipes" in action:
414 self.builder.show_recipes()
415 elif "Edit packages" in action:
416 self.builder.show_packages()
417 elif "Edit image" in action:
418 self.builder.show_configuration()
419
420 def restart_build_button_clicked_cb(self, button):
421 self.builder.just_bake()
422
423 def stop_primary_action_button_clicked_cb(self, button, action):
424 if "recipes" in action:
425 self.builder.show_recipes()
426 elif "packages" in action:
427 self.builder.show_packages()
428 elif "image" in action:
429 self.builder.show_configuration()
430
431 def open_log_button_clicked_cb(self, button, log_file):
432 if log_file:
433 log_file = "file:///" + log_file
434 gtk.show_uri(screen=button.get_screen(), uri=log_file, timestamp=0)
435
436 def failure_activate_file_bug_link_cb(self, button):
437 button.child.emit('activate-link', "http://bugzilla.yoctoproject.org")
diff --git a/bitbake/lib/bb/ui/crumbs/builder.py b/bitbake/lib/bb/ui/crumbs/builder.py
new file mode 100755
index 0000000000..455af320e8
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/builder.py
@@ -0,0 +1,1475 @@
1#!/usr/bin/env python
2#
3# BitBake Graphical GTK User Interface
4#
5# Copyright (C) 2011-2012 Intel Corporation
6#
7# Authored by Joshua Lock <josh@linux.intel.com>
8# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
9# Authored by Shane Wang <shane.wang@intel.com>
10#
11# This program is free software; you can redistribute it and/or modify
12# it under the terms of the GNU General Public License version 2 as
13# published by the Free Software Foundation.
14#
15# This program is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License along
21# with this program; if not, write to the Free Software Foundation, Inc.,
22# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23
24import glib
25import gtk, gobject
26import copy
27import os
28import subprocess
29import shlex
30import re
31import logging
32import sys
33import signal
34import time
35from bb.ui.crumbs.imageconfigurationpage import ImageConfigurationPage
36from bb.ui.crumbs.recipeselectionpage import RecipeSelectionPage
37from bb.ui.crumbs.packageselectionpage import PackageSelectionPage
38from bb.ui.crumbs.builddetailspage import BuildDetailsPage
39from bb.ui.crumbs.imagedetailspage import ImageDetailsPage
40from bb.ui.crumbs.sanitycheckpage import SanityCheckPage
41from bb.ui.crumbs.hobwidget import hwc, HobButton, HobAltButton
42from bb.ui.crumbs.persistenttooltip import PersistentTooltip
43import bb.ui.crumbs.utils
44from bb.ui.crumbs.hig.crumbsmessagedialog import CrumbsMessageDialog
45from bb.ui.crumbs.hig.simplesettingsdialog import SimpleSettingsDialog
46from bb.ui.crumbs.hig.advancedsettingsdialog import AdvancedSettingsDialog
47from bb.ui.crumbs.hig.deployimagedialog import DeployImageDialog
48from bb.ui.crumbs.hig.layerselectiondialog import LayerSelectionDialog
49from bb.ui.crumbs.hig.imageselectiondialog import ImageSelectionDialog
50from bb.ui.crumbs.hig.parsingwarningsdialog import ParsingWarningsDialog
51from bb.ui.crumbs.hig.propertydialog import PropertyDialog
52
53hobVer = 20120808
54
55class Configuration:
56 '''Represents the data structure of configuration.'''
57
58 @classmethod
59 def parse_proxy_string(cls, proxy):
60 pattern = "^\s*((http|https|ftp|socks|cvs)://)?((\S+):(\S+)@)?([^\s:]+)(:(\d+))?/?"
61 match = re.search(pattern, proxy)
62 if match:
63 return match.group(2), match.group(4), match.group(5), match.group(6), match.group(8)
64 else:
65 return None, None, None, "", ""
66
67 @classmethod
68 def make_host_string(cls, prot, user, passwd, host, default_prot=""):
69 if host == None or host == "":
70 return ""
71
72 passwd = passwd or ""
73
74 if user != None and user != "":
75 if prot == None or prot == "":
76 prot = default_prot
77 return prot + "://" + user + ":" + passwd + "@" + host
78 else:
79 if prot == None or prot == "":
80 return host
81 else:
82 return prot + "://" + host
83
84 @classmethod
85 def make_port_string(cls, port):
86 port = port or ""
87 return port
88
89 @classmethod
90 def make_proxy_string(cls, prot, user, passwd, host, port, default_prot=""):
91 if host == None or host == "":# or port == None or port == "":
92 return ""
93
94 return Configuration.make_host_string(prot, user, passwd, host, default_prot) + (":" + Configuration.make_port_string(port) if port else "")
95
96 def __init__(self):
97 self.curr_mach = ""
98 self.selected_image = None
99 # settings
100 self.curr_distro = ""
101 self.dldir = self.sstatedir = self.sstatemirror = ""
102 self.pmake = self.bbthread = 0
103 self.curr_package_format = ""
104 self.image_rootfs_size = self.image_extra_size = 0
105 self.image_overhead_factor = 1
106 self.incompat_license = ""
107 self.curr_sdk_machine = ""
108 self.conf_version = self.lconf_version = ""
109 self.extra_setting = {}
110 self.toolchain_build = False
111 self.image_fstypes = ""
112 self.image_size = None
113 self.image_packages = []
114 # bblayers.conf
115 self.layers = []
116 # image/recipes/packages
117 self.clear_selection()
118
119 self.user_selected_packages = []
120
121 self.default_task = "build"
122
123 # proxy settings
124 self.enable_proxy = None
125 self.same_proxy = False
126 self.proxies = {
127 "http" : [None, None, None, "", ""], # protocol : [prot, user, passwd, host, port]
128 "https" : [None, None, None, "", ""],
129 "ftp" : [None, None, None, "", ""],
130 "socks" : [None, None, None, "", ""],
131 "cvs" : [None, None, None, "", ""],
132 }
133
134 def clear_selection(self):
135 self.selected_recipes = []
136 self.selected_packages = []
137 self.initial_selected_image = None
138 self.initial_selected_packages = []
139 self.initial_user_selected_packages = []
140
141 def split_proxy(self, protocol, proxy):
142 entry = []
143 prot, user, passwd, host, port = Configuration.parse_proxy_string(proxy)
144 entry.append(prot)
145 entry.append(user)
146 entry.append(passwd)
147 entry.append(host)
148 entry.append(port)
149 self.proxies[protocol] = entry
150
151 def combine_proxy(self, protocol):
152 entry = self.proxies[protocol]
153 return Configuration.make_proxy_string(entry[0], entry[1], entry[2], entry[3], entry[4], protocol)
154
155 def combine_host_only(self, protocol):
156 entry = self.proxies[protocol]
157 return Configuration.make_host_string(entry[0], entry[1], entry[2], entry[3], protocol)
158
159 def combine_port_only(self, protocol):
160 entry = self.proxies[protocol]
161 return Configuration.make_port_string(entry[4])
162
163 def update(self, params):
164 # settings
165 self.curr_distro = params["distro"]
166 self.dldir = params["dldir"]
167 self.sstatedir = params["sstatedir"]
168 self.sstatemirror = params["sstatemirror"]
169 self.pmake = int(params["pmake"].split()[1])
170 self.bbthread = params["bbthread"]
171 self.curr_package_format = " ".join(params["pclass"].split("package_")).strip()
172 self.image_rootfs_size = params["image_rootfs_size"]
173 self.image_extra_size = params["image_extra_size"]
174 self.image_overhead_factor = params['image_overhead_factor']
175 self.incompat_license = params["incompat_license"]
176 self.curr_sdk_machine = params["sdk_machine"]
177 self.conf_version = params["conf_version"]
178 self.lconf_version = params["lconf_version"]
179 self.image_fstypes = params["image_fstypes"]
180 # self.extra_setting/self.toolchain_build
181 # bblayers.conf
182 self.layers = params["layer"].split()
183 self.layers_non_removable = params["layers_non_removable"].split()
184 self.default_task = params["default_task"]
185
186 # proxy settings
187 self.enable_proxy = params["http_proxy"] != "" or params["https_proxy"] != "" \
188 or params["ftp_proxy"] != "" or params["socks_proxy"] != "" \
189 or params["cvs_proxy_host"] != "" or params["cvs_proxy_port"] != ""
190 self.split_proxy("http", params["http_proxy"])
191 self.split_proxy("https", params["https_proxy"])
192 self.split_proxy("ftp", params["ftp_proxy"])
193 self.split_proxy("socks", params["socks_proxy"])
194 self.split_proxy("cvs", params["cvs_proxy_host"] + ":" + params["cvs_proxy_port"])
195
196 def save(self, handler, defaults=False):
197 # bblayers.conf
198 handler.set_var_in_file("BBLAYERS", self.layers, "bblayers.conf")
199 # local.conf
200 if not defaults:
201 handler.early_assign_var_in_file("MACHINE", self.curr_mach, "local.conf")
202 handler.set_var_in_file("DISTRO", self.curr_distro, "local.conf")
203 handler.set_var_in_file("DL_DIR", self.dldir, "local.conf")
204 handler.set_var_in_file("SSTATE_DIR", self.sstatedir, "local.conf")
205 sstate_mirror_list = self.sstatemirror.split("\\n ")
206 sstate_mirror_list_modified = []
207 for mirror in sstate_mirror_list:
208 if mirror != "":
209 mirror = mirror + "\\n"
210 sstate_mirror_list_modified.append(mirror)
211 handler.set_var_in_file("SSTATE_MIRRORS", sstate_mirror_list_modified, "local.conf")
212 handler.set_var_in_file("PARALLEL_MAKE", "-j %s" % self.pmake, "local.conf")
213 handler.set_var_in_file("BB_NUMBER_THREADS", self.bbthread, "local.conf")
214 handler.set_var_in_file("PACKAGE_CLASSES", " ".join(["package_" + i for i in self.curr_package_format.split()]), "local.conf")
215 handler.set_var_in_file("IMAGE_ROOTFS_SIZE", self.image_rootfs_size, "local.conf")
216 handler.set_var_in_file("IMAGE_EXTRA_SPACE", self.image_extra_size, "local.conf")
217 handler.set_var_in_file("INCOMPATIBLE_LICENSE", self.incompat_license, "local.conf")
218 handler.set_var_in_file("SDKMACHINE", self.curr_sdk_machine, "local.conf")
219 handler.set_var_in_file("CONF_VERSION", self.conf_version, "local.conf")
220 handler.set_var_in_file("LCONF_VERSION", self.lconf_version, "bblayers.conf")
221 handler.set_extra_config(self.extra_setting)
222 handler.set_var_in_file("TOOLCHAIN_BUILD", self.toolchain_build, "local.conf")
223 handler.set_var_in_file("IMAGE_FSTYPES", self.image_fstypes, "local.conf")
224 if not defaults:
225 # image/recipes/packages
226 handler.set_var_in_file("__SELECTED_IMAGE__", self.selected_image, "local.conf")
227 handler.set_var_in_file("DEPENDS", self.selected_recipes, "local.conf")
228 handler.set_var_in_file("IMAGE_INSTALL", self.user_selected_packages, "local.conf")
229 # proxy
230 if self.enable_proxy == True:
231 handler.set_var_in_file("http_proxy", self.combine_proxy("http"), "local.conf")
232 handler.set_var_in_file("https_proxy", self.combine_proxy("https"), "local.conf")
233 handler.set_var_in_file("ftp_proxy", self.combine_proxy("ftp"), "local.conf")
234 handler.set_var_in_file("all_proxy", self.combine_proxy("socks"), "local.conf")
235 handler.set_var_in_file("CVS_PROXY_HOST", self.combine_host_only("cvs"), "local.conf")
236 handler.set_var_in_file("CVS_PROXY_PORT", self.combine_port_only("cvs"), "local.conf")
237 else:
238 handler.set_var_in_file("http_proxy", "", "local.conf")
239 handler.set_var_in_file("https_proxy", "", "local.conf")
240 handler.set_var_in_file("ftp_proxy", "", "local.conf")
241 handler.set_var_in_file("all_proxy", "", "local.conf")
242 handler.set_var_in_file("CVS_PROXY_HOST", "", "local.conf")
243 handler.set_var_in_file("CVS_PROXY_PORT", "", "local.conf")
244
245 def __str__(self):
246 s = "VERSION: '%s', BBLAYERS: '%s', MACHINE: '%s', DISTRO: '%s', DL_DIR: '%s'," % \
247 (hobVer, " ".join(self.layers), self.curr_mach, self.curr_distro, self.dldir )
248 s += "SSTATE_DIR: '%s', SSTATE_MIRROR: '%s', PARALLEL_MAKE: '-j %s', BB_NUMBER_THREADS: '%s', PACKAGE_CLASSES: '%s', " % \
249 (self.sstatedir, self.sstatemirror, self.pmake, self.bbthread, " ".join(["package_" + i for i in self.curr_package_format.split()]))
250 s += "IMAGE_ROOTFS_SIZE: '%s', IMAGE_EXTRA_SPACE: '%s', INCOMPATIBLE_LICENSE: '%s', SDKMACHINE: '%s', CONF_VERSION: '%s', " % \
251 (self.image_rootfs_size, self.image_extra_size, self.incompat_license, self.curr_sdk_machine, self.conf_version)
252 s += "LCONF_VERSION: '%s', EXTRA_SETTING: '%s', TOOLCHAIN_BUILD: '%s', IMAGE_FSTYPES: '%s', __SELECTED_IMAGE__: '%s', " % \
253 (self.lconf_version, self.extra_setting, self.toolchain_build, self.image_fstypes, self.selected_image)
254 s += "DEPENDS: '%s', IMAGE_INSTALL: '%s', enable_proxy: '%s', use_same_proxy: '%s', http_proxy: '%s', " % \
255 (self.selected_recipes, self.user_selected_packages, self.enable_proxy, self.same_proxy, self.combine_proxy("http"))
256 s += "https_proxy: '%s', ftp_proxy: '%s', all_proxy: '%s', CVS_PROXY_HOST: '%s', CVS_PROXY_PORT: '%s'" % \
257 (self.combine_proxy("https"), self.combine_proxy("ftp"), self.combine_proxy("socks"),
258 self.combine_host_only("cvs"), self.combine_port_only("cvs"))
259 return s
260
261class Parameters:
262 '''Represents other variables like available machines, etc.'''
263
264 def __init__(self):
265 # Variables
266 self.max_threads = 65535
267 self.core_base = ""
268 self.image_addr = ""
269 self.image_types = []
270 self.runnable_image_types = []
271 self.runnable_machine_patterns = []
272 self.deployable_image_types = []
273 self.tmpdir = ""
274
275 self.all_machines = []
276 self.all_package_formats = []
277 self.all_distros = []
278 self.all_sdk_machines = []
279 self.all_layers = []
280 self.image_names = []
281 self.image_white_pattern = ""
282 self.image_black_pattern = ""
283
284 # for build log to show
285 self.bb_version = ""
286 self.target_arch = ""
287 self.target_os = ""
288 self.distro_version = ""
289 self.tune_pkgarch = ""
290
291 def update(self, params):
292 self.max_threads = params["max_threads"]
293 self.core_base = params["core_base"]
294 self.image_addr = params["image_addr"]
295 self.image_types = params["image_types"].split()
296 self.runnable_image_types = params["runnable_image_types"].split()
297 self.runnable_machine_patterns = params["runnable_machine_patterns"].split()
298 self.deployable_image_types = params["deployable_image_types"].split()
299 self.tmpdir = params["tmpdir"]
300 self.image_white_pattern = params["image_white_pattern"]
301 self.image_black_pattern = params["image_black_pattern"]
302 self.kernel_image_type = params["kernel_image_type"]
303 # for build log to show
304 self.bb_version = params["bb_version"]
305 self.target_arch = params["target_arch"]
306 self.target_os = params["target_os"]
307 self.distro_version = params["distro_version"]
308 self.tune_pkgarch = params["tune_pkgarch"]
309
310def hob_conf_filter(fn, data):
311 if fn.endswith("/local.conf"):
312 distro = data.getVar("DISTRO_HOB")
313 if distro:
314 if distro != "defaultsetup":
315 data.setVar("DISTRO", distro)
316 else:
317 data.delVar("DISTRO")
318
319 keys = ["MACHINE_HOB", "SDKMACHINE_HOB", "PACKAGE_CLASSES_HOB", \
320 "BB_NUMBER_THREADS_HOB", "PARALLEL_MAKE_HOB", "DL_DIR_HOB", \
321 "SSTATE_DIR_HOB", "SSTATE_MIRRORS_HOB", "INCOMPATIBLE_LICENSE_HOB"]
322 for key in keys:
323 var_hob = data.getVar(key)
324 if var_hob:
325 data.setVar(key.split("_HOB")[0], var_hob)
326 return
327
328 if fn.endswith("/bblayers.conf"):
329 layers = data.getVar("BBLAYERS_HOB")
330 if layers:
331 data.setVar("BBLAYERS", layers)
332 return
333
334class Builder(gtk.Window):
335
336 (INITIAL_CHECKS,
337 MACHINE_SELECTION,
338 RCPPKGINFO_POPULATING,
339 RCPPKGINFO_POPULATED,
340 BASEIMG_SELECTED,
341 RECIPE_SELECTION,
342 PACKAGE_GENERATING,
343 PACKAGE_GENERATED,
344 PACKAGE_SELECTION,
345 FAST_IMAGE_GENERATING,
346 IMAGE_GENERATING,
347 IMAGE_GENERATED,
348 MY_IMAGE_OPENED,
349 BACK,
350 END_NOOP) = range(15)
351
352 (SANITY_CHECK,
353 IMAGE_CONFIGURATION,
354 RECIPE_DETAILS,
355 BUILD_DETAILS,
356 PACKAGE_DETAILS,
357 IMAGE_DETAILS,
358 END_TAB) = range(7)
359
360 __step2page__ = {
361 INITIAL_CHECKS : SANITY_CHECK,
362 MACHINE_SELECTION : IMAGE_CONFIGURATION,
363 RCPPKGINFO_POPULATING : IMAGE_CONFIGURATION,
364 RCPPKGINFO_POPULATED : IMAGE_CONFIGURATION,
365 BASEIMG_SELECTED : IMAGE_CONFIGURATION,
366 RECIPE_SELECTION : RECIPE_DETAILS,
367 PACKAGE_GENERATING : BUILD_DETAILS,
368 PACKAGE_GENERATED : PACKAGE_DETAILS,
369 PACKAGE_SELECTION : PACKAGE_DETAILS,
370 FAST_IMAGE_GENERATING : BUILD_DETAILS,
371 IMAGE_GENERATING : BUILD_DETAILS,
372 IMAGE_GENERATED : IMAGE_DETAILS,
373 MY_IMAGE_OPENED : IMAGE_DETAILS,
374 END_NOOP : None,
375 }
376
377 SANITY_CHECK_MIN_DISPLAY_TIME = 5
378
379 def __init__(self, hobHandler, recipe_model, package_model):
380 super(Builder, self).__init__()
381
382 self.hob_image = "hob-image"
383
384 # handler
385 self.handler = hobHandler
386
387 # logger
388 self.logger = logging.getLogger("BitBake")
389 self.consolelog = None
390 self.current_logfile = None
391
392 # configuration and parameters
393 self.configuration = Configuration()
394 self.parameters = Parameters()
395
396 # build step
397 self.current_step = None
398 self.previous_step = None
399
400 self.stopping = False
401
402 # recipe model and package model
403 self.recipe_model = recipe_model
404 self.package_model = package_model
405
406 # Indicate whether user has customized the image
407 self.customized = False
408
409 # Indicate whether the UI is working
410 self.sensitive = True
411
412 # Indicate whether the sanity check ran
413 self.sanity_checked = False
414
415 # save parsing warnings
416 self.parsing_warnings = []
417
418 # create visual elements
419 self.create_visual_elements()
420
421 # connect the signals to functions
422 self.connect("delete-event", self.destroy_window_cb)
423 self.recipe_model.connect ("recipe-selection-changed", self.recipelist_changed_cb)
424 self.package_model.connect("package-selection-changed", self.packagelist_changed_cb)
425 self.handler.connect("config-updated", self.handler_config_updated_cb)
426 self.handler.connect("package-formats-updated", self.handler_package_formats_updated_cb)
427 self.handler.connect("parsing-started", self.handler_parsing_started_cb)
428 self.handler.connect("parsing", self.handler_parsing_cb)
429 self.handler.connect("parsing-completed", self.handler_parsing_completed_cb)
430 self.handler.build.connect("build-started", self.handler_build_started_cb)
431 self.handler.build.connect("build-succeeded", self.handler_build_succeeded_cb)
432 self.handler.build.connect("build-failed", self.handler_build_failed_cb)
433 self.handler.build.connect("build-aborted", self.handler_build_aborted_cb)
434 self.handler.build.connect("task-started", self.handler_task_started_cb)
435 self.handler.build.connect("disk-full", self.handler_disk_full_cb)
436 self.handler.build.connect("log-error", self.handler_build_failure_cb)
437 self.handler.build.connect("log-warning", self.handler_build_failure_cb)
438 self.handler.build.connect("log", self.handler_build_log_cb)
439 self.handler.build.connect("no-provider", self.handler_no_provider_cb)
440 self.handler.connect("generating-data", self.handler_generating_data_cb)
441 self.handler.connect("data-generated", self.handler_data_generated_cb)
442 self.handler.connect("command-succeeded", self.handler_command_succeeded_cb)
443 self.handler.connect("command-failed", self.handler_command_failed_cb)
444 self.handler.connect("parsing-warning", self.handler_parsing_warning_cb)
445 self.handler.connect("sanity-failed", self.handler_sanity_failed_cb)
446 self.handler.connect("recipe-populated", self.handler_recipe_populated_cb)
447 self.handler.connect("package-populated", self.handler_package_populated_cb)
448
449 self.handler.append_to_bbfiles("${TOPDIR}/recipes/images/custom/*.bb")
450 self.handler.append_to_bbfiles("${TOPDIR}/recipes/images/*.bb")
451 self.initiate_new_build_async()
452
453 signal.signal(signal.SIGINT, self.event_handle_SIGINT)
454
455 def create_visual_elements(self):
456 self.set_title("Hob")
457 self.set_icon_name("applications-development")
458 self.set_resizable(True)
459
460 try:
461 window_width = self.get_screen().get_width()
462 window_height = self.get_screen().get_height()
463 except AttributeError:
464 print "Please set DISPLAY variable before running Hob."
465 sys.exit(1)
466
467 if window_width >= hwc.MAIN_WIN_WIDTH:
468 window_width = hwc.MAIN_WIN_WIDTH
469 window_height = hwc.MAIN_WIN_HEIGHT
470 self.set_size_request(window_width, window_height)
471
472 self.vbox = gtk.VBox(False, 0)
473 self.vbox.set_border_width(0)
474 self.add(self.vbox)
475
476 # create pages
477 self.image_configuration_page = ImageConfigurationPage(self)
478 self.recipe_details_page = RecipeSelectionPage(self)
479 self.build_details_page = BuildDetailsPage(self)
480 self.package_details_page = PackageSelectionPage(self)
481 self.image_details_page = ImageDetailsPage(self)
482 self.sanity_check_page = SanityCheckPage(self)
483 self.display_sanity_check = False
484 self.sanity_check_post_func = False
485 self.had_network_error = False
486
487 self.nb = gtk.Notebook()
488 self.nb.set_show_tabs(False)
489 self.nb.insert_page(self.sanity_check_page, None, self.SANITY_CHECK)
490 self.nb.insert_page(self.image_configuration_page, None, self.IMAGE_CONFIGURATION)
491 self.nb.insert_page(self.recipe_details_page, None, self.RECIPE_DETAILS)
492 self.nb.insert_page(self.build_details_page, None, self.BUILD_DETAILS)
493 self.nb.insert_page(self.package_details_page, None, self.PACKAGE_DETAILS)
494 self.nb.insert_page(self.image_details_page, None, self.IMAGE_DETAILS)
495 self.vbox.pack_start(self.nb, expand=True, fill=True)
496
497 self.show_all()
498 self.nb.set_current_page(0)
499
500 def sanity_check_timeout(self):
501 # The minimum time for showing the 'sanity check' page has passe
502 # If someone set the 'sanity_check_post_step' meanwhile, execute it now
503 self.display_sanity_check = False
504 if self.sanity_check_post_func:
505 temp = self.sanity_check_post_func
506 self.sanity_check_post_func = None
507 temp()
508 return False
509
510 def show_sanity_check_page(self):
511 # This window must stay on screen for at least 5 seconds, according to the design document
512 self.nb.set_current_page(self.SANITY_CHECK)
513 self.sanity_check_post_step = None
514 self.display_sanity_check = True
515 self.sanity_check_page.start()
516 gobject.timeout_add(self.SANITY_CHECK_MIN_DISPLAY_TIME * 1000, self.sanity_check_timeout)
517
518 def execute_after_sanity_check(self, func):
519 if not self.display_sanity_check:
520 func()
521 else:
522 self.sanity_check_post_func = func
523
524 def generate_configuration(self):
525 if not self.sanity_checked:
526 self.show_sanity_check_page()
527 self.handler.generate_configuration()
528
529 def initiate_new_build_async(self):
530 self.configuration.selected_image = None
531 self.switch_page(self.MACHINE_SELECTION)
532 self.handler.init_cooker()
533 self.handler.set_extra_inherit("image_types")
534 self.generate_configuration()
535
536 def update_config_async(self):
537 self.set_user_config()
538 self.generate_configuration()
539 self.switch_page(self.MACHINE_SELECTION)
540
541 def sanity_check(self):
542 self.handler.trigger_sanity_check()
543
544 def populate_recipe_package_info_async(self):
545 self.switch_page(self.RCPPKGINFO_POPULATING)
546 # Parse recipes
547 self.set_user_config()
548 self.handler.generate_recipes()
549
550 def generate_packages_async(self, log = False):
551 self.switch_page(self.PACKAGE_GENERATING)
552 if log:
553 self.current_logfile = self.handler.get_logfile()
554 self.do_log(self.current_logfile)
555 # Build packages
556 _, all_recipes = self.recipe_model.get_selected_recipes()
557 self.set_user_config()
558 self.handler.reset_build()
559 self.handler.generate_packages(all_recipes, self.configuration.default_task)
560
561 def restore_initial_selected_packages(self):
562 self.package_model.set_selected_packages(self.configuration.initial_user_selected_packages, True)
563 self.package_model.set_selected_packages(self.configuration.initial_selected_packages)
564 for package in self.configuration.selected_packages:
565 if package not in self.configuration.initial_selected_packages:
566 self.package_model.exclude_item(self.package_model.find_path_for_item(package))
567
568 def fast_generate_image_async(self, log = False):
569 self.switch_page(self.FAST_IMAGE_GENERATING)
570 if log:
571 self.current_logfile = self.handler.get_logfile()
572 self.do_log(self.current_logfile)
573 # Build packages
574 _, all_recipes = self.recipe_model.get_selected_recipes()
575 self.set_user_config()
576 self.handler.reset_build()
577 self.handler.generate_packages(all_recipes, self.configuration.default_task)
578
579 def generate_image_async(self, cont = False):
580 self.switch_page(self.IMAGE_GENERATING)
581 self.handler.reset_build()
582 if not cont:
583 self.current_logfile = self.handler.get_logfile()
584 self.do_log(self.current_logfile)
585 # Build image
586 self.set_user_config()
587 toolchain_packages = []
588 base_image = None
589 if self.configuration.toolchain_build:
590 toolchain_packages = self.package_model.get_selected_packages_toolchain()
591 if self.configuration.selected_image == self.recipe_model.__custom_image__:
592 packages = self.package_model.get_selected_packages()
593 image = self.hob_image
594 base_image = self.configuration.initial_selected_image
595 else:
596 packages = []
597 image = self.configuration.selected_image
598 self.handler.generate_image(image,
599 base_image,
600 packages,
601 toolchain_packages,
602 self.configuration.default_task)
603
604 def generate_new_image(self, image, description):
605 base_image = self.configuration.initial_selected_image
606 if base_image == self.recipe_model.__custom_image__:
607 base_image = None
608 packages = self.package_model.get_selected_packages()
609 self.handler.generate_new_image(image, base_image, packages, description)
610
611 def ensure_dir(self, directory):
612 self.handler.ensure_dir(directory)
613
614 def get_parameters_sync(self):
615 return self.handler.get_parameters()
616
617 def request_package_info_async(self):
618 self.handler.request_package_info()
619
620 def cancel_build_sync(self, force=False):
621 self.handler.cancel_build(force)
622
623 def cancel_parse_sync(self):
624 self.handler.cancel_parse()
625
626 def switch_page(self, next_step):
627 # Main Workflow (Business Logic)
628 self.nb.set_current_page(self.__step2page__[next_step])
629
630 if next_step == self.MACHINE_SELECTION: # init step
631 self.image_configuration_page.show_machine()
632
633 elif next_step == self.RCPPKGINFO_POPULATING:
634 # MACHINE CHANGED action or SETTINGS CHANGED
635 # show the progress bar
636 self.image_configuration_page.show_info_populating()
637
638 elif next_step == self.RCPPKGINFO_POPULATED:
639 self.image_configuration_page.show_info_populated()
640
641 elif next_step == self.BASEIMG_SELECTED:
642 self.image_configuration_page.show_baseimg_selected()
643
644 elif next_step == self.RECIPE_SELECTION:
645 if self.recipe_model.get_selected_image() == self.recipe_model.__custom_image__:
646 self.recipe_details_page.set_recipe_curr_tab(self.recipe_details_page.ALL)
647 else:
648 self.recipe_details_page.set_recipe_curr_tab(self.recipe_details_page.INCLUDED)
649
650 elif next_step == self.PACKAGE_SELECTION:
651 self.configuration.initial_selected_packages = self.configuration.selected_packages
652 self.configuration.initial_user_selected_packages = self.configuration.user_selected_packages
653 self.package_details_page.set_title("Edit packages")
654 if self.recipe_model.get_selected_image() == self.recipe_model.__custom_image__:
655 self.package_details_page.set_packages_curr_tab(self.package_details_page.ALL)
656 else:
657 self.package_details_page.set_packages_curr_tab(self.package_details_page.INCLUDED)
658 self.package_details_page.show_page(self.current_logfile)
659
660
661 elif next_step == self.PACKAGE_GENERATING or next_step == self.FAST_IMAGE_GENERATING:
662 # both PACKAGE_GENERATING and FAST_IMAGE_GENERATING share the same page
663 self.build_details_page.show_page(next_step)
664
665 elif next_step == self.PACKAGE_GENERATED:
666 self.package_details_page.set_title("Step 2 of 2: Edit packages")
667 if self.recipe_model.get_selected_image() == self.recipe_model.__custom_image__:
668 self.package_details_page.set_packages_curr_tab(self.package_details_page.ALL)
669 else:
670 self.package_details_page.set_packages_curr_tab(self.package_details_page.INCLUDED)
671 self.package_details_page.show_page(self.current_logfile)
672
673 elif next_step == self.IMAGE_GENERATING:
674 # after packages are generated, selected_packages need to
675 # be updated in package_model per selected_image in recipe_model
676 self.build_details_page.show_page(next_step)
677
678 elif next_step == self.IMAGE_GENERATED:
679 self.image_details_page.show_page(next_step)
680
681 elif next_step == self.MY_IMAGE_OPENED:
682 self.image_details_page.show_page(next_step)
683
684 self.previous_step = self.current_step
685 self.current_step = next_step
686
687 def set_user_config_proxies(self):
688 if self.configuration.enable_proxy == True:
689 self.handler.set_http_proxy(self.configuration.combine_proxy("http"))
690 self.handler.set_https_proxy(self.configuration.combine_proxy("https"))
691 self.handler.set_ftp_proxy(self.configuration.combine_proxy("ftp"))
692 self.handler.set_socks_proxy(self.configuration.combine_proxy("socks"))
693 self.handler.set_cvs_proxy(self.configuration.combine_host_only("cvs"), self.configuration.combine_port_only("cvs"))
694 elif self.configuration.enable_proxy == False:
695 self.handler.set_http_proxy("")
696 self.handler.set_https_proxy("")
697 self.handler.set_ftp_proxy("")
698 self.handler.set_socks_proxy("")
699 self.handler.set_cvs_proxy("", "")
700
701 def set_user_config_extra(self):
702 self.handler.set_rootfs_size(self.configuration.image_rootfs_size)
703 self.handler.set_extra_size(self.configuration.image_extra_size)
704 self.handler.set_incompatible_license(self.configuration.incompat_license)
705 self.handler.set_sdk_machine(self.configuration.curr_sdk_machine)
706 self.handler.set_image_fstypes(self.configuration.image_fstypes)
707 self.handler.set_extra_config(self.configuration.extra_setting)
708 self.handler.set_extra_inherit("packageinfo image_types")
709 self.set_user_config_proxies()
710
711 def set_user_config(self):
712 # set bb layers
713 self.handler.set_bblayers(self.configuration.layers)
714 # set local configuration
715 self.handler.set_machine(self.configuration.curr_mach)
716 self.handler.set_package_format(self.configuration.curr_package_format)
717 self.handler.set_distro(self.configuration.curr_distro)
718 self.handler.set_dl_dir(self.configuration.dldir)
719 self.handler.set_sstate_dir(self.configuration.sstatedir)
720 self.handler.set_sstate_mirrors(self.configuration.sstatemirror)
721 self.handler.set_pmake(self.configuration.pmake)
722 self.handler.set_bbthreads(self.configuration.bbthread)
723 self.set_user_config_extra()
724
725 def update_recipe_model(self, selected_image, selected_recipes):
726 self.recipe_model.set_selected_image(selected_image)
727 self.recipe_model.set_selected_recipes(selected_recipes)
728
729 def update_package_model(self, selected_packages, user_selected_packages=None):
730 if user_selected_packages:
731 left = self.package_model.set_selected_packages(user_selected_packages, True)
732 self.configuration.user_selected_packages += left
733 left = self.package_model.set_selected_packages(selected_packages)
734 self.configuration.selected_packages += left
735
736 def update_configuration_parameters(self, params):
737 if params:
738 self.configuration.update(params)
739 self.parameters.update(params)
740
741 def set_base_image(self):
742 self.configuration.initial_selected_image = self.configuration.selected_image
743 if self.configuration.selected_image != self.recipe_model.__custom_image__:
744 self.hob_image = self.configuration.selected_image + "-edited"
745
746 def reset(self):
747 self.configuration.curr_mach = ""
748 self.configuration.clear_selection()
749 self.image_configuration_page.switch_machine_combo()
750 self.switch_page(self.MACHINE_SELECTION)
751
752 # Callback Functions
753 def handler_config_updated_cb(self, handler, which, values):
754 if which == "distro":
755 self.parameters.all_distros = values
756 elif which == "machine":
757 self.parameters.all_machines = values
758 self.image_configuration_page.update_machine_combo()
759 elif which == "machine-sdk":
760 self.parameters.all_sdk_machines = values
761
762 def handler_package_formats_updated_cb(self, handler, formats):
763 self.parameters.all_package_formats = formats
764
765 def switch_to_image_configuration_helper(self):
766 self.sanity_check_page.stop()
767 self.switch_page(self.IMAGE_CONFIGURATION)
768 self.image_configuration_page.switch_machine_combo()
769
770 def show_network_error_dialog_helper(self):
771 self.sanity_check_page.stop()
772 self.show_network_error_dialog()
773
774 def handler_command_succeeded_cb(self, handler, initcmd):
775 if initcmd == self.handler.GENERATE_CONFIGURATION:
776 if not self.configuration.curr_mach:
777 self.configuration.curr_mach = self.handler.runCommand(["getVariable", "HOB_MACHINE"]) or ""
778 self.update_configuration_parameters(self.get_parameters_sync())
779 if not self.sanity_checked:
780 self.sanity_check()
781 self.sanity_checked = True
782 elif initcmd == self.handler.SANITY_CHECK:
783 if self.had_network_error:
784 self.had_network_error = False
785 self.execute_after_sanity_check(self.show_network_error_dialog_helper)
786 else:
787 # Switch to the 'image configuration' page now, but we might need
788 # to wait for the minimum display time of the sanity check page
789 self.execute_after_sanity_check(self.switch_to_image_configuration_helper)
790 elif initcmd in [self.handler.GENERATE_RECIPES,
791 self.handler.GENERATE_PACKAGES,
792 self.handler.GENERATE_IMAGE]:
793 self.update_configuration_parameters(self.get_parameters_sync())
794 self.request_package_info_async()
795 elif initcmd == self.handler.POPULATE_PACKAGEINFO:
796 if self.current_step == self.RCPPKGINFO_POPULATING:
797 self.switch_page(self.RCPPKGINFO_POPULATED)
798 self.rcppkglist_populated()
799 return
800
801 self.rcppkglist_populated()
802 if self.current_step == self.FAST_IMAGE_GENERATING:
803 self.generate_image_async(True)
804
805 def show_error_dialog(self, msg):
806 lbl = "<b>Hob found an error</b>"
807 dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_ERROR, msg)
808 button = dialog.add_button("Close", gtk.RESPONSE_OK)
809 HobButton.style_button(button)
810 response = dialog.run()
811 dialog.destroy()
812
813 def show_warning_dialog(self):
814 dialog = ParsingWarningsDialog(title = "View warnings",
815 warnings = self.parsing_warnings,
816 parent = None,
817 flags = gtk.DIALOG_DESTROY_WITH_PARENT
818 | gtk.DIALOG_NO_SEPARATOR)
819 response = dialog.run()
820 dialog.destroy()
821
822 def show_network_error_dialog(self):
823 lbl = "<b>Hob cannot connect to the network</b>"
824 msg = msg + "Please check your network connection. If you are using a proxy server, please make sure it is configured correctly."
825 dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_ERROR, msg)
826 button = dialog.add_button("Close", gtk.RESPONSE_OK)
827 HobButton.style_button(button)
828 button = dialog.add_button("Proxy settings", gtk.RESPONSE_CANCEL)
829 HobButton.style_button(button)
830 res = dialog.run()
831 dialog.destroy()
832 if res == gtk.RESPONSE_CANCEL:
833 res, settings_changed = self.show_simple_settings_dialog(SimpleSettingsDialog.PROXIES_PAGE_ID)
834 if not res:
835 return
836 if settings_changed:
837 self.reparse_post_adv_settings()
838
839 def handler_command_failed_cb(self, handler, msg):
840 if msg:
841 self.show_error_dialog(msg)
842 self.reset()
843
844 def handler_parsing_warning_cb(self, handler, warn_msg):
845 self.parsing_warnings.append(warn_msg)
846
847 def handler_sanity_failed_cb(self, handler, msg, network_error):
848 self.reset()
849 if network_error:
850 # Mark this in an internal field. The "network error" dialog will be
851 # shown later, when a SanityCheckPassed event will be handled
852 # (as sent by sanity.bbclass)
853 self.had_network_error = True
854 else:
855 msg = msg.replace("your local.conf", "Settings")
856 self.show_error_dialog(msg)
857 self.reset()
858
859 def window_sensitive(self, sensitive):
860 self.image_configuration_page.machine_combo.set_sensitive(sensitive)
861 self.image_configuration_page.machine_combo.child.set_sensitive(sensitive)
862 self.image_configuration_page.image_combo.set_sensitive(sensitive)
863 self.image_configuration_page.image_combo.child.set_sensitive(sensitive)
864 self.image_configuration_page.layer_button.set_sensitive(sensitive)
865 self.image_configuration_page.layer_info_icon.set_sensitive(sensitive)
866 self.image_configuration_page.toolbar.set_sensitive(sensitive)
867 self.image_configuration_page.view_adv_configuration_button.set_sensitive(sensitive)
868 self.image_configuration_page.config_build_button.set_sensitive(sensitive)
869
870 self.recipe_details_page.set_sensitive(sensitive)
871 self.package_details_page.set_sensitive(sensitive)
872 self.build_details_page.set_sensitive(sensitive)
873 self.image_details_page.set_sensitive(sensitive)
874
875 if sensitive:
876 self.window.set_cursor(None)
877 else:
878 self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
879 self.sensitive = sensitive
880
881
882 def handler_generating_data_cb(self, handler):
883 self.window_sensitive(False)
884
885 def handler_data_generated_cb(self, handler):
886 self.window_sensitive(True)
887
888 def rcppkglist_populated(self):
889 selected_image = self.configuration.selected_image
890 selected_recipes = self.configuration.selected_recipes[:]
891 selected_packages = self.configuration.selected_packages[:]
892 user_selected_packages = self.configuration.user_selected_packages[:]
893
894 self.image_configuration_page.update_image_combo(self.recipe_model, selected_image)
895 self.image_configuration_page.update_image_desc()
896 self.update_recipe_model(selected_image, selected_recipes)
897 self.update_package_model(selected_packages, user_selected_packages)
898
899 def recipelist_changed_cb(self, recipe_model):
900 self.recipe_details_page.refresh_selection()
901
902 def packagelist_changed_cb(self, package_model):
903 self.package_details_page.refresh_selection()
904
905 def handler_recipe_populated_cb(self, handler):
906 self.image_configuration_page.update_progress_bar("Populating recipes", 0.99)
907
908 def handler_package_populated_cb(self, handler):
909 self.image_configuration_page.update_progress_bar("Populating packages", 1.0)
910
911 def handler_parsing_started_cb(self, handler, message):
912 if self.current_step != self.RCPPKGINFO_POPULATING:
913 return
914
915 fraction = 0
916 if message["eventname"] == "TreeDataPreparationStarted":
917 fraction = 0.6 + fraction
918 self.image_configuration_page.stop_button.set_sensitive(False)
919 self.image_configuration_page.update_progress_bar("Generating dependency tree", fraction)
920 else:
921 self.image_configuration_page.stop_button.set_sensitive(True)
922 self.image_configuration_page.update_progress_bar(message["title"], fraction)
923
924 def handler_parsing_cb(self, handler, message):
925 if self.current_step != self.RCPPKGINFO_POPULATING:
926 return
927
928 fraction = message["current"] * 1.0/message["total"]
929 if message["eventname"] == "TreeDataPreparationProgress":
930 fraction = 0.6 + 0.38 * fraction
931 self.image_configuration_page.update_progress_bar("Generating dependency tree", fraction)
932 else:
933 fraction = 0.6 * fraction
934 self.image_configuration_page.update_progress_bar(message["title"], fraction)
935
936 def handler_parsing_completed_cb(self, handler, message):
937 if self.current_step != self.RCPPKGINFO_POPULATING:
938 return
939
940 if message["eventname"] == "TreeDataPreparationCompleted":
941 fraction = 0.98
942 else:
943 fraction = 0.6
944 self.image_configuration_page.update_progress_bar("Generating dependency tree", fraction)
945
946 def handler_build_started_cb(self, running_build):
947 if self.current_step == self.FAST_IMAGE_GENERATING:
948 fraction = 0
949 elif self.current_step == self.IMAGE_GENERATING:
950 if self.previous_step == self.FAST_IMAGE_GENERATING:
951 fraction = 0.9
952 else:
953 fraction = 0
954 elif self.current_step == self.PACKAGE_GENERATING:
955 fraction = 0
956 self.build_details_page.update_progress_bar("Build Started: ", fraction)
957 self.build_details_page.show_configurations(self.configuration, self.parameters)
958
959 def build_succeeded(self):
960 if self.current_step == self.FAST_IMAGE_GENERATING:
961 fraction = 0.9
962 elif self.current_step == self.IMAGE_GENERATING:
963 fraction = 1.0
964 version = ""
965 self.parameters.image_names = []
966 selected_image = self.recipe_model.get_selected_image()
967 if selected_image == self.recipe_model.__custom_image__:
968 if self.configuration.initial_selected_image != selected_image:
969 version = self.recipe_model.get_custom_image_version()
970 linkname = self.hob_image + version + "-" + self.configuration.curr_mach
971 else:
972 linkname = selected_image + '-' + self.configuration.curr_mach
973 image_extension = self.get_image_extension()
974 for image_type in self.parameters.image_types:
975 if image_type in image_extension:
976 real_types = image_extension[image_type]
977 else:
978 real_types = [image_type]
979 for real_image_type in real_types:
980 linkpath = self.parameters.image_addr + '/' + linkname + '.' + real_image_type
981 if os.path.exists(linkpath):
982 self.parameters.image_names.append(os.readlink(linkpath))
983 elif self.current_step == self.PACKAGE_GENERATING:
984 fraction = 1.0
985 self.build_details_page.update_progress_bar("Build Completed: ", fraction)
986 self.handler.build_succeeded_async()
987 self.stopping = False
988
989 if self.current_step == self.PACKAGE_GENERATING:
990 self.switch_page(self.PACKAGE_GENERATED)
991 elif self.current_step == self.IMAGE_GENERATING:
992 self.switch_page(self.IMAGE_GENERATED)
993
994 def build_failed(self):
995 if self.stopping:
996 status = "stop"
997 message = "Build stopped: "
998 fraction = self.build_details_page.progress_bar.get_fraction()
999 stop_to_next_edit = ""
1000 if self.current_step == self.FAST_IMAGE_GENERATING:
1001 stop_to_next_edit = "image configuration"
1002 elif self.current_step == self.IMAGE_GENERATING:
1003 if self.previous_step == self.FAST_IMAGE_GENERATING:
1004 stop_to_next_edit = "image configuration"
1005 else:
1006 stop_to_next_edit = "packages"
1007 elif self.current_step == self.PACKAGE_GENERATING:
1008 stop_to_next_edit = "recipes"
1009 button = self.build_details_page.show_stop_page(stop_to_next_edit.split(' ')[0])
1010 self.set_default(button)
1011 else:
1012 fail_to_next_edit = ""
1013 if self.current_step == self.FAST_IMAGE_GENERATING:
1014 fail_to_next_edit = "image configuration"
1015 fraction = 0.9
1016 elif self.current_step == self.IMAGE_GENERATING:
1017 if self.previous_step == self.FAST_IMAGE_GENERATING:
1018 fail_to_next_edit = "image configuration"
1019 else:
1020 fail_to_next_edit = "packages"
1021 fraction = 1.0
1022 elif self.current_step == self.PACKAGE_GENERATING:
1023 fail_to_next_edit = "recipes"
1024 fraction = 1.0
1025 self.build_details_page.show_fail_page(fail_to_next_edit.split(' ')[0])
1026 status = "fail"
1027 message = "Build failed: "
1028 self.build_details_page.update_progress_bar(message, fraction, status)
1029 self.build_details_page.show_back_button()
1030 self.build_details_page.hide_stop_button()
1031 self.handler.build_failed_async()
1032 self.stopping = False
1033
1034 def handler_build_succeeded_cb(self, running_build):
1035 if not self.stopping:
1036 self.build_succeeded()
1037 else:
1038 self.build_failed()
1039
1040
1041 def handler_build_failed_cb(self, running_build):
1042 self.build_failed()
1043
1044 def handler_build_aborted_cb(self, running_build):
1045 self.build_failed()
1046
1047 def handler_no_provider_cb(self, running_build, msg):
1048 dialog = CrumbsMessageDialog(self, glib.markup_escape_text(msg), gtk.MESSAGE_INFO)
1049 button = dialog.add_button("Close", gtk.RESPONSE_OK)
1050 HobButton.style_button(button)
1051 dialog.run()
1052 dialog.destroy()
1053 self.build_failed()
1054
1055 def handler_task_started_cb(self, running_build, message):
1056 fraction = message["current"] * 1.0/message["total"]
1057 title = "Build packages"
1058 if self.current_step == self.FAST_IMAGE_GENERATING:
1059 if message["eventname"] == "sceneQueueTaskStarted":
1060 fraction = 0.27 * fraction
1061 elif message["eventname"] == "runQueueTaskStarted":
1062 fraction = 0.27 + 0.63 * fraction
1063 elif self.current_step == self.IMAGE_GENERATING:
1064 title = "Build image"
1065 if self.previous_step == self.FAST_IMAGE_GENERATING:
1066 if message["eventname"] == "sceneQueueTaskStarted":
1067 fraction = 0.27 + 0.63 + 0.03 * fraction
1068 elif message["eventname"] == "runQueueTaskStarted":
1069 fraction = 0.27 + 0.63 + 0.03 + 0.07 * fraction
1070 else:
1071 if message["eventname"] == "sceneQueueTaskStarted":
1072 fraction = 0.2 * fraction
1073 elif message["eventname"] == "runQueueTaskStarted":
1074 fraction = 0.2 + 0.8 * fraction
1075 elif self.current_step == self.PACKAGE_GENERATING:
1076 if message["eventname"] == "sceneQueueTaskStarted":
1077 fraction = 0.2 * fraction
1078 elif message["eventname"] == "runQueueTaskStarted":
1079 fraction = 0.2 + 0.8 * fraction
1080 self.build_details_page.update_progress_bar(title + ": ", fraction)
1081 self.build_details_page.update_build_status(message["current"], message["total"], message["task"])
1082
1083 def handler_disk_full_cb(self, running_build):
1084 self.disk_full = True
1085
1086 def handler_build_failure_cb(self, running_build):
1087 self.build_details_page.show_issues()
1088
1089 def handler_build_log_cb(self, running_build, func, obj):
1090 if hasattr(self.logger, func):
1091 getattr(self.logger, func)(obj)
1092
1093 def destroy_window_cb(self, widget, event):
1094 if not self.sensitive:
1095 return True
1096 elif self.handler.building:
1097 self.stop_build()
1098 return True
1099 else:
1100 gtk.main_quit()
1101
1102 def event_handle_SIGINT(self, signal, frame):
1103 for w in gtk.window_list_toplevels():
1104 if w.get_modal():
1105 w.response(gtk.RESPONSE_DELETE_EVENT)
1106 sys.exit(0)
1107
1108 def build_packages(self):
1109 _, all_recipes = self.recipe_model.get_selected_recipes()
1110 if not all_recipes:
1111 lbl = "<b>No selections made</b>"
1112 msg = "You have not made any selections"
1113 msg = msg + " so there isn't anything to bake at this time."
1114 dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_INFO, msg)
1115 button = dialog.add_button("Close", gtk.RESPONSE_OK)
1116 HobButton.style_button(button)
1117 dialog.run()
1118 dialog.destroy()
1119 return
1120 self.generate_packages_async(True)
1121
1122 def build_image(self):
1123 selected_packages = self.package_model.get_selected_packages()
1124 if not selected_packages:
1125 lbl = "<b>No selections made</b>"
1126 msg = "You have not made any selections"
1127 msg = msg + " so there isn't anything to bake at this time."
1128 dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_INFO, msg)
1129 button = dialog.add_button("Close", gtk.RESPONSE_OK)
1130 HobButton.style_button(button)
1131 dialog.run()
1132 dialog.destroy()
1133 return
1134 self.generate_image_async(True)
1135
1136 def just_bake(self):
1137 selected_image = self.recipe_model.get_selected_image()
1138 selected_packages = self.package_model.get_selected_packages() or []
1139
1140 # If no base image and no selected packages don't build anything
1141 if not (selected_packages or selected_image != self.recipe_model.__custom_image__):
1142 lbl = "<b>No selections made</b>"
1143 msg = "You have not made any selections"
1144 msg = msg + " so there isn't anything to bake at this time."
1145 dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_INFO, msg)
1146 button = dialog.add_button("Close", gtk.RESPONSE_OK)
1147 HobButton.style_button(button)
1148 dialog.run()
1149 dialog.destroy()
1150 return
1151
1152 self.fast_generate_image_async(True)
1153
1154 def show_recipe_property_dialog(self, properties):
1155 information = {}
1156 dialog = PropertyDialog(title = properties["name"] +' '+ "properties",
1157 parent = self,
1158 information = properties,
1159 flags = gtk.DIALOG_DESTROY_WITH_PARENT
1160 | gtk.DIALOG_NO_SEPARATOR)
1161
1162 dialog.set_modal(False)
1163
1164 button = dialog.add_button("Close", gtk.RESPONSE_NO)
1165 HobAltButton.style_button(button)
1166 button.connect("clicked", lambda w: dialog.destroy())
1167
1168 dialog.run()
1169
1170 def show_packages_property_dialog(self, properties):
1171 information = {}
1172 dialog = PropertyDialog(title = properties["name"] +' '+ "properties",
1173 parent = self,
1174 information = properties,
1175 flags = gtk.DIALOG_DESTROY_WITH_PARENT
1176 | gtk.DIALOG_NO_SEPARATOR)
1177
1178 dialog.set_modal(False)
1179
1180 button = dialog.add_button("Close", gtk.RESPONSE_NO)
1181 HobAltButton.style_button(button)
1182 button.connect("clicked", lambda w: dialog.destroy())
1183
1184 dialog.run()
1185
1186 def show_layer_selection_dialog(self):
1187 dialog = LayerSelectionDialog(title = "Layers",
1188 layers = copy.deepcopy(self.configuration.layers),
1189 layers_non_removable = copy.deepcopy(self.configuration.layers_non_removable),
1190 all_layers = self.parameters.all_layers,
1191 parent = self,
1192 flags = gtk.DIALOG_MODAL
1193 | gtk.DIALOG_DESTROY_WITH_PARENT
1194 | gtk.DIALOG_NO_SEPARATOR)
1195 button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
1196 HobAltButton.style_button(button)
1197 button = dialog.add_button("OK", gtk.RESPONSE_YES)
1198 HobButton.style_button(button)
1199 response = dialog.run()
1200 if response == gtk.RESPONSE_YES:
1201 self.configuration.layers = dialog.layers
1202 # DO refresh layers
1203 if dialog.layers_changed:
1204 self.update_config_async()
1205 dialog.destroy()
1206
1207 def get_image_extension(self):
1208 image_extension = {}
1209 for type in self.parameters.image_types:
1210 ext = self.handler.runCommand(["getVariable", "IMAGE_EXTENSION_%s" % type])
1211 if ext:
1212 image_extension[type] = ext.split(' ')
1213
1214 return image_extension
1215
1216 def show_load_my_images_dialog(self):
1217 image_extension = self.get_image_extension()
1218 dialog = ImageSelectionDialog(self.parameters.image_addr, self.parameters.image_types,
1219 "Open My Images", self,
1220 gtk.FILE_CHOOSER_ACTION_SAVE, None,
1221 image_extension)
1222 button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
1223 HobAltButton.style_button(button)
1224 button = dialog.add_button("Open", gtk.RESPONSE_YES)
1225 HobButton.style_button(button)
1226 response = dialog.run()
1227 if response == gtk.RESPONSE_YES:
1228 if not dialog.image_names:
1229 lbl = "<b>No selections made</b>"
1230 msg = "You have not made any selections"
1231 crumbs_dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_INFO, msg)
1232 button = crumbs_dialog.add_button("Close", gtk.RESPONSE_OK)
1233 HobButton.style_button(button)
1234 crumbs_dialog.run()
1235 crumbs_dialog.destroy()
1236 dialog.destroy()
1237 return
1238
1239 self.parameters.image_addr = dialog.image_folder
1240 self.parameters.image_names = dialog.image_names[:]
1241 self.switch_page(self.MY_IMAGE_OPENED)
1242
1243 dialog.destroy()
1244
1245 def show_adv_settings_dialog(self, tab=None):
1246 dialog = AdvancedSettingsDialog(title = "Advanced configuration",
1247 configuration = copy.deepcopy(self.configuration),
1248 all_image_types = self.parameters.image_types,
1249 all_package_formats = self.parameters.all_package_formats,
1250 all_distros = self.parameters.all_distros,
1251 all_sdk_machines = self.parameters.all_sdk_machines,
1252 max_threads = self.parameters.max_threads,
1253 parent = self,
1254 flags = gtk.DIALOG_MODAL
1255 | gtk.DIALOG_DESTROY_WITH_PARENT
1256 | gtk.DIALOG_NO_SEPARATOR)
1257 button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
1258 HobAltButton.style_button(button)
1259 button = dialog.add_button("Save", gtk.RESPONSE_YES)
1260 HobButton.style_button(button)
1261 dialog.set_save_button(button)
1262 response = dialog.run()
1263 settings_changed = False
1264 if response == gtk.RESPONSE_YES:
1265 self.configuration = dialog.configuration
1266 self.configuration.save(self.handler, True) # remember settings
1267 settings_changed = dialog.settings_changed
1268 dialog.destroy()
1269 return response == gtk.RESPONSE_YES, settings_changed
1270
1271 def show_simple_settings_dialog(self, tab=None):
1272 dialog = SimpleSettingsDialog(title = "Settings",
1273 configuration = copy.deepcopy(self.configuration),
1274 all_image_types = self.parameters.image_types,
1275 all_package_formats = self.parameters.all_package_formats,
1276 all_distros = self.parameters.all_distros,
1277 all_sdk_machines = self.parameters.all_sdk_machines,
1278 max_threads = self.parameters.max_threads,
1279 parent = self,
1280 flags = gtk.DIALOG_MODAL
1281 | gtk.DIALOG_DESTROY_WITH_PARENT
1282 | gtk.DIALOG_NO_SEPARATOR,
1283 handler = self.handler)
1284 button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
1285 HobAltButton.style_button(button)
1286 button = dialog.add_button("Save", gtk.RESPONSE_YES)
1287 HobButton.style_button(button)
1288 if tab:
1289 dialog.switch_to_page(tab)
1290 response = dialog.run()
1291 settings_changed = False
1292 if response == gtk.RESPONSE_YES:
1293 self.configuration = dialog.configuration
1294 self.configuration.save(self.handler, True) # remember settings
1295 settings_changed = dialog.settings_changed
1296 if dialog.proxy_settings_changed:
1297 self.set_user_config_proxies()
1298 elif dialog.proxy_test_ran:
1299 # The user might have modified the proxies in the "Proxy"
1300 # tab, which in turn made the proxy settings modify in bb.
1301 # If "Cancel" was pressed, restore the previous proxy
1302 # settings inside bb.
1303 self.set_user_config_proxies()
1304 dialog.destroy()
1305 return response == gtk.RESPONSE_YES, settings_changed
1306
1307 def reparse_post_adv_settings(self):
1308 if not self.configuration.curr_mach:
1309 self.update_config_async()
1310 else:
1311 self.configuration.clear_selection()
1312 # DO reparse recipes
1313 self.populate_recipe_package_info_async()
1314
1315 def deploy_image(self, image_name):
1316 if not image_name:
1317 lbl = "<b>Please select an image to deploy.</b>"
1318 dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_INFO)
1319 button = dialog.add_button("Close", gtk.RESPONSE_OK)
1320 HobButton.style_button(button)
1321 dialog.run()
1322 dialog.destroy()
1323 return
1324
1325 image_path = os.path.join(self.parameters.image_addr, image_name)
1326 dialog = DeployImageDialog(title = "Usb Image Maker",
1327 image_path = image_path,
1328 parent = self,
1329 flags = gtk.DIALOG_MODAL
1330 | gtk.DIALOG_DESTROY_WITH_PARENT
1331 | gtk.DIALOG_NO_SEPARATOR)
1332 button = dialog.add_button("Close", gtk.RESPONSE_NO)
1333 HobAltButton.style_button(button)
1334 button = dialog.add_button("Make usb image", gtk.RESPONSE_YES)
1335 HobButton.style_button(button)
1336 response = dialog.run()
1337 dialog.destroy()
1338
1339 def show_load_kernel_dialog(self):
1340 dialog = gtk.FileChooserDialog("Load Kernel Files", self,
1341 gtk.FILE_CHOOSER_ACTION_SAVE)
1342 button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
1343 HobAltButton.style_button(button)
1344 button = dialog.add_button("Open", gtk.RESPONSE_YES)
1345 HobButton.style_button(button)
1346 filter = gtk.FileFilter()
1347 filter.set_name("Kernel Files")
1348 filter.add_pattern("*.bin")
1349 dialog.add_filter(filter)
1350
1351 dialog.set_current_folder(self.parameters.image_addr)
1352
1353 response = dialog.run()
1354 kernel_path = ""
1355 if response == gtk.RESPONSE_YES:
1356 kernel_path = dialog.get_filename()
1357
1358 dialog.destroy()
1359
1360 return kernel_path
1361
1362 def runqemu_image(self, image_name, kernel_name):
1363 if not image_name or not kernel_name:
1364 lbl = "<b>Please select %s to launch in QEMU.</b>" % ("a kernel" if image_name else "an image")
1365 dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_INFO)
1366 button = dialog.add_button("Close", gtk.RESPONSE_OK)
1367 HobButton.style_button(button)
1368 dialog.run()
1369 dialog.destroy()
1370 return
1371
1372 kernel_path = os.path.join(self.parameters.image_addr, kernel_name)
1373 image_path = os.path.join(self.parameters.image_addr, image_name)
1374
1375 source_env_path = os.path.join(self.parameters.core_base, "oe-init-build-env")
1376 tmp_path = self.parameters.tmpdir
1377 cmdline = bb.ui.crumbs.utils.which_terminal()
1378 if os.path.exists(image_path) and os.path.exists(kernel_path) \
1379 and os.path.exists(source_env_path) and os.path.exists(tmp_path) \
1380 and cmdline:
1381 cmdline += "\' bash -c \"export OE_TMPDIR=" + tmp_path + "; "
1382 cmdline += "source " + source_env_path + " " + os.getcwd() + "; "
1383 cmdline += "runqemu " + kernel_path + " " + image_path + "\"\'"
1384 subprocess.Popen(shlex.split(cmdline))
1385 else:
1386 lbl = "<b>Path error</b>"
1387 msg = "One of your paths is wrong,"
1388 msg = msg + " please make sure the following paths exist:\n"
1389 msg = msg + "image path:" + image_path + "\n"
1390 msg = msg + "kernel path:" + kernel_path + "\n"
1391 msg = msg + "source environment path:" + source_env_path + "\n"
1392 msg = msg + "tmp path: " + tmp_path + "."
1393 msg = msg + "You may be missing either xterm or vte for terminal services."
1394 dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_ERROR, msg)
1395 button = dialog.add_button("Close", gtk.RESPONSE_OK)
1396 HobButton.style_button(button)
1397 dialog.run()
1398 dialog.destroy()
1399
1400 def show_packages(self):
1401 self.package_details_page.refresh_tables()
1402 self.switch_page(self.PACKAGE_SELECTION)
1403
1404 def show_recipes(self):
1405 self.switch_page(self.RECIPE_SELECTION)
1406
1407 def show_image_details(self):
1408 self.switch_page(self.IMAGE_GENERATED)
1409
1410 def show_configuration(self):
1411 self.switch_page(self.BASEIMG_SELECTED)
1412
1413 def stop_build(self):
1414 if self.stopping:
1415 lbl = "<b>Force Stop build?</b>"
1416 msg = "You've already selected Stop once,"
1417 msg = msg + " would you like to 'Force Stop' the build?\n\n"
1418 msg = msg + "This will stop the build as quickly as possible but may"
1419 msg = msg + " well leave your build directory in an unusable state"
1420 msg = msg + " that requires manual steps to fix."
1421 dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_WARNING, msg)
1422 button = dialog.add_button("Cancel", gtk.RESPONSE_CANCEL)
1423 HobAltButton.style_button(button)
1424 button = dialog.add_button("Force Stop", gtk.RESPONSE_YES)
1425 HobButton.style_button(button)
1426 else:
1427 lbl = "<b>Stop build?</b>"
1428 msg = "Are you sure you want to stop this"
1429 msg = msg + " build?\n\n'Stop' will stop the build as soon as all in"
1430 msg = msg + " progress build tasks are finished. However if a"
1431 msg = msg + " lengthy compilation phase is in progress this may take"
1432 msg = msg + " some time.\n\n"
1433 msg = msg + "'Force Stop' will stop the build as quickly as"
1434 msg = msg + " possible but may well leave your build directory in an"
1435 msg = msg + " unusable state that requires manual steps to fix."
1436 dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_WARNING, msg)
1437 button = dialog.add_button("Cancel", gtk.RESPONSE_CANCEL)
1438 HobAltButton.style_button(button)
1439 button = dialog.add_button("Force stop", gtk.RESPONSE_YES)
1440 HobAltButton.style_button(button)
1441 button = dialog.add_button("Stop", gtk.RESPONSE_OK)
1442 HobButton.style_button(button)
1443 response = dialog.run()
1444 dialog.destroy()
1445 if response != gtk.RESPONSE_CANCEL:
1446 self.stopping = True
1447 if response == gtk.RESPONSE_OK:
1448 self.build_details_page.progress_bar.set_stop_title("Stopping the build....")
1449 self.build_details_page.progress_bar.set_rcstyle("stop")
1450 self.cancel_build_sync()
1451 elif response == gtk.RESPONSE_YES:
1452 self.cancel_build_sync(True)
1453
1454 def do_log(self, consolelogfile = None):
1455 if consolelogfile:
1456 bb.utils.mkdirhier(os.path.dirname(consolelogfile))
1457 if self.consolelog:
1458 self.logger.removeHandler(self.consolelog)
1459 self.consolelog = None
1460 self.consolelog = logging.FileHandler(consolelogfile)
1461 bb.msg.addDefaultlogFilter(self.consolelog)
1462 format = bb.msg.BBLogFormatter("%(levelname)s: %(message)s")
1463 self.consolelog.setFormatter(format)
1464
1465 self.logger.addHandler(self.consolelog)
1466
1467 def get_topdir(self):
1468 return self.handler.get_topdir()
1469
1470 def wait(self, delay):
1471 time_start = time.time()
1472 time_end = time_start + delay
1473 while time_end > time.time():
1474 while gtk.events_pending():
1475 gtk.main_iteration()
diff --git a/bitbake/lib/bb/ui/crumbs/buildmanager.py b/bitbake/lib/bb/ui/crumbs/buildmanager.py
new file mode 100644
index 0000000000..e858d75e4c
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/buildmanager.py
@@ -0,0 +1,455 @@
1#
2# BitBake Graphical GTK User Interface
3#
4# Copyright (C) 2008 Intel Corporation
5#
6# Authored by Rob Bradford <rob@linux.intel.com>
7#
8# 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
10# published by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21import gtk
22import gobject
23import threading
24import os
25import datetime
26import time
27
28class BuildConfiguration:
29 """ Represents a potential *or* historic *or* concrete build. It
30 encompasses all the things that we need to tell bitbake to do to make it
31 build what we want it to build.
32
33 It also stored the metadata URL and the set of possible machines (and the
34 distros / images / uris for these. Apart from the metdata URL these are
35 not serialised to file (since they may be transient). In some ways this
36 functionality might be shifted to the loader class."""
37
38 def __init__ (self):
39 self.metadata_url = None
40
41 # Tuple of (distros, image, urls)
42 self.machine_options = {}
43
44 self.machine = None
45 self.distro = None
46 self.image = None
47 self.urls = []
48 self.extra_urls = []
49 self.extra_pkgs = []
50
51 def get_machines_model (self):
52 model = gtk.ListStore (gobject.TYPE_STRING)
53 for machine in self.machine_options.keys():
54 model.append ([machine])
55
56 return model
57
58 def get_distro_and_images_models (self, machine):
59 distro_model = gtk.ListStore (gobject.TYPE_STRING)
60
61 for distro in self.machine_options[machine][0]:
62 distro_model.append ([distro])
63
64 image_model = gtk.ListStore (gobject.TYPE_STRING)
65
66 for image in self.machine_options[machine][1]:
67 image_model.append ([image])
68
69 return (distro_model, image_model)
70
71 def get_repos (self):
72 self.urls = self.machine_options[self.machine][2]
73 return self.urls
74
75 # It might be a lot lot better if we stored these in like, bitbake conf
76 # file format.
77 @staticmethod
78 def load_from_file (filename):
79
80 conf = BuildConfiguration()
81 with open(filename, "r") as f:
82 for line in f:
83 data = line.split (";")[1]
84 if (line.startswith ("metadata-url;")):
85 conf.metadata_url = data.strip()
86 continue
87 if (line.startswith ("url;")):
88 conf.urls += [data.strip()]
89 continue
90 if (line.startswith ("extra-url;")):
91 conf.extra_urls += [data.strip()]
92 continue
93 if (line.startswith ("machine;")):
94 conf.machine = data.strip()
95 continue
96 if (line.startswith ("distribution;")):
97 conf.distro = data.strip()
98 continue
99 if (line.startswith ("image;")):
100 conf.image = data.strip()
101 continue
102
103 return conf
104
105 # Serialise to a file. This is part of the build process and we use this
106 # to be able to repeat a given build (using the same set of parameters)
107 # but also so that we can include the details of the image / machine /
108 # distro in the build manager tree view.
109 def write_to_file (self, filename):
110 f = open (filename, "w")
111
112 lines = []
113
114 if (self.metadata_url):
115 lines += ["metadata-url;%s\n" % (self.metadata_url)]
116
117 for url in self.urls:
118 lines += ["url;%s\n" % (url)]
119
120 for url in self.extra_urls:
121 lines += ["extra-url;%s\n" % (url)]
122
123 if (self.machine):
124 lines += ["machine;%s\n" % (self.machine)]
125
126 if (self.distro):
127 lines += ["distribution;%s\n" % (self.distro)]
128
129 if (self.image):
130 lines += ["image;%s\n" % (self.image)]
131
132 f.writelines (lines)
133 f.close ()
134
135class BuildResult(gobject.GObject):
136 """ Represents an historic build. Perhaps not successful. But it includes
137 things such as the files that are in the directory (the output from the
138 build) as well as a deserialised BuildConfiguration file that is stored in
139 ".conf" in the directory for the build.
140
141 This is GObject so that it can be included in the TreeStore."""
142
143 (STATE_COMPLETE, STATE_FAILED, STATE_ONGOING) = \
144 (0, 1, 2)
145
146 def __init__ (self, parent, identifier):
147 gobject.GObject.__init__ (self)
148 self.date = None
149
150 self.files = []
151 self.status = None
152 self.identifier = identifier
153 self.path = os.path.join (parent, identifier)
154
155 # Extract the date, since the directory name is of the
156 # format build-<year><month><day>-<ordinal> we can easily
157 # pull it out.
158 # TODO: Better to stat a file?
159 (_, date, revision) = identifier.split ("-")
160 print(date)
161
162 year = int (date[0:4])
163 month = int (date[4:6])
164 day = int (date[6:8])
165
166 self.date = datetime.date (year, month, day)
167
168 self.conf = None
169
170 # By default builds are STATE_FAILED unless we find a "complete" file
171 # in which case they are STATE_COMPLETE
172 self.state = BuildResult.STATE_FAILED
173 for file in os.listdir (self.path):
174 if (file.startswith (".conf")):
175 conffile = os.path.join (self.path, file)
176 self.conf = BuildConfiguration.load_from_file (conffile)
177 elif (file.startswith ("complete")):
178 self.state = BuildResult.STATE_COMPLETE
179 else:
180 self.add_file (file)
181
182 def add_file (self, file):
183 # Just add the file for now. Don't care about the type.
184 self.files += [(file, None)]
185
186class BuildManagerModel (gtk.TreeStore):
187 """ Model for the BuildManagerTreeView. This derives from gtk.TreeStore
188 but it abstracts nicely what the columns mean and the setup of the columns
189 in the model. """
190
191 (COL_IDENT, COL_DESC, COL_MACHINE, COL_DISTRO, COL_BUILD_RESULT, COL_DATE, COL_STATE) = \
192 (0, 1, 2, 3, 4, 5, 6)
193
194 def __init__ (self):
195 gtk.TreeStore.__init__ (self,
196 gobject.TYPE_STRING,
197 gobject.TYPE_STRING,
198 gobject.TYPE_STRING,
199 gobject.TYPE_STRING,
200 gobject.TYPE_OBJECT,
201 gobject.TYPE_INT64,
202 gobject.TYPE_INT)
203
204class BuildManager (gobject.GObject):
205 """ This class manages the historic builds that have been found in the
206 "results" directory but is also used for starting a new build."""
207
208 __gsignals__ = {
209 'population-finished' : (gobject.SIGNAL_RUN_LAST,
210 gobject.TYPE_NONE,
211 ()),
212 'populate-error' : (gobject.SIGNAL_RUN_LAST,
213 gobject.TYPE_NONE,
214 ())
215 }
216
217 def update_build_result (self, result, iter):
218 # Convert the date into something we can sort by.
219 date = long (time.mktime (result.date.timetuple()))
220
221 # Add a top level entry for the build
222
223 self.model.set (iter,
224 BuildManagerModel.COL_IDENT, result.identifier,
225 BuildManagerModel.COL_DESC, result.conf.image,
226 BuildManagerModel.COL_MACHINE, result.conf.machine,
227 BuildManagerModel.COL_DISTRO, result.conf.distro,
228 BuildManagerModel.COL_BUILD_RESULT, result,
229 BuildManagerModel.COL_DATE, date,
230 BuildManagerModel.COL_STATE, result.state)
231
232 # And then we use the files in the directory as the children for the
233 # top level iter.
234 for file in result.files:
235 self.model.append (iter, (None, file[0], None, None, None, date, -1))
236
237 # This function is called as an idle by the BuildManagerPopulaterThread
238 def add_build_result (self, result):
239 gtk.gdk.threads_enter()
240 self.known_builds += [result]
241
242 self.update_build_result (result, self.model.append (None))
243
244 gtk.gdk.threads_leave()
245
246 def notify_build_finished (self):
247 # This is a bit of a hack. If we have a running build running then we
248 # will have a row in the model in STATE_ONGOING. Find it and make it
249 # as if it was a proper historic build (well, it is completed now....)
250
251 # We need to use the iters here rather than the Python iterator
252 # interface to the model since we need to pass it into
253 # update_build_result
254
255 iter = self.model.get_iter_first()
256
257 while (iter):
258 (ident, state) = self.model.get(iter,
259 BuildManagerModel.COL_IDENT,
260 BuildManagerModel.COL_STATE)
261
262 if state == BuildResult.STATE_ONGOING:
263 result = BuildResult (self.results_directory, ident)
264 self.update_build_result (result, iter)
265 iter = self.model.iter_next(iter)
266
267 def notify_build_succeeded (self):
268 # Write the "complete" file so that when we create the BuildResult
269 # object we put into the model
270
271 complete_file_path = os.path.join (self.cur_build_directory, "complete")
272 f = file (complete_file_path, "w")
273 f.close()
274 self.notify_build_finished()
275
276 def notify_build_failed (self):
277 # Without a "complete" file then this will mark the build as failed:
278 self.notify_build_finished()
279
280 # This function is called as an idle
281 def emit_population_finished_signal (self):
282 gtk.gdk.threads_enter()
283 self.emit ("population-finished")
284 gtk.gdk.threads_leave()
285
286 class BuildManagerPopulaterThread (threading.Thread):
287 def __init__ (self, manager, directory):
288 threading.Thread.__init__ (self)
289 self.manager = manager
290 self.directory = directory
291
292 def run (self):
293 # For each of the "build-<...>" directories ..
294
295 if os.path.exists (self.directory):
296 for directory in os.listdir (self.directory):
297
298 if not directory.startswith ("build-"):
299 continue
300
301 build_result = BuildResult (self.directory, directory)
302 self.manager.add_build_result (build_result)
303
304 gobject.idle_add (BuildManager.emit_population_finished_signal,
305 self.manager)
306
307 def __init__ (self, server, results_directory):
308 gobject.GObject.__init__ (self)
309
310 # The builds that we've found from walking the result directory
311 self.known_builds = []
312
313 # Save out the bitbake server, we need this for issuing commands to
314 # the cooker:
315 self.server = server
316
317 # The TreeStore that we use
318 self.model = BuildManagerModel ()
319
320 # The results directory is where we create (and look for) the
321 # build-<xyz>-<n> directories. We need to populate ourselves from
322 # directory
323 self.results_directory = results_directory
324 self.populate_from_directory (self.results_directory)
325
326 def populate_from_directory (self, directory):
327 thread = BuildManager.BuildManagerPopulaterThread (self, directory)
328 thread.start()
329
330 # Come up with the name for the next build ident by combining "build-"
331 # with the date formatted as yyyymmdd and then an ordinal. We do this by
332 # an optimistic algorithm incrementing the ordinal if we find that it
333 # already exists.
334 def get_next_build_ident (self):
335 today = datetime.date.today ()
336 datestr = str (today.year) + str (today.month) + str (today.day)
337
338 revision = 0
339 test_name = "build-%s-%d" % (datestr, revision)
340 test_path = os.path.join (self.results_directory, test_name)
341
342 while (os.path.exists (test_path)):
343 revision += 1
344 test_name = "build-%s-%d" % (datestr, revision)
345 test_path = os.path.join (self.results_directory, test_name)
346
347 return test_name
348
349 # Take a BuildConfiguration and then try and build it based on the
350 # parameters of that configuration. S
351 def do_build (self, conf):
352 server = self.server
353
354 # Work out the build directory. Note we actually create the
355 # directories here since we need to write the ".conf" file. Otherwise
356 # we could have relied on bitbake's builder thread to actually make
357 # the directories as it proceeds with the build.
358 ident = self.get_next_build_ident ()
359 build_directory = os.path.join (self.results_directory,
360 ident)
361 self.cur_build_directory = build_directory
362 os.makedirs (build_directory)
363
364 conffile = os.path.join (build_directory, ".conf")
365 conf.write_to_file (conffile)
366
367 # Add a row to the model representing this ongoing build. It's kinda a
368 # fake entry. If this build completes or fails then this gets updated
369 # with the real stuff like the historic builds
370 date = long (time.time())
371 self.model.append (None, (ident, conf.image, conf.machine, conf.distro,
372 None, date, BuildResult.STATE_ONGOING))
373 try:
374 server.runCommand(["setVariable", "BUILD_IMAGES_FROM_FEEDS", 1])
375 server.runCommand(["setVariable", "MACHINE", conf.machine])
376 server.runCommand(["setVariable", "DISTRO", conf.distro])
377 server.runCommand(["setVariable", "PACKAGE_CLASSES", "package_ipk"])
378 server.runCommand(["setVariable", "BBFILES", \
379 """${OEROOT}/meta/packages/*/*.bb ${OEROOT}/meta-moblin/packages/*/*.bb"""])
380 server.runCommand(["setVariable", "TMPDIR", "${OEROOT}/build/tmp"])
381 server.runCommand(["setVariable", "IPK_FEED_URIS", \
382 " ".join(conf.get_repos())])
383 server.runCommand(["setVariable", "DEPLOY_DIR_IMAGE",
384 build_directory])
385 server.runCommand(["buildTargets", [conf.image], "rootfs"])
386
387 except Exception as e:
388 print(e)
389
390class BuildManagerTreeView (gtk.TreeView):
391 """ The tree view for the build manager. This shows the historic builds
392 and so forth. """
393
394 # We use this function to control what goes in the cell since we store
395 # the date in the model as seconds since the epoch (for sorting) and so we
396 # need to make it human readable.
397 def date_format_custom_cell_data_func (self, col, cell, model, iter):
398 date = model.get (iter, BuildManagerModel.COL_DATE)[0]
399 datestr = time.strftime("%A %d %B %Y", time.localtime(date))
400 cell.set_property ("text", datestr)
401
402 # This format function controls what goes in the cell. We use this to map
403 # the integer state to a string and also to colourise the text
404 def state_format_custom_cell_data_fun (self, col, cell, model, iter):
405 state = model.get (iter, BuildManagerModel.COL_STATE)[0]
406
407 if (state == BuildResult.STATE_ONGOING):
408 cell.set_property ("text", "Active")
409 cell.set_property ("foreground", "#000000")
410 elif (state == BuildResult.STATE_FAILED):
411 cell.set_property ("text", "Failed")
412 cell.set_property ("foreground", "#ff0000")
413 elif (state == BuildResult.STATE_COMPLETE):
414 cell.set_property ("text", "Complete")
415 cell.set_property ("foreground", "#00ff00")
416 else:
417 cell.set_property ("text", "")
418
419 def __init__ (self):
420 gtk.TreeView.__init__(self)
421
422 # Misc descriptiony thing
423 renderer = gtk.CellRendererText ()
424 col = gtk.TreeViewColumn (None, renderer,
425 text=BuildManagerModel.COL_DESC)
426 self.append_column (col)
427
428 # Machine
429 renderer = gtk.CellRendererText ()
430 col = gtk.TreeViewColumn ("Machine", renderer,
431 text=BuildManagerModel.COL_MACHINE)
432 self.append_column (col)
433
434 # distro
435 renderer = gtk.CellRendererText ()
436 col = gtk.TreeViewColumn ("Distribution", renderer,
437 text=BuildManagerModel.COL_DISTRO)
438 self.append_column (col)
439
440 # date (using a custom function for formatting the cell contents it
441 # takes epoch -> human readable string)
442 renderer = gtk.CellRendererText ()
443 col = gtk.TreeViewColumn ("Date", renderer,
444 text=BuildManagerModel.COL_DATE)
445 self.append_column (col)
446 col.set_cell_data_func (renderer,
447 self.date_format_custom_cell_data_func)
448
449 # For status.
450 renderer = gtk.CellRendererText ()
451 col = gtk.TreeViewColumn ("Status", renderer,
452 text = BuildManagerModel.COL_STATE)
453 self.append_column (col)
454 col.set_cell_data_func (renderer,
455 self.state_format_custom_cell_data_fun)
diff --git a/bitbake/lib/bb/ui/crumbs/hig/__init__.py b/bitbake/lib/bb/ui/crumbs/hig/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hig/__init__.py
diff --git a/bitbake/lib/bb/ui/crumbs/hig/advancedsettingsdialog.py b/bitbake/lib/bb/ui/crumbs/hig/advancedsettingsdialog.py
new file mode 100644
index 0000000000..e0b3553c2f
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hig/advancedsettingsdialog.py
@@ -0,0 +1,341 @@
1#
2# BitBake Graphical GTK User Interface
3#
4# Copyright (C) 2011-2012 Intel Corporation
5#
6# Authored by Joshua Lock <josh@linux.intel.com>
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
23import gtk
24import hashlib
25from bb.ui.crumbs.hobwidget import HobInfoButton, HobButton
26from bb.ui.crumbs.progressbar import HobProgressBar
27from bb.ui.crumbs.hig.settingsuihelper import SettingsUIHelper
28from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog
29from bb.ui.crumbs.hig.crumbsmessagedialog import CrumbsMessageDialog
30from bb.ui.crumbs.hig.proxydetailsdialog import ProxyDetailsDialog
31
32"""
33The following are convenience classes for implementing GNOME HIG compliant
34BitBake GUI's
35In summary: spacing = 12px, border-width = 6px
36"""
37
38class AdvancedSettingsDialog (CrumbsDialog, SettingsUIHelper):
39
40 def details_cb(self, button, parent, protocol):
41 dialog = ProxyDetailsDialog(title = protocol.upper() + " Proxy Details",
42 user = self.configuration.proxies[protocol][1],
43 passwd = self.configuration.proxies[protocol][2],
44 parent = parent,
45 flags = gtk.DIALOG_MODAL
46 | gtk.DIALOG_DESTROY_WITH_PARENT
47 | gtk.DIALOG_NO_SEPARATOR)
48 dialog.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_OK)
49 response = dialog.run()
50 if response == gtk.RESPONSE_OK:
51 self.configuration.proxies[protocol][1] = dialog.user
52 self.configuration.proxies[protocol][2] = dialog.passwd
53 self.refresh_proxy_components()
54 dialog.destroy()
55
56 def set_save_button(self, button):
57 self.save_button = button
58
59 def rootfs_combo_changed_cb(self, rootfs_combo, all_package_format, check_hbox):
60 combo_item = self.rootfs_combo.get_active_text()
61 modified = False
62 for child in check_hbox.get_children():
63 if isinstance(child, gtk.CheckButton):
64 check_hbox.remove(child)
65 modified = True
66 for format in all_package_format:
67 if format != combo_item:
68 check_button = gtk.CheckButton(format)
69 check_hbox.pack_start(check_button, expand=False, fill=False)
70 modified = True
71 if modified:
72 check_hbox.remove(self.pkgfmt_info)
73 check_hbox.pack_start(self.pkgfmt_info, expand=False, fill=False)
74 check_hbox.show_all()
75
76 def gen_pkgfmt_widget(self, curr_package_format, all_package_format, tooltip_combo="", tooltip_extra=""):
77 pkgfmt_vbox = gtk.VBox(False, 6)
78
79 label = self.gen_label_widget("Root file system package format")
80 pkgfmt_vbox.pack_start(label, expand=False, fill=False)
81
82 rootfs_format = ""
83 if curr_package_format:
84 rootfs_format = curr_package_format.split()[0]
85
86 rootfs_format_widget, rootfs_combo = self.gen_combo_widget(rootfs_format, all_package_format, tooltip_combo)
87 pkgfmt_vbox.pack_start(rootfs_format_widget, expand=False, fill=False)
88
89 label = self.gen_label_widget("Additional package formats")
90 pkgfmt_vbox.pack_start(label, expand=False, fill=False)
91
92 check_hbox = gtk.HBox(False, 12)
93 pkgfmt_vbox.pack_start(check_hbox, expand=False, fill=False)
94 for format in all_package_format:
95 if format != rootfs_format:
96 check_button = gtk.CheckButton(format)
97 is_active = (format in curr_package_format.split())
98 check_button.set_active(is_active)
99 check_hbox.pack_start(check_button, expand=False, fill=False)
100
101 self.pkgfmt_info = HobInfoButton(tooltip_extra, self)
102 check_hbox.pack_start(self.pkgfmt_info, expand=False, fill=False)
103
104 rootfs_combo.connect("changed", self.rootfs_combo_changed_cb, all_package_format, check_hbox)
105
106 pkgfmt_vbox.show_all()
107
108 return pkgfmt_vbox, rootfs_combo, check_hbox
109
110 def __init__(self, title, configuration, all_image_types,
111 all_package_formats, all_distros, all_sdk_machines,
112 max_threads, parent, flags, buttons=None):
113 super(AdvancedSettingsDialog, self).__init__(title, parent, flags, buttons)
114
115 # class members from other objects
116 # bitbake settings from Builder.Configuration
117 self.configuration = configuration
118 self.image_types = all_image_types
119 self.all_package_formats = all_package_formats
120 self.all_distros = all_distros[:]
121 self.all_sdk_machines = all_sdk_machines
122 self.max_threads = max_threads
123
124 # class members for internal use
125 self.distro_combo = None
126 self.dldir_text = None
127 self.sstatedir_text = None
128 self.sstatemirror_text = None
129 self.bb_spinner = None
130 self.pmake_spinner = None
131 self.rootfs_size_spinner = None
132 self.extra_size_spinner = None
133 self.gplv3_checkbox = None
134 self.sdk_checkbox = None
135 self.image_types_checkbuttons = {}
136
137 self.md5 = self.config_md5()
138 self.settings_changed = False
139
140 # create visual elements on the dialog
141 self.save_button = None
142 self.create_visual_elements()
143 self.connect("response", self.response_cb)
144
145 def _get_sorted_value(self, var):
146 return " ".join(sorted(str(var).split())) + "\n"
147
148 def config_md5(self):
149 data = ""
150 data += ("PACKAGE_CLASSES: " + self.configuration.curr_package_format + '\n')
151 data += ("DISTRO: " + self._get_sorted_value(self.configuration.curr_distro))
152 data += ("IMAGE_ROOTFS_SIZE: " + self._get_sorted_value(self.configuration.image_rootfs_size))
153 data += ("IMAGE_EXTRA_SIZE: " + self._get_sorted_value(self.configuration.image_extra_size))
154 data += ("INCOMPATIBLE_LICENSE: " + self._get_sorted_value(self.configuration.incompat_license))
155 data += ("SDK_MACHINE: " + self._get_sorted_value(self.configuration.curr_sdk_machine))
156 data += ("TOOLCHAIN_BUILD: " + self._get_sorted_value(self.configuration.toolchain_build))
157 data += ("IMAGE_FSTYPES: " + self._get_sorted_value(self.configuration.image_fstypes))
158 return hashlib.md5(data).hexdigest()
159
160 def create_visual_elements(self):
161 self.nb = gtk.Notebook()
162 self.nb.set_show_tabs(True)
163 self.nb.append_page(self.create_image_types_page(), gtk.Label("Image types"))
164 self.nb.append_page(self.create_output_page(), gtk.Label("Output"))
165 self.nb.set_current_page(0)
166 self.vbox.pack_start(self.nb, expand=True, fill=True)
167 self.vbox.pack_end(gtk.HSeparator(), expand=True, fill=True)
168
169 self.show_all()
170
171 def get_num_checked_image_types(self):
172 total = 0
173 for b in self.image_types_checkbuttons.values():
174 if b.get_active():
175 total = total + 1
176 return total
177
178 def set_save_button_state(self):
179 if self.save_button:
180 self.save_button.set_sensitive(self.get_num_checked_image_types() > 0)
181
182 def image_type_checkbutton_clicked_cb(self, button):
183 self.set_save_button_state()
184 if self.get_num_checked_image_types() == 0:
185 # Show an error dialog
186 lbl = "<b>Select an image type</b>"
187 msg = "You need to select at least one image type."
188 dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_WARNING, msg)
189 button = dialog.add_button("OK", gtk.RESPONSE_OK)
190 HobButton.style_button(button)
191 response = dialog.run()
192 dialog.destroy()
193
194 def create_image_types_page(self):
195 main_vbox = gtk.VBox(False, 16)
196 main_vbox.set_border_width(6)
197
198 advanced_vbox = gtk.VBox(False, 6)
199 advanced_vbox.set_border_width(6)
200
201 distro_vbox = gtk.VBox(False, 6)
202 label = self.gen_label_widget("Distro:")
203 tooltip = "Selects the Yocto Project distribution you want"
204 try:
205 i = self.all_distros.index( "defaultsetup" )
206 except ValueError:
207 i = -1
208 if i != -1:
209 self.all_distros[ i ] = "Default"
210 if self.configuration.curr_distro == "defaultsetup":
211 self.configuration.curr_distro = "Default"
212 distro_widget, self.distro_combo = self.gen_combo_widget(self.configuration.curr_distro, self.all_distros,"<b>Distro</b>" + "*" + tooltip)
213 distro_vbox.pack_start(label, expand=False, fill=False)
214 distro_vbox.pack_start(distro_widget, expand=False, fill=False)
215 main_vbox.pack_start(distro_vbox, expand=False, fill=False)
216
217
218 rows = (len(self.image_types)+1)/3
219 table = gtk.Table(rows + 1, 10, True)
220 advanced_vbox.pack_start(table, expand=False, fill=False)
221
222 tooltip = "Image file system types you want."
223 info = HobInfoButton("<b>Image types</b>" + "*" + tooltip, self)
224 label = self.gen_label_widget("Image types:")
225 align = gtk.Alignment(0, 0.5, 0, 0)
226 table.attach(align, 0, 4, 0, 1)
227 align.add(label)
228 table.attach(info, 4, 5, 0, 1)
229
230 i = 1
231 j = 1
232 for image_type in sorted(self.image_types):
233 self.image_types_checkbuttons[image_type] = gtk.CheckButton(image_type)
234 self.image_types_checkbuttons[image_type].connect("toggled", self.image_type_checkbutton_clicked_cb)
235 article = ""
236 if image_type.startswith(("a", "e", "i", "o", "u")):
237 article = "n"
238 if image_type == "live":
239 self.image_types_checkbuttons[image_type].set_tooltip_text("Build iso and hddimg images")
240 else:
241 self.image_types_checkbuttons[image_type].set_tooltip_text("Build a%s %s image" % (article, image_type))
242 table.attach(self.image_types_checkbuttons[image_type], j - 1, j + 3, i, i + 1)
243 if image_type in self.configuration.image_fstypes.split():
244 self.image_types_checkbuttons[image_type].set_active(True)
245 i += 1
246 if i > rows:
247 i = 1
248 j = j + 4
249
250 main_vbox.pack_start(advanced_vbox, expand=False, fill=False)
251 self.set_save_button_state()
252
253 return main_vbox
254
255 def create_output_page(self):
256 advanced_vbox = gtk.VBox(False, 6)
257 advanced_vbox.set_border_width(6)
258
259 advanced_vbox.pack_start(self.gen_label_widget('<span weight="bold">Package format</span>'), expand=False, fill=False)
260 sub_vbox = gtk.VBox(False, 6)
261 advanced_vbox.pack_start(sub_vbox, expand=False, fill=False)
262 tooltip_combo = "Selects the package format used to generate rootfs."
263 tooltip_extra = "Selects extra package formats to build"
264 pkgfmt_widget, self.rootfs_combo, self.check_hbox = self.gen_pkgfmt_widget(self.configuration.curr_package_format, self.all_package_formats,"<b>Root file system package format</b>" + "*" + tooltip_combo,"<b>Additional package formats</b>" + "*" + tooltip_extra)
265 sub_vbox.pack_start(pkgfmt_widget, expand=False, fill=False)
266
267 advanced_vbox.pack_start(self.gen_label_widget('<span weight="bold">Image size</span>'), expand=False, fill=False)
268 sub_vbox = gtk.VBox(False, 6)
269 advanced_vbox.pack_start(sub_vbox, expand=False, fill=False)
270 label = self.gen_label_widget("Image basic size (in MB)")
271 tooltip = "Defines the size for the generated image. The OpenEmbedded build system determines the final size for the generated image using an algorithm that takes into account the initial disk space used for the generated image, the Image basic size value, and the Additional free space value.\n\nFor more information, check the <a href=\"http://www.yoctoproject.org/docs/current/poky-ref-manual/poky-ref-manual.html#var-IMAGE_ROOTFS_SIZE\">Yocto Project Reference Manual</a>."
272 rootfs_size_widget, self.rootfs_size_spinner = self.gen_spinner_widget(int(self.configuration.image_rootfs_size*1.0/1024), 0, 65536,"<b>Image basic size</b>" + "*" + tooltip)
273 sub_vbox.pack_start(label, expand=False, fill=False)
274 sub_vbox.pack_start(rootfs_size_widget, expand=False, fill=False)
275
276 sub_vbox = gtk.VBox(False, 6)
277 advanced_vbox.pack_start(sub_vbox, expand=False, fill=False)
278 label = self.gen_label_widget("Additional free space (in MB)")
279 tooltip = "Sets extra free disk space to be added to the generated image. Use this variable when you want to ensure that a specific amount of free disk space is available on a device after an image is installed and running."
280 extra_size_widget, self.extra_size_spinner = self.gen_spinner_widget(int(self.configuration.image_extra_size*1.0/1024), 0, 65536,"<b>Additional free space</b>" + "*" + tooltip)
281 sub_vbox.pack_start(label, expand=False, fill=False)
282 sub_vbox.pack_start(extra_size_widget, expand=False, fill=False)
283
284 advanced_vbox.pack_start(self.gen_label_widget('<span weight="bold">Licensing</span>'), expand=False, fill=False)
285 self.gplv3_checkbox = gtk.CheckButton("Exclude GPLv3 packages")
286 self.gplv3_checkbox.set_tooltip_text("Check this box to prevent GPLv3 packages from being included in your image")
287 if "GPLv3" in self.configuration.incompat_license.split():
288 self.gplv3_checkbox.set_active(True)
289 else:
290 self.gplv3_checkbox.set_active(False)
291 advanced_vbox.pack_start(self.gplv3_checkbox, expand=False, fill=False)
292
293 advanced_vbox.pack_start(self.gen_label_widget('<span weight="bold">SDK</span>'), expand=False, fill=False)
294 sub_hbox = gtk.HBox(False, 6)
295 advanced_vbox.pack_start(sub_hbox, expand=False, fill=False)
296 self.sdk_checkbox = gtk.CheckButton("Populate SDK")
297 tooltip = "Check this box to generate an SDK tarball that consists of the cross-toolchain and a sysroot that contains development packages for your image."
298 self.sdk_checkbox.set_tooltip_text(tooltip)
299 self.sdk_checkbox.set_active(self.configuration.toolchain_build)
300 sub_hbox.pack_start(self.sdk_checkbox, expand=False, fill=False)
301
302 tooltip = "Select the host platform for which you want to run the toolchain contained in the SDK tarball."
303 sdk_machine_widget, self.sdk_machine_combo = self.gen_combo_widget(self.configuration.curr_sdk_machine, self.all_sdk_machines,"<b>Populate SDK</b>" + "*" + tooltip)
304 sub_hbox.pack_start(sdk_machine_widget, expand=False, fill=False)
305
306 return advanced_vbox
307
308 def response_cb(self, dialog, response_id):
309 package_format = []
310 package_format.append(self.rootfs_combo.get_active_text())
311 for child in self.check_hbox:
312 if isinstance(child, gtk.CheckButton) and child.get_active():
313 package_format.append(child.get_label())
314 self.configuration.curr_package_format = " ".join(package_format)
315
316 distro = self.distro_combo.get_active_text()
317 if distro == "Default":
318 distro = "defaultsetup"
319 self.configuration.curr_distro = distro
320 self.configuration.image_rootfs_size = self.rootfs_size_spinner.get_value_as_int() * 1024
321 self.configuration.image_extra_size = self.extra_size_spinner.get_value_as_int() * 1024
322
323 self.configuration.image_fstypes = ""
324 for image_type in self.image_types:
325 if self.image_types_checkbuttons[image_type].get_active():
326 self.configuration.image_fstypes += (" " + image_type)
327 self.configuration.image_fstypes.strip()
328
329 if self.gplv3_checkbox.get_active():
330 if "GPLv3" not in self.configuration.incompat_license.split():
331 self.configuration.incompat_license += " GPLv3"
332 else:
333 if "GPLv3" in self.configuration.incompat_license.split():
334 self.configuration.incompat_license = self.configuration.incompat_license.split().remove("GPLv3")
335 self.configuration.incompat_license = " ".join(self.configuration.incompat_license or [])
336 self.configuration.incompat_license = self.configuration.incompat_license.strip()
337
338 self.configuration.toolchain_build = self.sdk_checkbox.get_active()
339 self.configuration.curr_sdk_machine = self.sdk_machine_combo.get_active_text()
340 md5 = self.config_md5()
341 self.settings_changed = (self.md5 != md5)
diff --git a/bitbake/lib/bb/ui/crumbs/hig/crumbsdialog.py b/bitbake/lib/bb/ui/crumbs/hig/crumbsdialog.py
new file mode 100644
index 0000000000..c679f9a070
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hig/crumbsdialog.py
@@ -0,0 +1,44 @@
1#
2# BitBake Graphical GTK User Interface
3#
4# Copyright (C) 2011-2012 Intel Corporation
5#
6# Authored by Joshua Lock <josh@linux.intel.com>
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
23import gtk
24
25"""
26The following are convenience classes for implementing GNOME HIG compliant
27BitBake GUI's
28In summary: spacing = 12px, border-width = 6px
29"""
30
31class CrumbsDialog(gtk.Dialog):
32 """
33 A GNOME HIG compliant dialog widget.
34 Add buttons with gtk.Dialog.add_button or gtk.Dialog.add_buttons
35 """
36 def __init__(self, title="", parent=None, flags=0, buttons=None):
37 super(CrumbsDialog, self).__init__(title, parent, flags, buttons)
38
39 self.set_property("has-separator", False) # note: deprecated in 2.22
40
41 self.set_border_width(6)
42 self.vbox.set_property("spacing", 12)
43 self.action_area.set_property("spacing", 12)
44 self.action_area.set_property("border-width", 6)
diff --git a/bitbake/lib/bb/ui/crumbs/hig/crumbsmessagedialog.py b/bitbake/lib/bb/ui/crumbs/hig/crumbsmessagedialog.py
new file mode 100644
index 0000000000..3b998e4637
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hig/crumbsmessagedialog.py
@@ -0,0 +1,70 @@
1#
2# BitBake Graphical GTK User Interface
3#
4# Copyright (C) 2011-2012 Intel Corporation
5#
6# Authored by Joshua Lock <josh@linux.intel.com>
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
23import glib
24import gtk
25from bb.ui.crumbs.hobwidget import HobIconChecker
26from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog
27
28"""
29The following are convenience classes for implementing GNOME HIG compliant
30BitBake GUI's
31In summary: spacing = 12px, border-width = 6px
32"""
33
34class CrumbsMessageDialog(gtk.MessageDialog):
35 """
36 A GNOME HIG compliant dialog widget.
37 Add buttons with gtk.Dialog.add_button or gtk.Dialog.add_buttons
38 """
39 def __init__(self, parent = None, label="", dialog_type = gtk.MESSAGE_QUESTION, msg=""):
40 super(CrumbsMessageDialog, self).__init__(None,
41 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
42 dialog_type,
43 gtk.BUTTONS_NONE,
44 None)
45
46 self.set_skip_taskbar_hint(False)
47
48 self.set_markup(label)
49
50 if 0 <= len(msg) < 300:
51 self.format_secondary_markup(msg)
52 else:
53 vbox = self.get_message_area()
54 vbox.set_border_width(1)
55 vbox.set_property("spacing", 12)
56 self.textWindow = gtk.ScrolledWindow()
57 self.textWindow.set_shadow_type(gtk.SHADOW_IN)
58 self.textWindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
59 self.msgView = gtk.TextView()
60 self.msgView.set_editable(False)
61 self.msgView.set_wrap_mode(gtk.WRAP_WORD)
62 self.msgView.set_cursor_visible(False)
63 self.msgView.set_size_request(300, 300)
64 self.buf = gtk.TextBuffer()
65 self.buf.set_text(msg)
66 self.msgView.set_buffer(self.buf)
67 self.textWindow.add(self.msgView)
68 self.msgView.show()
69 vbox.add(self.textWindow)
70 self.textWindow.show()
diff --git a/bitbake/lib/bb/ui/crumbs/hig/deployimagedialog.py b/bitbake/lib/bb/ui/crumbs/hig/deployimagedialog.py
new file mode 100644
index 0000000000..a13fff906a
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hig/deployimagedialog.py
@@ -0,0 +1,219 @@
1#
2# BitBake Graphical GTK User Interface
3#
4# Copyright (C) 2011-2012 Intel Corporation
5#
6# Authored by Joshua Lock <josh@linux.intel.com>
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
23import glob
24import gtk
25import gobject
26import os
27import re
28import shlex
29import subprocess
30import tempfile
31from bb.ui.crumbs.hobwidget import hic, HobButton
32from bb.ui.crumbs.progressbar import HobProgressBar
33import bb.ui.crumbs.utils
34import bb.process
35from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog
36from bb.ui.crumbs.hig.crumbsmessagedialog import CrumbsMessageDialog
37
38"""
39The following are convenience classes for implementing GNOME HIG compliant
40BitBake GUI's
41In summary: spacing = 12px, border-width = 6px
42"""
43
44class DeployImageDialog (CrumbsDialog):
45
46 __dummy_usb__ = "--select a usb drive--"
47
48 def __init__(self, title, image_path, parent, flags, buttons=None, standalone=False):
49 super(DeployImageDialog, self).__init__(title, parent, flags, buttons)
50
51 self.image_path = image_path
52 self.standalone = standalone
53
54 self.create_visual_elements()
55 self.connect("response", self.response_cb)
56
57 def create_visual_elements(self):
58 self.set_size_request(600, 400)
59 label = gtk.Label()
60 label.set_alignment(0.0, 0.5)
61 markup = "<span font_desc='12'>The image to be written into usb drive:</span>"
62 label.set_markup(markup)
63 self.vbox.pack_start(label, expand=False, fill=False, padding=2)
64
65 table = gtk.Table(2, 10, False)
66 table.set_col_spacings(5)
67 table.set_row_spacings(5)
68 self.vbox.pack_start(table, expand=True, fill=True)
69
70 scroll = gtk.ScrolledWindow()
71 scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
72 scroll.set_shadow_type(gtk.SHADOW_IN)
73 tv = gtk.TextView()
74 tv.set_editable(False)
75 tv.set_wrap_mode(gtk.WRAP_WORD)
76 tv.set_cursor_visible(False)
77 self.buf = gtk.TextBuffer()
78 self.buf.set_text(self.image_path)
79 tv.set_buffer(self.buf)
80 scroll.add(tv)
81 table.attach(scroll, 0, 10, 0, 1)
82
83 # There are 2 ways to use DeployImageDialog
84 # One way is that called by HOB when the 'Deploy Image' button is clicked
85 # The other way is that called by a standalone script.
86 # Following block of codes handles the latter way. It adds a 'Select Image' button and
87 # emit a signal when the button is clicked.
88 if self.standalone:
89 gobject.signal_new("select_image_clicked", self, gobject.SIGNAL_RUN_FIRST,
90 gobject.TYPE_NONE, ())
91 icon = gtk.Image()
92 pix_buffer = gtk.gdk.pixbuf_new_from_file(hic.ICON_IMAGES_DISPLAY_FILE)
93 icon.set_from_pixbuf(pix_buffer)
94 button = gtk.Button("Select Image")
95 button.set_image(icon)
96 #button.set_size_request(140, 50)
97 table.attach(button, 9, 10, 1, 2, gtk.FILL, 0, 0, 0)
98 button.connect("clicked", self.select_image_button_clicked_cb)
99
100 separator = gtk.HSeparator()
101 self.vbox.pack_start(separator, expand=False, fill=False, padding=10)
102
103 self.usb_desc = gtk.Label()
104 self.usb_desc.set_alignment(0.0, 0.5)
105 markup = "<span font_desc='12'>You haven't chosen any USB drive.</span>"
106 self.usb_desc.set_markup(markup)
107
108 self.usb_combo = gtk.combo_box_new_text()
109 self.usb_combo.connect("changed", self.usb_combo_changed_cb)
110 model = self.usb_combo.get_model()
111 model.clear()
112 self.usb_combo.append_text(self.__dummy_usb__)
113 for usb in self.find_all_usb_devices():
114 self.usb_combo.append_text("/dev/" + usb)
115 self.usb_combo.set_active(0)
116 self.vbox.pack_start(self.usb_combo, expand=False, fill=False)
117 self.vbox.pack_start(self.usb_desc, expand=False, fill=False, padding=2)
118
119 self.progress_bar = HobProgressBar()
120 self.vbox.pack_start(self.progress_bar, expand=False, fill=False)
121 separator = gtk.HSeparator()
122 self.vbox.pack_start(separator, expand=False, fill=True, padding=10)
123
124 self.vbox.show_all()
125 self.progress_bar.hide()
126
127 def set_image_text_buffer(self, image_path):
128 self.buf.set_text(image_path)
129
130 def set_image_path(self, image_path):
131 self.image_path = image_path
132
133 def popen_read(self, cmd):
134 tmpout, errors = bb.process.run("%s" % cmd)
135 return tmpout.strip()
136
137 def find_all_usb_devices(self):
138 usb_devs = [ os.readlink(u)
139 for u in glob.glob('/dev/disk/by-id/usb*')
140 if not re.search(r'part\d+', u) ]
141 return [ '%s' % u[u.rfind('/')+1:] for u in usb_devs ]
142
143 def get_usb_info(self, dev):
144 return "%s %s" % \
145 (self.popen_read('cat /sys/class/block/%s/device/vendor' % dev),
146 self.popen_read('cat /sys/class/block/%s/device/model' % dev))
147
148 def select_image_button_clicked_cb(self, button):
149 self.emit('select_image_clicked')
150
151 def usb_combo_changed_cb(self, usb_combo):
152 combo_item = self.usb_combo.get_active_text()
153 if not combo_item or combo_item == self.__dummy_usb__:
154 markup = "<span font_desc='12'>You haven't chosen any USB drive.</span>"
155 self.usb_desc.set_markup(markup)
156 else:
157 markup = "<span font_desc='12'>" + self.get_usb_info(combo_item.lstrip("/dev/")) + "</span>"
158 self.usb_desc.set_markup(markup)
159
160 def response_cb(self, dialog, response_id):
161 if response_id == gtk.RESPONSE_YES:
162 lbl = ''
163 msg = ''
164 combo_item = self.usb_combo.get_active_text()
165 if combo_item and combo_item != self.__dummy_usb__ and self.image_path:
166 cmdline = bb.ui.crumbs.utils.which_terminal()
167 if cmdline:
168 tmpfile = tempfile.NamedTemporaryFile()
169 cmdline += "\"sudo dd if=" + self.image_path + \
170 " of=" + combo_item + " && sync; echo $? > " + tmpfile.name + "\""
171 subprocess.call(shlex.split(cmdline))
172
173 if int(tmpfile.readline().strip()) == 0:
174 lbl = "<b>Deploy image successfully.</b>"
175 else:
176 lbl = "<b>Failed to deploy image.</b>"
177 msg = "Please check image <b>%s</b> exists and USB device <b>%s</b> is writable." % (self.image_path, combo_item)
178 tmpfile.close()
179 else:
180 if not self.image_path:
181 lbl = "<b>No selection made.</b>"
182 msg = "You have not selected an image to deploy."
183 else:
184 lbl = "<b>No selection made.</b>"
185 msg = "You have not selected a USB device."
186 if len(lbl):
187 crumbs_dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_INFO, msg)
188 button = crumbs_dialog.add_button("Close", gtk.RESPONSE_OK)
189 HobButton.style_button(button)
190 crumbs_dialog.run()
191 crumbs_dialog.destroy()
192
193 def update_progress_bar(self, title, fraction, status=None):
194 self.progress_bar.update(fraction)
195 self.progress_bar.set_title(title)
196 self.progress_bar.set_rcstyle(status)
197
198 def write_file(self, ifile, ofile):
199 self.progress_bar.reset()
200 self.progress_bar.show()
201
202 f_from = os.open(ifile, os.O_RDONLY)
203 f_to = os.open(ofile, os.O_WRONLY)
204
205 total_size = os.stat(ifile).st_size
206 written_size = 0
207
208 while True:
209 buf = os.read(f_from, 1024*1024)
210 if not buf:
211 break
212 os.write(f_to, buf)
213 written_size += 1024*1024
214 self.update_progress_bar("Writing to usb:", written_size * 1.0/total_size)
215
216 self.update_progress_bar("Writing completed:", 1.0)
217 os.close(f_from)
218 os.close(f_to)
219 self.progress_bar.hide()
diff --git a/bitbake/lib/bb/ui/crumbs/hig/imageselectiondialog.py b/bitbake/lib/bb/ui/crumbs/hig/imageselectiondialog.py
new file mode 100644
index 0000000000..21216adc97
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hig/imageselectiondialog.py
@@ -0,0 +1,172 @@
1#
2# BitBake Graphical GTK User Interface
3#
4# Copyright (C) 2011-2012 Intel Corporation
5#
6# Authored by Joshua Lock <josh@linux.intel.com>
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
23import gtk
24import gobject
25import os
26from bb.ui.crumbs.hobwidget import HobViewTable, HobInfoButton, HobButton, HobAltButton
27from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog
28from bb.ui.crumbs.hig.layerselectiondialog import LayerSelectionDialog
29
30"""
31The following are convenience classes for implementing GNOME HIG compliant
32BitBake GUI's
33In summary: spacing = 12px, border-width = 6px
34"""
35
36class ImageSelectionDialog (CrumbsDialog):
37
38 __columns__ = [{
39 'col_name' : 'Image name',
40 'col_id' : 0,
41 'col_style': 'text',
42 'col_min' : 400,
43 'col_max' : 400
44 }, {
45 'col_name' : 'Select',
46 'col_id' : 1,
47 'col_style': 'radio toggle',
48 'col_min' : 160,
49 'col_max' : 160
50 }]
51
52
53 def __init__(self, image_folder, image_types, title, parent, flags, buttons=None, image_extension = {}):
54 super(ImageSelectionDialog, self).__init__(title, parent, flags, buttons)
55 self.connect("response", self.response_cb)
56
57 self.image_folder = image_folder
58 self.image_types = image_types
59 self.image_list = []
60 self.image_names = []
61 self.image_extension = image_extension
62
63 # create visual elements on the dialog
64 self.create_visual_elements()
65
66 self.image_store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_BOOLEAN)
67 self.fill_image_store()
68
69 def create_visual_elements(self):
70 hbox = gtk.HBox(False, 6)
71
72 self.vbox.pack_start(hbox, expand=False, fill=False)
73
74 entry = gtk.Entry()
75 entry.set_text(self.image_folder)
76 table = gtk.Table(1, 10, True)
77 table.set_size_request(560, -1)
78 hbox.pack_start(table, expand=False, fill=False)
79 table.attach(entry, 0, 9, 0, 1)
80 image = gtk.Image()
81 image.set_from_stock(gtk.STOCK_OPEN, gtk.ICON_SIZE_BUTTON)
82 open_button = gtk.Button()
83 open_button.set_image(image)
84 open_button.connect("clicked", self.select_path_cb, self, entry)
85 table.attach(open_button, 9, 10, 0, 1)
86
87 self.image_table = HobViewTable(self.__columns__, "Images")
88 self.image_table.set_size_request(-1, 300)
89 self.image_table.connect("toggled", self.toggled_cb)
90 self.image_table.connect_group_selection(self.table_selected_cb)
91 self.image_table.connect("row-activated", self.row_actived_cb)
92 self.vbox.pack_start(self.image_table, expand=True, fill=True)
93
94 self.show_all()
95
96 def change_image_cb(self, model, path, columnid):
97 if not model:
98 return
99 iter = model.get_iter_first()
100 while iter:
101 rowpath = model.get_path(iter)
102 model[rowpath][columnid] = False
103 iter = model.iter_next(iter)
104
105 model[path][columnid] = True
106
107 def toggled_cb(self, table, cell, path, columnid, tree):
108 model = tree.get_model()
109 self.change_image_cb(model, path, columnid)
110
111 def table_selected_cb(self, selection):
112 model, paths = selection.get_selected_rows()
113 if paths:
114 self.change_image_cb(model, paths[0], 1)
115
116 def row_actived_cb(self, tab, model, path):
117 self.change_image_cb(model, path, 1)
118 self.emit('response', gtk.RESPONSE_YES)
119
120 def select_path_cb(self, action, parent, entry):
121 dialog = gtk.FileChooserDialog("", parent,
122 gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
123 text = entry.get_text()
124 dialog.set_current_folder(text if len(text) > 0 else os.getcwd())
125 button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
126 HobAltButton.style_button(button)
127 button = dialog.add_button("Open", gtk.RESPONSE_YES)
128 HobButton.style_button(button)
129 response = dialog.run()
130 if response == gtk.RESPONSE_YES:
131 path = dialog.get_filename()
132 entry.set_text(path)
133 self.image_folder = path
134 self.fill_image_store()
135
136 dialog.destroy()
137
138 def fill_image_store(self):
139 self.image_list = []
140 self.image_store.clear()
141 imageset = set()
142 for root, dirs, files in os.walk(self.image_folder):
143 # ignore the sub directories
144 dirs[:] = []
145 for f in files:
146 for image_type in self.image_types:
147 if image_type in self.image_extension:
148 real_types = self.image_extension[image_type]
149 else:
150 real_types = [image_type]
151 for real_image_type in real_types:
152 if f.endswith('.' + real_image_type):
153 imageset.add(f.rsplit('.' + real_image_type)[0].rsplit('.rootfs')[0])
154 self.image_list.append(f)
155
156 for image in imageset:
157 self.image_store.set(self.image_store.append(), 0, image, 1, False)
158
159 self.image_table.set_model(self.image_store)
160
161 def response_cb(self, dialog, response_id):
162 self.image_names = []
163 if response_id == gtk.RESPONSE_YES:
164 iter = self.image_store.get_iter_first()
165 while iter:
166 path = self.image_store.get_path(iter)
167 if self.image_store[path][1]:
168 for f in self.image_list:
169 if f.startswith(self.image_store[path][0] + '.'):
170 self.image_names.append(f)
171 break
172 iter = self.image_store.iter_next(iter)
diff --git a/bitbake/lib/bb/ui/crumbs/hig/layerselectiondialog.py b/bitbake/lib/bb/ui/crumbs/hig/layerselectiondialog.py
new file mode 100644
index 0000000000..52d57b6738
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hig/layerselectiondialog.py
@@ -0,0 +1,298 @@
1#
2# BitBake Graphical GTK User Interface
3#
4# Copyright (C) 2011-2012 Intel Corporation
5#
6# Authored by Joshua Lock <josh@linux.intel.com>
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
23import gtk
24import gobject
25import os
26import tempfile
27from bb.ui.crumbs.hobwidget import hic, HobButton, HobAltButton
28from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog
29from bb.ui.crumbs.hig.crumbsmessagedialog import CrumbsMessageDialog
30
31"""
32The following are convenience classes for implementing GNOME HIG compliant
33BitBake GUI's
34In summary: spacing = 12px, border-width = 6px
35"""
36
37class CellRendererPixbufActivatable(gtk.CellRendererPixbuf):
38 """
39 A custom CellRenderer implementation which is activatable
40 so that we can handle user clicks
41 """
42 __gsignals__ = { 'clicked' : (gobject.SIGNAL_RUN_LAST,
43 gobject.TYPE_NONE,
44 (gobject.TYPE_STRING,)), }
45
46 def __init__(self):
47 gtk.CellRendererPixbuf.__init__(self)
48 self.set_property('mode', gtk.CELL_RENDERER_MODE_ACTIVATABLE)
49 self.set_property('follow-state', True)
50
51 """
52 Respond to a user click on a cell
53 """
54 def do_activate(self, even, widget, path, background_area, cell_area, flags):
55 self.emit('clicked', path)
56
57#
58# LayerSelectionDialog
59#
60class LayerSelectionDialog (CrumbsDialog):
61
62 TARGETS = [
63 ("MY_TREE_MODEL_ROW", gtk.TARGET_SAME_WIDGET, 0),
64 ("text/plain", 0, 1),
65 ("TEXT", 0, 2),
66 ("STRING", 0, 3),
67 ]
68
69 def gen_label_widget(self, content):
70 label = gtk.Label()
71 label.set_alignment(0, 0)
72 label.set_markup(content)
73 label.show()
74 return label
75
76 def layer_widget_toggled_cb(self, cell, path, layer_store):
77 name = layer_store[path][0]
78 toggle = not layer_store[path][1]
79 layer_store[path][1] = toggle
80
81 def layer_widget_add_clicked_cb(self, action, layer_store, parent):
82 dialog = gtk.FileChooserDialog("Add new layer", parent,
83 gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
84 button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
85 HobAltButton.style_button(button)
86 button = dialog.add_button("Open", gtk.RESPONSE_YES)
87 HobButton.style_button(button)
88 label = gtk.Label("Select the layer you wish to add")
89 label.show()
90 dialog.set_extra_widget(label)
91 response = dialog.run()
92 path = dialog.get_filename()
93 dialog.destroy()
94
95 lbl = "<b>Error</b>"
96 msg = "Unable to load layer <i>%s</i> because " % path
97 if response == gtk.RESPONSE_YES:
98 import os
99 import os.path
100 layers = []
101 it = layer_store.get_iter_first()
102 while it:
103 layers.append(layer_store.get_value(it, 0))
104 it = layer_store.iter_next(it)
105
106 if not path:
107 msg += "it is an invalid path."
108 elif not os.path.exists(path+"/conf/layer.conf"):
109 msg += "there is no layer.conf inside the directory."
110 elif path in layers:
111 msg += "it is already in loaded layers."
112 else:
113 layer_store.append([path])
114 return
115 dialog = CrumbsMessageDialog(parent, lbl, gtk.MESSAGE_ERROR, msg)
116 dialog.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_OK)
117 response = dialog.run()
118 dialog.destroy()
119
120 def layer_widget_del_clicked_cb(self, action, tree_selection, layer_store):
121 model, iter = tree_selection.get_selected()
122 if iter:
123 layer_store.remove(iter)
124
125
126 def gen_layer_widget(self, layers, layers_avail, window, tooltip=""):
127 hbox = gtk.HBox(False, 6)
128
129 layer_tv = gtk.TreeView()
130 layer_tv.set_rules_hint(True)
131 layer_tv.set_headers_visible(False)
132 tree_selection = layer_tv.get_selection()
133 tree_selection.set_mode(gtk.SELECTION_SINGLE)
134
135 # Allow enable drag and drop of rows including row move
136 dnd_internal_target = ''
137 dnd_targets = [(dnd_internal_target, gtk.TARGET_SAME_WIDGET, 0)]
138 layer_tv.enable_model_drag_source( gtk.gdk.BUTTON1_MASK,
139 dnd_targets,
140 gtk.gdk.ACTION_MOVE)
141 layer_tv.enable_model_drag_dest(dnd_targets,
142 gtk.gdk.ACTION_MOVE)
143 layer_tv.connect("drag_data_get", self.drag_data_get_cb)
144 layer_tv.connect("drag_data_received", self.drag_data_received_cb)
145
146 col0= gtk.TreeViewColumn('Path')
147 cell0 = gtk.CellRendererText()
148 cell0.set_padding(5,2)
149 col0.pack_start(cell0, True)
150 col0.set_cell_data_func(cell0, self.draw_layer_path_cb)
151 layer_tv.append_column(col0)
152
153 scroll = gtk.ScrolledWindow()
154 scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
155 scroll.set_shadow_type(gtk.SHADOW_IN)
156 scroll.add(layer_tv)
157
158 table_layer = gtk.Table(2, 10, False)
159 hbox.pack_start(table_layer, expand=True, fill=True)
160
161 table_layer.attach(scroll, 0, 10, 0, 1)
162
163 layer_store = gtk.ListStore(gobject.TYPE_STRING)
164 for layer in layers:
165 layer_store.append([layer])
166
167 col1 = gtk.TreeViewColumn('Enabled')
168 layer_tv.append_column(col1)
169
170 cell1 = CellRendererPixbufActivatable()
171 cell1.set_fixed_size(-1,35)
172 cell1.connect("clicked", self.del_cell_clicked_cb, layer_store)
173 col1.pack_start(cell1, True)
174 col1.set_cell_data_func(cell1, self.draw_delete_button_cb, layer_tv)
175
176 add_button = gtk.Button()
177 add_button.set_relief(gtk.RELIEF_NONE)
178 box = gtk.HBox(False, 6)
179 box.show()
180 add_button.add(box)
181 add_button.connect("enter-notify-event", self.add_hover_cb)
182 add_button.connect("leave-notify-event", self.add_leave_cb)
183 self.im = gtk.Image()
184 self.im.set_from_file(hic.ICON_INDI_ADD_FILE)
185 self.im.show()
186 box.pack_start(self.im, expand=False, fill=False, padding=6)
187 lbl = gtk.Label("Add layer")
188 lbl.set_alignment(0.0, 0.5)
189 lbl.show()
190 box.pack_start(lbl, expand=True, fill=True, padding=6)
191 add_button.connect("clicked", self.layer_widget_add_clicked_cb, layer_store, window)
192 table_layer.attach(add_button, 0, 10, 1, 2, gtk.EXPAND | gtk.FILL, 0, 0, 6)
193 layer_tv.set_model(layer_store)
194
195 hbox.show_all()
196
197 return hbox, layer_store
198
199 def drag_data_get_cb(self, treeview, context, selection, target_id, etime):
200 treeselection = treeview.get_selection()
201 model, iter = treeselection.get_selected()
202 data = model.get_value(iter, 0)
203 selection.set(selection.target, 8, data)
204
205 def drag_data_received_cb(self, treeview, context, x, y, selection, info, etime):
206 model = treeview.get_model()
207 data = selection.data
208 drop_info = treeview.get_dest_row_at_pos(x, y)
209 if drop_info:
210 path, position = drop_info
211 iter = model.get_iter(path)
212 if (position == gtk.TREE_VIEW_DROP_BEFORE or position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE):
213 model.insert_before(iter, [data])
214 else:
215 model.insert_after(iter, [data])
216 else:
217 model.append([data])
218 if context.action == gtk.gdk.ACTION_MOVE:
219 context.finish(True, True, etime)
220 return
221
222 def add_hover_cb(self, button, event):
223 self.im.set_from_file(hic.ICON_INDI_ADD_HOVER_FILE)
224
225 def add_leave_cb(self, button, event):
226 self.im.set_from_file(hic.ICON_INDI_ADD_FILE)
227
228 def __init__(self, title, layers, layers_non_removable, all_layers, parent, flags, buttons=None):
229 super(LayerSelectionDialog, self).__init__(title, parent, flags, buttons)
230
231 # class members from other objects
232 self.layers = layers
233 self.layers_non_removable = layers_non_removable
234 self.all_layers = all_layers
235 self.layers_changed = False
236
237 # icon for remove button in TreeView
238 im = gtk.Image()
239 im.set_from_file(hic.ICON_INDI_REMOVE_FILE)
240 self.rem_icon = im.get_pixbuf()
241
242 # class members for internal use
243 self.layer_store = None
244
245 # create visual elements on the dialog
246 self.create_visual_elements()
247 self.connect("response", self.response_cb)
248
249 def create_visual_elements(self):
250 layer_widget, self.layer_store = self.gen_layer_widget(self.layers, self.all_layers, self, None)
251 layer_widget.set_size_request(450, 250)
252 self.vbox.pack_start(layer_widget, expand=True, fill=True)
253 self.show_all()
254
255 def response_cb(self, dialog, response_id):
256 model = self.layer_store
257 it = model.get_iter_first()
258 layers = []
259 while it:
260 layers.append(model.get_value(it, 0))
261 it = model.iter_next(it)
262
263 self.layers_changed = (self.layers != layers)
264 self.layers = layers
265
266 """
267 A custom cell_data_func to draw a delete 'button' in the TreeView for layers
268 other than the meta layer. The deletion of which is prevented so that the
269 user can't shoot themselves in the foot too badly.
270 """
271 def draw_delete_button_cb(self, col, cell, model, it, tv):
272 path = model.get_value(it, 0)
273 if path in self.layers_non_removable:
274 cell.set_sensitive(False)
275 cell.set_property('pixbuf', None)
276 cell.set_property('mode', gtk.CELL_RENDERER_MODE_INERT)
277 else:
278 cell.set_property('pixbuf', self.rem_icon)
279 cell.set_sensitive(True)
280 cell.set_property('mode', gtk.CELL_RENDERER_MODE_ACTIVATABLE)
281
282 return True
283
284 """
285 A custom cell_data_func to write an extra message into the layer path cell
286 for the meta layer. We should inform the user that they can't remove it for
287 their own safety.
288 """
289 def draw_layer_path_cb(self, col, cell, model, it):
290 path = model.get_value(it, 0)
291 if path in self.layers_non_removable:
292 cell.set_property('markup', "<b>It cannot be removed</b>\n%s" % path)
293 else:
294 cell.set_property('text', path)
295
296 def del_cell_clicked_cb(self, cell, path, model):
297 it = model.get_iter_from_string(path)
298 model.remove(it)
diff --git a/bitbake/lib/bb/ui/crumbs/hig/parsingwarningsdialog.py b/bitbake/lib/bb/ui/crumbs/hig/parsingwarningsdialog.py
new file mode 100644
index 0000000000..33bac39db8
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hig/parsingwarningsdialog.py
@@ -0,0 +1,163 @@
1#
2# BitBake Graphical GTK User Interface
3#
4# Copyright (C) 2011-2012 Intel Corporation
5#
6# Authored by Cristiana Voicu <cristiana.voicu@intel.com>
7#
8# 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
10# published by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21import gtk
22import gobject
23from bb.ui.crumbs.hobwidget import HobAltButton
24from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog
25
26"""
27The following are convenience classes for implementing GNOME HIG compliant
28BitBake GUI's
29In summary: spacing = 12px, border-width = 6px
30"""
31
32#
33# ParsingWarningsDialog
34#
35class ParsingWarningsDialog (CrumbsDialog):
36
37 def __init__(self, title, warnings, parent, flags, buttons=None):
38 super(ParsingWarningsDialog, self).__init__(title, parent, flags, buttons)
39
40 self.warnings = warnings
41 self.warning_on = 0
42 self.warn_nb = len(warnings)
43
44 # create visual elements on the dialog
45 self.create_visual_elements()
46
47 def cancel_button_cb(self, button):
48 self.destroy()
49
50 def previous_button_cb(self, button):
51 self.warning_on = self.warning_on - 1
52 self.refresh_components()
53
54 def next_button_cb(self, button):
55 self.warning_on = self.warning_on + 1
56 self.refresh_components()
57
58 def refresh_components(self):
59 lbl = self.warnings[self.warning_on]
60 #when the warning text has more than 400 chars, it uses a scroll bar
61 if 0<= len(lbl) < 400:
62 self.warning_label.set_size_request(320, 230)
63 self.warning_label.set_use_markup(True)
64 self.warning_label.set_line_wrap(True)
65 self.warning_label.set_markup(lbl)
66 self.warning_label.set_property("yalign", 0.00)
67 else:
68 self.textWindow.set_shadow_type(gtk.SHADOW_IN)
69 self.textWindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
70 self.msgView = gtk.TextView()
71 self.msgView.set_editable(False)
72 self.msgView.set_wrap_mode(gtk.WRAP_WORD)
73 self.msgView.set_cursor_visible(False)
74 self.msgView.set_size_request(320, 230)
75 self.buf = gtk.TextBuffer()
76 self.buf.set_text(lbl)
77 self.msgView.set_buffer(self.buf)
78 self.textWindow.add(self.msgView)
79 self.msgView.show()
80
81 if self.warning_on==0:
82 self.previous_button.set_sensitive(False)
83 else:
84 self.previous_button.set_sensitive(True)
85
86 if self.warning_on==self.warn_nb-1:
87 self.next_button.set_sensitive(False)
88 else:
89 self.next_button.set_sensitive(True)
90
91 if self.warn_nb>1:
92 self.heading = "Warning " + str(self.warning_on + 1) + " of " + str(self.warn_nb)
93 self.heading_label.set_markup('<span weight="bold">%s</span>' % self.heading)
94 else:
95 self.heading = "Warning"
96 self.heading_label.set_markup('<span weight="bold">%s</span>' % self.heading)
97
98 self.show_all()
99
100 if 0<= len(lbl) < 400:
101 self.textWindow.hide()
102 else:
103 self.warning_label.hide()
104
105 def create_visual_elements(self):
106 self.set_size_request(350, 350)
107 self.heading_label = gtk.Label()
108 self.heading_label.set_alignment(0, 0)
109 self.warning_label = gtk.Label()
110 self.warning_label.set_selectable(True)
111 self.warning_label.set_alignment(0, 0)
112 self.textWindow = gtk.ScrolledWindow()
113
114 table = gtk.Table(1, 10, False)
115
116 cancel_button = gtk.Button()
117 cancel_button.set_label("Close")
118 cancel_button.connect("clicked", self.cancel_button_cb)
119 cancel_button.set_size_request(110, 30)
120
121 self.previous_button = gtk.Button()
122 image1 = gtk.image_new_from_stock(gtk.STOCK_GO_BACK, gtk.ICON_SIZE_BUTTON)
123 image1.show()
124 box = gtk.HBox(False, 6)
125 box.show()
126 self.previous_button.add(box)
127 lbl = gtk.Label("Previous")
128 lbl.show()
129 box.pack_start(image1, expand=False, fill=False, padding=3)
130 box.pack_start(lbl, expand=True, fill=True, padding=3)
131 self.previous_button.connect("clicked", self.previous_button_cb)
132 self.previous_button.set_size_request(110, 30)
133
134 self.next_button = gtk.Button()
135 image2 = gtk.image_new_from_stock(gtk.STOCK_GO_FORWARD, gtk.ICON_SIZE_BUTTON)
136 image2.show()
137 box = gtk.HBox(False, 6)
138 box.show()
139 self.next_button.add(box)
140 lbl = gtk.Label("Next")
141 lbl.show()
142 box.pack_start(lbl, expand=True, fill=True, padding=3)
143 box.pack_start(image2, expand=False, fill=False, padding=3)
144 self.next_button.connect("clicked", self.next_button_cb)
145 self.next_button.set_size_request(110, 30)
146
147 #when there more than one warning, we need "previous" and "next" button
148 if self.warn_nb>1:
149 self.vbox.pack_start(self.heading_label, expand=False, fill=False)
150 self.vbox.pack_start(self.warning_label, expand=False, fill=False)
151 self.vbox.pack_start(self.textWindow, expand=False, fill=False)
152 table.attach(cancel_button, 6, 7, 0, 1, xoptions=gtk.SHRINK)
153 table.attach(self.previous_button, 7, 8, 0, 1, xoptions=gtk.SHRINK)
154 table.attach(self.next_button, 8, 9, 0, 1, xoptions=gtk.SHRINK)
155 self.vbox.pack_end(table, expand=False, fill=False)
156 else:
157 self.vbox.pack_start(self.heading_label, expand=False, fill=False)
158 self.vbox.pack_start(self.warning_label, expand=False, fill=False)
159 self.vbox.pack_start(self.textWindow, expand=False, fill=False)
160 cancel_button = self.add_button("Close", gtk.RESPONSE_CANCEL)
161 HobAltButton.style_button(cancel_button)
162
163 self.refresh_components()
diff --git a/bitbake/lib/bb/ui/crumbs/hig/propertydialog.py b/bitbake/lib/bb/ui/crumbs/hig/propertydialog.py
new file mode 100644
index 0000000000..09b9ce6de3
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hig/propertydialog.py
@@ -0,0 +1,437 @@
1#
2# BitBake Graphical GTK User Interface
3#
4# Copyright (C) 2011-2013 Intel Corporation
5#
6# Authored by Andrei Dinu <andrei.adrianx.dinu@intel.com>
7#
8# 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
10# published by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21import string
22import gtk
23import gobject
24import os
25import tempfile
26import glib
27from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog
28from bb.ui.crumbs.hig.settingsuihelper import SettingsUIHelper
29from bb.ui.crumbs.hig.crumbsmessagedialog import CrumbsMessageDialog
30from bb.ui.crumbs.hig.layerselectiondialog import LayerSelectionDialog
31
32"""
33The following are convenience classes for implementing GNOME HIG compliant
34BitBake GUI's
35In summary: spacing = 12px, border-width = 6px
36"""
37
38class PropertyDialog(CrumbsDialog):
39
40 def __init__(self, title, parent, information, flags, buttons=None):
41
42 super(PropertyDialog, self).__init__(title, parent, flags, buttons)
43
44 self.properties = information
45
46 if len(self.properties) == 10:
47 self.create_recipe_visual_elements()
48 elif len(self.properties) == 5:
49 self.create_package_visual_elements()
50 else:
51 self.create_information_visual_elements()
52
53
54 def create_information_visual_elements(self):
55
56 HOB_ICON_BASE_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), ("icons/"))
57 ICON_PACKAGES_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('info/info_display.png'))
58
59 self.set_resizable(False)
60
61 self.table = gtk.Table(1,1,False)
62 self.table.set_row_spacings(0)
63 self.table.set_col_spacings(0)
64
65 self.image = gtk.Image()
66 self.image.set_from_file(ICON_PACKAGES_DISPLAY_FILE)
67 self.image.set_property("xalign",0)
68 #self.vbox.add(self.image)
69
70 image_info = self.properties.split("*")[0]
71 info = self.properties.split("*")[1]
72
73 vbox = gtk.VBox(True, spacing=30)
74
75 self.label_short = gtk.Label()
76 self.label_short.set_line_wrap(False)
77 self.label_short.set_markup(image_info)
78 self.label_short.set_property("xalign", 0)
79
80 self.info_label = gtk.Label()
81 self.info_label.set_line_wrap(True)
82 self.info_label.set_markup(info)
83 self.info_label.set_property("yalign", 0.5)
84
85 self.table.attach(self.image, 0,1,0,1, xoptions=gtk.FILL|gtk.EXPAND, yoptions=gtk.FILL,xpadding=5,ypadding=5)
86 self.table.attach(self.label_short, 0,1,0,1, xoptions=gtk.FILL|gtk.EXPAND, yoptions=gtk.FILL,xpadding=40,ypadding=5)
87 self.table.attach(self.info_label, 0,1,1,2, xoptions=gtk.FILL|gtk.EXPAND, yoptions=gtk.FILL,xpadding=40,ypadding=10)
88
89 self.vbox.add(self.table)
90 self.connect('delete-event', lambda w, e: self.destroy() or True)
91
92 def treeViewTooltip( self, widget, e, tooltips, cell, emptyText="" ):
93 try:
94 (path,col,x,y) = widget.get_path_at_pos( int(e.x), int(e.y) )
95 it = widget.get_model().get_iter(path)
96 value = widget.get_model().get_value(it,cell)
97 if value in self.tooltip_items:
98 tooltips.set_tip(widget, self.tooltip_items[value])
99 tooltips.enable()
100 else:
101 tooltips.set_tip(widget, emptyText)
102 except:
103 tooltips.set_tip(widget, emptyText)
104
105
106 def create_package_visual_elements(self):
107
108 import json
109
110 name = self.properties['name']
111 binb = self.properties['binb']
112 size = self.properties['size']
113 recipe = self.properties['recipe']
114 file_list = json.loads(self.properties['files_list'])
115
116 files_temp = ''
117 paths_temp = ''
118 files_binb = []
119 paths_binb = []
120
121 self.tooltip_items = {}
122
123 self.set_resizable(False)
124
125 #cleaning out the recipe variable
126 recipe = recipe.split("+")[0]
127
128 vbox = gtk.VBox(True,spacing = 0)
129
130 ###################################### NAME ROW + COL #################################
131
132 self.label_short = gtk.Label()
133 self.label_short.set_size_request(300,-1)
134 self.label_short.set_selectable(True)
135 self.label_short.set_line_wrap(True)
136 self.label_short.set_markup("<span weight=\"bold\">Name: </span>" + name)
137 self.label_short.set_property("xalign", 0)
138
139 self.vbox.add(self.label_short)
140
141 ###################################### SIZE ROW + COL ######################################
142
143 self.label_short = gtk.Label()
144 self.label_short.set_size_request(300,-1)
145 self.label_short.set_selectable(True)
146 self.label_short.set_line_wrap(True)
147 self.label_short.set_markup("<span weight=\"bold\">Size: </span>" + size)
148 self.label_short.set_property("xalign", 0)
149
150 self.vbox.add(self.label_short)
151
152 ##################################### RECIPE ROW + COL #########################################
153
154 self.label_short = gtk.Label()
155 self.label_short.set_size_request(300,-1)
156 self.label_short.set_selectable(True)
157 self.label_short.set_line_wrap(True)
158 self.label_short.set_markup("<span weight=\"bold\">Recipe: </span>" + recipe)
159 self.label_short.set_property("xalign", 0)
160
161 self.vbox.add(self.label_short)
162
163 ##################################### BINB ROW + COL #######################################
164
165 if binb != '':
166 self.label_short = gtk.Label()
167 self.label_short.set_selectable(True)
168 self.label_short.set_line_wrap(True)
169 self.label_short.set_markup("<span weight=\"bold\">Brought in by: </span>")
170 self.label_short.set_property("xalign", 0)
171
172 self.label_info = gtk.Label()
173 self.label_info.set_size_request(300,-1)
174 self.label_info.set_selectable(True)
175 self.label_info.set_line_wrap(True)
176 self.label_info.set_markup(binb)
177 self.label_info.set_property("xalign", 0)
178
179 self.vbox.add(self.label_short)
180 self.vbox.add(self.label_info)
181
182 #################################### FILES BROUGHT BY PACKAGES ###################################
183
184 if file_list:
185
186 self.textWindow = gtk.ScrolledWindow()
187 self.textWindow.set_shadow_type(gtk.SHADOW_IN)
188 self.textWindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
189 self.textWindow.set_size_request(100, 170)
190
191 packagefiles_store = gtk.ListStore(str)
192
193 self.packagefiles_tv = gtk.TreeView()
194 self.packagefiles_tv.set_rules_hint(True)
195 self.packagefiles_tv.set_headers_visible(True)
196 self.textWindow.add(self.packagefiles_tv)
197
198 self.cell1 = gtk.CellRendererText()
199 col1 = gtk.TreeViewColumn('Package files', self.cell1)
200 col1.set_cell_data_func(self.cell1, self.regex_field)
201 self.packagefiles_tv.append_column(col1)
202
203 items = file_list.keys()
204 items.sort()
205 for item in items:
206 fullpath = item
207 while len(item) > 35:
208 item = item[:len(item)/2] + "" + item[len(item)/2+1:]
209 if len(item) == 35:
210 item = item[:len(item)/2] + "..." + item[len(item)/2+3:]
211 self.tooltip_items[item] = fullpath
212
213 packagefiles_store.append([str(item)])
214
215 self.packagefiles_tv.set_model(packagefiles_store)
216
217 tips = gtk.Tooltips()
218 tips.set_tip(self.packagefiles_tv, "")
219 self.packagefiles_tv.connect("motion-notify-event", self.treeViewTooltip, tips, 0)
220 self.packagefiles_tv.set_events(gtk.gdk.POINTER_MOTION_MASK)
221
222 self.vbox.add(self.textWindow)
223
224 self.vbox.show_all()
225
226
227 def regex_field(self, column, cell, model, iter):
228 cell.set_property('text', model.get_value(iter, 0))
229 return
230
231
232 def create_recipe_visual_elements(self):
233
234 summary = self.properties['summary']
235 name = self.properties['name']
236 version = self.properties['version']
237 revision = self.properties['revision']
238 binb = self.properties['binb']
239 group = self.properties['group']
240 license = self.properties['license']
241 homepage = self.properties['homepage']
242 bugtracker = self.properties['bugtracker']
243 description = self.properties['description']
244
245 self.set_resizable(False)
246
247 #cleaning out the version variable and also the summary
248 version = version.split(":")[1]
249 if len(version) > 30:
250 version = version.split("+")[0]
251 else:
252 version = version.split("-")[0]
253 license = license.replace("&" , "and")
254 if (homepage == ''):
255 homepage = 'unknown'
256 if (bugtracker == ''):
257 bugtracker = 'unknown'
258 summary = summary.split("+")[0]
259
260 #calculating the rows needed for the table
261 binb_items_count = len(binb.split(','))
262 binb_items = binb.split(',')
263
264 vbox = gtk.VBox(False,spacing = 0)
265
266 ######################################## SUMMARY LABEL #########################################
267
268 if summary != '':
269 self.label_short = gtk.Label()
270 self.label_short.set_width_chars(37)
271 self.label_short.set_selectable(True)
272 self.label_short.set_line_wrap(True)
273 self.label_short.set_markup("<b>" + summary + "</b>")
274 self.label_short.set_property("xalign", 0)
275
276 self.vbox.add(self.label_short)
277
278 ########################################## NAME ROW + COL #######################################
279
280 self.label_short = gtk.Label()
281 self.label_short.set_selectable(True)
282 self.label_short.set_line_wrap(True)
283 self.label_short.set_markup("<span weight=\"bold\">Name: </span>" + name)
284 self.label_short.set_property("xalign", 0)
285
286 self.vbox.add(self.label_short)
287
288 ####################################### VERSION ROW + COL ####################################
289
290 self.label_short = gtk.Label()
291 self.label_short.set_selectable(True)
292 self.label_short.set_line_wrap(True)
293 self.label_short.set_markup("<span weight=\"bold\">Version: </span>" + version)
294 self.label_short.set_property("xalign", 0)
295
296 self.vbox.add(self.label_short)
297
298 ##################################### REVISION ROW + COL #####################################
299
300 self.label_short = gtk.Label()
301 self.label_short.set_line_wrap(True)
302 self.label_short.set_selectable(True)
303 self.label_short.set_markup("<span weight=\"bold\">Revision: </span>" + revision)
304 self.label_short.set_property("xalign", 0)
305
306 self.vbox.add(self.label_short)
307
308 ################################## GROUP ROW + COL ############################################
309
310 self.label_short = gtk.Label()
311 self.label_short.set_selectable(True)
312 self.label_short.set_line_wrap(True)
313 self.label_short.set_markup("<span weight=\"bold\">Group: </span>" + group)
314 self.label_short.set_property("xalign", 0)
315
316 self.vbox.add(self.label_short)
317
318 ################################# HOMEPAGE ROW + COL ############################################
319
320 if homepage != 'unknown':
321 self.label_info = gtk.Label()
322 self.label_info.set_selectable(True)
323 self.label_info.set_line_wrap(True)
324 if len(homepage) > 35:
325 self.label_info.set_markup("<a href=\"" + homepage + "\">" + homepage[0:35] + "..." + "</a>")
326 else:
327 self.label_info.set_markup("<a href=\"" + homepage + "\">" + homepage[0:60] + "</a>")
328
329 self.label_info.set_property("xalign", 0)
330
331 self.label_short = gtk.Label()
332 self.label_short.set_selectable(True)
333 self.label_short.set_line_wrap(True)
334 self.label_short.set_markup("<b>Homepage: </b>")
335 self.label_short.set_property("xalign", 0)
336
337 self.vbox.add(self.label_short)
338 self.vbox.add(self.label_info)
339
340 ################################# BUGTRACKER ROW + COL ###########################################
341
342 if bugtracker != 'unknown':
343 self.label_info = gtk.Label()
344 self.label_info.set_selectable(True)
345 self.label_info.set_line_wrap(True)
346 if len(bugtracker) > 35:
347 self.label_info.set_markup("<a href=\"" + bugtracker + "\">" + bugtracker[0:35] + "..." + "</a>")
348 else:
349 self.label_info.set_markup("<a href=\"" + bugtracker + "\">" + bugtracker[0:60] + "</a>")
350 self.label_info.set_property("xalign", 0)
351
352 self.label_short = gtk.Label()
353 self.label_short.set_selectable(True)
354 self.label_short.set_line_wrap(True)
355 self.label_short.set_markup("<b>Bugtracker: </b>")
356 self.label_short.set_property("xalign", 0)
357
358 self.vbox.add(self.label_short)
359 self.vbox.add(self.label_info)
360
361 ################################# LICENSE ROW + COL ############################################
362
363 self.label_info = gtk.Label()
364 self.label_info.set_selectable(True)
365 self.label_info.set_line_wrap(True)
366 self.label_info.set_markup(license)
367 self.label_info.set_property("xalign", 0)
368
369 self.label_short = gtk.Label()
370 self.label_short.set_selectable(True)
371 self.label_short.set_line_wrap(True)
372 self.label_short.set_markup("<span weight=\"bold\">License: </span>")
373 self.label_short.set_property("xalign", 0)
374
375 self.vbox.add(self.label_short)
376 self.vbox.add(self.label_info)
377
378 ################################### BINB ROW+COL #############################################
379
380 if binb != '':
381 self.label_short = gtk.Label()
382 self.label_short.set_selectable(True)
383 self.label_short.set_line_wrap(True)
384 self.label_short.set_markup("<span weight=\"bold\">Brought in by: </span>")
385 self.label_short.set_property("xalign", 0)
386 self.vbox.add(self.label_short)
387 self.label_info = gtk.Label()
388 self.label_info.set_selectable(True)
389 self.label_info.set_width_chars(36)
390 if len(binb) > 200:
391 scrolled_window = gtk.ScrolledWindow()
392 scrolled_window.set_policy(gtk.POLICY_NEVER,gtk.POLICY_ALWAYS)
393 scrolled_window.set_size_request(100,100)
394 self.label_info.set_markup(binb)
395 self.label_info.set_padding(6,6)
396 self.label_info.set_alignment(0,0)
397 self.label_info.set_line_wrap(True)
398 scrolled_window.add_with_viewport(self.label_info)
399 self.vbox.add(scrolled_window)
400 else:
401 self.label_info.set_markup(binb)
402 self.label_info.set_property("xalign", 0)
403 self.label_info.set_line_wrap(True)
404 self.vbox.add(self.label_info)
405
406 ################################ DESCRIPTION TAG ROW #################################################
407
408 self.label_short = gtk.Label()
409 self.label_short.set_line_wrap(True)
410 self.label_short.set_markup("<span weight=\"bold\">Description </span>")
411 self.label_short.set_property("xalign", 0)
412 self.vbox.add(self.label_short)
413
414 ################################ DESCRIPTION INFORMATION ROW ##########################################
415
416 hbox = gtk.HBox(True,spacing = 0)
417
418 self.label_short = gtk.Label()
419 self.label_short.set_selectable(True)
420 self.label_short.set_width_chars(36)
421 if len(description) > 200:
422 scrolled_window = gtk.ScrolledWindow()
423 scrolled_window.set_policy(gtk.POLICY_NEVER,gtk.POLICY_ALWAYS)
424 scrolled_window.set_size_request(100,100)
425 self.label_short.set_markup(description)
426 self.label_short.set_padding(6,6)
427 self.label_short.set_alignment(0,0)
428 self.label_short.set_line_wrap(True)
429 scrolled_window.add_with_viewport(self.label_short)
430 self.vbox.add(scrolled_window)
431 else:
432 self.label_short.set_markup(description)
433 self.label_short.set_property("xalign", 0)
434 self.label_short.set_line_wrap(True)
435 self.vbox.add(self.label_short)
436
437 self.vbox.show_all()
diff --git a/bitbake/lib/bb/ui/crumbs/hig/proxydetailsdialog.py b/bitbake/lib/bb/ui/crumbs/hig/proxydetailsdialog.py
new file mode 100644
index 0000000000..69e7dffb6d
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hig/proxydetailsdialog.py
@@ -0,0 +1,90 @@
1#
2# BitBake Graphical GTK User Interface
3#
4# Copyright (C) 2011-2012 Intel Corporation
5#
6# Authored by Joshua Lock <josh@linux.intel.com>
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
23import gtk
24from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog
25
26"""
27The following are convenience classes for implementing GNOME HIG compliant
28BitBake GUI's
29In summary: spacing = 12px, border-width = 6px
30"""
31
32class ProxyDetailsDialog (CrumbsDialog):
33
34 def __init__(self, title, user, passwd, parent, flags, buttons=None):
35 super(ProxyDetailsDialog, self).__init__(title, parent, flags, buttons)
36 self.connect("response", self.response_cb)
37
38 self.auth = not (user == None or passwd == None or user == "")
39 self.user = user or ""
40 self.passwd = passwd or ""
41
42 # create visual elements on the dialog
43 self.create_visual_elements()
44
45 def create_visual_elements(self):
46 self.auth_checkbox = gtk.CheckButton("Use authentication")
47 self.auth_checkbox.set_tooltip_text("Check this box to set the username and the password")
48 self.auth_checkbox.set_active(self.auth)
49 self.auth_checkbox.connect("toggled", self.auth_checkbox_toggled_cb)
50 self.vbox.pack_start(self.auth_checkbox, expand=False, fill=False)
51
52 hbox = gtk.HBox(False, 6)
53 self.user_label = gtk.Label("Username:")
54 self.user_text = gtk.Entry()
55 self.user_text.set_text(self.user)
56 hbox.pack_start(self.user_label, expand=False, fill=False)
57 hbox.pack_end(self.user_text, expand=False, fill=False)
58 self.vbox.pack_start(hbox, expand=False, fill=False)
59
60 hbox = gtk.HBox(False, 6)
61 self.passwd_label = gtk.Label("Password:")
62 self.passwd_text = gtk.Entry()
63 self.passwd_text.set_text(self.passwd)
64 hbox.pack_start(self.passwd_label, expand=False, fill=False)
65 hbox.pack_end(self.passwd_text, expand=False, fill=False)
66 self.vbox.pack_start(hbox, expand=False, fill=False)
67
68 self.refresh_auth_components()
69 self.show_all()
70
71 def refresh_auth_components(self):
72 self.user_label.set_sensitive(self.auth)
73 self.user_text.set_editable(self.auth)
74 self.user_text.set_sensitive(self.auth)
75 self.passwd_label.set_sensitive(self.auth)
76 self.passwd_text.set_editable(self.auth)
77 self.passwd_text.set_sensitive(self.auth)
78
79 def auth_checkbox_toggled_cb(self, button):
80 self.auth = self.auth_checkbox.get_active()
81 self.refresh_auth_components()
82
83 def response_cb(self, dialog, response_id):
84 if response_id == gtk.RESPONSE_OK:
85 if self.auth:
86 self.user = self.user_text.get_text()
87 self.passwd = self.passwd_text.get_text()
88 else:
89 self.user = None
90 self.passwd = None
diff --git a/bitbake/lib/bb/ui/crumbs/hig/retrieveimagedialog.py b/bitbake/lib/bb/ui/crumbs/hig/retrieveimagedialog.py
new file mode 100644
index 0000000000..9017139850
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hig/retrieveimagedialog.py
@@ -0,0 +1,51 @@
1#
2# BitBake Graphical GTK User Interface
3#
4# Copyright (C) 2013 Intel Corporation
5#
6# Authored by Cristiana Voicu <cristiana.voicu@intel.com>
7#
8# 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
10# published by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21import gtk
22
23class RetrieveImageDialog (gtk.FileChooserDialog):
24 """
25 This class is used to create a dialog that permits to retrieve
26 a custom image saved previously from Hob.
27 """
28 def __init__(self, directory,title, parent, flags, buttons=None):
29 super(RetrieveImageDialog, self).__init__(title, None, gtk.FILE_CHOOSER_ACTION_OPEN,
30 (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN, gtk.RESPONSE_OK))
31 self.directory = directory
32
33 # create visual elements on the dialog
34 self.create_visual_elements()
35
36 def create_visual_elements(self):
37 self.set_show_hidden(True)
38 self.set_default_response(gtk.RESPONSE_OK)
39 self.set_current_folder(self.directory)
40
41 vbox = self.get_children()[0].get_children()[0].get_children()[0]
42 for child in vbox.get_children()[0].get_children()[0].get_children()[0].get_children():
43 vbox.get_children()[0].get_children()[0].get_children()[0].remove(child)
44
45 label1 = gtk.Label()
46 label1.set_text("File system" + self.directory)
47 label1.show()
48 vbox.get_children()[0].get_children()[0].get_children()[0].pack_start(label1, expand=False, fill=False, padding=0)
49 vbox.get_children()[0].get_children()[1].get_children()[0].hide()
50
51 self.get_children()[0].get_children()[1].get_children()[0].set_label("Select")
diff --git a/bitbake/lib/bb/ui/crumbs/hig/saveimagedialog.py b/bitbake/lib/bb/ui/crumbs/hig/saveimagedialog.py
new file mode 100644
index 0000000000..4195f70e1e
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hig/saveimagedialog.py
@@ -0,0 +1,159 @@
1#
2# BitBake Graphical GTK User Interface
3#
4# Copyright (C) 2013 Intel Corporation
5#
6# Authored by Cristiana Voicu <cristiana.voicu@intel.com>
7#
8# 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
10# published by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21import gtk
22import glib
23from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog
24from bb.ui.crumbs.hig.crumbsmessagedialog import CrumbsMessageDialog
25from bb.ui.crumbs.hobwidget import HobButton
26
27class SaveImageDialog (CrumbsDialog):
28 """
29 This class is used to create a dialog that permits to save
30 a custom image in a predefined directory.
31 """
32 def __init__(self, directory, name, description, title, parent, flags, buttons=None):
33 super(SaveImageDialog, self).__init__(title, parent, flags, buttons)
34 self.directory = directory
35 self.builder = parent
36 self.name_field = name
37 self.description_field = description
38
39 # create visual elements on the dialog
40 self.create_visual_elements()
41
42 def create_visual_elements(self):
43 self.set_default_response(gtk.RESPONSE_OK)
44 self.vbox.set_border_width(6)
45
46 sub_vbox = gtk.VBox(False, 12)
47 self.vbox.pack_start(sub_vbox, expand=False, fill=False)
48 label = gtk.Label()
49 label.set_alignment(0, 0)
50 label.set_markup("<b>Name</b>")
51 sub_label = gtk.Label()
52 sub_label.set_alignment(0, 0)
53 content = "Image recipe names should be all lowercase and include only alphanumeric\n"
54 content += "characters. The only special character you can use is the ASCII hyphen (-)."
55 sub_label.set_markup(content)
56 self.name_entry = gtk.Entry()
57 self.name_entry.set_text(self.name_field)
58 self.name_entry.set_size_request(350,30)
59 self.name_entry.connect("changed", self.name_entry_changed)
60 sub_vbox.pack_start(label, expand=False, fill=False)
61 sub_vbox.pack_start(sub_label, expand=False, fill=False)
62 sub_vbox.pack_start(self.name_entry, expand=False, fill=False)
63
64 sub_vbox = gtk.VBox(False, 12)
65 self.vbox.pack_start(sub_vbox, expand=False, fill=False)
66 label = gtk.Label()
67 label.set_alignment(0, 0)
68 label.set_markup("<b>Description</b> (optional)")
69 sub_label = gtk.Label()
70 sub_label.set_alignment(0, 0)
71 sub_label.set_markup("The description should be less than 150 characters long.")
72 self.description_entry = gtk.TextView()
73 description_buffer = self.description_entry.get_buffer()
74 description_buffer.set_text(self.description_field)
75 description_buffer.connect("insert-text", self.limit_description_length)
76 self.description_entry.set_wrap_mode(gtk.WRAP_WORD)
77 self.description_entry.set_size_request(350,50)
78 sub_vbox.pack_start(label, expand=False, fill=False)
79 sub_vbox.pack_start(sub_label, expand=False, fill=False)
80 sub_vbox.pack_start(self.description_entry, expand=False, fill=False)
81
82 sub_vbox = gtk.VBox(False, 12)
83 self.vbox.pack_start(sub_vbox, expand=False, fill=False)
84 label = gtk.Label()
85 label.set_alignment(0, 0)
86 label.set_markup("Your image recipe will be saved to:")
87 sub_label = gtk.Label()
88 sub_label.set_alignment(0, 0)
89 sub_label.set_markup(self.directory)
90 sub_vbox.pack_start(label, expand=False, fill=False)
91 sub_vbox.pack_start(sub_label, expand=False, fill=False)
92
93 table = gtk.Table(1, 4, True)
94
95 cancel_button = gtk.Button()
96 cancel_button.set_label("Cancel")
97 cancel_button.connect("clicked", self.cancel_button_cb)
98 cancel_button.set_size_request(110, 30)
99
100 self.save_button = gtk.Button()
101 self.save_button.set_label("Save")
102 self.save_button.connect("clicked", self.save_button_cb)
103 self.save_button.set_size_request(110, 30)
104 if self.name_entry.get_text() == '':
105 self.save_button.set_sensitive(False)
106
107 table.attach(cancel_button, 2, 3, 0, 1)
108 table.attach(self.save_button, 3, 4, 0, 1)
109 self.vbox.pack_end(table, expand=False, fill=False)
110
111 self.show_all()
112
113 def limit_description_length(self, textbuffer, iter, text, length):
114 buffer_bounds = textbuffer.get_bounds()
115 entire_text = textbuffer.get_text(*buffer_bounds)
116 entire_text += text
117 if len(entire_text)>150 or text=="\n":
118 textbuffer.emit_stop_by_name("insert-text")
119
120 def name_entry_changed(self, entry):
121 text = entry.get_text()
122 if text == '':
123 self.save_button.set_sensitive(False)
124 else:
125 self.save_button.set_sensitive(True)
126
127 def cancel_button_cb(self, button):
128 self.destroy()
129
130 def save_button_cb(self, button):
131 text = self.name_entry.get_text()
132 new_text = text.replace("-","")
133 description_buffer = self.description_entry.get_buffer()
134 description = description_buffer.get_text(description_buffer.get_start_iter(),description_buffer.get_end_iter())
135 if new_text.islower() and new_text.isalnum():
136 self.builder.image_details_page.image_saved = True
137 self.builder.customized = False
138 self.builder.generate_new_image(self.directory+text, description)
139 self.builder.recipe_model.set_in_list(text, description)
140 self.builder.recipe_model.set_selected_image(text)
141 self.builder.image_details_page.show_page(self.builder.IMAGE_GENERATED)
142 self.builder.image_details_page.name_field_template = text
143 self.builder.image_details_page.description_field_template = description
144 self.destroy()
145 else:
146 self.show_invalid_input_error_dialog()
147
148 def show_invalid_input_error_dialog(self):
149 lbl = "<b>Invalid characters in image recipe name</b>"
150 msg = "Image recipe names should be all lowercase and\n"
151 msg += "include only alphanumeric characters. The only\n"
152 msg += "special character you can use is the ASCII hyphen (-)."
153 dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_ERROR, msg)
154 button = dialog.add_button("Close", gtk.RESPONSE_OK)
155 HobButton.style_button(button)
156
157 res = dialog.run()
158 self.name_entry.grab_focus()
159 dialog.destroy()
diff --git a/bitbake/lib/bb/ui/crumbs/hig/settingsuihelper.py b/bitbake/lib/bb/ui/crumbs/hig/settingsuihelper.py
new file mode 100644
index 0000000000..e0285c93ce
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hig/settingsuihelper.py
@@ -0,0 +1,122 @@
1#
2# BitBake Graphical GTK User Interface
3#
4# Copyright (C) 2011-2012 Intel Corporation
5#
6# Authored by Joshua Lock <josh@linux.intel.com>
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
23import gtk
24import os
25from bb.ui.crumbs.hobwidget import HobInfoButton, HobButton, HobAltButton
26
27"""
28The following are convenience classes for implementing GNOME HIG compliant
29BitBake GUI's
30In summary: spacing = 12px, border-width = 6px
31"""
32
33class SettingsUIHelper():
34
35 def gen_label_widget(self, content):
36 label = gtk.Label()
37 label.set_alignment(0, 0)
38 label.set_markup(content)
39 label.show()
40 return label
41
42 def gen_label_info_widget(self, content, tooltip):
43 table = gtk.Table(1, 10, False)
44 label = self.gen_label_widget(content)
45 info = HobInfoButton(tooltip, self)
46 table.attach(label, 0, 1, 0, 1, xoptions=gtk.FILL)
47 table.attach(info, 1, 2, 0, 1, xoptions=gtk.FILL, xpadding=10)
48 return table
49
50 def gen_spinner_widget(self, content, lower, upper, tooltip=""):
51 hbox = gtk.HBox(False, 12)
52 adjust = gtk.Adjustment(value=content, lower=lower, upper=upper, step_incr=1)
53 spinner = gtk.SpinButton(adjustment=adjust, climb_rate=1, digits=0)
54
55 spinner.set_value(content)
56 hbox.pack_start(spinner, expand=False, fill=False)
57
58 info = HobInfoButton(tooltip, self)
59 hbox.pack_start(info, expand=False, fill=False)
60
61 hbox.show_all()
62 return hbox, spinner
63
64 def gen_combo_widget(self, curr_item, all_item, tooltip=""):
65 hbox = gtk.HBox(False, 12)
66 combo = gtk.combo_box_new_text()
67 hbox.pack_start(combo, expand=False, fill=False)
68
69 index = 0
70 for item in all_item or []:
71 combo.append_text(item)
72 if item == curr_item:
73 combo.set_active(index)
74 index += 1
75
76 info = HobInfoButton(tooltip, self)
77 hbox.pack_start(info, expand=False, fill=False)
78
79 hbox.show_all()
80 return hbox, combo
81
82 def entry_widget_select_path_cb(self, action, parent, entry):
83 dialog = gtk.FileChooserDialog("", parent,
84 gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
85 text = entry.get_text()
86 dialog.set_current_folder(text if len(text) > 0 else os.getcwd())
87 button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
88 HobAltButton.style_button(button)
89 button = dialog.add_button("Open", gtk.RESPONSE_YES)
90 HobButton.style_button(button)
91 response = dialog.run()
92 if response == gtk.RESPONSE_YES:
93 path = dialog.get_filename()
94 entry.set_text(path)
95
96 dialog.destroy()
97
98 def gen_entry_widget(self, content, parent, tooltip="", need_button=True):
99 hbox = gtk.HBox(False, 12)
100 entry = gtk.Entry()
101 entry.set_text(content)
102 entry.set_size_request(350,30)
103
104 if need_button:
105 table = gtk.Table(1, 10, False)
106 hbox.pack_start(table, expand=True, fill=True)
107 table.attach(entry, 0, 9, 0, 1, xoptions=gtk.SHRINK)
108 image = gtk.Image()
109 image.set_from_stock(gtk.STOCK_OPEN,gtk.ICON_SIZE_BUTTON)
110 open_button = gtk.Button()
111 open_button.set_image(image)
112 open_button.connect("clicked", self.entry_widget_select_path_cb, parent, entry)
113 table.attach(open_button, 9, 10, 0, 1, xoptions=gtk.SHRINK)
114 else:
115 hbox.pack_start(entry, expand=True, fill=True)
116
117 if tooltip != "":
118 info = HobInfoButton(tooltip, self)
119 hbox.pack_start(info, expand=False, fill=False)
120
121 hbox.show_all()
122 return hbox, entry
diff --git a/bitbake/lib/bb/ui/crumbs/hig/simplesettingsdialog.py b/bitbake/lib/bb/ui/crumbs/hig/simplesettingsdialog.py
new file mode 100644
index 0000000000..ab5b614c8d
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hig/simplesettingsdialog.py
@@ -0,0 +1,894 @@
1#
2# BitBake Graphical GTK User Interface
3#
4# Copyright (C) 2011-2012 Intel Corporation
5#
6# Authored by Joshua Lock <josh@linux.intel.com>
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
23import gtk
24import gobject
25import hashlib
26from bb.ui.crumbs.hobwidget import hic, HobInfoButton, HobButton, HobAltButton
27from bb.ui.crumbs.progressbar import HobProgressBar
28from bb.ui.crumbs.hig.settingsuihelper import SettingsUIHelper
29from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog
30from bb.ui.crumbs.hig.crumbsmessagedialog import CrumbsMessageDialog
31from bb.ui.crumbs.hig.proxydetailsdialog import ProxyDetailsDialog
32
33"""
34The following are convenience classes for implementing GNOME HIG compliant
35BitBake GUI's
36In summary: spacing = 12px, border-width = 6px
37"""
38
39class SimpleSettingsDialog (CrumbsDialog, SettingsUIHelper):
40
41 (BUILD_ENV_PAGE_ID,
42 SHARED_STATE_PAGE_ID,
43 PROXIES_PAGE_ID,
44 OTHERS_PAGE_ID) = range(4)
45
46 (TEST_NETWORK_NONE,
47 TEST_NETWORK_INITIAL,
48 TEST_NETWORK_RUNNING,
49 TEST_NETWORK_PASSED,
50 TEST_NETWORK_FAILED,
51 TEST_NETWORK_CANCELED) = range(6)
52
53 TARGETS = [
54 ("MY_TREE_MODEL_ROW", gtk.TARGET_SAME_WIDGET, 0),
55 ("text/plain", 0, 1),
56 ("TEXT", 0, 2),
57 ("STRING", 0, 3),
58 ]
59
60 def __init__(self, title, configuration, all_image_types,
61 all_package_formats, all_distros, all_sdk_machines,
62 max_threads, parent, flags, handler, buttons=None):
63 super(SimpleSettingsDialog, self).__init__(title, parent, flags, buttons)
64
65 # class members from other objects
66 # bitbake settings from Builder.Configuration
67 self.configuration = configuration
68 self.image_types = all_image_types
69 self.all_package_formats = all_package_formats
70 self.all_distros = all_distros
71 self.all_sdk_machines = all_sdk_machines
72 self.max_threads = max_threads
73
74 # class members for internal use
75 self.dldir_text = None
76 self.sstatedir_text = None
77 self.sstatemirrors_list = []
78 self.sstatemirrors_changed = 0
79 self.bb_spinner = None
80 self.pmake_spinner = None
81 self.rootfs_size_spinner = None
82 self.extra_size_spinner = None
83 self.gplv3_checkbox = None
84 self.toolchain_checkbox = None
85 self.setting_store = None
86 self.image_types_checkbuttons = {}
87
88 self.md5 = self.config_md5()
89 self.proxy_md5 = self.config_proxy_md5()
90 self.settings_changed = False
91 self.proxy_settings_changed = False
92 self.handler = handler
93 self.proxy_test_ran = False
94 self.selected_mirror_row = 0
95 self.new_mirror = False
96
97 # create visual elements on the dialog
98 self.create_visual_elements()
99 self.connect("response", self.response_cb)
100
101 def _get_sorted_value(self, var):
102 return " ".join(sorted(str(var).split())) + "\n"
103
104 def config_proxy_md5(self):
105 data = ("ENABLE_PROXY: " + self._get_sorted_value(self.configuration.enable_proxy))
106 if self.configuration.enable_proxy:
107 for protocol in self.configuration.proxies.keys():
108 data += (protocol + ": " + self._get_sorted_value(self.configuration.combine_proxy(protocol)))
109 return hashlib.md5(data).hexdigest()
110
111 def config_md5(self):
112 data = ""
113 for key in self.configuration.extra_setting.keys():
114 data += (key + ": " + self._get_sorted_value(self.configuration.extra_setting[key]))
115 return hashlib.md5(data).hexdigest()
116
117 def gen_proxy_entry_widget(self, protocol, parent, need_button=True, line=0):
118 label = gtk.Label(protocol.upper() + " proxy")
119 self.proxy_table.attach(label, 0, 1, line, line+1, xpadding=24)
120
121 proxy_entry = gtk.Entry()
122 proxy_entry.set_size_request(300, -1)
123 self.proxy_table.attach(proxy_entry, 1, 2, line, line+1, ypadding=4)
124
125 self.proxy_table.attach(gtk.Label(":"), 2, 3, line, line+1, xpadding=12, ypadding=4)
126
127 port_entry = gtk.Entry()
128 port_entry.set_size_request(60, -1)
129 self.proxy_table.attach(port_entry, 3, 4, line, line+1, ypadding=4)
130
131 details_button = HobAltButton("Details")
132 details_button.connect("clicked", self.details_cb, parent, protocol)
133 self.proxy_table.attach(details_button, 4, 5, line, line+1, xpadding=4, yoptions=gtk.EXPAND)
134
135 return proxy_entry, port_entry, details_button
136
137 def refresh_proxy_components(self):
138 self.same_checkbox.set_sensitive(self.configuration.enable_proxy)
139
140 self.http_proxy.set_text(self.configuration.combine_host_only("http"))
141 self.http_proxy.set_editable(self.configuration.enable_proxy)
142 self.http_proxy.set_sensitive(self.configuration.enable_proxy)
143 self.http_proxy_port.set_text(self.configuration.combine_port_only("http"))
144 self.http_proxy_port.set_editable(self.configuration.enable_proxy)
145 self.http_proxy_port.set_sensitive(self.configuration.enable_proxy)
146 self.http_proxy_details.set_sensitive(self.configuration.enable_proxy)
147
148 self.https_proxy.set_text(self.configuration.combine_host_only("https"))
149 self.https_proxy.set_editable(self.configuration.enable_proxy and (not self.configuration.same_proxy))
150 self.https_proxy.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy))
151 self.https_proxy_port.set_text(self.configuration.combine_port_only("https"))
152 self.https_proxy_port.set_editable(self.configuration.enable_proxy and (not self.configuration.same_proxy))
153 self.https_proxy_port.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy))
154 self.https_proxy_details.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy))
155
156 self.ftp_proxy.set_text(self.configuration.combine_host_only("ftp"))
157 self.ftp_proxy.set_editable(self.configuration.enable_proxy and (not self.configuration.same_proxy))
158 self.ftp_proxy.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy))
159 self.ftp_proxy_port.set_text(self.configuration.combine_port_only("ftp"))
160 self.ftp_proxy_port.set_editable(self.configuration.enable_proxy and (not self.configuration.same_proxy))
161 self.ftp_proxy_port.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy))
162 self.ftp_proxy_details.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy))
163
164 self.socks_proxy.set_text(self.configuration.combine_host_only("socks"))
165 self.socks_proxy.set_editable(self.configuration.enable_proxy and (not self.configuration.same_proxy))
166 self.socks_proxy.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy))
167 self.socks_proxy_port.set_text(self.configuration.combine_port_only("socks"))
168 self.socks_proxy_port.set_editable(self.configuration.enable_proxy and (not self.configuration.same_proxy))
169 self.socks_proxy_port.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy))
170 self.socks_proxy_details.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy))
171
172 self.cvs_proxy.set_text(self.configuration.combine_host_only("cvs"))
173 self.cvs_proxy.set_editable(self.configuration.enable_proxy and (not self.configuration.same_proxy))
174 self.cvs_proxy.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy))
175 self.cvs_proxy_port.set_text(self.configuration.combine_port_only("cvs"))
176 self.cvs_proxy_port.set_editable(self.configuration.enable_proxy and (not self.configuration.same_proxy))
177 self.cvs_proxy_port.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy))
178 self.cvs_proxy_details.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy))
179
180 if self.configuration.same_proxy:
181 if self.http_proxy.get_text():
182 [w.set_text(self.http_proxy.get_text()) for w in self.same_proxy_addresses]
183 if self.http_proxy_port.get_text():
184 [w.set_text(self.http_proxy_port.get_text()) for w in self.same_proxy_ports]
185
186 def proxy_checkbox_toggled_cb(self, button):
187 self.configuration.enable_proxy = self.proxy_checkbox.get_active()
188 if not self.configuration.enable_proxy:
189 self.configuration.same_proxy = False
190 self.same_checkbox.set_active(self.configuration.same_proxy)
191 self.save_proxy_data()
192 self.refresh_proxy_components()
193
194 def same_checkbox_toggled_cb(self, button):
195 self.configuration.same_proxy = self.same_checkbox.get_active()
196 self.save_proxy_data()
197 self.refresh_proxy_components()
198
199 def save_proxy_data(self):
200 self.configuration.split_proxy("http", self.http_proxy.get_text() + ":" + self.http_proxy_port.get_text())
201 if self.configuration.same_proxy:
202 self.configuration.split_proxy("https", self.http_proxy.get_text() + ":" + self.http_proxy_port.get_text())
203 self.configuration.split_proxy("ftp", self.http_proxy.get_text() + ":" + self.http_proxy_port.get_text())
204 self.configuration.split_proxy("socks", self.http_proxy.get_text() + ":" + self.http_proxy_port.get_text())
205 self.configuration.split_proxy("cvs", self.http_proxy.get_text() + ":" + self.http_proxy_port.get_text())
206 else:
207 self.configuration.split_proxy("https", self.https_proxy.get_text() + ":" + self.https_proxy_port.get_text())
208 self.configuration.split_proxy("ftp", self.ftp_proxy.get_text() + ":" + self.ftp_proxy_port.get_text())
209 self.configuration.split_proxy("socks", self.socks_proxy.get_text() + ":" + self.socks_proxy_port.get_text())
210 self.configuration.split_proxy("cvs", self.cvs_proxy.get_text() + ":" + self.cvs_proxy_port.get_text())
211
212 def response_cb(self, dialog, response_id):
213 if response_id == gtk.RESPONSE_YES:
214 if self.proxy_checkbox.get_active():
215 # Check that all proxy entries have a corresponding port
216 for proxy, port in zip(self.all_proxy_addresses, self.all_proxy_ports):
217 if proxy.get_text() and not port.get_text():
218 lbl = "<b>Enter all port numbers</b>"
219 msg = "Proxy servers require a port number. Please make sure you have entered a port number for each proxy server."
220 dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_WARNING, msg)
221 button = dialog.add_button("Close", gtk.RESPONSE_OK)
222 HobButton.style_button(button)
223 response = dialog.run()
224 dialog.destroy()
225 self.emit_stop_by_name("response")
226 return
227
228 self.configuration.dldir = self.dldir_text.get_text()
229 self.configuration.sstatedir = self.sstatedir_text.get_text()
230 self.configuration.sstatemirror = ""
231 for mirror in self.sstatemirrors_list:
232 if mirror[1] != "" and mirror[2].startswith("file://"):
233 if mirror[1].endswith("\\1"):
234 smirror = mirror[2] + " " + mirror[1] + " \\n "
235 else:
236 smirror = mirror[2] + " " + mirror[1] + "\\1 \\n "
237 self.configuration.sstatemirror += smirror
238 self.configuration.bbthread = self.bb_spinner.get_value_as_int()
239 self.configuration.pmake = self.pmake_spinner.get_value_as_int()
240 self.save_proxy_data()
241 self.configuration.extra_setting = {}
242 it = self.setting_store.get_iter_first()
243 while it:
244 key = self.setting_store.get_value(it, 0)
245 value = self.setting_store.get_value(it, 1)
246 self.configuration.extra_setting[key] = value
247 it = self.setting_store.iter_next(it)
248
249 md5 = self.config_md5()
250 self.settings_changed = (self.md5 != md5)
251 self.proxy_settings_changed = (self.proxy_md5 != self.config_proxy_md5())
252
253 def create_build_environment_page(self):
254 advanced_vbox = gtk.VBox(False, 6)
255 advanced_vbox.set_border_width(6)
256
257 advanced_vbox.pack_start(self.gen_label_widget('<span weight="bold">Parallel threads</span>'), expand=False, fill=False)
258 sub_vbox = gtk.VBox(False, 6)
259 advanced_vbox.pack_start(sub_vbox, expand=False, fill=False)
260 label = self.gen_label_widget("BitBake parallel threads")
261 tooltip = "Sets the number of threads that BitBake tasks can simultaneously run. See the <a href=\""
262 tooltip += "http://www.yoctoproject.org/docs/current/poky-ref-manual/"
263 tooltip += "poky-ref-manual.html#var-BB_NUMBER_THREADS\">Poky reference manual</a> for information"
264 bbthread_widget, self.bb_spinner = self.gen_spinner_widget(self.configuration.bbthread, 1, self.max_threads,"<b>BitBake prallalel threads</b>" + "*" + tooltip)
265 sub_vbox.pack_start(label, expand=False, fill=False)
266 sub_vbox.pack_start(bbthread_widget, expand=False, fill=False)
267
268 sub_vbox = gtk.VBox(False, 6)
269 advanced_vbox.pack_start(sub_vbox, expand=False, fill=False)
270 label = self.gen_label_widget("Make parallel threads")
271 tooltip = "Sets the maximum number of threads the host can use during the build. See the <a href=\""
272 tooltip += "http://www.yoctoproject.org/docs/current/poky-ref-manual/"
273 tooltip += "poky-ref-manual.html#var-PARALLEL_MAKE\">Poky reference manual</a> for information"
274 pmake_widget, self.pmake_spinner = self.gen_spinner_widget(self.configuration.pmake, 1, self.max_threads,"<b>Make parallel threads</b>" + "*" + tooltip)
275 sub_vbox.pack_start(label, expand=False, fill=False)
276 sub_vbox.pack_start(pmake_widget, expand=False, fill=False)
277
278 advanced_vbox.pack_start(self.gen_label_widget('<span weight="bold">Downloaded source code</span>'), expand=False, fill=False)
279 sub_vbox = gtk.VBox(False, 6)
280 advanced_vbox.pack_start(sub_vbox, expand=False, fill=False)
281 label = self.gen_label_widget("Downloads directory")
282 tooltip = "Select a folder that caches the upstream project source code"
283 dldir_widget, self.dldir_text = self.gen_entry_widget(self.configuration.dldir, self,"<b>Downloaded source code</b>" + "*" + tooltip)
284 sub_vbox.pack_start(label, expand=False, fill=False)
285 sub_vbox.pack_start(dldir_widget, expand=False, fill=False)
286
287 return advanced_vbox
288
289 def create_shared_state_page(self):
290 advanced_vbox = gtk.VBox(False)
291 advanced_vbox.set_border_width(12)
292
293 sub_vbox = gtk.VBox(False)
294 advanced_vbox.pack_start(sub_vbox, expand=False, fill=False, padding=24)
295 content = "<span>Shared state directory</span>"
296 tooltip = "Select a folder that caches your prebuilt results"
297 label = self.gen_label_info_widget(content,"<b>Shared state directory</b>" + "*" + tooltip)
298 sstatedir_widget, self.sstatedir_text = self.gen_entry_widget(self.configuration.sstatedir, self)
299 sub_vbox.pack_start(label, expand=False, fill=False)
300 sub_vbox.pack_start(sstatedir_widget, expand=False, fill=False, padding=6)
301
302 content = "<span weight=\"bold\">Shared state mirrors</span>"
303 tooltip = "URLs pointing to pre-built mirrors that will speed your build. "
304 tooltip += "Select the \'Standard\' configuration if the structure of your "
305 tooltip += "mirror replicates the structure of your local shared state directory. "
306 tooltip += "For more information on shared state mirrors, check the <a href=\""
307 tooltip += "http://www.yoctoproject.org/docs/current/poky-ref-manual/"
308 tooltip += "poky-ref-manual.html#shared-state\">Yocto Project Reference Manual</a>."
309 table = self.gen_label_info_widget(content,"<b>Shared state mirrors</b>" + "*" + tooltip)
310 advanced_vbox.pack_start(table, expand=False, fill=False, padding=6)
311
312 sub_vbox = gtk.VBox(False)
313 advanced_vbox.pack_start(sub_vbox, gtk.TRUE, gtk.TRUE, 0)
314
315 if self.sstatemirrors_changed == 0:
316 self.sstatemirrors_changed = 1
317 sstatemirrors = self.configuration.sstatemirror
318 if sstatemirrors == "":
319 sm_list = ["Standard", "", "file://(.*)"]
320 self.sstatemirrors_list.append(sm_list)
321 else:
322 sstatemirrors = [x for x in sstatemirrors.split('\\n')]
323 for sstatemirror in sstatemirrors:
324 sstatemirror_fields = [x for x in sstatemirror.split(' ') if x.strip()]
325 if len(sstatemirror_fields) == 2:
326 if sstatemirror_fields[0] == "file://(.*)" or sstatemirror_fields[0] == "file://.*":
327 sm_list = ["Standard", sstatemirror_fields[1], sstatemirror_fields[0]]
328 else:
329 sm_list = ["Custom", sstatemirror_fields[1], sstatemirror_fields[0]]
330 self.sstatemirrors_list.append(sm_list)
331
332 sstatemirrors_widget, sstatemirrors_store = self.gen_shared_sstate_widget(self.sstatemirrors_list, self)
333 sub_vbox.pack_start(sstatemirrors_widget, expand=True, fill=True)
334
335 table = gtk.Table(1, 10, False)
336 table.set_col_spacings(6)
337 add_mirror_button = HobAltButton("Add mirror")
338 add_mirror_button.connect("clicked", self.add_mirror)
339 add_mirror_button.set_size_request(120,30)
340 table.attach(add_mirror_button, 1, 2, 0, 1, xoptions=gtk.SHRINK)
341
342 self.delete_button = HobAltButton("Delete mirror")
343 self.delete_button.connect("clicked", self.delete_cb)
344 self.delete_button.set_size_request(120, 30)
345 table.attach(self.delete_button, 3, 4, 0, 1, xoptions=gtk.SHRINK)
346
347 advanced_vbox.pack_start(table, expand=False, fill=False, padding=6)
348
349 return advanced_vbox
350
351 def gen_shared_sstate_widget(self, sstatemirrors_list, window):
352 hbox = gtk.HBox(False)
353
354 sstatemirrors_store = gtk.ListStore(str, str, str)
355 for sstatemirror in sstatemirrors_list:
356 sstatemirrors_store.append(sstatemirror)
357
358 self.sstatemirrors_tv = gtk.TreeView()
359 self.sstatemirrors_tv.set_rules_hint(True)
360 self.sstatemirrors_tv.set_headers_visible(True)
361 tree_selection = self.sstatemirrors_tv.get_selection()
362 tree_selection.set_mode(gtk.SELECTION_SINGLE)
363
364 # Allow enable drag and drop of rows including row move
365 self.sstatemirrors_tv.enable_model_drag_source( gtk.gdk.BUTTON1_MASK,
366 self.TARGETS,
367 gtk.gdk.ACTION_DEFAULT|
368 gtk.gdk.ACTION_MOVE)
369 self.sstatemirrors_tv.enable_model_drag_dest(self.TARGETS,
370 gtk.gdk.ACTION_DEFAULT)
371 self.sstatemirrors_tv.connect("drag_data_get", self.drag_data_get_cb)
372 self.sstatemirrors_tv.connect("drag_data_received", self.drag_data_received_cb)
373
374
375 self.scroll = gtk.ScrolledWindow()
376 self.scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
377 self.scroll.set_shadow_type(gtk.SHADOW_IN)
378 self.scroll.connect('size-allocate', self.scroll_changed)
379 self.scroll.add(self.sstatemirrors_tv)
380
381 #list store for cell renderer
382 m = gtk.ListStore(gobject.TYPE_STRING)
383 m.append(["Standard"])
384 m.append(["Custom"])
385
386 cell0 = gtk.CellRendererCombo()
387 cell0.set_property("model",m)
388 cell0.set_property("text-column", 0)
389 cell0.set_property("editable", True)
390 cell0.set_property("has-entry", False)
391 col0 = gtk.TreeViewColumn("Configuration")
392 col0.pack_start(cell0, False)
393 col0.add_attribute(cell0, "text", 0)
394 col0.set_cell_data_func(cell0, self.configuration_field)
395 self.sstatemirrors_tv.append_column(col0)
396
397 cell0.connect("edited", self.combo_changed, sstatemirrors_store)
398
399 self.cell1 = gtk.CellRendererText()
400 self.cell1.set_padding(5,2)
401 col1 = gtk.TreeViewColumn('Regex', self.cell1)
402 col1.set_cell_data_func(self.cell1, self.regex_field)
403 self.sstatemirrors_tv.append_column(col1)
404
405 self.cell1.connect("edited", self.regex_changed, sstatemirrors_store)
406
407 cell2 = gtk.CellRendererText()
408 cell2.set_padding(5,2)
409 cell2.set_property("editable", True)
410 col2 = gtk.TreeViewColumn('URL', cell2)
411 col2.set_cell_data_func(cell2, self.url_field)
412 self.sstatemirrors_tv.append_column(col2)
413
414 cell2.connect("edited", self.url_changed, sstatemirrors_store)
415
416 self.sstatemirrors_tv.set_model(sstatemirrors_store)
417 self.sstatemirrors_tv.set_cursor(self.selected_mirror_row)
418 hbox.pack_start(self.scroll, expand=True, fill=True)
419 hbox.show_all()
420
421 return hbox, sstatemirrors_store
422
423 def drag_data_get_cb(self, treeview, context, selection, target_id, etime):
424 treeselection = treeview.get_selection()
425 model, iter = treeselection.get_selected()
426 data = model.get_string_from_iter(iter)
427 selection.set(selection.target, 8, data)
428
429 def drag_data_received_cb(self, treeview, context, x, y, selection, info, etime):
430 model = treeview.get_model()
431 data = []
432 tree_iter = model.get_iter_from_string(selection.data)
433 data.append(model.get_value(tree_iter, 0))
434 data.append(model.get_value(tree_iter, 1))
435 data.append(model.get_value(tree_iter, 2))
436
437 drop_info = treeview.get_dest_row_at_pos(x, y)
438 if drop_info:
439 path, position = drop_info
440 iter = model.get_iter(path)
441 if (position == gtk.TREE_VIEW_DROP_BEFORE or position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE):
442 model.insert_before(iter, data)
443 else:
444 model.insert_after(iter, data)
445 else:
446 model.append(data)
447 if context.action == gtk.gdk.ACTION_MOVE:
448 context.finish(True, True, etime)
449 return
450
451 def delete_cb(self, button):
452 selection = self.sstatemirrors_tv.get_selection()
453 tree_model, tree_iter = selection.get_selected()
454 index = int(tree_model.get_string_from_iter(tree_iter))
455 if index == 0:
456 self.selected_mirror_row = index
457 else:
458 self.selected_mirror_row = index - 1
459 self.sstatemirrors_list.pop(index)
460 self.refresh_shared_state_page()
461 if not self.sstatemirrors_list:
462 self.delete_button.set_sensitive(False)
463
464 def add_mirror(self, button):
465 self.new_mirror = True
466 tooltip = "Select the pre-built mirror that will speed your build"
467 index = len(self.sstatemirrors_list)
468 self.selected_mirror_row = index
469 sm_list = ["Standard", "", "file://(.*)"]
470 self.sstatemirrors_list.append(sm_list)
471 self.refresh_shared_state_page()
472
473 def scroll_changed(self, widget, event, data=None):
474 if self.new_mirror == True:
475 adj = widget.get_vadjustment()
476 adj.set_value(adj.upper - adj.page_size)
477 self.new_mirror = False
478
479 def combo_changed(self, widget, path, text, model):
480 model[path][0] = text
481 selection = self.sstatemirrors_tv.get_selection()
482 tree_model, tree_iter = selection.get_selected()
483 index = int(tree_model.get_string_from_iter(tree_iter))
484 self.sstatemirrors_list[index][0] = text
485
486 def regex_changed(self, cell, path, new_text, user_data):
487 user_data[path][2] = new_text
488 selection = self.sstatemirrors_tv.get_selection()
489 tree_model, tree_iter = selection.get_selected()
490 index = int(tree_model.get_string_from_iter(tree_iter))
491 self.sstatemirrors_list[index][2] = new_text
492 return
493
494 def url_changed(self, cell, path, new_text, user_data):
495 if new_text!="Enter the mirror URL" and new_text!="Match regex and replace it with this URL":
496 user_data[path][1] = new_text
497 selection = self.sstatemirrors_tv.get_selection()
498 tree_model, tree_iter = selection.get_selected()
499 index = int(tree_model.get_string_from_iter(tree_iter))
500 self.sstatemirrors_list[index][1] = new_text
501 return
502
503 def configuration_field(self, column, cell, model, iter):
504 cell.set_property('text', model.get_value(iter, 0))
505 if model.get_value(iter, 0) == "Standard":
506 self.cell1.set_property("sensitive", False)
507 self.cell1.set_property("editable", False)
508 else:
509 self.cell1.set_property("sensitive", True)
510 self.cell1.set_property("editable", True)
511 return
512
513 def regex_field(self, column, cell, model, iter):
514 cell.set_property('text', model.get_value(iter, 2))
515 return
516
517 def url_field(self, column, cell, model, iter):
518 text = model.get_value(iter, 1)
519 if text == "":
520 if model.get_value(iter, 0) == "Standard":
521 text = "Enter the mirror URL"
522 else:
523 text = "Match regex and replace it with this URL"
524 cell.set_property('text', text)
525 return
526
527 def refresh_shared_state_page(self):
528 page_num = self.nb.get_current_page()
529 self.nb.remove_page(page_num);
530 self.nb.insert_page(self.create_shared_state_page(), gtk.Label("Shared state"),page_num)
531 self.show_all()
532 self.nb.set_current_page(page_num)
533
534 def test_proxy_ended(self, passed):
535 self.proxy_test_running = False
536 self.set_test_proxy_state(self.TEST_NETWORK_PASSED if passed else self.TEST_NETWORK_FAILED)
537 self.set_sensitive(True)
538 self.refresh_proxy_components()
539
540 def timer_func(self):
541 self.test_proxy_progress.pulse()
542 return self.proxy_test_running
543
544 def test_network_button_cb(self, b):
545 self.set_test_proxy_state(self.TEST_NETWORK_RUNNING)
546 self.set_sensitive(False)
547 self.save_proxy_data()
548 if self.configuration.enable_proxy == True:
549 self.handler.set_http_proxy(self.configuration.combine_proxy("http"))
550 self.handler.set_https_proxy(self.configuration.combine_proxy("https"))
551 self.handler.set_ftp_proxy(self.configuration.combine_proxy("ftp"))
552 self.handler.set_socks_proxy(self.configuration.combine_proxy("socks"))
553 self.handler.set_cvs_proxy(self.configuration.combine_host_only("cvs"), self.configuration.combine_port_only("cvs"))
554 elif self.configuration.enable_proxy == False:
555 self.handler.set_http_proxy("")
556 self.handler.set_https_proxy("")
557 self.handler.set_ftp_proxy("")
558 self.handler.set_socks_proxy("")
559 self.handler.set_cvs_proxy("", "")
560 self.proxy_test_ran = True
561 self.proxy_test_running = True
562 gobject.timeout_add(100, self.timer_func)
563 self.handler.trigger_network_test()
564
565 def test_proxy_focus_event(self, w, direction):
566 if self.test_proxy_state in [self.TEST_NETWORK_PASSED, self.TEST_NETWORK_FAILED]:
567 self.set_test_proxy_state(self.TEST_NETWORK_INITIAL)
568 return False
569
570 def http_proxy_changed(self, e):
571 if not self.configuration.same_proxy:
572 return
573 if e == self.http_proxy:
574 [w.set_text(self.http_proxy.get_text()) for w in self.same_proxy_addresses]
575 else:
576 [w.set_text(self.http_proxy_port.get_text()) for w in self.same_proxy_ports]
577
578 def proxy_address_focus_out_event(self, w, direction):
579 text = w.get_text()
580 if not text:
581 return False
582 if text.find("//") == -1:
583 w.set_text("http://" + text)
584 return False
585
586 def set_test_proxy_state(self, state):
587 if self.test_proxy_state == state:
588 return
589 [self.proxy_table.remove(w) for w in self.test_gui_elements]
590 if state == self.TEST_NETWORK_INITIAL:
591 self.proxy_table.attach(self.test_network_button, 1, 2, 5, 6)
592 self.test_network_button.show()
593 elif state == self.TEST_NETWORK_RUNNING:
594 self.test_proxy_progress.set_rcstyle("running")
595 self.test_proxy_progress.set_text("Testing network configuration")
596 self.proxy_table.attach(self.test_proxy_progress, 0, 5, 5, 6, xpadding=4)
597 self.test_proxy_progress.show()
598 else: # passed or failed
599 self.dummy_progress.update(1.0)
600 if state == self.TEST_NETWORK_PASSED:
601 self.dummy_progress.set_text("Your network is properly configured")
602 self.dummy_progress.set_rcstyle("running")
603 else:
604 self.dummy_progress.set_text("Network test failed")
605 self.dummy_progress.set_rcstyle("fail")
606 self.proxy_table.attach(self.dummy_progress, 0, 4, 5, 6)
607 self.proxy_table.attach(self.retest_network_button, 4, 5, 5, 6, xpadding=4)
608 self.dummy_progress.show()
609 self.retest_network_button.show()
610 self.test_proxy_state = state
611
612 def create_network_page(self):
613 advanced_vbox = gtk.VBox(False, 6)
614 advanced_vbox.set_border_width(6)
615 self.same_proxy_addresses = []
616 self.same_proxy_ports = []
617 self.all_proxy_ports = []
618 self.all_proxy_addresses = []
619
620 sub_vbox = gtk.VBox(False, 6)
621 advanced_vbox.pack_start(sub_vbox, expand=False, fill=False)
622 label = self.gen_label_widget("<span weight=\"bold\">Set the proxies used when fetching source code</span>")
623 tooltip = "Set the proxies used when fetching source code. A blank field uses a direct internet connection."
624 info = HobInfoButton("<span weight=\"bold\">Set the proxies used when fetching source code</span>" + "*" + tooltip, self)
625 hbox = gtk.HBox(False, 12)
626 hbox.pack_start(label, expand=True, fill=True)
627 hbox.pack_start(info, expand=False, fill=False)
628 sub_vbox.pack_start(hbox, expand=False, fill=False)
629
630 proxy_test_focus = []
631 self.direct_checkbox = gtk.RadioButton(None, "Direct network connection")
632 proxy_test_focus.append(self.direct_checkbox)
633 self.direct_checkbox.set_tooltip_text("Check this box to use a direct internet connection with no proxy")
634 self.direct_checkbox.set_active(not self.configuration.enable_proxy)
635 sub_vbox.pack_start(self.direct_checkbox, expand=False, fill=False)
636
637 self.proxy_checkbox = gtk.RadioButton(self.direct_checkbox, "Manual proxy configuration")
638 proxy_test_focus.append(self.proxy_checkbox)
639 self.proxy_checkbox.set_tooltip_text("Check this box to manually set up a specific proxy")
640 self.proxy_checkbox.set_active(self.configuration.enable_proxy)
641 sub_vbox.pack_start(self.proxy_checkbox, expand=False, fill=False)
642
643 self.same_checkbox = gtk.CheckButton("Use the HTTP proxy for all protocols")
644 proxy_test_focus.append(self.same_checkbox)
645 self.same_checkbox.set_tooltip_text("Check this box to use the HTTP proxy for all five proxies")
646 self.same_checkbox.set_active(self.configuration.same_proxy)
647 hbox = gtk.HBox(False, 12)
648 hbox.pack_start(self.same_checkbox, expand=False, fill=False, padding=24)
649 sub_vbox.pack_start(hbox, expand=False, fill=False)
650
651 self.proxy_table = gtk.Table(6, 5, False)
652 self.http_proxy, self.http_proxy_port, self.http_proxy_details = self.gen_proxy_entry_widget(
653 "http", self, True, 0)
654 proxy_test_focus +=[self.http_proxy, self.http_proxy_port]
655 self.http_proxy.connect("changed", self.http_proxy_changed)
656 self.http_proxy_port.connect("changed", self.http_proxy_changed)
657
658 self.https_proxy, self.https_proxy_port, self.https_proxy_details = self.gen_proxy_entry_widget(
659 "https", self, True, 1)
660 proxy_test_focus += [self.https_proxy, self.https_proxy_port]
661 self.same_proxy_addresses.append(self.https_proxy)
662 self.same_proxy_ports.append(self.https_proxy_port)
663
664 self.ftp_proxy, self.ftp_proxy_port, self.ftp_proxy_details = self.gen_proxy_entry_widget(
665 "ftp", self, True, 2)
666 proxy_test_focus += [self.ftp_proxy, self.ftp_proxy_port]
667 self.same_proxy_addresses.append(self.ftp_proxy)
668 self.same_proxy_ports.append(self.ftp_proxy_port)
669
670 self.socks_proxy, self.socks_proxy_port, self.socks_proxy_details = self.gen_proxy_entry_widget(
671 "socks", self, True, 3)
672 proxy_test_focus += [self.socks_proxy, self.socks_proxy_port]
673 self.same_proxy_addresses.append(self.socks_proxy)
674 self.same_proxy_ports.append(self.socks_proxy_port)
675
676 self.cvs_proxy, self.cvs_proxy_port, self.cvs_proxy_details = self.gen_proxy_entry_widget(
677 "cvs", self, True, 4)
678 proxy_test_focus += [self.cvs_proxy, self.cvs_proxy_port]
679 self.same_proxy_addresses.append(self.cvs_proxy)
680 self.same_proxy_ports.append(self.cvs_proxy_port)
681 self.all_proxy_ports = self.same_proxy_ports + [self.http_proxy_port]
682 self.all_proxy_addresses = self.same_proxy_addresses + [self.http_proxy]
683 sub_vbox.pack_start(self.proxy_table, expand=False, fill=False)
684 self.proxy_table.show_all()
685
686 # Create the graphical elements for the network test feature, but don't display them yet
687 self.test_network_button = HobAltButton("Test network configuration")
688 self.test_network_button.connect("clicked", self.test_network_button_cb)
689 self.test_proxy_progress = HobProgressBar()
690 self.dummy_progress = HobProgressBar()
691 self.retest_network_button = HobAltButton("Retest")
692 self.retest_network_button.connect("clicked", self.test_network_button_cb)
693 self.test_gui_elements = [self.test_network_button, self.test_proxy_progress, self.dummy_progress, self.retest_network_button]
694 # Initialize the network tester
695 self.test_proxy_state = self.TEST_NETWORK_NONE
696 self.set_test_proxy_state(self.TEST_NETWORK_INITIAL)
697 self.proxy_test_passed_id = self.handler.connect("network-passed", lambda h:self.test_proxy_ended(True))
698 self.proxy_test_failed_id = self.handler.connect("network-failed", lambda h:self.test_proxy_ended(False))
699 [w.connect("focus-in-event", self.test_proxy_focus_event) for w in proxy_test_focus]
700 [w.connect("focus-out-event", self.proxy_address_focus_out_event) for w in self.all_proxy_addresses]
701
702 self.direct_checkbox.connect("toggled", self.proxy_checkbox_toggled_cb)
703 self.proxy_checkbox.connect("toggled", self.proxy_checkbox_toggled_cb)
704 self.same_checkbox.connect("toggled", self.same_checkbox_toggled_cb)
705
706 self.refresh_proxy_components()
707 return advanced_vbox
708
709 def switch_to_page(self, page_id):
710 self.nb.set_current_page(page_id)
711
712 def details_cb(self, button, parent, protocol):
713 self.save_proxy_data()
714 dialog = ProxyDetailsDialog(title = protocol.upper() + " Proxy Details",
715 user = self.configuration.proxies[protocol][1],
716 passwd = self.configuration.proxies[protocol][2],
717 parent = parent,
718 flags = gtk.DIALOG_MODAL
719 | gtk.DIALOG_DESTROY_WITH_PARENT
720 | gtk.DIALOG_NO_SEPARATOR)
721 dialog.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_OK)
722 response = dialog.run()
723 if response == gtk.RESPONSE_OK:
724 self.configuration.proxies[protocol][1] = dialog.user
725 self.configuration.proxies[protocol][2] = dialog.passwd
726 self.refresh_proxy_components()
727 dialog.destroy()
728
729 def rootfs_combo_changed_cb(self, rootfs_combo, all_package_format, check_hbox):
730 combo_item = self.rootfs_combo.get_active_text()
731 for child in check_hbox.get_children():
732 if isinstance(child, gtk.CheckButton):
733 check_hbox.remove(child)
734 for format in all_package_format:
735 if format != combo_item:
736 check_button = gtk.CheckButton(format)
737 check_hbox.pack_start(check_button, expand=False, fill=False)
738 check_hbox.show_all()
739
740 def gen_pkgfmt_widget(self, curr_package_format, all_package_format, tooltip_combo="", tooltip_extra=""):
741 pkgfmt_hbox = gtk.HBox(False, 24)
742
743 rootfs_vbox = gtk.VBox(False, 6)
744 pkgfmt_hbox.pack_start(rootfs_vbox, expand=False, fill=False)
745
746 label = self.gen_label_widget("Root file system package format")
747 rootfs_vbox.pack_start(label, expand=False, fill=False)
748
749 rootfs_format = ""
750 if curr_package_format:
751 rootfs_format = curr_package_format.split()[0]
752
753 rootfs_format_widget, rootfs_combo = self.gen_combo_widget(rootfs_format, all_package_format, tooltip_combo)
754 rootfs_vbox.pack_start(rootfs_format_widget, expand=False, fill=False)
755
756 extra_vbox = gtk.VBox(False, 6)
757 pkgfmt_hbox.pack_start(extra_vbox, expand=False, fill=False)
758
759 label = self.gen_label_widget("Additional package formats")
760 extra_vbox.pack_start(label, expand=False, fill=False)
761
762 check_hbox = gtk.HBox(False, 12)
763 extra_vbox.pack_start(check_hbox, expand=False, fill=False)
764 for format in all_package_format:
765 if format != rootfs_format:
766 check_button = gtk.CheckButton(format)
767 is_active = (format in curr_package_format.split())
768 check_button.set_active(is_active)
769 check_hbox.pack_start(check_button, expand=False, fill=False)
770
771 info = HobInfoButton(tooltip_extra, self)
772 check_hbox.pack_end(info, expand=False, fill=False)
773
774 rootfs_combo.connect("changed", self.rootfs_combo_changed_cb, all_package_format, check_hbox)
775
776 pkgfmt_hbox.show_all()
777
778 return pkgfmt_hbox, rootfs_combo, check_hbox
779
780 def editable_settings_cell_edited(self, cell, path_string, new_text, model):
781 it = model.get_iter_from_string(path_string)
782 column = cell.get_data("column")
783 model.set(it, column, new_text)
784
785 def editable_settings_add_item_clicked(self, button, model):
786 new_item = ["##KEY##", "##VALUE##"]
787
788 iter = model.append()
789 model.set (iter,
790 0, new_item[0],
791 1, new_item[1],
792 )
793
794 def editable_settings_remove_item_clicked(self, button, treeview):
795 selection = treeview.get_selection()
796 model, iter = selection.get_selected()
797
798 if iter:
799 path = model.get_path(iter)[0]
800 model.remove(iter)
801
802 def gen_editable_settings(self, setting, tooltip=""):
803 setting_hbox = gtk.HBox(False, 12)
804
805 vbox = gtk.VBox(False, 12)
806 setting_hbox.pack_start(vbox, expand=True, fill=True)
807
808 setting_store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
809 for key in setting.keys():
810 setting_store.set(setting_store.append(), 0, key, 1, setting[key])
811
812 setting_tree = gtk.TreeView(setting_store)
813 setting_tree.set_headers_visible(True)
814 setting_tree.set_size_request(300, 100)
815
816 col = gtk.TreeViewColumn('Key')
817 col.set_min_width(100)
818 col.set_max_width(150)
819 col.set_resizable(True)
820 col1 = gtk.TreeViewColumn('Value')
821 col1.set_min_width(100)
822 col1.set_max_width(150)
823 col1.set_resizable(True)
824 setting_tree.append_column(col)
825 setting_tree.append_column(col1)
826 cell = gtk.CellRendererText()
827 cell.set_property('width-chars', 10)
828 cell.set_property('editable', True)
829 cell.set_data("column", 0)
830 cell.connect("edited", self.editable_settings_cell_edited, setting_store)
831 cell1 = gtk.CellRendererText()
832 cell1.set_property('width-chars', 10)
833 cell1.set_property('editable', True)
834 cell1.set_data("column", 1)
835 cell1.connect("edited", self.editable_settings_cell_edited, setting_store)
836 col.pack_start(cell, True)
837 col1.pack_end(cell1, True)
838 col.set_attributes(cell, text=0)
839 col1.set_attributes(cell1, text=1)
840
841 scroll = gtk.ScrolledWindow()
842 scroll.set_shadow_type(gtk.SHADOW_IN)
843 scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
844 scroll.add(setting_tree)
845 vbox.pack_start(scroll, expand=True, fill=True)
846
847 # some buttons
848 hbox = gtk.HBox(True, 6)
849 vbox.pack_start(hbox, False, False)
850
851 button = gtk.Button(stock=gtk.STOCK_ADD)
852 button.connect("clicked", self.editable_settings_add_item_clicked, setting_store)
853 hbox.pack_start(button)
854
855 button = gtk.Button(stock=gtk.STOCK_REMOVE)
856 button.connect("clicked", self.editable_settings_remove_item_clicked, setting_tree)
857 hbox.pack_start(button)
858
859 info = HobInfoButton(tooltip, self)
860 setting_hbox.pack_start(info, expand=False, fill=False)
861
862 return setting_hbox, setting_store
863
864 def create_others_page(self):
865 advanced_vbox = gtk.VBox(False, 6)
866 advanced_vbox.set_border_width(6)
867
868 sub_vbox = gtk.VBox(False, 6)
869 advanced_vbox.pack_start(sub_vbox, expand=True, fill=True)
870 label = self.gen_label_widget("<span weight=\"bold\">Add your own variables:</span>")
871 tooltip = "These are key/value pairs for your extra settings. Click \'Add\' and then directly edit the key and the value"
872 setting_widget, self.setting_store = self.gen_editable_settings(self.configuration.extra_setting,"<b>Add your own variables</b>" + "*" + tooltip)
873 sub_vbox.pack_start(label, expand=False, fill=False)
874 sub_vbox.pack_start(setting_widget, expand=True, fill=True)
875
876 return advanced_vbox
877
878 def create_visual_elements(self):
879 self.nb = gtk.Notebook()
880 self.nb.set_show_tabs(True)
881 self.nb.append_page(self.create_build_environment_page(), gtk.Label("Build environment"))
882 self.nb.append_page(self.create_shared_state_page(), gtk.Label("Shared state"))
883 self.nb.append_page(self.create_network_page(), gtk.Label("Network"))
884 self.nb.append_page(self.create_others_page(), gtk.Label("Others"))
885 self.nb.set_current_page(0)
886 self.vbox.pack_start(self.nb, expand=True, fill=True)
887 self.vbox.pack_end(gtk.HSeparator(), expand=True, fill=True)
888
889 self.show_all()
890
891 def destroy(self):
892 self.handler.disconnect(self.proxy_test_passed_id)
893 self.handler.disconnect(self.proxy_test_failed_id)
894 super(SimpleSettingsDialog, self).destroy()
diff --git a/bitbake/lib/bb/ui/crumbs/hobcolor.py b/bitbake/lib/bb/ui/crumbs/hobcolor.py
new file mode 100644
index 0000000000..3316542a20
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hobcolor.py
@@ -0,0 +1,38 @@
1#
2# BitBake Graphical GTK User Interface
3#
4# Copyright (C) 2012 Intel Corporation
5#
6# Authored by Shane Wang <shane.wang@intel.com>
7#
8# 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
10# published by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21class HobColors:
22 WHITE = "#ffffff"
23 PALE_GREEN = "#aaffaa"
24 ORANGE = "#eb8e68"
25 PALE_RED = "#ffaaaa"
26 GRAY = "#aaaaaa"
27 LIGHT_GRAY = "#dddddd"
28 SLIGHT_DARK = "#5f5f5f"
29 DARK = "#3c3b37"
30 BLACK = "#000000"
31 PALE_BLUE = "#53b8ff"
32 DEEP_RED = "#aa3e3e"
33 KHAKI = "#fff68f"
34
35 OK = WHITE
36 RUNNING = PALE_GREEN
37 WARNING = ORANGE
38 ERROR = PALE_RED
diff --git a/bitbake/lib/bb/ui/crumbs/hobeventhandler.py b/bitbake/lib/bb/ui/crumbs/hobeventhandler.py
new file mode 100644
index 0000000000..43edb70b08
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hobeventhandler.py
@@ -0,0 +1,639 @@
1#
2# BitBake Graphical GTK User Interface
3#
4# Copyright (C) 2011 Intel Corporation
5#
6# Authored by Joshua Lock <josh@linux.intel.com>
7# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
8#
9# This program is free software; you can redistribute it and/or modify
10# it under the terms of the GNU General Public License version 2 as
11# published by the Free Software Foundation.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License along
19# with this program; if not, write to the Free Software Foundation, Inc.,
20# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
22import gobject
23import logging
24import ast
25from bb.ui.crumbs.runningbuild import RunningBuild
26
27class HobHandler(gobject.GObject):
28
29 """
30 This object does BitBake event handling for the hob gui.
31 """
32 __gsignals__ = {
33 "package-formats-updated" : (gobject.SIGNAL_RUN_LAST,
34 gobject.TYPE_NONE,
35 (gobject.TYPE_PYOBJECT,)),
36 "config-updated" : (gobject.SIGNAL_RUN_LAST,
37 gobject.TYPE_NONE,
38 (gobject.TYPE_STRING, gobject.TYPE_PYOBJECT,)),
39 "command-succeeded" : (gobject.SIGNAL_RUN_LAST,
40 gobject.TYPE_NONE,
41 (gobject.TYPE_INT,)),
42 "command-failed" : (gobject.SIGNAL_RUN_LAST,
43 gobject.TYPE_NONE,
44 (gobject.TYPE_STRING,)),
45 "parsing-warning" : (gobject.SIGNAL_RUN_LAST,
46 gobject.TYPE_NONE,
47 (gobject.TYPE_STRING,)),
48 "sanity-failed" : (gobject.SIGNAL_RUN_LAST,
49 gobject.TYPE_NONE,
50 (gobject.TYPE_STRING, gobject.TYPE_INT)),
51 "generating-data" : (gobject.SIGNAL_RUN_LAST,
52 gobject.TYPE_NONE,
53 ()),
54 "data-generated" : (gobject.SIGNAL_RUN_LAST,
55 gobject.TYPE_NONE,
56 ()),
57 "parsing-started" : (gobject.SIGNAL_RUN_LAST,
58 gobject.TYPE_NONE,
59 (gobject.TYPE_PYOBJECT,)),
60 "parsing" : (gobject.SIGNAL_RUN_LAST,
61 gobject.TYPE_NONE,
62 (gobject.TYPE_PYOBJECT,)),
63 "parsing-completed" : (gobject.SIGNAL_RUN_LAST,
64 gobject.TYPE_NONE,
65 (gobject.TYPE_PYOBJECT,)),
66 "recipe-populated" : (gobject.SIGNAL_RUN_LAST,
67 gobject.TYPE_NONE,
68 ()),
69 "package-populated" : (gobject.SIGNAL_RUN_LAST,
70 gobject.TYPE_NONE,
71 ()),
72 "network-passed" : (gobject.SIGNAL_RUN_LAST,
73 gobject.TYPE_NONE,
74 ()),
75 "network-failed" : (gobject.SIGNAL_RUN_LAST,
76 gobject.TYPE_NONE,
77 ()),
78 }
79
80 (GENERATE_CONFIGURATION, GENERATE_RECIPES, GENERATE_PACKAGES, GENERATE_IMAGE, POPULATE_PACKAGEINFO, SANITY_CHECK, NETWORK_TEST) = range(7)
81 (SUB_PATH_LAYERS, SUB_FILES_DISTRO, SUB_FILES_MACH, SUB_FILES_SDKMACH, SUB_MATCH_CLASS, SUB_PARSE_CONFIG, SUB_SANITY_CHECK,
82 SUB_GNERATE_TGTS, SUB_GENERATE_PKGINFO, SUB_BUILD_RECIPES, SUB_BUILD_IMAGE, SUB_NETWORK_TEST) = range(12)
83
84 def __init__(self, server, recipe_model, package_model):
85 super(HobHandler, self).__init__()
86
87 self.build = RunningBuild(sequential=True)
88
89 self.recipe_model = recipe_model
90 self.package_model = package_model
91
92 self.commands_async = []
93 self.generating = False
94 self.current_phase = None
95 self.building = False
96 self.recipe_queue = []
97 self.package_queue = []
98
99 self.server = server
100 self.error_msg = ""
101 self.initcmd = None
102 self.parsing = False
103
104 def set_busy(self):
105 if not self.generating:
106 self.emit("generating-data")
107 self.generating = True
108
109 def clear_busy(self):
110 if self.generating:
111 self.emit("data-generated")
112 self.generating = False
113
114 def runCommand(self, commandline):
115 try:
116 result, error = self.server.runCommand(commandline)
117 if error:
118 raise Exception("Error running command '%s': %s" % (commandline, error))
119 return result
120 except Exception as e:
121 self.commands_async = []
122 self.clear_busy()
123 self.emit("command-failed", "Hob Exception - %s" % (str(e)))
124 return None
125
126 def run_next_command(self, initcmd=None):
127 if initcmd != None:
128 self.initcmd = initcmd
129
130 if self.commands_async:
131 self.set_busy()
132 next_command = self.commands_async.pop(0)
133 else:
134 self.clear_busy()
135 if self.initcmd != None:
136 self.emit("command-succeeded", self.initcmd)
137 return
138
139 if next_command == self.SUB_PATH_LAYERS:
140 self.runCommand(["findConfigFilePath", "bblayers.conf"])
141 elif next_command == self.SUB_FILES_DISTRO:
142 self.runCommand(["findConfigFiles", "DISTRO"])
143 elif next_command == self.SUB_FILES_MACH:
144 self.runCommand(["findConfigFiles", "MACHINE"])
145 elif next_command == self.SUB_FILES_SDKMACH:
146 self.runCommand(["findConfigFiles", "MACHINE-SDK"])
147 elif next_command == self.SUB_MATCH_CLASS:
148 self.runCommand(["findFilesMatchingInDir", "rootfs_", "classes"])
149 elif next_command == self.SUB_PARSE_CONFIG:
150 self.runCommand(["resetCooker"])
151 elif next_command == self.SUB_GNERATE_TGTS:
152 self.runCommand(["generateTargetsTree", "classes/image.bbclass", []])
153 elif next_command == self.SUB_GENERATE_PKGINFO:
154 self.runCommand(["triggerEvent", "bb.event.RequestPackageInfo()"])
155 elif next_command == self.SUB_SANITY_CHECK:
156 self.runCommand(["triggerEvent", "bb.event.SanityCheck()"])
157 elif next_command == self.SUB_NETWORK_TEST:
158 self.runCommand(["triggerEvent", "bb.event.NetworkTest()"])
159 elif next_command == self.SUB_BUILD_RECIPES:
160 self.clear_busy()
161 self.building = True
162 self.runCommand(["buildTargets", self.recipe_queue, self.default_task])
163 self.recipe_queue = []
164 elif next_command == self.SUB_BUILD_IMAGE:
165 self.clear_busy()
166 self.building = True
167 target = self.image
168
169 if self.base_image:
170 # Request the build of a custom image
171 self.generate_hob_base_image(target)
172 self.set_var_in_file("LINGUAS_INSTALL", "", "local.conf")
173 hobImage = self.runCommand(["matchFile", target + ".bb"])
174 if self.base_image != self.recipe_model.__custom_image__:
175 baseImage = self.runCommand(["matchFile", self.base_image + ".bb"])
176 version = self.runCommand(["generateNewImage", hobImage, baseImage, self.package_queue, True, ""])
177 target += version
178 self.recipe_model.set_custom_image_version(version)
179
180 targets = [target]
181 if self.toolchain_packages:
182 self.set_var_in_file("TOOLCHAIN_TARGET_TASK", " ".join(self.toolchain_packages), "local.conf")
183 targets.append(target + ":do_populate_sdk")
184
185 self.runCommand(["buildTargets", targets, self.default_task])
186
187 def display_error(self):
188 self.clear_busy()
189 self.emit("command-failed", self.error_msg)
190 self.error_msg = ""
191 if self.building:
192 self.building = False
193
194 def handle_event(self, event):
195 if not event:
196 return
197 if self.building:
198 self.current_phase = "building"
199 self.build.handle_event(event)
200
201 if isinstance(event, bb.event.PackageInfo):
202 self.package_model.populate(event._pkginfolist)
203 self.emit("package-populated")
204 self.run_next_command()
205
206 elif isinstance(event, bb.event.SanityCheckPassed):
207 reparse = self.runCommand(["getVariable", "BB_INVALIDCONF"]) or None
208 if reparse is True:
209 self.set_var_in_file("BB_INVALIDCONF", False, "local.conf")
210 self.runCommand(["setPrePostConfFiles", "conf/.hob.conf", ""])
211 self.commands_async.prepend(self.SUB_PARSE_CONFIG)
212 self.run_next_command()
213
214 elif isinstance(event, bb.event.SanityCheckFailed):
215 self.emit("sanity-failed", event._msg, event._network_error)
216
217 elif isinstance(event, logging.LogRecord):
218 if not self.building:
219 if event.levelno >= logging.ERROR:
220 formatter = bb.msg.BBLogFormatter()
221 msg = formatter.format(event)
222 self.error_msg += msg + '\n'
223 elif event.levelno >= logging.WARNING and self.parsing == True:
224 formatter = bb.msg.BBLogFormatter()
225 msg = formatter.format(event)
226 warn_msg = msg + '\n'
227 self.emit("parsing-warning", warn_msg)
228
229 elif isinstance(event, bb.event.TargetsTreeGenerated):
230 self.current_phase = "data generation"
231 if event._model:
232 self.recipe_model.populate(event._model)
233 self.emit("recipe-populated")
234 elif isinstance(event, bb.event.ConfigFilesFound):
235 self.current_phase = "configuration lookup"
236 var = event._variable
237 values = event._values
238 values.sort()
239 self.emit("config-updated", var, values)
240 elif isinstance(event, bb.event.ConfigFilePathFound):
241 self.current_phase = "configuration lookup"
242 elif isinstance(event, bb.event.FilesMatchingFound):
243 self.current_phase = "configuration lookup"
244 # FIXME: hard coding, should at least be a variable shared between
245 # here and the caller
246 if event._pattern == "rootfs_":
247 formats = []
248 for match in event._matches:
249 classname, sep, cls = match.rpartition(".")
250 fs, sep, format = classname.rpartition("_")
251 formats.append(format)
252 formats.sort()
253 self.emit("package-formats-updated", formats)
254 elif isinstance(event, bb.command.CommandCompleted):
255 self.current_phase = None
256 self.run_next_command()
257 elif isinstance(event, bb.command.CommandFailed):
258 if event.error not in ("Forced shutdown", "Stopped build"):
259 self.error_msg += event.error
260 self.commands_async = []
261 self.display_error()
262 elif isinstance(event, (bb.event.ParseStarted,
263 bb.event.CacheLoadStarted,
264 bb.event.TreeDataPreparationStarted,
265 )):
266 message = {}
267 message["eventname"] = bb.event.getName(event)
268 message["current"] = 0
269 message["total"] = None
270 message["title"] = "Parsing recipes"
271 self.emit("parsing-started", message)
272 if isinstance(event, bb.event.ParseStarted):
273 self.parsing = True
274 elif isinstance(event, (bb.event.ParseProgress,
275 bb.event.CacheLoadProgress,
276 bb.event.TreeDataPreparationProgress)):
277 message = {}
278 message["eventname"] = bb.event.getName(event)
279 message["current"] = event.current
280 message["total"] = event.total
281 message["title"] = "Parsing recipes"
282 self.emit("parsing", message)
283 elif isinstance(event, (bb.event.ParseCompleted,
284 bb.event.CacheLoadCompleted,
285 bb.event.TreeDataPreparationCompleted)):
286 message = {}
287 message["eventname"] = bb.event.getName(event)
288 message["current"] = event.total
289 message["total"] = event.total
290 message["title"] = "Parsing recipes"
291 self.emit("parsing-completed", message)
292 if isinstance(event, bb.event.ParseCompleted):
293 self.parsing = False
294 elif isinstance(event, bb.event.NetworkTestFailed):
295 self.emit("network-failed")
296 self.run_next_command()
297 elif isinstance(event, bb.event.NetworkTestPassed):
298 self.emit("network-passed")
299 self.run_next_command()
300
301 if self.error_msg and not self.commands_async:
302 self.display_error()
303
304 return
305
306 def init_cooker(self):
307 self.runCommand(["createConfigFile", ".hob.conf"])
308
309 def set_extra_inherit(self, bbclass):
310 self.append_var_in_file("INHERIT", bbclass, ".hob.conf")
311
312 def set_bblayers(self, bblayers):
313 self.set_var_in_file("BBLAYERS", " ".join(bblayers), "bblayers.conf")
314
315 def set_machine(self, machine):
316 if machine:
317 self.early_assign_var_in_file("MACHINE", machine, "local.conf")
318
319 def set_sdk_machine(self, sdk_machine):
320 self.set_var_in_file("SDKMACHINE", sdk_machine, "local.conf")
321
322 def set_image_fstypes(self, image_fstypes):
323 self.set_var_in_file("IMAGE_FSTYPES", image_fstypes, "local.conf")
324
325 def set_distro(self, distro):
326 self.set_var_in_file("DISTRO", distro, "local.conf")
327
328 def set_package_format(self, format):
329 package_classes = ""
330 for pkgfmt in format.split():
331 package_classes += ("package_%s" % pkgfmt + " ")
332 self.set_var_in_file("PACKAGE_CLASSES", package_classes, "local.conf")
333
334 def set_bbthreads(self, threads):
335 self.set_var_in_file("BB_NUMBER_THREADS", threads, "local.conf")
336
337 def set_pmake(self, threads):
338 pmake = "-j %s" % threads
339 self.set_var_in_file("PARALLEL_MAKE", pmake, "local.conf")
340
341 def set_dl_dir(self, directory):
342 self.set_var_in_file("DL_DIR", directory, "local.conf")
343
344 def set_sstate_dir(self, directory):
345 self.set_var_in_file("SSTATE_DIR", directory, "local.conf")
346
347 def set_sstate_mirrors(self, url):
348 self.set_var_in_file("SSTATE_MIRRORS", url, "local.conf")
349
350 def set_extra_size(self, image_extra_size):
351 self.set_var_in_file("IMAGE_ROOTFS_EXTRA_SPACE", str(image_extra_size), "local.conf")
352
353 def set_rootfs_size(self, image_rootfs_size):
354 self.set_var_in_file("IMAGE_ROOTFS_SIZE", str(image_rootfs_size), "local.conf")
355
356 def set_incompatible_license(self, incompat_license):
357 self.set_var_in_file("INCOMPATIBLE_LICENSE", incompat_license, "local.conf")
358
359 def set_extra_setting(self, extra_setting):
360 self.set_var_in_file("EXTRA_SETTING", extra_setting, "local.conf")
361
362 def set_extra_config(self, extra_setting):
363 old_extra_setting = self.runCommand(["getVariable", "EXTRA_SETTING"]) or {}
364 old_extra_setting = str(old_extra_setting)
365
366 old_extra_setting = ast.literal_eval(old_extra_setting)
367 if not type(old_extra_setting) == dict:
368 old_extra_setting = {}
369
370 # settings not changed
371 if old_extra_setting == extra_setting:
372 return
373
374 # remove the old EXTRA SETTING variable
375 self.remove_var_from_file("EXTRA_SETTING")
376
377 # remove old settings from conf
378 for key in old_extra_setting.keys():
379 if key not in extra_setting:
380 self.remove_var_from_file(key)
381
382 # add new settings
383 for key, value in extra_setting.iteritems():
384 self.set_var_in_file(key, value, "local.conf")
385
386 if extra_setting:
387 self.set_var_in_file("EXTRA_SETTING", extra_setting, "local.conf")
388
389 def set_http_proxy(self, http_proxy):
390 self.set_var_in_file("http_proxy", http_proxy, "local.conf")
391
392 def set_https_proxy(self, https_proxy):
393 self.set_var_in_file("https_proxy", https_proxy, "local.conf")
394
395 def set_ftp_proxy(self, ftp_proxy):
396 self.set_var_in_file("ftp_proxy", ftp_proxy, "local.conf")
397
398 def set_socks_proxy(self, socks_proxy):
399 self.set_var_in_file("all_proxy", socks_proxy, "local.conf")
400
401 def set_cvs_proxy(self, host, port):
402 self.set_var_in_file("CVS_PROXY_HOST", host, "local.conf")
403 self.set_var_in_file("CVS_PROXY_PORT", port, "local.conf")
404
405 def request_package_info(self):
406 self.commands_async.append(self.SUB_GENERATE_PKGINFO)
407 self.run_next_command(self.POPULATE_PACKAGEINFO)
408
409 def trigger_sanity_check(self):
410 self.commands_async.append(self.SUB_SANITY_CHECK)
411 self.run_next_command(self.SANITY_CHECK)
412
413 def trigger_network_test(self):
414 self.commands_async.append(self.SUB_NETWORK_TEST)
415 self.run_next_command(self.NETWORK_TEST)
416
417 def generate_configuration(self):
418 self.runCommand(["setPrePostConfFiles", "conf/.hob.conf", ""])
419 self.commands_async.append(self.SUB_PARSE_CONFIG)
420 self.commands_async.append(self.SUB_PATH_LAYERS)
421 self.commands_async.append(self.SUB_FILES_DISTRO)
422 self.commands_async.append(self.SUB_FILES_MACH)
423 self.commands_async.append(self.SUB_FILES_SDKMACH)
424 self.commands_async.append(self.SUB_MATCH_CLASS)
425 self.run_next_command(self.GENERATE_CONFIGURATION)
426
427 def generate_recipes(self):
428 self.runCommand(["setPrePostConfFiles", "conf/.hob.conf", ""])
429 self.commands_async.append(self.SUB_PARSE_CONFIG)
430 self.commands_async.append(self.SUB_GNERATE_TGTS)
431 self.run_next_command(self.GENERATE_RECIPES)
432
433 def generate_packages(self, tgts, default_task="build"):
434 targets = []
435 targets.extend(tgts)
436 self.recipe_queue = targets
437 self.default_task = default_task
438 self.runCommand(["setPrePostConfFiles", "conf/.hob.conf", ""])
439 self.commands_async.append(self.SUB_PARSE_CONFIG)
440 self.commands_async.append(self.SUB_BUILD_RECIPES)
441 self.run_next_command(self.GENERATE_PACKAGES)
442
443 def generate_image(self, image, base_image, image_packages=[], toolchain_packages=[], default_task="build"):
444 self.image = image
445 self.base_image = base_image
446 self.package_queue = image_packages
447 self.toolchain_packages = toolchain_packages
448 self.default_task = default_task
449 self.runCommand(["setPrePostConfFiles", "conf/.hob.conf", ""])
450 self.commands_async.append(self.SUB_PARSE_CONFIG)
451 self.commands_async.append(self.SUB_BUILD_IMAGE)
452 self.run_next_command(self.GENERATE_IMAGE)
453
454 def generate_new_image(self, image, base_image, package_queue, description):
455 if base_image:
456 base_image = self.runCommand(["matchFile", self.base_image + ".bb"])
457 self.runCommand(["generateNewImage", image, base_image, package_queue, False, description])
458
459 def generate_hob_base_image(self, hob_image):
460 image_dir = self.get_topdir() + "/recipes/images/"
461 recipe_name = hob_image + ".bb"
462 self.ensure_dir(image_dir)
463 self.generate_new_image(image_dir + recipe_name, None, [], "")
464
465 def ensure_dir(self, directory):
466 self.runCommand(["ensureDir", directory])
467
468 def build_succeeded_async(self):
469 self.building = False
470
471 def build_failed_async(self):
472 self.initcmd = None
473 self.commands_async = []
474 self.building = False
475
476 def cancel_parse(self):
477 self.runCommand(["stateForceShutdown"])
478
479 def cancel_build(self, force=False):
480 if force:
481 # Force the cooker to stop as quickly as possible
482 self.runCommand(["stateForceShutdown"])
483 else:
484 # Wait for tasks to complete before shutting down, this helps
485 # leave the workdir in a usable state
486 self.runCommand(["stateShutdown"])
487
488 def reset_build(self):
489 self.build.reset()
490
491 def get_logfile(self):
492 return self.server.runCommand(["getVariable", "BB_CONSOLELOG"])[0]
493
494 def get_topdir(self):
495 return self.runCommand(["getVariable", "TOPDIR"]) or ""
496
497 def _remove_redundant(self, string):
498 ret = []
499 for i in string.split():
500 if i not in ret:
501 ret.append(i)
502 return " ".join(ret)
503
504 def set_var_in_file(self, var, val, default_file=None):
505 self.runCommand(["enableDataTracking"])
506 self.server.runCommand(["setVarFile", var, val, default_file, "set"])
507 self.runCommand(["disableDataTracking"])
508
509 def early_assign_var_in_file(self, var, val, default_file=None):
510 self.runCommand(["enableDataTracking"])
511 self.server.runCommand(["setVarFile", var, val, default_file, "earlyAssign"])
512 self.runCommand(["disableDataTracking"])
513
514 def remove_var_from_file(self, var):
515 self.server.runCommand(["removeVarFile", var])
516
517 def append_var_in_file(self, var, val, default_file=None):
518 self.server.runCommand(["setVarFile", var, val, default_file, "append"])
519
520 def append_to_bbfiles(self, val):
521 bbfiles = self.runCommand(["getVariable", "BBFILES", "False"]) or ""
522 bbfiles = bbfiles.split()
523 if val not in bbfiles:
524 self.append_var_in_file("BBFILES", val, "bblayers.conf")
525
526 def get_parameters(self):
527 # retrieve the parameters from bitbake
528 params = {}
529 params["core_base"] = self.runCommand(["getVariable", "COREBASE"]) or ""
530 params["layer"] = self.runCommand(["getVariable", "BBLAYERS"]) or ""
531 params["layers_non_removable"] = self.runCommand(["getVariable", "BBLAYERS_NON_REMOVABLE"]) or ""
532 params["dldir"] = self.runCommand(["getVariable", "DL_DIR"]) or ""
533 params["machine"] = self.runCommand(["getVariable", "MACHINE"]) or ""
534 params["distro"] = self.runCommand(["getVariable", "DISTRO"]) or "defaultsetup"
535 params["pclass"] = self.runCommand(["getVariable", "PACKAGE_CLASSES"]) or ""
536 params["sstatedir"] = self.runCommand(["getVariable", "SSTATE_DIR"]) or ""
537 params["sstatemirror"] = self.runCommand(["getVariable", "SSTATE_MIRRORS"]) or ""
538
539 num_threads = self.runCommand(["getCpuCount"])
540 if not num_threads:
541 num_threads = 1
542 max_threads = 65536
543 else:
544 try:
545 num_threads = int(num_threads)
546 max_threads = 16 * num_threads
547 except:
548 num_threads = 1
549 max_threads = 65536
550 params["max_threads"] = max_threads
551
552 bbthread = self.runCommand(["getVariable", "BB_NUMBER_THREADS"])
553 if not bbthread:
554 bbthread = num_threads
555 else:
556 try:
557 bbthread = int(bbthread)
558 except:
559 bbthread = num_threads
560 params["bbthread"] = bbthread
561
562 pmake = self.runCommand(["getVariable", "PARALLEL_MAKE"])
563 if not pmake:
564 pmake = num_threads
565 elif isinstance(pmake, int):
566 pass
567 else:
568 try:
569 pmake = int(pmake.lstrip("-j "))
570 except:
571 pmake = num_threads
572 params["pmake"] = "-j %s" % pmake
573
574 params["image_addr"] = self.runCommand(["getVariable", "DEPLOY_DIR_IMAGE"]) or ""
575
576 image_extra_size = self.runCommand(["getVariable", "IMAGE_ROOTFS_EXTRA_SPACE"])
577 if not image_extra_size:
578 image_extra_size = 0
579 else:
580 try:
581 image_extra_size = int(image_extra_size)
582 except:
583 image_extra_size = 0
584 params["image_extra_size"] = image_extra_size
585
586 image_rootfs_size = self.runCommand(["getVariable", "IMAGE_ROOTFS_SIZE"])
587 if not image_rootfs_size:
588 image_rootfs_size = 0
589 else:
590 try:
591 image_rootfs_size = int(image_rootfs_size)
592 except:
593 image_rootfs_size = 0
594 params["image_rootfs_size"] = image_rootfs_size
595
596 image_overhead_factor = self.runCommand(["getVariable", "IMAGE_OVERHEAD_FACTOR"])
597 if not image_overhead_factor:
598 image_overhead_factor = 1
599 else:
600 try:
601 image_overhead_factor = float(image_overhead_factor)
602 except:
603 image_overhead_factor = 1
604 params['image_overhead_factor'] = image_overhead_factor
605
606 params["incompat_license"] = self._remove_redundant(self.runCommand(["getVariable", "INCOMPATIBLE_LICENSE"]) or "")
607 params["sdk_machine"] = self.runCommand(["getVariable", "SDKMACHINE"]) or self.runCommand(["getVariable", "SDK_ARCH"]) or ""
608
609 params["image_fstypes"] = self._remove_redundant(self.runCommand(["getVariable", "IMAGE_FSTYPES"]) or "")
610
611 params["image_types"] = self._remove_redundant(self.runCommand(["getVariable", "IMAGE_TYPES"]) or "")
612
613 params["conf_version"] = self.runCommand(["getVariable", "CONF_VERSION"]) or ""
614 params["lconf_version"] = self.runCommand(["getVariable", "LCONF_VERSION"]) or ""
615
616 params["runnable_image_types"] = self._remove_redundant(self.runCommand(["getVariable", "RUNNABLE_IMAGE_TYPES"]) or "")
617 params["runnable_machine_patterns"] = self._remove_redundant(self.runCommand(["getVariable", "RUNNABLE_MACHINE_PATTERNS"]) or "")
618 params["deployable_image_types"] = self._remove_redundant(self.runCommand(["getVariable", "DEPLOYABLE_IMAGE_TYPES"]) or "")
619 params["kernel_image_type"] = self.runCommand(["getVariable", "KERNEL_IMAGETYPE"]) or ""
620 params["tmpdir"] = self.runCommand(["getVariable", "TMPDIR"]) or ""
621 params["distro_version"] = self.runCommand(["getVariable", "DISTRO_VERSION"]) or ""
622 params["target_os"] = self.runCommand(["getVariable", "TARGET_OS"]) or ""
623 params["target_arch"] = self.runCommand(["getVariable", "TARGET_ARCH"]) or ""
624 params["tune_pkgarch"] = self.runCommand(["getVariable", "TUNE_PKGARCH"]) or ""
625 params["bb_version"] = self.runCommand(["getVariable", "BB_MIN_VERSION"]) or ""
626
627 params["default_task"] = self.runCommand(["getVariable", "BB_DEFAULT_TASK"]) or "build"
628
629 params["socks_proxy"] = self.runCommand(["getVariable", "all_proxy"]) or ""
630 params["http_proxy"] = self.runCommand(["getVariable", "http_proxy"]) or ""
631 params["ftp_proxy"] = self.runCommand(["getVariable", "ftp_proxy"]) or ""
632 params["https_proxy"] = self.runCommand(["getVariable", "https_proxy"]) or ""
633
634 params["cvs_proxy_host"] = self.runCommand(["getVariable", "CVS_PROXY_HOST"]) or ""
635 params["cvs_proxy_port"] = self.runCommand(["getVariable", "CVS_PROXY_PORT"]) or ""
636
637 params["image_white_pattern"] = self.runCommand(["getVariable", "BBUI_IMAGE_WHITE_PATTERN"]) or ""
638 params["image_black_pattern"] = self.runCommand(["getVariable", "BBUI_IMAGE_BLACK_PATTERN"]) or ""
639 return params
diff --git a/bitbake/lib/bb/ui/crumbs/hoblistmodel.py b/bitbake/lib/bb/ui/crumbs/hoblistmodel.py
new file mode 100644
index 0000000000..50df156f4d
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hoblistmodel.py
@@ -0,0 +1,903 @@
1#
2# BitBake Graphical GTK User Interface
3#
4# Copyright (C) 2011 Intel Corporation
5#
6# Authored by Joshua Lock <josh@linux.intel.com>
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
23import gtk
24import gobject
25from bb.ui.crumbs.hobpages import HobPage
26
27#
28# PackageListModel
29#
30class PackageListModel(gtk.ListStore):
31 """
32 This class defines an gtk.ListStore subclass which will convert the output
33 of the bb.event.TargetsTreeGenerated event into a gtk.ListStore whilst also
34 providing convenience functions to access gtk.TreeModel subclasses which
35 provide filtered views of the data.
36 """
37
38 (COL_NAME, COL_VER, COL_REV, COL_RNM, COL_SEC, COL_SUM, COL_RDEP, COL_RPROV, COL_SIZE, COL_RCP, COL_BINB, COL_INC, COL_FADE_INC, COL_FONT, COL_FLIST) = range(15)
39
40 __gsignals__ = {
41 "package-selection-changed" : (gobject.SIGNAL_RUN_LAST,
42 gobject.TYPE_NONE,
43 ()),
44 }
45
46 __toolchain_required_packages__ = ["packagegroup-core-standalone-sdk-target", "packagegroup-core-standalone-sdk-target-dbg"]
47
48 def __init__(self):
49 self.rprov_pkg = {}
50 gtk.ListStore.__init__ (self,
51 gobject.TYPE_STRING,
52 gobject.TYPE_STRING,
53 gobject.TYPE_STRING,
54 gobject.TYPE_STRING,
55 gobject.TYPE_STRING,
56 gobject.TYPE_STRING,
57 gobject.TYPE_STRING,
58 gobject.TYPE_STRING,
59 gobject.TYPE_STRING,
60 gobject.TYPE_STRING,
61 gobject.TYPE_STRING,
62 gobject.TYPE_BOOLEAN,
63 gobject.TYPE_BOOLEAN,
64 gobject.TYPE_STRING,
65 gobject.TYPE_STRING)
66 self.sort_column_id, self.sort_order = PackageListModel.COL_NAME, gtk.SORT_ASCENDING
67
68 """
69 Find the model path for the item_name
70 Returns the path in the model or None
71 """
72 def find_path_for_item(self, item_name):
73 pkg = item_name
74 if item_name not in self.pn_path.keys():
75 if item_name not in self.rprov_pkg.keys():
76 return None
77 pkg = self.rprov_pkg[item_name]
78 if pkg not in self.pn_path.keys():
79 return None
80
81 return self.pn_path[pkg]
82
83 def find_item_for_path(self, item_path):
84 return self[item_path][self.COL_NAME]
85
86 """
87 Helper function to determine whether an item is an item specified by filter
88 """
89 def tree_model_filter(self, model, it, filter):
90 name = model.get_value(it, self.COL_NAME)
91
92 for key in filter.keys():
93 if key == self.COL_NAME:
94 if filter[key] != 'Search packages by name':
95 if name and filter[key] not in name:
96 return False
97 else:
98 if model.get_value(it, key) not in filter[key]:
99 return False
100 self.filtered_nb += 1
101 return True
102
103 """
104 Create, if required, and return a filtered gtk.TreeModelSort
105 containing only the items specified by filter
106 """
107 def tree_model(self, filter, excluded_items_ahead=False, included_items_ahead=False, search_data=None, initial=False):
108 model = self.filter_new()
109 self.filtered_nb = 0
110 model.set_visible_func(self.tree_model_filter, filter)
111
112 sort = gtk.TreeModelSort(model)
113 sort.connect ('sort-column-changed', self.sort_column_changed_cb)
114 if initial:
115 sort.set_sort_column_id(PackageListModel.COL_NAME, gtk.SORT_ASCENDING)
116 sort.set_default_sort_func(None)
117 elif excluded_items_ahead:
118 sort.set_default_sort_func(self.exclude_item_sort_func, search_data)
119 elif included_items_ahead:
120 sort.set_default_sort_func(self.include_item_sort_func, search_data)
121 else:
122 if search_data and search_data!='Search recipes by name' and search_data!='Search package groups by name':
123 sort.set_default_sort_func(self.sort_func, search_data)
124 else:
125 sort.set_sort_column_id(self.sort_column_id, self.sort_order)
126 sort.set_default_sort_func(None)
127
128 sort.set_sort_func(PackageListModel.COL_INC, self.sort_column, PackageListModel.COL_INC)
129 sort.set_sort_func(PackageListModel.COL_SIZE, self.sort_column, PackageListModel.COL_SIZE)
130 sort.set_sort_func(PackageListModel.COL_BINB, self.sort_binb_column)
131 sort.set_sort_func(PackageListModel.COL_RCP, self.sort_column, PackageListModel.COL_RCP)
132 return sort
133
134 def sort_column_changed_cb (self, data):
135 self.sort_column_id, self.sort_order = data.get_sort_column_id ()
136
137 def sort_column(self, model, row1, row2, col):
138 value1 = model.get_value(row1, col)
139 value2 = model.get_value(row2, col)
140 if col==PackageListModel.COL_SIZE:
141 value1 = HobPage._string_to_size(value1)
142 value2 = HobPage._string_to_size(value2)
143
144 cmp_res = cmp(value1, value2)
145 if cmp_res!=0:
146 if col==PackageListModel.COL_INC:
147 return -cmp_res
148 else:
149 return cmp_res
150 else:
151 name1 = model.get_value(row1, PackageListModel.COL_NAME)
152 name2 = model.get_value(row2, PackageListModel.COL_NAME)
153 return cmp(name1,name2)
154
155 def sort_binb_column(self, model, row1, row2):
156 value1 = model.get_value(row1, PackageListModel.COL_BINB)
157 value2 = model.get_value(row2, PackageListModel.COL_BINB)
158 value1_list = value1.split(', ')
159 value2_list = value2.split(', ')
160
161 value1 = value1_list[0]
162 value2 = value2_list[0]
163
164 cmp_res = cmp(value1, value2)
165 if cmp_res==0:
166 cmp_size = cmp(len(value1_list), len(value2_list))
167 if cmp_size==0:
168 name1 = model.get_value(row1, PackageListModel.COL_NAME)
169 name2 = model.get_value(row2, PackageListModel.COL_NAME)
170 return cmp(name1,name2)
171 else:
172 return cmp_size
173 else:
174 return cmp_res
175
176 def exclude_item_sort_func(self, model, iter1, iter2, user_data=None):
177 if user_data:
178 val1 = model.get_value(iter1, PackageListModel.COL_NAME)
179 val2 = model.get_value(iter2, PackageListModel.COL_NAME)
180 return self.cmp_vals(val1, val2, user_data)
181 else:
182 val1 = model.get_value(iter1, PackageListModel.COL_FADE_INC)
183 val2 = model.get_value(iter2, PackageListModel.COL_INC)
184 return ((val1 == True) and (val2 == False))
185
186 def include_item_sort_func(self, model, iter1, iter2, user_data=None):
187 if user_data:
188 val1 = model.get_value(iter1, PackageListModel.COL_NAME)
189 val2 = model.get_value(iter2, PackageListModel.COL_NAME)
190 return self.cmp_vals(val1, val2, user_data)
191 else:
192 val1 = model.get_value(iter1, PackageListModel.COL_INC)
193 val2 = model.get_value(iter2, PackageListModel.COL_INC)
194 return ((val1 == False) and (val2 == True))
195
196 def sort_func(self, model, iter1, iter2, user_data):
197 val1 = model.get_value(iter1, PackageListModel.COL_NAME)
198 val2 = model.get_value(iter2, PackageListModel.COL_NAME)
199 return self.cmp_vals(val1, val2, user_data)
200
201 def cmp_vals(self, val1, val2, user_data):
202 if val1 is None or val2 is None:
203 return 0
204 elif val1.startswith(user_data) and not val2.startswith(user_data):
205 return -1
206 elif not val1.startswith(user_data) and val2.startswith(user_data):
207 return 1
208 else:
209 return cmp(val1, val2)
210
211 def convert_vpath_to_path(self, view_model, view_path):
212 # view_model is the model sorted
213 # get the path of the model filtered
214 filtered_model_path = view_model.convert_path_to_child_path(view_path)
215 # get the model filtered
216 filtered_model = view_model.get_model()
217 # get the path of the original model
218 path = filtered_model.convert_path_to_child_path(filtered_model_path)
219 return path
220
221 def convert_path_to_vpath(self, view_model, path):
222 it = view_model.get_iter_first()
223 while it:
224 name = self.find_item_for_path(path)
225 view_name = view_model.get_value(it, PackageListModel.COL_NAME)
226 if view_name == name:
227 view_path = view_model.get_path(it)
228 return view_path
229 it = view_model.iter_next(it)
230 return None
231
232 """
233 The populate() function takes as input the data from a
234 bb.event.PackageInfo event and populates the package list.
235 """
236 def populate(self, pkginfolist):
237 # First clear the model, in case repopulating
238 self.clear()
239
240 def getpkgvalue(pkgdict, key, pkgname, defaultval = None):
241 value = pkgdict.get('%s_%s' % (key, pkgname), None)
242 if not value:
243 value = pkgdict.get(key, defaultval)
244 return value
245
246 for pkginfo in pkginfolist:
247 pn = pkginfo['PN']
248 pv = pkginfo['PV']
249 pr = pkginfo['PR']
250 pkg = pkginfo['PKG']
251 pkgv = getpkgvalue(pkginfo, 'PKGV', pkg)
252 pkgr = getpkgvalue(pkginfo, 'PKGR', pkg)
253 # PKGSIZE is artificial, will always be overridden with the package name if present
254 pkgsize = int(pkginfo.get('PKGSIZE_%s' % pkg, "0"))
255 # PKG_%s is the renamed version
256 pkg_rename = pkginfo.get('PKG_%s' % pkg, "")
257 # The rest may be overridden or not
258 section = getpkgvalue(pkginfo, 'SECTION', pkg, "")
259 summary = getpkgvalue(pkginfo, 'SUMMARY', pkg, "")
260 rdep = getpkgvalue(pkginfo, 'RDEPENDS', pkg, "")
261 rrec = getpkgvalue(pkginfo, 'RRECOMMENDS', pkg, "")
262 rprov = getpkgvalue(pkginfo, 'RPROVIDES', pkg, "")
263 files_list = getpkgvalue(pkginfo, 'FILES_INFO', pkg, "")
264 for i in rprov.split():
265 self.rprov_pkg[i] = pkg
266
267 recipe = pn + '-' + pv + '-' + pr
268
269 allow_empty = getpkgvalue(pkginfo, 'ALLOW_EMPTY', pkg, "")
270
271 if pkgsize == 0 and not allow_empty:
272 continue
273
274 size = HobPage._size_to_string(pkgsize)
275 self.set(self.append(), self.COL_NAME, pkg, self.COL_VER, pkgv,
276 self.COL_REV, pkgr, self.COL_RNM, pkg_rename,
277 self.COL_SEC, section, self.COL_SUM, summary,
278 self.COL_RDEP, rdep + ' ' + rrec,
279 self.COL_RPROV, rprov, self.COL_SIZE, size,
280 self.COL_RCP, recipe, self.COL_BINB, "",
281 self.COL_INC, False, self.COL_FONT, '10', self.COL_FLIST, files_list)
282
283 self.pn_path = {}
284 it = self.get_iter_first()
285 while it:
286 pn = self.get_value(it, self.COL_NAME)
287 path = self.get_path(it)
288 self.pn_path[pn] = path
289 it = self.iter_next(it)
290
291 """
292 Update the model, send out the notification.
293 """
294 def selection_change_notification(self):
295 self.emit("package-selection-changed")
296
297 """
298 Check whether the item at item_path is included or not
299 """
300 def path_included(self, item_path):
301 return self[item_path][self.COL_INC]
302
303 """
304 Add this item, and any of its dependencies, to the image contents
305 """
306 def include_item(self, item_path, binb=""):
307 if self.path_included(item_path):
308 return
309
310 item_name = self[item_path][self.COL_NAME]
311 item_deps = self[item_path][self.COL_RDEP]
312
313 self[item_path][self.COL_INC] = True
314
315 item_bin = self[item_path][self.COL_BINB].split(', ')
316 if binb and not binb in item_bin:
317 item_bin.append(binb)
318 self[item_path][self.COL_BINB] = ', '.join(item_bin).lstrip(', ')
319
320 if item_deps:
321 # Ensure all of the items deps are included and, where appropriate,
322 # add this item to their COL_BINB
323 for dep in item_deps.split(" "):
324 if dep.startswith('('):
325 continue
326 # If the contents model doesn't already contain dep, add it
327 dep_path = self.find_path_for_item(dep)
328 if not dep_path:
329 continue
330 dep_included = self.path_included(dep_path)
331
332 if dep_included and not dep in item_bin:
333 # don't set the COL_BINB to this item if the target is an
334 # item in our own COL_BINB
335 dep_bin = self[dep_path][self.COL_BINB].split(', ')
336 if not item_name in dep_bin:
337 dep_bin.append(item_name)
338 self[dep_path][self.COL_BINB] = ', '.join(dep_bin).lstrip(', ')
339 elif not dep_included:
340 self.include_item(dep_path, binb=item_name)
341
342 def exclude_item(self, item_path):
343 if not self.path_included(item_path):
344 return
345
346 self[item_path][self.COL_INC] = False
347
348 item_name = self[item_path][self.COL_NAME]
349 item_deps = self[item_path][self.COL_RDEP]
350 if item_deps:
351 for dep in item_deps.split(" "):
352 if dep.startswith('('):
353 continue
354 dep_path = self.find_path_for_item(dep)
355 if not dep_path:
356 continue
357 dep_bin = self[dep_path][self.COL_BINB].split(', ')
358 if item_name in dep_bin:
359 dep_bin.remove(item_name)
360 self[dep_path][self.COL_BINB] = ', '.join(dep_bin).lstrip(', ')
361
362 item_bin = self[item_path][self.COL_BINB].split(', ')
363 if item_bin:
364 for binb in item_bin:
365 binb_path = self.find_path_for_item(binb)
366 if not binb_path:
367 continue
368 self.exclude_item(binb_path)
369
370 """
371 Empty self.contents by setting the include of each entry to None
372 """
373 def reset(self):
374 it = self.get_iter_first()
375 while it:
376 self.set(it,
377 self.COL_INC, False,
378 self.COL_BINB, "")
379 it = self.iter_next(it)
380
381 self.selection_change_notification()
382
383 def get_selected_packages(self):
384 packagelist = []
385
386 it = self.get_iter_first()
387 while it:
388 if self.get_value(it, self.COL_INC):
389 name = self.get_value(it, self.COL_NAME)
390 packagelist.append(name)
391 it = self.iter_next(it)
392
393 return packagelist
394
395 def get_user_selected_packages(self):
396 packagelist = []
397
398 it = self.get_iter_first()
399 while it:
400 if self.get_value(it, self.COL_INC):
401 binb = self.get_value(it, self.COL_BINB)
402 if binb == "User Selected":
403 name = self.get_value(it, self.COL_NAME)
404 packagelist.append(name)
405 it = self.iter_next(it)
406
407 return packagelist
408
409 def get_selected_packages_toolchain(self):
410 packagelist = []
411
412 it = self.get_iter_first()
413 while it:
414 if self.get_value(it, self.COL_INC):
415 name = self.get_value(it, self.COL_NAME)
416 if name.endswith("-dev") or name.endswith("-dbg"):
417 packagelist.append(name)
418 it = self.iter_next(it)
419
420 return list(set(packagelist + self.__toolchain_required_packages__));
421
422 """
423 Package model may be incomplete, therefore when calling the
424 set_selected_packages(), some packages will not be set included.
425 Return the un-set packages list.
426 """
427 def set_selected_packages(self, packagelist, user_selected=False):
428 left = []
429 binb = 'User Selected' if user_selected else ''
430 for pn in packagelist:
431 if pn in self.pn_path.keys():
432 path = self.pn_path[pn]
433 self.include_item(item_path=path, binb=binb)
434 else:
435 left.append(pn)
436
437 self.selection_change_notification()
438 return left
439
440 """
441 Return the selected package size, unit is B.
442 """
443 def get_packages_size(self):
444 packages_size = 0
445 it = self.get_iter_first()
446 while it:
447 if self.get_value(it, self.COL_INC):
448 str_size = self.get_value(it, self.COL_SIZE)
449 if not str_size:
450 continue
451
452 packages_size += HobPage._string_to_size(str_size)
453
454 it = self.iter_next(it)
455 return packages_size
456
457 """
458 Resync the state of included items to a backup column before performing the fadeout visible effect
459 """
460 def resync_fadeout_column(self, model_first_iter=None):
461 it = model_first_iter
462 while it:
463 active = self.get_value(it, self.COL_INC)
464 self.set(it, self.COL_FADE_INC, active)
465 it = self.iter_next(it)
466
467#
468# RecipeListModel
469#
470class RecipeListModel(gtk.ListStore):
471 """
472 This class defines an gtk.ListStore subclass which will convert the output
473 of the bb.event.TargetsTreeGenerated event into a gtk.ListStore whilst also
474 providing convenience functions to access gtk.TreeModel subclasses which
475 provide filtered views of the data.
476 """
477 (COL_NAME, COL_DESC, COL_LIC, COL_GROUP, COL_DEPS, COL_BINB, COL_TYPE, COL_INC, COL_IMG, COL_INSTALL, COL_PN, COL_FADE_INC, COL_SUMMARY, COL_VERSION,
478 COL_REVISION, COL_HOMEPAGE, COL_BUGTRACKER, COL_FILE) = range(18)
479
480 __custom_image__ = "Start with an empty image recipe"
481
482 __gsignals__ = {
483 "recipe-selection-changed" : (gobject.SIGNAL_RUN_LAST,
484 gobject.TYPE_NONE,
485 ()),
486 }
487
488 """
489 """
490 def __init__(self):
491 gtk.ListStore.__init__ (self,
492 gobject.TYPE_STRING,
493 gobject.TYPE_STRING,
494 gobject.TYPE_STRING,
495 gobject.TYPE_STRING,
496 gobject.TYPE_STRING,
497 gobject.TYPE_STRING,
498 gobject.TYPE_STRING,
499 gobject.TYPE_BOOLEAN,
500 gobject.TYPE_BOOLEAN,
501 gobject.TYPE_STRING,
502 gobject.TYPE_STRING,
503 gobject.TYPE_BOOLEAN,
504 gobject.TYPE_STRING,
505 gobject.TYPE_STRING,
506 gobject.TYPE_STRING,
507 gobject.TYPE_STRING,
508 gobject.TYPE_STRING,
509 gobject.TYPE_STRING)
510 self.sort_column_id, self.sort_order = RecipeListModel.COL_NAME, gtk.SORT_ASCENDING
511
512 """
513 Find the model path for the item_name
514 Returns the path in the model or None
515 """
516 def find_path_for_item(self, item_name):
517 if self.non_target_name(item_name) or item_name not in self.pn_path.keys():
518 return None
519 else:
520 return self.pn_path[item_name]
521
522 def find_item_for_path(self, item_path):
523 return self[item_path][self.COL_NAME]
524
525 """
526 Helper method to determine whether name is a target pn
527 """
528 def non_target_name(self, name):
529 if name and ('-native' in name):
530 return True
531 return False
532
533 """
534 Helper function to determine whether an item is an item specified by filter
535 """
536 def tree_model_filter(self, model, it, filter):
537 name = model.get_value(it, self.COL_NAME)
538 if self.non_target_name(name):
539 return False
540
541 for key in filter.keys():
542 if key == self.COL_NAME:
543 if filter[key] != 'Search recipes by name' and filter[key] != 'Search package groups by name':
544 if filter[key] not in name:
545 return False
546 else:
547 if model.get_value(it, key) not in filter[key]:
548 return False
549 self.filtered_nb += 1
550
551 return True
552
553 def exclude_item_sort_func(self, model, iter1, iter2, user_data=None):
554 if user_data:
555 val1 = model.get_value(iter1, RecipeListModel.COL_NAME)
556 val2 = model.get_value(iter2, RecipeListModel.COL_NAME)
557 return self.cmp_vals(val1, val2, user_data)
558 else:
559 val1 = model.get_value(iter1, RecipeListModel.COL_FADE_INC)
560 val2 = model.get_value(iter2, RecipeListModel.COL_INC)
561 return ((val1 == True) and (val2 == False))
562
563 def include_item_sort_func(self, model, iter1, iter2, user_data=None):
564 if user_data:
565 val1 = model.get_value(iter1, RecipeListModel.COL_NAME)
566 val2 = model.get_value(iter2, RecipeListModel.COL_NAME)
567 return self.cmp_vals(val1, val2, user_data)
568 else:
569 val1 = model.get_value(iter1, RecipeListModel.COL_INC)
570 val2 = model.get_value(iter2, RecipeListModel.COL_INC)
571 return ((val1 == False) and (val2 == True))
572
573 def sort_func(self, model, iter1, iter2, user_data):
574 val1 = model.get_value(iter1, RecipeListModel.COL_NAME)
575 val2 = model.get_value(iter2, RecipeListModel.COL_NAME)
576 return self.cmp_vals(val1, val2, user_data)
577
578 def cmp_vals(self, val1, val2, user_data):
579 if val1 is None or val2 is None:
580 return 0
581 elif val1.startswith(user_data) and not val2.startswith(user_data):
582 return -1
583 elif not val1.startswith(user_data) and val2.startswith(user_data):
584 return 1
585 else:
586 return cmp(val1, val2)
587
588 """
589 Create, if required, and return a filtered gtk.TreeModelSort
590 containing only the items specified by filter
591 """
592 def tree_model(self, filter, excluded_items_ahead=False, included_items_ahead=False, search_data=None, initial=False):
593 model = self.filter_new()
594 self.filtered_nb = 0
595 model.set_visible_func(self.tree_model_filter, filter)
596
597 sort = gtk.TreeModelSort(model)
598 sort.connect ('sort-column-changed', self.sort_column_changed_cb)
599 if initial:
600 sort.set_sort_column_id(RecipeListModel.COL_NAME, gtk.SORT_ASCENDING)
601 sort.set_default_sort_func(None)
602 elif excluded_items_ahead:
603 sort.set_default_sort_func(self.exclude_item_sort_func, search_data)
604 elif included_items_ahead:
605 sort.set_default_sort_func(self.include_item_sort_func, search_data)
606 else:
607 if search_data and search_data!='Search recipes by name' and search_data!='Search package groups by name':
608 sort.set_default_sort_func(self.sort_func, search_data)
609 else:
610 sort.set_sort_column_id(self.sort_column_id, self.sort_order)
611 sort.set_default_sort_func(None)
612
613 sort.set_sort_func(RecipeListModel.COL_INC, self.sort_column, RecipeListModel.COL_INC)
614 sort.set_sort_func(RecipeListModel.COL_GROUP, self.sort_column, RecipeListModel.COL_GROUP)
615 sort.set_sort_func(RecipeListModel.COL_BINB, self.sort_binb_column)
616 sort.set_sort_func(RecipeListModel.COL_LIC, self.sort_column, RecipeListModel.COL_LIC)
617 return sort
618
619 def sort_column_changed_cb (self, data):
620 self.sort_column_id, self.sort_order = data.get_sort_column_id ()
621
622 def sort_column(self, model, row1, row2, col):
623 value1 = model.get_value(row1, col)
624 value2 = model.get_value(row2, col)
625 cmp_res = cmp(value1, value2)
626 if cmp_res!=0:
627 if col==RecipeListModel.COL_INC:
628 return -cmp_res
629 else:
630 return cmp_res
631 else:
632 name1 = model.get_value(row1, RecipeListModel.COL_NAME)
633 name2 = model.get_value(row2, RecipeListModel.COL_NAME)
634 return cmp(name1,name2)
635
636 def sort_binb_column(self, model, row1, row2):
637 value1 = model.get_value(row1, RecipeListModel.COL_BINB)
638 value2 = model.get_value(row2, RecipeListModel.COL_BINB)
639 value1_list = value1.split(', ')
640 value2_list = value2.split(', ')
641
642 value1 = value1_list[0]
643 value2 = value2_list[0]
644
645 cmp_res = cmp(value1, value2)
646 if cmp_res==0:
647 cmp_size = cmp(len(value1_list), len(value2_list))
648 if cmp_size==0:
649 name1 = model.get_value(row1, RecipeListModel.COL_NAME)
650 name2 = model.get_value(row2, RecipeListModel.COL_NAME)
651 return cmp(name1,name2)
652 else:
653 return cmp_size
654 else:
655 return cmp_res
656
657 def convert_vpath_to_path(self, view_model, view_path):
658 filtered_model_path = view_model.convert_path_to_child_path(view_path)
659 filtered_model = view_model.get_model()
660
661 # get the path of the original model
662 path = filtered_model.convert_path_to_child_path(filtered_model_path)
663 return path
664
665 def convert_path_to_vpath(self, view_model, path):
666 it = view_model.get_iter_first()
667 while it:
668 name = self.find_item_for_path(path)
669 view_name = view_model.get_value(it, RecipeListModel.COL_NAME)
670 if view_name == name:
671 view_path = view_model.get_path(it)
672 return view_path
673 it = view_model.iter_next(it)
674 return None
675
676 """
677 The populate() function takes as input the data from a
678 bb.event.TargetsTreeGenerated event and populates the RecipeList.
679 """
680 def populate(self, event_model):
681 # First clear the model, in case repopulating
682 self.clear()
683
684 # dummy image for prompt
685 self.set_in_list(self.__custom_image__, "Use 'Edit image recipe' to customize recipes and packages " \
686 "to be included in your image ")
687
688 for item in event_model["pn"]:
689 name = item
690 desc = event_model["pn"][item]["description"]
691 lic = event_model["pn"][item]["license"]
692 group = event_model["pn"][item]["section"]
693 inherits = event_model["pn"][item]["inherits"]
694 summary = event_model["pn"][item]["summary"]
695 version = event_model["pn"][item]["version"]
696 revision = event_model["pn"][item]["prevision"]
697 homepage = event_model["pn"][item]["homepage"]
698 bugtracker = event_model["pn"][item]["bugtracker"]
699 filename = event_model["pn"][item]["filename"]
700 install = []
701
702 depends = event_model["depends"].get(item, []) + event_model["rdepends-pn"].get(item, [])
703
704 if ('packagegroup.bbclass' in " ".join(inherits)):
705 atype = 'packagegroup'
706 elif ('/image.bbclass' in " ".join(inherits)):
707 if "edited" not in name:
708 atype = 'image'
709 install = event_model["rdepends-pkg"].get(item, []) + event_model["rrecs-pkg"].get(item, [])
710 elif ('meta-' in name):
711 atype = 'toolchain'
712 elif (name == 'dummy-image' or name == 'dummy-toolchain'):
713 atype = 'dummy'
714 else:
715 atype = 'recipe'
716
717 self.set(self.append(), self.COL_NAME, item, self.COL_DESC, desc,
718 self.COL_LIC, lic, self.COL_GROUP, group,
719 self.COL_DEPS, " ".join(depends), self.COL_BINB, "",
720 self.COL_TYPE, atype, self.COL_INC, False,
721 self.COL_IMG, False, self.COL_INSTALL, " ".join(install), self.COL_PN, item,
722 self.COL_SUMMARY, summary, self.COL_VERSION, version, self.COL_REVISION, revision,
723 self.COL_HOMEPAGE, homepage, self.COL_BUGTRACKER, bugtracker,
724 self.COL_FILE, filename)
725
726 self.pn_path = {}
727 it = self.get_iter_first()
728 while it:
729 pn = self.get_value(it, self.COL_NAME)
730 path = self.get_path(it)
731 self.pn_path[pn] = path
732 it = self.iter_next(it)
733
734 def set_in_list(self, item, desc):
735 self.set(self.append(), self.COL_NAME, item,
736 self.COL_DESC, desc,
737 self.COL_LIC, "", self.COL_GROUP, "",
738 self.COL_DEPS, "", self.COL_BINB, "",
739 self.COL_TYPE, "image", self.COL_INC, False,
740 self.COL_IMG, False, self.COL_INSTALL, "", self.COL_PN, item,
741 self.COL_SUMMARY, "", self.COL_VERSION, "", self.COL_REVISION, "",
742 self.COL_HOMEPAGE, "", self.COL_BUGTRACKER, "")
743 self.pn_path = {}
744 it = self.get_iter_first()
745 while it:
746 pn = self.get_value(it, self.COL_NAME)
747 path = self.get_path(it)
748 self.pn_path[pn] = path
749 it = self.iter_next(it)
750
751 """
752 Update the model, send out the notification.
753 """
754 def selection_change_notification(self):
755 self.emit("recipe-selection-changed")
756
757 def path_included(self, item_path):
758 return self[item_path][self.COL_INC]
759
760 """
761 Add this item, and any of its dependencies, to the image contents
762 """
763 def include_item(self, item_path, binb="", image_contents=False):
764 if self.path_included(item_path):
765 return
766
767 item_name = self[item_path][self.COL_NAME]
768 item_deps = self[item_path][self.COL_DEPS]
769
770 self[item_path][self.COL_INC] = True
771
772 item_bin = self[item_path][self.COL_BINB].split(', ')
773 if binb and not binb in item_bin:
774 item_bin.append(binb)
775 self[item_path][self.COL_BINB] = ', '.join(item_bin).lstrip(', ')
776
777 # We want to do some magic with things which are brought in by the
778 # base image so tag them as so
779 if image_contents:
780 self[item_path][self.COL_IMG] = True
781
782 if item_deps:
783 # Ensure all of the items deps are included and, where appropriate,
784 # add this item to their COL_BINB
785 for dep in item_deps.split(" "):
786 # If the contents model doesn't already contain dep, add it
787 dep_path = self.find_path_for_item(dep)
788 if not dep_path:
789 continue
790 dep_included = self.path_included(dep_path)
791
792 if dep_included and not dep in item_bin:
793 # don't set the COL_BINB to this item if the target is an
794 # item in our own COL_BINB
795 dep_bin = self[dep_path][self.COL_BINB].split(', ')
796 if not item_name in dep_bin:
797 dep_bin.append(item_name)
798 self[dep_path][self.COL_BINB] = ', '.join(dep_bin).lstrip(', ')
799 elif not dep_included:
800 self.include_item(dep_path, binb=item_name, image_contents=image_contents)
801 dep_bin = self[item_path][self.COL_BINB].split(', ')
802 if self[item_path][self.COL_NAME] in dep_bin:
803 dep_bin.remove(self[item_path][self.COL_NAME])
804 self[item_path][self.COL_BINB] = ', '.join(dep_bin).lstrip(', ')
805
806 def exclude_item(self, item_path):
807 if not self.path_included(item_path):
808 return
809
810 self[item_path][self.COL_INC] = False
811
812 item_name = self[item_path][self.COL_NAME]
813 item_deps = self[item_path][self.COL_DEPS]
814 if item_deps:
815 for dep in item_deps.split(" "):
816 dep_path = self.find_path_for_item(dep)
817 if not dep_path:
818 continue
819 dep_bin = self[dep_path][self.COL_BINB].split(', ')
820 if item_name in dep_bin:
821 dep_bin.remove(item_name)
822 self[dep_path][self.COL_BINB] = ', '.join(dep_bin).lstrip(', ')
823
824 item_bin = self[item_path][self.COL_BINB].split(', ')
825 if item_bin:
826 for binb in item_bin:
827 binb_path = self.find_path_for_item(binb)
828 if not binb_path:
829 continue
830 self.exclude_item(binb_path)
831
832 def reset(self):
833 it = self.get_iter_first()
834 while it:
835 self.set(it,
836 self.COL_INC, False,
837 self.COL_BINB, "",
838 self.COL_IMG, False)
839 it = self.iter_next(it)
840
841 self.selection_change_notification()
842
843 """
844 Returns two lists. One of user selected recipes and the other containing
845 all selected recipes
846 """
847 def get_selected_recipes(self):
848 allrecipes = []
849 userrecipes = []
850
851 it = self.get_iter_first()
852 while it:
853 if self.get_value(it, self.COL_INC):
854 name = self.get_value(it, self.COL_PN)
855 type = self.get_value(it, self.COL_TYPE)
856 if type != "image":
857 allrecipes.append(name)
858 sel = "User Selected" in self.get_value(it, self.COL_BINB)
859 if sel:
860 userrecipes.append(name)
861 it = self.iter_next(it)
862
863 return list(set(userrecipes)), list(set(allrecipes))
864
865 def set_selected_recipes(self, recipelist):
866 for pn in recipelist:
867 if pn in self.pn_path.keys():
868 path = self.pn_path[pn]
869 self.include_item(item_path=path,
870 binb="User Selected")
871 self.selection_change_notification()
872
873 def get_selected_image(self):
874 it = self.get_iter_first()
875 while it:
876 if self.get_value(it, self.COL_INC):
877 name = self.get_value(it, self.COL_PN)
878 type = self.get_value(it, self.COL_TYPE)
879 if type == "image":
880 sel = "User Selected" in self.get_value(it, self.COL_BINB)
881 if sel:
882 return name
883 it = self.iter_next(it)
884 return None
885
886 def set_selected_image(self, img):
887 if not img:
888 return
889 self.reset()
890 path = self.find_path_for_item(img)
891 self.include_item(item_path=path,
892 binb="User Selected",
893 image_contents=True)
894 self.selection_change_notification()
895
896 def set_custom_image_version(self, version):
897 self.custom_image_version = version
898
899 def get_custom_image_version(self):
900 return self.custom_image_version
901
902 def is_custom_image(self):
903 return self.get_selected_image() == self.__custom_image__
diff --git a/bitbake/lib/bb/ui/crumbs/hobpages.py b/bitbake/lib/bb/ui/crumbs/hobpages.py
new file mode 100755
index 0000000000..0fd3598c3a
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hobpages.py
@@ -0,0 +1,128 @@
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
23import gtk
24from bb.ui.crumbs.hobcolor import HobColors
25from bb.ui.crumbs.hobwidget import hwc
26
27#
28# HobPage: the super class for all Hob-related pages
29#
30class HobPage (gtk.VBox):
31
32 def __init__(self, builder, title = None):
33 super(HobPage, self).__init__(False, 0)
34 self.builder = builder
35 self.builder_width, self.builder_height = self.builder.size_request()
36
37 if not title:
38 self.title = "Hob -- Image Creator"
39 else:
40 self.title = title
41 self.title_label = gtk.Label()
42
43 self.box_group_area = gtk.VBox(False, 12)
44 self.box_group_area.set_size_request(self.builder_width - 73 - 73, self.builder_height - 88 - 15 - 15)
45 self.group_align = gtk.Alignment(xalign = 0, yalign=0.5, xscale=1, yscale=1)
46 self.group_align.set_padding(15, 15, 73, 73)
47 self.group_align.add(self.box_group_area)
48 self.box_group_area.set_homogeneous(False)
49
50 def set_title(self, title):
51 self.title = title
52 self.title_label.set_markup("<span size='x-large'>%s</span>" % self.title)
53
54 def add_onto_top_bar(self, widget = None, padding = 0):
55 # the top button occupies 1/7 of the page height
56 # setup an event box
57 eventbox = gtk.EventBox()
58 style = eventbox.get_style().copy()
59 style.bg[gtk.STATE_NORMAL] = eventbox.get_colormap().alloc_color(HobColors.LIGHT_GRAY, False, False)
60 eventbox.set_style(style)
61 eventbox.set_size_request(-1, 88)
62
63 hbox = gtk.HBox()
64
65 self.title_label = gtk.Label()
66 self.title_label.set_markup("<span size='x-large'>%s</span>" % self.title)
67 hbox.pack_start(self.title_label, expand=False, fill=False, padding=20)
68
69 if widget:
70 # add the widget in the event box
71 hbox.pack_end(widget, expand=False, fill=False, padding=padding)
72 eventbox.add(hbox)
73
74 return eventbox
75
76 def span_tag(self, size="medium", weight="normal", forground="#1c1c1c"):
77 span_tag = "weight='%s' foreground='%s' size='%s'" % (weight, forground, size)
78 return span_tag
79
80 def append_toolbar_button(self, toolbar, buttonname, icon_disp, icon_hovor, tip, cb):
81 # Create a button and append it on the toolbar according to button name
82 icon = gtk.Image()
83 icon_display = icon_disp
84 icon_hover = icon_hovor
85 pix_buffer = gtk.gdk.pixbuf_new_from_file(icon_display)
86 icon.set_from_pixbuf(pix_buffer)
87 tip_text = tip
88 button = toolbar.append_item(buttonname, tip, None, icon, cb)
89 return button
90
91 @staticmethod
92 def _size_to_string(size):
93 try:
94 if not size:
95 size_str = "0 B"
96 else:
97 if len(str(int(size))) > 6:
98 size_str = '%.1f' % (size*1.0/(1024*1024)) + ' MB'
99 elif len(str(int(size))) > 3:
100 size_str = '%.1f' % (size*1.0/1024) + ' KB'
101 else:
102 size_str = str(size) + ' B'
103 except:
104 size_str = "0 B"
105 return size_str
106
107 @staticmethod
108 def _string_to_size(str_size):
109 try:
110 if not str_size:
111 size = 0
112 else:
113 unit = str_size.split()
114 if len(unit) > 1:
115 if unit[1] == 'MB':
116 size = float(unit[0])*1024*1024
117 elif unit[1] == 'KB':
118 size = float(unit[0])*1024
119 elif unit[1] == 'B':
120 size = float(unit[0])
121 else:
122 size = 0
123 else:
124 size = float(unit[0])
125 except:
126 size = 0
127 return size
128
diff --git a/bitbake/lib/bb/ui/crumbs/hobwidget.py b/bitbake/lib/bb/ui/crumbs/hobwidget.py
new file mode 100644
index 0000000000..2b969c146e
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hobwidget.py
@@ -0,0 +1,904 @@
1# BitBake Graphical GTK User Interface
2#
3# Copyright (C) 2011-2012 Intel Corporation
4#
5# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
6# Authored by Shane Wang <shane.wang@intel.com>
7#
8# 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
10# published by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20import gtk
21import gobject
22import os
23import os.path
24import sys
25import pango, pangocairo
26import cairo
27import math
28
29from bb.ui.crumbs.hobcolor import HobColors
30from bb.ui.crumbs.persistenttooltip import PersistentTooltip
31
32class hwc:
33
34 MAIN_WIN_WIDTH = 1024
35 MAIN_WIN_HEIGHT = 700
36
37class hic:
38
39 HOB_ICON_BASE_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), ("ui/icons/"))
40
41 ICON_RCIPE_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('recipe/recipe_display.png'))
42 ICON_RCIPE_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('recipe/recipe_hover.png'))
43 ICON_PACKAGES_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('packages/packages_display.png'))
44 ICON_PACKAGES_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('packages/packages_hover.png'))
45 ICON_LAYERS_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('layers/layers_display.png'))
46 ICON_LAYERS_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('layers/layers_hover.png'))
47 ICON_IMAGES_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('images/images_display.png'))
48 ICON_IMAGES_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('images/images_hover.png'))
49 ICON_SETTINGS_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('settings/settings_display.png'))
50 ICON_SETTINGS_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('settings/settings_hover.png'))
51 ICON_INFO_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('info/info_display.png'))
52 ICON_INFO_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('info/info_hover.png'))
53 ICON_INDI_CONFIRM_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/confirmation.png'))
54 ICON_INDI_ERROR_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/denied.png'))
55 ICON_INDI_REMOVE_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/remove.png'))
56 ICON_INDI_REMOVE_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/remove-hover.png'))
57 ICON_INDI_ADD_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/add.png'))
58 ICON_INDI_ADD_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/add-hover.png'))
59 ICON_INDI_REFRESH_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/refresh.png'))
60 ICON_INDI_ALERT_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/alert.png'))
61 ICON_INDI_TICK_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/tick.png'))
62 ICON_INDI_INFO_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/info.png'))
63
64class HobViewTable (gtk.VBox):
65 """
66 A VBox to contain the table for different recipe views and package view
67 """
68 __gsignals__ = {
69 "toggled" : (gobject.SIGNAL_RUN_LAST,
70 gobject.TYPE_NONE,
71 (gobject.TYPE_PYOBJECT,
72 gobject.TYPE_STRING,
73 gobject.TYPE_INT,
74 gobject.TYPE_PYOBJECT,)),
75 "row-activated" : (gobject.SIGNAL_RUN_LAST,
76 gobject.TYPE_NONE,
77 (gobject.TYPE_PYOBJECT,
78 gobject.TYPE_PYOBJECT,)),
79 "cell-fadeinout-stopped" : (gobject.SIGNAL_RUN_LAST,
80 gobject.TYPE_NONE,
81 (gobject.TYPE_PYOBJECT,
82 gobject.TYPE_PYOBJECT,
83 gobject.TYPE_PYOBJECT,)),
84 }
85
86 def __init__(self, columns, name):
87 gtk.VBox.__init__(self, False, 6)
88 self.table_tree = gtk.TreeView()
89 self.table_tree.set_headers_visible(True)
90 self.table_tree.set_headers_clickable(True)
91 self.table_tree.set_rules_hint(True)
92 self.table_tree.set_enable_tree_lines(True)
93 self.table_tree.get_selection().set_mode(gtk.SELECTION_SINGLE)
94 self.toggle_columns = []
95 self.table_tree.connect("row-activated", self.row_activated_cb)
96 self.top_bar = None
97 self.tab_name = name
98
99 for i, column in enumerate(columns):
100 col_name = column['col_name']
101 col = gtk.TreeViewColumn(col_name)
102 col.set_clickable(True)
103 col.set_resizable(True)
104 if self.tab_name.startswith('Included'):
105 if col_name!='Included':
106 col.set_sort_column_id(column['col_id'])
107 else:
108 col.set_sort_column_id(column['col_id'])
109 if 'col_min' in column.keys():
110 col.set_min_width(column['col_min'])
111 if 'col_max' in column.keys():
112 col.set_max_width(column['col_max'])
113 if 'expand' in column.keys():
114 col.set_expand(True)
115 self.table_tree.append_column(col)
116
117 if (not 'col_style' in column.keys()) or column['col_style'] == 'text':
118 cell = gtk.CellRendererText()
119 col.pack_start(cell, True)
120 col.set_attributes(cell, text=column['col_id'])
121 if 'col_t_id' in column.keys():
122 col.add_attribute(cell, 'font', column['col_t_id'])
123 elif column['col_style'] == 'check toggle':
124 cell = HobCellRendererToggle()
125 cell.set_property('activatable', True)
126 cell.connect("toggled", self.toggled_cb, i, self.table_tree)
127 cell.connect_render_state_changed(self.stop_cell_fadeinout_cb, self.table_tree)
128 self.toggle_id = i
129 col.pack_end(cell, True)
130 col.set_attributes(cell, active=column['col_id'])
131 self.toggle_columns.append(col_name)
132 if 'col_group' in column.keys():
133 col.set_cell_data_func(cell, self.set_group_number_cb)
134 elif column['col_style'] == 'radio toggle':
135 cell = gtk.CellRendererToggle()
136 cell.set_property('activatable', True)
137 cell.set_radio(True)
138 cell.connect("toggled", self.toggled_cb, i, self.table_tree)
139 self.toggle_id = i
140 col.pack_end(cell, True)
141 col.set_attributes(cell, active=column['col_id'])
142 self.toggle_columns.append(col_name)
143 elif column['col_style'] == 'binb':
144 cell = gtk.CellRendererText()
145 col.pack_start(cell, True)
146 col.set_cell_data_func(cell, self.display_binb_cb, column['col_id'])
147 if 'col_t_id' in column.keys():
148 col.add_attribute(cell, 'font', column['col_t_id'])
149
150 self.scroll = gtk.ScrolledWindow()
151 self.scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
152 self.scroll.add(self.table_tree)
153
154 self.pack_end(self.scroll, True, True, 0)
155
156 def add_no_result_bar(self, entry):
157 color = HobColors.KHAKI
158 self.top_bar = gtk.EventBox()
159 self.top_bar.set_size_request(-1, 70)
160 self.top_bar.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(color))
161 self.top_bar.set_flags(gtk.CAN_DEFAULT)
162 self.top_bar.grab_default()
163
164 no_result_tab = gtk.Table(5, 20, True)
165 self.top_bar.add(no_result_tab)
166
167 label = gtk.Label()
168 label.set_alignment(0.0, 0.5)
169 title = "No results matching your search"
170 label.set_markup("<span size='x-large'><b>%s</b></span>" % title)
171 no_result_tab.attach(label, 1, 14, 1, 4)
172
173 clear_button = HobButton("Clear search")
174 clear_button.set_tooltip_text("Clear search query")
175 clear_button.connect('clicked', self.set_search_entry_clear_cb, entry)
176 no_result_tab.attach(clear_button, 16, 19, 1, 4)
177
178 self.pack_start(self.top_bar, False, True, 12)
179 self.top_bar.show_all()
180
181 def set_search_entry_clear_cb(self, button, search):
182 if search.get_editable() == True:
183 search.set_text("")
184 search.set_icon_sensitive(gtk.ENTRY_ICON_SECONDARY, False)
185 search.grab_focus()
186
187 def display_binb_cb(self, col, cell, model, it, col_id):
188 binb = model.get_value(it, col_id)
189 # Just display the first item
190 if binb:
191 bin = binb.split(', ')
192 total_no = len(bin)
193 if total_no > 1 and bin[0] == "User Selected":
194 if total_no > 2:
195 present_binb = bin[1] + ' (+' + str(total_no - 1) + ')'
196 else:
197 present_binb = bin[1]
198 else:
199 if total_no > 1:
200 present_binb = bin[0] + ' (+' + str(total_no - 1) + ')'
201 else:
202 present_binb = bin[0]
203 cell.set_property('text', present_binb)
204 else:
205 cell.set_property('text', "")
206 return True
207
208 def set_model(self, tree_model):
209 self.table_tree.set_model(tree_model)
210
211 def toggle_default(self):
212 model = self.table_tree.get_model()
213 if not model:
214 return
215 iter = model.get_iter_first()
216 if iter:
217 rowpath = model.get_path(iter)
218 model[rowpath][self.toggle_id] = True
219
220 def toggled_cb(self, cell, path, columnid, tree):
221 self.emit("toggled", cell, path, columnid, tree)
222
223 def row_activated_cb(self, tree, path, view_column):
224 if not view_column.get_title() in self.toggle_columns:
225 self.emit("row-activated", tree.get_model(), path)
226
227 def stop_cell_fadeinout_cb(self, ctrl, cell, tree):
228 self.emit("cell-fadeinout-stopped", ctrl, cell, tree)
229
230 def set_group_number_cb(self, col, cell, model, iter):
231 if model and (model.iter_parent(iter) == None):
232 cell.cell_attr["number_of_children"] = model.iter_n_children(iter)
233 else:
234 cell.cell_attr["number_of_children"] = 0
235
236 def connect_group_selection(self, cb_func):
237 self.table_tree.get_selection().connect("changed", cb_func)
238
239"""
240A method to calculate a softened value for the colour of widget when in the
241provided state.
242
243widget: the widget whose style to use
244state: the state of the widget to use the style for
245
246Returns a string value representing the softened colour
247"""
248def soften_color(widget, state=gtk.STATE_NORMAL):
249 # this colour munging routine is heavily inspired bu gdu_util_get_mix_color()
250 # from gnome-disk-utility:
251 # http://git.gnome.org/browse/gnome-disk-utility/tree/src/gdu-gtk/gdu-gtk.c?h=gnome-3-0
252 blend = 0.7
253 style = widget.get_style()
254 color = style.text[state]
255 color.red = color.red * blend + style.base[state].red * (1.0 - blend)
256 color.green = color.green * blend + style.base[state].green * (1.0 - blend)
257 color.blue = color.blue * blend + style.base[state].blue * (1.0 - blend)
258 return color.to_string()
259
260class BaseHobButton(gtk.Button):
261 """
262 A gtk.Button subclass which follows the visual design of Hob for primary
263 action buttons
264
265 label: the text to display as the button's label
266 """
267 def __init__(self, label):
268 gtk.Button.__init__(self, label)
269 HobButton.style_button(self)
270
271 @staticmethod
272 def style_button(button):
273 style = button.get_style()
274 style = gtk.rc_get_style_by_paths(gtk.settings_get_default(), 'gtk-button', 'gtk-button', gobject.TYPE_NONE)
275
276 button.set_flags(gtk.CAN_DEFAULT)
277 button.grab_default()
278
279# label = "<span size='x-large'><b>%s</b></span>" % gobject.markup_escape_text(button.get_label())
280 label = button.get_label()
281 button.set_label(label)
282 button.child.set_use_markup(True)
283
284class HobButton(BaseHobButton):
285 """
286 A gtk.Button subclass which follows the visual design of Hob for primary
287 action buttons
288
289 label: the text to display as the button's label
290 """
291 def __init__(self, label):
292 BaseHobButton.__init__(self, label)
293 HobButton.style_button(self)
294
295class HobAltButton(BaseHobButton):
296 """
297 A gtk.Button subclass which has no relief, and so is more discrete
298 """
299 def __init__(self, label):
300 BaseHobButton.__init__(self, label)
301 HobAltButton.style_button(self)
302
303 """
304 A callback for the state-changed event to ensure the text is displayed
305 differently when the widget is not sensitive
306 """
307 @staticmethod
308 def desensitise_on_state_change_cb(button, state):
309 if not button.get_property("sensitive"):
310 HobAltButton.set_text(button, False)
311 else:
312 HobAltButton.set_text(button, True)
313
314 """
315 Set the button label with an appropriate colour for the current widget state
316 """
317 @staticmethod
318 def set_text(button, sensitive=True):
319 if sensitive:
320 colour = HobColors.PALE_BLUE
321 else:
322 colour = HobColors.LIGHT_GRAY
323 button.set_label("<span size='large' color='%s'><b>%s</b></span>" % (colour, gobject.markup_escape_text(button.text)))
324 button.child.set_use_markup(True)
325
326class HobImageButton(gtk.Button):
327 """
328 A gtk.Button with an icon and two rows of text, the second of which is
329 displayed in a blended colour.
330
331 primary_text: the main button label
332 secondary_text: optional second line of text
333 icon_path: path to the icon file to display on the button
334 """
335 def __init__(self, primary_text, secondary_text="", icon_path="", hover_icon_path=""):
336 gtk.Button.__init__(self)
337 self.set_relief(gtk.RELIEF_NONE)
338
339 self.icon_path = icon_path
340 self.hover_icon_path = hover_icon_path
341
342 hbox = gtk.HBox(False, 10)
343 hbox.show()
344 self.add(hbox)
345 self.icon = gtk.Image()
346 self.icon.set_from_file(self.icon_path)
347 self.icon.set_alignment(0.5, 0.0)
348 self.icon.show()
349 if self.hover_icon_path and len(self.hover_icon_path):
350 self.connect("enter-notify-event", self.set_hover_icon_cb)
351 self.connect("leave-notify-event", self.set_icon_cb)
352 hbox.pack_start(self.icon, False, False, 0)
353 label = gtk.Label()
354 label.set_alignment(0.0, 0.5)
355 colour = soften_color(label)
356 mark = "<span size='x-large'>%s</span>\n<span size='medium' fgcolor='%s' weight='ultralight'>%s</span>" % (primary_text, colour, secondary_text)
357 label.set_markup(mark)
358 label.show()
359 hbox.pack_start(label, True, True, 0)
360
361 def set_hover_icon_cb(self, widget, event):
362 self.icon.set_from_file(self.hover_icon_path)
363
364 def set_icon_cb(self, widget, event):
365 self.icon.set_from_file(self.icon_path)
366
367class HobInfoButton(gtk.EventBox):
368 """
369 This class implements a button-like widget per the Hob visual and UX designs
370 which will display a persistent tooltip, with the contents of tip_markup, when
371 clicked.
372
373 tip_markup: the Pango Markup to be displayed in the persistent tooltip
374 """
375 def __init__(self, tip_markup, parent=None):
376 gtk.EventBox.__init__(self)
377 self.image = gtk.Image()
378 self.image.set_from_file(
379 hic.ICON_INFO_DISPLAY_FILE)
380 self.image.show()
381 self.add(self.image)
382 self.tip_markup = tip_markup
383 self.my_parent = parent
384
385 self.set_events(gtk.gdk.BUTTON_RELEASE |
386 gtk.gdk.ENTER_NOTIFY_MASK |
387 gtk.gdk.LEAVE_NOTIFY_MASK)
388
389 self.connect("button-release-event", self.button_release_cb)
390 self.connect("enter-notify-event", self.mouse_in_cb)
391 self.connect("leave-notify-event", self.mouse_out_cb)
392
393 """
394 When the mouse click is released emulate a button-click and show the associated
395 PersistentTooltip
396 """
397 def button_release_cb(self, widget, event):
398 from bb.ui.crumbs.hig.propertydialog import PropertyDialog
399 self.dialog = PropertyDialog(title = '',
400 parent = self.my_parent,
401 information = self.tip_markup,
402 flags = gtk.DIALOG_DESTROY_WITH_PARENT
403 | gtk.DIALOG_NO_SEPARATOR)
404
405 button = self.dialog.add_button("Close", gtk.RESPONSE_CANCEL)
406 HobAltButton.style_button(button)
407 button.connect("clicked", lambda w: self.dialog.destroy())
408 self.dialog.show_all()
409 self.dialog.run()
410
411 """
412 Change to the prelight image when the mouse enters the widget
413 """
414 def mouse_in_cb(self, widget, event):
415 self.image.set_from_file(hic.ICON_INFO_HOVER_FILE)
416
417 """
418 Change to the stock image when the mouse enters the widget
419 """
420 def mouse_out_cb(self, widget, event):
421 self.image.set_from_file(hic.ICON_INFO_DISPLAY_FILE)
422
423class HobIndicator(gtk.DrawingArea):
424 def __init__(self, count):
425 gtk.DrawingArea.__init__(self)
426 # Set no window for transparent background
427 self.set_has_window(False)
428 self.set_size_request(38,38)
429 # We need to pass through button clicks
430 self.add_events(gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK)
431
432 self.connect('expose-event', self.expose)
433
434 self.count = count
435 self.color = HobColors.GRAY
436
437 def expose(self, widget, event):
438 if self.count and self.count > 0:
439 ctx = widget.window.cairo_create()
440
441 x, y, w, h = self.allocation
442
443 ctx.set_operator(cairo.OPERATOR_OVER)
444 ctx.set_source_color(gtk.gdk.color_parse(self.color))
445 ctx.translate(w/2, h/2)
446 ctx.arc(x, y, min(w,h)/2 - 2, 0, 2*math.pi)
447 ctx.fill_preserve()
448
449 layout = self.create_pango_layout(str(self.count))
450 textw, texth = layout.get_pixel_size()
451 x = (w/2)-(textw/2) + x
452 y = (h/2) - (texth/2) + y
453 ctx.move_to(x, y)
454 self.window.draw_layout(self.style.light_gc[gtk.STATE_NORMAL], int(x), int(y), layout)
455
456 def set_count(self, count):
457 self.count = count
458
459 def set_active(self, active):
460 if active:
461 self.color = HobColors.DEEP_RED
462 else:
463 self.color = HobColors.GRAY
464
465class HobTabLabel(gtk.HBox):
466 def __init__(self, text, count=0):
467 gtk.HBox.__init__(self, False, 0)
468 self.indicator = HobIndicator(count)
469 self.indicator.show()
470 self.pack_end(self.indicator, False, False)
471 self.lbl = gtk.Label(text)
472 self.lbl.set_alignment(0.0, 0.5)
473 self.lbl.show()
474 self.pack_end(self.lbl, True, True, 6)
475
476 def set_count(self, count):
477 self.indicator.set_count(count)
478
479 def set_active(self, active=True):
480 self.indicator.set_active(active)
481
482class HobNotebook(gtk.Notebook):
483 def __init__(self):
484 gtk.Notebook.__init__(self)
485 self.set_property('homogeneous', True)
486
487 self.pages = []
488
489 self.search = None
490 self.search_focus = False
491 self.page_changed = False
492
493 self.connect("switch-page", self.page_changed_cb)
494
495 self.show_all()
496
497 def page_changed_cb(self, nb, page, page_num):
498 for p, lbl in enumerate(self.pages):
499 if p == page_num:
500 lbl.set_active()
501 else:
502 lbl.set_active(False)
503
504 if self.search:
505 self.page_changed = True
506 self.reset_entry(self.search, page_num)
507
508 def append_page(self, child, tab_label, tab_tooltip=None):
509 label = HobTabLabel(tab_label)
510 if tab_tooltip:
511 label.set_tooltip_text(tab_tooltip)
512 label.set_active(False)
513 self.pages.append(label)
514 gtk.Notebook.append_page(self, child, label)
515
516 def set_entry(self, names, tips):
517 self.search = gtk.Entry()
518 self.search_names = names
519 self.search_tips = tips
520 style = self.search.get_style()
521 style.text[gtk.STATE_NORMAL] = self.get_colormap().alloc_color(HobColors.GRAY, False, False)
522 self.search.set_style(style)
523 self.search.set_text(names[0])
524 self.search.set_tooltip_text(self.search_tips[0])
525 self.search.props.has_tooltip = True
526
527 self.search.set_editable(False)
528 self.search.set_icon_from_stock(gtk.ENTRY_ICON_SECONDARY, gtk.STOCK_CLEAR)
529 self.search.set_icon_sensitive(gtk.ENTRY_ICON_SECONDARY, False)
530 self.search.connect("icon-release", self.set_search_entry_clear_cb)
531 self.search.set_width_chars(30)
532 self.search.show()
533
534 self.search.connect("focus-in-event", self.set_search_entry_editable_cb)
535 self.search.connect("focus-out-event", self.set_search_entry_reset_cb)
536 self.set_action_widget(self.search, gtk.PACK_END)
537
538 def show_indicator_icon(self, title, number):
539 for child in self.pages:
540 if child.lbl.get_label() == title:
541 child.set_count(number)
542
543 def hide_indicator_icon(self, title):
544 for child in self.pages:
545 if child.lbl.get_label() == title:
546 child.set_count(0)
547
548 def set_search_entry_editable_cb(self, search, event):
549 self.search_focus = True
550 search.set_editable(True)
551 text = search.get_text()
552 if text in self.search_names:
553 search.set_text("")
554 style = self.search.get_style()
555 style.text[gtk.STATE_NORMAL] = self.get_colormap().alloc_color(HobColors.BLACK, False, False)
556 search.set_style(style)
557
558 def set_search_entry_reset_cb(self, search, event):
559 page_num = self.get_current_page()
560 text = search.get_text()
561 if not text:
562 self.reset_entry(search, page_num)
563
564 def reset_entry(self, entry, page_num):
565 style = entry.get_style()
566 style.text[gtk.STATE_NORMAL] = self.get_colormap().alloc_color(HobColors.GRAY, False, False)
567 entry.set_style(style)
568 entry.set_text(self.search_names[page_num])
569 entry.set_tooltip_text(self.search_tips[page_num])
570 entry.set_editable(False)
571 entry.set_icon_sensitive(gtk.ENTRY_ICON_SECONDARY, False)
572
573 def set_search_entry_clear_cb(self, search, icon_pos, event):
574 if search.get_editable() == True:
575 search.set_text("")
576 search.set_icon_sensitive(gtk.ENTRY_ICON_SECONDARY, False)
577 search.grab_focus()
578
579 def set_page(self, title):
580 for child in self.pages:
581 if child.lbl.get_label() == title:
582 child.grab_focus()
583 self.set_current_page(self.pages.index(child))
584 return
585
586class HobWarpCellRendererText(gtk.CellRendererText):
587 def __init__(self, col_number):
588 gtk.CellRendererText.__init__(self)
589 self.set_property("wrap-mode", pango.WRAP_WORD_CHAR)
590 self.set_property("wrap-width", 300) # default value wrap width is 300
591 self.col_n = col_number
592
593 def do_render(self, window, widget, background_area, cell_area, expose_area, flags):
594 if widget:
595 self.props.wrap_width = self.get_resized_wrap_width(widget, widget.get_column(self.col_n))
596 return gtk.CellRendererText.do_render(self, window, widget, background_area, cell_area, expose_area, flags)
597
598 def get_resized_wrap_width(self, treeview, column):
599 otherCols = []
600 for col in treeview.get_columns():
601 if col != column:
602 otherCols.append(col)
603 adjwidth = treeview.allocation.width - sum(c.get_width() for c in otherCols)
604 adjwidth -= treeview.style_get_property("horizontal-separator") * 4
605 if self.props.wrap_width == adjwidth or adjwidth <= 0:
606 adjwidth = self.props.wrap_width
607 return adjwidth
608
609gobject.type_register(HobWarpCellRendererText)
610
611class HobIconChecker(hic):
612 def set_hob_icon_to_stock_icon(self, file_path, stock_id=""):
613 try:
614 pixbuf = gtk.gdk.pixbuf_new_from_file(file_path)
615 except Exception, e:
616 return None
617
618 if stock_id and (gtk.icon_factory_lookup_default(stock_id) == None):
619 icon_factory = gtk.IconFactory()
620 icon_factory.add_default()
621 icon_factory.add(stock_id, gtk.IconSet(pixbuf))
622 gtk.stock_add([(stock_id, '_label', 0, 0, '')])
623
624 return icon_factory.lookup(stock_id)
625
626 return None
627
628 """
629 For make hob icon consistently by request, and avoid icon view diff by system or gtk version, we use some 'hob icon' to replace the 'gtk icon'.
630 this function check the stock_id and make hob_id to replaced the gtk_id then return it or ""
631 """
632 def check_stock_icon(self, stock_name=""):
633 HOB_CHECK_STOCK_NAME = {
634 ('hic-dialog-info', 'gtk-dialog-info', 'dialog-info') : self.ICON_INDI_INFO_FILE,
635 ('hic-ok', 'gtk-ok', 'ok') : self.ICON_INDI_TICK_FILE,
636 ('hic-dialog-error', 'gtk-dialog-error', 'dialog-error') : self.ICON_INDI_ERROR_FILE,
637 ('hic-dialog-warning', 'gtk-dialog-warning', 'dialog-warning') : self.ICON_INDI_ALERT_FILE,
638 ('hic-task-refresh', 'gtk-execute', 'execute') : self.ICON_INDI_REFRESH_FILE,
639 }
640 valid_stock_id = stock_name
641 if stock_name:
642 for names, path in HOB_CHECK_STOCK_NAME.iteritems():
643 if stock_name in names:
644 valid_stock_id = names[0]
645 if not gtk.icon_factory_lookup_default(valid_stock_id):
646 self.set_hob_icon_to_stock_icon(path, valid_stock_id)
647
648 return valid_stock_id
649
650class HobCellRendererController(gobject.GObject):
651 (MODE_CYCLE_RUNNING, MODE_ONE_SHORT) = range(2)
652 __gsignals__ = {
653 "run-timer-stopped" : (gobject.SIGNAL_RUN_LAST,
654 gobject.TYPE_NONE,
655 ()),
656 }
657 def __init__(self, runningmode=MODE_CYCLE_RUNNING, is_draw_row=False):
658 gobject.GObject.__init__(self)
659 self.timeout_id = None
660 self.current_angle_pos = 0.0
661 self.step_angle = 0.0
662 self.tree_headers_height = 0
663 self.running_cell_areas = []
664 self.running_mode = runningmode
665 self.is_queue_draw_row_area = is_draw_row
666 self.force_stop_enable = False
667
668 def is_active(self):
669 if self.timeout_id:
670 return True
671 else:
672 return False
673
674 def reset_run(self):
675 self.force_stop()
676 self.running_cell_areas = []
677 self.current_angle_pos = 0.0
678 self.step_angle = 0.0
679
680 ''' time_iterval: (1~1000)ms, which will be as the basic interval count for timer
681 init_usrdata: the current data which related the progress-bar will be at
682 min_usrdata: the range of min of user data
683 max_usrdata: the range of max of user data
684 step: each step which you want to progress
685 Note: the init_usrdata should in the range of from min to max, and max should > min
686 step should < (max - min)
687 '''
688 def start_run(self, time_iterval, init_usrdata, min_usrdata, max_usrdata, step, tree):
689 if (not time_iterval) or (not max_usrdata):
690 return
691 usr_range = (max_usrdata - min_usrdata) * 1.0
692 self.current_angle_pos = (init_usrdata * 1.0) / usr_range
693 self.step_angle = (step * 1) / usr_range
694 self.timeout_id = gobject.timeout_add(int(time_iterval),
695 self.make_image_on_progressing_cb, tree)
696 self.tree_headers_height = self.get_treeview_headers_height(tree)
697 self.force_stop_enable = False
698
699 def force_stop(self):
700 self.emit("run-timer-stopped")
701 self.force_stop_enable = True
702 if self.timeout_id:
703 if gobject.source_remove(self.timeout_id):
704 self.timeout_id = None
705
706 def on_draw_pixbuf_cb(self, pixbuf, cr, x, y, img_width, img_height, do_refresh=True):
707 if pixbuf:
708 r = max(img_width/2, img_height/2)
709 cr.translate(x + r, y + r)
710 if do_refresh:
711 cr.rotate(2 * math.pi * self.current_angle_pos)
712
713 cr.set_source_pixbuf(pixbuf, -img_width/2, -img_height/2)
714 cr.paint()
715
716 def on_draw_fadeinout_cb(self, cr, color, x, y, width, height, do_fadeout=True):
717 if do_fadeout:
718 alpha = self.current_angle_pos * 0.8
719 else:
720 alpha = (1.0 - self.current_angle_pos) * 0.8
721
722 cr.set_source_rgba(color.red, color.green, color.blue, alpha)
723 cr.rectangle(x, y, width, height)
724 cr.fill()
725
726 def get_treeview_headers_height(self, tree):
727 if tree and (tree.get_property("headers-visible") == True):
728 height = tree.get_allocation().height - tree.get_bin_window().get_size()[1]
729 return height
730
731 return 0
732
733 def make_image_on_progressing_cb(self, tree):
734 self.current_angle_pos += self.step_angle
735 if self.running_mode == self.MODE_CYCLE_RUNNING:
736 if (self.current_angle_pos >= 1):
737 self.current_angle_pos = 0
738 else:
739 if self.current_angle_pos > 1:
740 self.force_stop()
741 return False
742
743 if self.is_queue_draw_row_area:
744 for path in self.running_cell_areas:
745 rect = tree.get_cell_area(path, tree.get_column(0))
746 row_x, _, row_width, _ = tree.get_visible_rect()
747 tree.queue_draw_area(row_x, rect.y + self.tree_headers_height, row_width, rect.height)
748 else:
749 for rect in self.running_cell_areas:
750 tree.queue_draw_area(rect.x, rect.y + self.tree_headers_height, rect.width, rect.height)
751
752 return (not self.force_stop_enable)
753
754 def append_running_cell_area(self, cell_area):
755 if cell_area and (cell_area not in self.running_cell_areas):
756 self.running_cell_areas.append(cell_area)
757
758 def remove_running_cell_area(self, cell_area):
759 if cell_area in self.running_cell_areas:
760 self.running_cell_areas.remove(cell_area)
761 if not self.running_cell_areas:
762 self.reset_run()
763
764gobject.type_register(HobCellRendererController)
765
766class HobCellRendererPixbuf(gtk.CellRendererPixbuf):
767 def __init__(self):
768 gtk.CellRendererPixbuf.__init__(self)
769 self.control = HobCellRendererController()
770 # add icon checker for make the gtk-icon transfer to hob-icon
771 self.checker = HobIconChecker()
772 self.set_property("stock-size", gtk.ICON_SIZE_DND)
773
774 def get_pixbuf_from_stock_icon(self, widget, stock_id="", size=gtk.ICON_SIZE_DIALOG):
775 if widget and stock_id and gtk.icon_factory_lookup_default(stock_id):
776 return widget.render_icon(stock_id, size)
777
778 return None
779
780 def set_icon_name_to_id(self, new_name):
781 if new_name and type(new_name) == str:
782 # check the name is need to transfer to hob icon or not
783 name = self.checker.check_stock_icon(new_name)
784 if name.startswith("hic") or name.startswith("gtk"):
785 stock_id = name
786 else:
787 stock_id = 'gtk-' + name
788
789 return stock_id
790
791 ''' render cell exactly, "icon-name" is priority
792 if use the 'hic-task-refresh' will make the pix animation
793 if 'pix' will change the pixbuf for it from the pixbuf or image.
794 '''
795 def do_render(self, window, tree, background_area,cell_area, expose_area, flags):
796 if (not self.control) or (not tree):
797 return
798
799 x, y, w, h = self.on_get_size(tree, cell_area)
800 x += cell_area.x
801 y += cell_area.y
802 w -= 2 * self.get_property("xpad")
803 h -= 2 * self.get_property("ypad")
804
805 stock_id = ""
806 if self.props.icon_name:
807 stock_id = self.set_icon_name_to_id(self.props.icon_name)
808 elif self.props.stock_id:
809 stock_id = self.props.stock_id
810 elif self.props.pixbuf:
811 pix = self.props.pixbuf
812 else:
813 return
814
815 if stock_id:
816 pix = self.get_pixbuf_from_stock_icon(tree, stock_id, self.props.stock_size)
817 if stock_id == 'hic-task-refresh':
818 self.control.append_running_cell_area(cell_area)
819 if self.control.is_active():
820 self.control.on_draw_pixbuf_cb(pix, window.cairo_create(), x, y, w, h, True)
821 else:
822 self.control.start_run(200, 0, 0, 1000, 150, tree)
823 else:
824 self.control.remove_running_cell_area(cell_area)
825 self.control.on_draw_pixbuf_cb(pix, window.cairo_create(), x, y, w, h, False)
826
827 def on_get_size(self, widget, cell_area):
828 if self.props.icon_name or self.props.pixbuf or self.props.stock_id:
829 w, h = gtk.icon_size_lookup(self.props.stock_size)
830 calc_width = self.get_property("xpad") * 2 + w
831 calc_height = self.get_property("ypad") * 2 + h
832 x_offset = 0
833 y_offset = 0
834 if cell_area and w > 0 and h > 0:
835 x_offset = self.get_property("xalign") * (cell_area.width - calc_width - self.get_property("xpad"))
836 y_offset = self.get_property("yalign") * (cell_area.height - calc_height - self.get_property("ypad"))
837
838 return x_offset, y_offset, w, h
839
840 return 0, 0, 0, 0
841
842gobject.type_register(HobCellRendererPixbuf)
843
844class HobCellRendererToggle(gtk.CellRendererToggle):
845 def __init__(self):
846 gtk.CellRendererToggle.__init__(self)
847 self.ctrl = HobCellRendererController(is_draw_row=True)
848 self.ctrl.running_mode = self.ctrl.MODE_ONE_SHORT
849 self.cell_attr = {"fadeout": False, "number_of_children": 0}
850
851 def do_render(self, window, widget, background_area, cell_area, expose_area, flags):
852 if (not self.ctrl) or (not widget):
853 return
854
855 if flags & gtk.CELL_RENDERER_SELECTED:
856 state = gtk.STATE_SELECTED
857 else:
858 state = gtk.STATE_NORMAL
859
860 if self.ctrl.is_active():
861 path = widget.get_path_at_pos(cell_area.x + cell_area.width/2, cell_area.y + cell_area.height/2)
862 # sometimes the parameters of cell_area will be a negative number,such as pull up down the scroll bar
863 # it's over the tree container range, so the path will be bad
864 if not path: return
865 path = path[0]
866 if path in self.ctrl.running_cell_areas:
867 cr = window.cairo_create()
868 color = widget.get_style().base[state]
869
870 row_x, _, row_width, _ = widget.get_visible_rect()
871 border_y = self.get_property("ypad")
872 self.ctrl.on_draw_fadeinout_cb(cr, color, row_x, cell_area.y - border_y, row_width, \
873 cell_area.height + border_y * 2, self.cell_attr["fadeout"])
874 # draw number of a group
875 if self.cell_attr["number_of_children"]:
876 text = "%d pkg" % self.cell_attr["number_of_children"]
877 pangolayout = widget.create_pango_layout(text)
878 textw, texth = pangolayout.get_pixel_size()
879 x = cell_area.x + (cell_area.width/2) - (textw/2)
880 y = cell_area.y + (cell_area.height/2) - (texth/2)
881
882 widget.style.paint_layout(window, state, True, cell_area, widget, "checkbox", x, y, pangolayout)
883 else:
884 return gtk.CellRendererToggle.do_render(self, window, widget, background_area, cell_area, expose_area, flags)
885
886 '''delay: normally delay time is 1000ms
887 cell_list: whilch cells need to be render
888 '''
889 def fadeout(self, tree, delay, cell_list=None):
890 if (delay < 200) or (not tree):
891 return
892 self.cell_attr["fadeout"] = True
893 self.ctrl.running_cell_areas = cell_list
894 self.ctrl.start_run(200, 0, 0, delay, (delay * 200 / 1000), tree)
895
896 def connect_render_state_changed(self, func, usrdata=None):
897 if not func:
898 return
899 if usrdata:
900 self.ctrl.connect("run-timer-stopped", func, self, usrdata)
901 else:
902 self.ctrl.connect("run-timer-stopped", func, self)
903
904gobject.type_register(HobCellRendererToggle)
diff --git a/bitbake/lib/bb/ui/crumbs/imageconfigurationpage.py b/bitbake/lib/bb/ui/crumbs/imageconfigurationpage.py
new file mode 100644
index 0000000000..2766bea8c7
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/imageconfigurationpage.py
@@ -0,0 +1,561 @@
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
23import gtk
24import glib
25import re
26from bb.ui.crumbs.progressbar import HobProgressBar
27from bb.ui.crumbs.hobcolor import HobColors
28from bb.ui.crumbs.hobwidget import hic, HobImageButton, HobInfoButton, HobAltButton, HobButton
29from bb.ui.crumbs.hoblistmodel import RecipeListModel
30from bb.ui.crumbs.hobpages import HobPage
31from bb.ui.crumbs.hig.retrieveimagedialog import RetrieveImageDialog
32
33#
34# ImageConfigurationPage
35#
36class ImageConfigurationPage (HobPage):
37
38 __dummy_machine__ = "--select a machine--"
39 __dummy_image__ = "--select an image recipe--"
40 __custom_image__ = "Select from my image recipes"
41
42 def __init__(self, builder):
43 super(ImageConfigurationPage, self).__init__(builder, "Image configuration")
44
45 self.image_combo_id = None
46 # we use machine_combo_changed_by_manual to identify the machine is changed by code
47 # or by manual. If by manual, all user's recipe selection and package selection are
48 # cleared.
49 self.machine_combo_changed_by_manual = True
50 self.stopping = False
51 self.warning_shift = 0
52 self.custom_image_selected = None
53 self.create_visual_elements()
54
55 def create_visual_elements(self):
56 # create visual elements
57 self.toolbar = gtk.Toolbar()
58 self.toolbar.set_orientation(gtk.ORIENTATION_HORIZONTAL)
59 self.toolbar.set_style(gtk.TOOLBAR_BOTH)
60
61 my_images_button = self.append_toolbar_button(self.toolbar,
62 "Images",
63 hic.ICON_IMAGES_DISPLAY_FILE,
64 hic.ICON_IMAGES_HOVER_FILE,
65 "Open previously built images",
66 self.my_images_button_clicked_cb)
67 settings_button = self.append_toolbar_button(self.toolbar,
68 "Settings",
69 hic.ICON_SETTINGS_DISPLAY_FILE,
70 hic.ICON_SETTINGS_HOVER_FILE,
71 "View additional build settings",
72 self.settings_button_clicked_cb)
73
74 self.config_top_button = self.add_onto_top_bar(self.toolbar)
75
76 self.gtable = gtk.Table(40, 40, True)
77 self.create_config_machine()
78 self.create_config_baseimg()
79 self.config_build_button = self.create_config_build_button()
80
81 def _remove_all_widget(self):
82 children = self.gtable.get_children() or []
83 for child in children:
84 self.gtable.remove(child)
85 children = self.box_group_area.get_children() or []
86 for child in children:
87 self.box_group_area.remove(child)
88 children = self.get_children() or []
89 for child in children:
90 self.remove(child)
91
92 def _pack_components(self, pack_config_build_button = False):
93 self._remove_all_widget()
94 self.pack_start(self.config_top_button, expand=False, fill=False)
95 self.pack_start(self.group_align, expand=True, fill=True)
96
97 self.box_group_area.pack_start(self.gtable, expand=True, fill=True)
98 if pack_config_build_button:
99 self.box_group_area.pack_end(self.config_build_button, expand=False, fill=False)
100 else:
101 box = gtk.HBox(False, 6)
102 box.show()
103 subbox = gtk.HBox(False, 0)
104 subbox.set_size_request(205, 49)
105 subbox.show()
106 box.add(subbox)
107 self.box_group_area.pack_end(box, False, False)
108
109 def show_machine(self):
110 self.progress_bar.reset()
111 self._pack_components(pack_config_build_button = False)
112 self.set_config_machine_layout(show_progress_bar = False)
113 self.show_all()
114
115 def update_progress_bar(self, title, fraction, status=None):
116 if self.stopping == False:
117 self.progress_bar.update(fraction)
118 self.progress_bar.set_text(title)
119 self.progress_bar.set_rcstyle(status)
120
121 def show_info_populating(self):
122 self._pack_components(pack_config_build_button = False)
123 self.set_config_machine_layout(show_progress_bar = True)
124 self.show_all()
125
126 def show_info_populated(self):
127 self.progress_bar.reset()
128 self._pack_components(pack_config_build_button = False)
129 self.set_config_machine_layout(show_progress_bar = False)
130 self.set_config_baseimg_layout()
131 self.show_all()
132
133 def show_baseimg_selected(self):
134 self.progress_bar.reset()
135 self._pack_components(pack_config_build_button = True)
136 self.set_config_machine_layout(show_progress_bar = False)
137 self.set_config_baseimg_layout()
138 self.show_all()
139 if self.builder.recipe_model.get_selected_image() == self.builder.recipe_model.__custom_image__:
140 self.just_bake_button.hide()
141
142 def add_warnings_bar(self):
143 #create the warnings bar shown when recipes parsing generates warnings
144 color = HobColors.KHAKI
145 warnings_bar = gtk.EventBox()
146 warnings_bar.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(color))
147 warnings_bar.set_flags(gtk.CAN_DEFAULT)
148 warnings_bar.grab_default()
149
150 build_stop_tab = gtk.Table(10, 20, True)
151 warnings_bar.add(build_stop_tab)
152
153 icon = gtk.Image()
154 icon_pix_buffer = gtk.gdk.pixbuf_new_from_file(hic.ICON_INDI_ALERT_FILE)
155 icon.set_from_pixbuf(icon_pix_buffer)
156 build_stop_tab.attach(icon, 0, 2, 0, 10)
157
158 label = gtk.Label()
159 label.set_alignment(0.0, 0.5)
160 warnings_nb = len(self.builder.parsing_warnings)
161 if warnings_nb == 1:
162 label.set_markup("<span size='x-large'><b>1 recipe parsing warning</b></span>")
163 else:
164 label.set_markup("<span size='x-large'><b>%s recipe parsing warnings</b></span>" % warnings_nb)
165 build_stop_tab.attach(label, 2, 12, 0, 10)
166
167 view_warnings_button = HobButton("View warnings")
168 view_warnings_button.connect('clicked', self.view_warnings_button_clicked_cb)
169 build_stop_tab.attach(view_warnings_button, 15, 19, 1, 9)
170
171 return warnings_bar
172
173 def disable_warnings_bar(self):
174 if self.builder.parsing_warnings:
175 if hasattr(self, 'warnings_bar'):
176 self.warnings_bar.hide_all()
177 self.builder.parsing_warnings = []
178
179 def create_config_machine(self):
180 self.machine_title = gtk.Label()
181 self.machine_title.set_alignment(0.0, 0.5)
182 mark = "<span %s>Select a machine</span>" % self.span_tag('x-large', 'bold')
183 self.machine_title.set_markup(mark)
184
185 self.machine_title_desc = gtk.Label()
186 self.machine_title_desc.set_alignment(0.0, 0.5)
187 mark = ("<span %s>Your selection is the profile of the target machine for which you"
188 " are building the image.\n</span>") % (self.span_tag('medium'))
189 self.machine_title_desc.set_markup(mark)
190
191 self.machine_combo = gtk.combo_box_new_text()
192 self.machine_combo.connect("changed", self.machine_combo_changed_cb)
193
194 icon_file = hic.ICON_LAYERS_DISPLAY_FILE
195 hover_file = hic.ICON_LAYERS_HOVER_FILE
196 self.layer_button = HobImageButton("Layers", "Add support for machines, software, etc.",
197 icon_file, hover_file)
198 self.layer_button.connect("clicked", self.layer_button_clicked_cb)
199
200 markup = "Layers are a powerful mechanism to extend the Yocto Project "
201 markup += "with your own functionality.\n"
202 markup += "For more on layers, check the <a href=\""
203 markup += "http://www.yoctoproject.org/docs/current/dev-manual/"
204 markup += "dev-manual.html#understanding-and-using-layers\">reference manual</a>."
205 self.layer_info_icon = HobInfoButton("<b>Layers</b>" + "*" + markup, self.get_parent())
206 self.progress_bar = HobProgressBar()
207 self.stop_button = HobAltButton("Stop")
208 self.stop_button.connect("clicked", self.stop_button_clicked_cb)
209 self.machine_separator = gtk.HSeparator()
210
211 def set_config_machine_layout(self, show_progress_bar = False):
212 self.gtable.attach(self.machine_title, 0, 40, 0, 4)
213 self.gtable.attach(self.machine_title_desc, 0, 40, 4, 6)
214 self.gtable.attach(self.machine_combo, 0, 12, 7, 10)
215 self.gtable.attach(self.layer_button, 14, 36, 7, 12)
216 self.gtable.attach(self.layer_info_icon, 36, 40, 7, 11)
217 if show_progress_bar:
218 #self.gtable.attach(self.progress_box, 0, 40, 15, 18)
219 self.gtable.attach(self.progress_bar, 0, 37, 15, 18)
220 self.gtable.attach(self.stop_button, 37, 40, 15, 18, 0, 0)
221 if self.builder.parsing_warnings:
222 self.warnings_bar = self.add_warnings_bar()
223 self.gtable.attach(self.warnings_bar, 0, 40, 14, 18)
224 self.warning_shift = 4
225 else:
226 self.warning_shift = 0
227 self.gtable.attach(self.machine_separator, 0, 40, 13, 14)
228
229 def create_config_baseimg(self):
230 self.image_title = gtk.Label()
231 self.image_title.set_alignment(0, 1.0)
232 mark = "<span %s>Select an image recipe</span>" % self.span_tag('x-large', 'bold')
233 self.image_title.set_markup(mark)
234
235 self.image_title_desc = gtk.Label()
236 self.image_title_desc.set_alignment(0, 0.5)
237
238 mark = ("<span %s>Image recipes are a starting point for the type of image you want. "
239 "You can build them as \n"
240 "they are or edit them to suit your needs.\n</span>") % self.span_tag('medium')
241 self.image_title_desc.set_markup(mark)
242
243 self.image_combo = gtk.combo_box_new_text()
244 self.image_combo.set_row_separator_func(self.combo_separator_func, None)
245 self.image_combo_id = self.image_combo.connect("changed", self.image_combo_changed_cb)
246
247 self.image_desc = gtk.Label()
248 self.image_desc.set_alignment(0.0, 0.5)
249 self.image_desc.set_size_request(256, -1)
250 self.image_desc.set_justify(gtk.JUSTIFY_LEFT)
251 self.image_desc.set_line_wrap(True)
252
253 # button to view recipes
254 icon_file = hic.ICON_RCIPE_DISPLAY_FILE
255 hover_file = hic.ICON_RCIPE_HOVER_FILE
256 self.view_adv_configuration_button = HobImageButton("Advanced configuration",
257 "Select image types, package formats, etc",
258 icon_file, hover_file)
259 self.view_adv_configuration_button.connect("clicked", self.view_adv_configuration_button_clicked_cb)
260
261 self.image_separator = gtk.HSeparator()
262
263 def combo_separator_func(self, model, iter, user_data):
264 name = model.get_value(iter, 0)
265 if name == "--Separator--":
266 return True
267
268 def set_config_baseimg_layout(self):
269 self.gtable.attach(self.image_title, 0, 40, 15+self.warning_shift, 17+self.warning_shift)
270 self.gtable.attach(self.image_title_desc, 0, 40, 18+self.warning_shift, 22+self.warning_shift)
271 self.gtable.attach(self.image_combo, 0, 12, 23+self.warning_shift, 26+self.warning_shift)
272 self.gtable.attach(self.image_desc, 0, 12, 27+self.warning_shift, 33+self.warning_shift)
273 self.gtable.attach(self.view_adv_configuration_button, 14, 36, 23+self.warning_shift, 28+self.warning_shift)
274 self.gtable.attach(self.image_separator, 0, 40, 35+self.warning_shift, 36+self.warning_shift)
275
276 def create_config_build_button(self):
277 # Create the "Build packages" and "Build image" buttons at the bottom
278 button_box = gtk.HBox(False, 6)
279
280 # create button "Build image"
281 self.just_bake_button = HobButton("Build image")
282 self.just_bake_button.set_tooltip_text("Build the image recipe as it is")
283 self.just_bake_button.connect("clicked", self.just_bake_button_clicked_cb)
284 button_box.pack_end(self.just_bake_button, expand=False, fill=False)
285
286 # create button "Edit image recipe"
287 self.edit_image_button = HobAltButton("Edit image recipe")
288 self.edit_image_button.set_tooltip_text("Customize the recipes and packages to be included in your image")
289 self.edit_image_button.connect("clicked", self.edit_image_button_clicked_cb)
290 button_box.pack_end(self.edit_image_button, expand=False, fill=False)
291
292 return button_box
293
294 def stop_button_clicked_cb(self, button):
295 self.stopping = True
296 self.progress_bar.set_text("Stopping recipe parsing")
297 self.progress_bar.set_rcstyle("stop")
298 self.builder.cancel_parse_sync()
299
300 def view_warnings_button_clicked_cb(self, button):
301 self.builder.show_warning_dialog()
302
303 def machine_combo_changed_idle_cb(self):
304 self.builder.window.set_cursor(None)
305
306 def machine_combo_changed_cb(self, machine_combo):
307 self.stopping = False
308 self.builder.parsing_warnings = []
309 combo_item = machine_combo.get_active_text()
310 if not combo_item or combo_item == self.__dummy_machine__:
311 return
312
313 self.builder.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
314 self.builder.wait(0.1) #wait for combo and cursor to update
315
316 # remove __dummy_machine__ item from the store list after first user selection
317 # because it is no longer valid
318 combo_store = machine_combo.get_model()
319 if len(combo_store) and (combo_store[0][0] == self.__dummy_machine__):
320 machine_combo.remove_text(0)
321
322 self.builder.configuration.curr_mach = combo_item
323 if self.machine_combo_changed_by_manual:
324 self.builder.configuration.clear_selection()
325 # reset machine_combo_changed_by_manual
326 self.machine_combo_changed_by_manual = True
327
328 self.builder.configuration.selected_image = None
329
330 # Do reparse recipes
331 self.builder.populate_recipe_package_info_async()
332
333 glib.idle_add(self.machine_combo_changed_idle_cb)
334
335 def update_machine_combo(self):
336 self.disable_warnings_bar()
337 all_machines = [self.__dummy_machine__] + self.builder.parameters.all_machines
338
339 model = self.machine_combo.get_model()
340 model.clear()
341 for machine in all_machines:
342 self.machine_combo.append_text(machine)
343 self.machine_combo.set_active(0)
344
345 def switch_machine_combo(self):
346 self.disable_warnings_bar()
347 self.machine_combo_changed_by_manual = False
348 model = self.machine_combo.get_model()
349 active = 0
350 while active < len(model):
351 if model[active][0] == self.builder.configuration.curr_mach:
352 self.machine_combo.set_active(active)
353 return
354 active += 1
355
356 if model[0][0] != self.__dummy_machine__:
357 self.machine_combo.insert_text(0, self.__dummy_machine__)
358
359 self.machine_combo.set_active(0)
360
361 def update_image_desc(self):
362 desc = ""
363 selected_image = self.image_combo.get_active_text()
364 if selected_image and selected_image in self.builder.recipe_model.pn_path.keys():
365 image_path = self.builder.recipe_model.pn_path[selected_image]
366 image_iter = self.builder.recipe_model.get_iter(image_path)
367 desc = self.builder.recipe_model.get_value(image_iter, self.builder.recipe_model.COL_DESC)
368
369 mark = ("<span %s>%s</span>\n") % (self.span_tag('small'), desc)
370 self.image_desc.set_markup(mark)
371
372 def image_combo_changed_idle_cb(self, selected_image, selected_recipes, selected_packages):
373 self.builder.update_recipe_model(selected_image, selected_recipes)
374 self.builder.update_package_model(selected_packages)
375 self.builder.window_sensitive(True)
376
377 def image_combo_changed_cb(self, combo):
378 self.builder.window_sensitive(False)
379 selected_image = self.image_combo.get_active_text()
380 if selected_image == self.__custom_image__:
381 topdir = self.builder.get_topdir()
382 images_dir = topdir + "/recipes/images/custom/"
383 self.builder.ensure_dir(images_dir)
384
385 dialog = RetrieveImageDialog(images_dir, "Select from my image recipes",
386 self.builder, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
387 response = dialog.run()
388 if response == gtk.RESPONSE_OK:
389 image_name = dialog.get_filename()
390 head, tail = os.path.split(image_name)
391 selected_image = os.path.splitext(tail)[0]
392 self.custom_image_selected = selected_image
393 self.update_image_combo(self.builder.recipe_model, selected_image)
394 else:
395 selected_image = self.__dummy_image__
396 self.update_image_combo(self.builder.recipe_model, None)
397 dialog.destroy()
398 else:
399 if self.custom_image_selected:
400 self.custom_image_selected = None
401 self.update_image_combo(self.builder.recipe_model, selected_image)
402
403 if not selected_image or (selected_image == self.__dummy_image__):
404 self.builder.window_sensitive(True)
405 self.just_bake_button.hide()
406 self.edit_image_button.hide()
407 return
408
409 # remove __dummy_image__ item from the store list after first user selection
410 # because it is no longer valid
411 combo_store = combo.get_model()
412 if len(combo_store) and (combo_store[0][0] == self.__dummy_image__):
413 combo.remove_text(0)
414
415 self.builder.customized = False
416
417 selected_recipes = []
418
419 image_path = self.builder.recipe_model.pn_path[selected_image]
420 image_iter = self.builder.recipe_model.get_iter(image_path)
421 selected_packages = self.builder.recipe_model.get_value(image_iter, self.builder.recipe_model.COL_INSTALL).split()
422 self.update_image_desc()
423
424 self.builder.recipe_model.reset()
425 self.builder.package_model.reset()
426
427 self.show_baseimg_selected()
428
429 if selected_image == self.builder.recipe_model.__custom_image__:
430 self.just_bake_button.hide()
431
432 glib.idle_add(self.image_combo_changed_idle_cb, selected_image, selected_recipes, selected_packages)
433
434 def _image_combo_connect_signal(self):
435 if not self.image_combo_id:
436 self.image_combo_id = self.image_combo.connect("changed", self.image_combo_changed_cb)
437
438 def _image_combo_disconnect_signal(self):
439 if self.image_combo_id:
440 self.image_combo.disconnect(self.image_combo_id)
441 self.image_combo_id = None
442
443 def update_image_combo(self, recipe_model, selected_image):
444 # Update the image combo according to the images in the recipe_model
445 # populate image combo
446 filter = {RecipeListModel.COL_TYPE : ['image']}
447 image_model = recipe_model.tree_model(filter)
448 image_model.set_sort_column_id(recipe_model.COL_NAME, gtk.SORT_ASCENDING)
449 active = 0
450 cnt = 0
451
452 white_pattern = []
453 if self.builder.parameters.image_white_pattern:
454 for i in self.builder.parameters.image_white_pattern.split():
455 white_pattern.append(re.compile(i))
456
457 black_pattern = []
458 if self.builder.parameters.image_black_pattern:
459 for i in self.builder.parameters.image_black_pattern.split():
460 black_pattern.append(re.compile(i))
461 black_pattern.append(re.compile("hob-image"))
462 black_pattern.append(re.compile("edited(-[0-9]*)*.bb$"))
463
464 it = image_model.get_iter_first()
465 self._image_combo_disconnect_signal()
466 model = self.image_combo.get_model()
467 model.clear()
468 # Set a indicator text to combo store when first open
469 if not selected_image:
470 self.image_combo.append_text(self.__dummy_image__)
471 cnt = cnt + 1
472
473 self.image_combo.append_text(self.__custom_image__)
474 self.image_combo.append_text("--Separator--")
475 cnt = cnt + 2
476
477 topdir = self.builder.get_topdir()
478 # append and set active
479 while it:
480 path = image_model.get_path(it)
481 it = image_model.iter_next(it)
482 image_name = image_model[path][recipe_model.COL_NAME]
483 if image_name == self.builder.recipe_model.__custom_image__:
484 continue
485
486 if black_pattern:
487 allow = True
488 for pattern in black_pattern:
489 if pattern.search(image_name):
490 allow = False
491 break
492 elif white_pattern:
493 allow = False
494 for pattern in white_pattern:
495 if pattern.search(image_name):
496 allow = True
497 break
498 else:
499 allow = True
500
501 file_name = image_model[path][recipe_model.COL_FILE]
502 if file_name and topdir in file_name:
503 allow = False
504
505 if allow:
506 self.image_combo.append_text(image_name)
507 if image_name == selected_image:
508 active = cnt
509 cnt = cnt + 1
510 self.image_combo.append_text(self.builder.recipe_model.__custom_image__)
511
512 if selected_image == self.builder.recipe_model.__custom_image__:
513 active = cnt
514
515 if self.custom_image_selected:
516 self.image_combo.append_text("--Separator--")
517 self.image_combo.append_text(self.custom_image_selected)
518 cnt = cnt + 2
519 if self.custom_image_selected == selected_image:
520 active = cnt
521
522 self.image_combo.set_active(active)
523
524 if active != 0:
525 self.show_baseimg_selected()
526
527 self._image_combo_connect_signal()
528
529 def layer_button_clicked_cb(self, button):
530 # Create a layer selection dialog
531 self.builder.show_layer_selection_dialog()
532
533 def view_adv_configuration_button_clicked_cb(self, button):
534 # Create an advanced settings dialog
535 response, settings_changed = self.builder.show_adv_settings_dialog()
536 if not response:
537 return
538 if settings_changed:
539 self.builder.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
540 self.builder.wait(0.1) #wait for adv_settings_dialog to terminate
541 self.builder.reparse_post_adv_settings()
542 self.builder.window.set_cursor(None)
543
544 def just_bake_button_clicked_cb(self, button):
545 self.builder.parsing_warnings = []
546 self.builder.just_bake()
547
548 def edit_image_button_clicked_cb(self, button):
549 self.builder.set_base_image()
550 self.builder.show_recipes()
551
552 def my_images_button_clicked_cb(self, button):
553 self.builder.show_load_my_images_dialog()
554
555 def settings_button_clicked_cb(self, button):
556 # Create an advanced settings dialog
557 response, settings_changed = self.builder.show_simple_settings_dialog()
558 if not response:
559 return
560 if settings_changed:
561 self.builder.reparse_post_adv_settings()
diff --git a/bitbake/lib/bb/ui/crumbs/imagedetailspage.py b/bitbake/lib/bb/ui/crumbs/imagedetailspage.py
new file mode 100755
index 0000000000..352e9489fd
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/imagedetailspage.py
@@ -0,0 +1,669 @@
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
23import gobject
24import gtk
25from bb.ui.crumbs.hobcolor import HobColors
26from bb.ui.crumbs.hobwidget import hic, HobViewTable, HobAltButton, HobButton
27from bb.ui.crumbs.hobpages import HobPage
28import subprocess
29from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog
30from bb.ui.crumbs.hig.saveimagedialog import SaveImageDialog
31
32#
33# ImageDetailsPage
34#
35class ImageDetailsPage (HobPage):
36
37 class DetailBox (gtk.EventBox):
38 def __init__(self, widget = None, varlist = None, vallist = None, icon = None, button = None, button2=None, color = HobColors.LIGHT_GRAY):
39 gtk.EventBox.__init__(self)
40
41 # set color
42 style = self.get_style().copy()
43 style.bg[gtk.STATE_NORMAL] = self.get_colormap().alloc_color(color, False, False)
44 self.set_style(style)
45
46 self.row = gtk.Table(1, 2, False)
47 self.row.set_border_width(10)
48 self.add(self.row)
49
50 total_rows = 0
51 if widget:
52 total_rows = 10
53 if varlist and vallist:
54 # pack the icon and the text on the left
55 total_rows += len(varlist)
56 self.table = gtk.Table(total_rows, 20, True)
57 self.table.set_row_spacings(6)
58 self.table.set_size_request(100, -1)
59 self.row.attach(self.table, 0, 1, 0, 1, xoptions=gtk.FILL|gtk.EXPAND, yoptions=gtk.FILL)
60
61 colid = 0
62 rowid = 0
63 self.line_widgets = {}
64 if icon:
65 self.table.attach(icon, colid, colid + 2, 0, 1)
66 colid = colid + 2
67 if widget:
68 self.table.attach(widget, colid, 20, 0, 10)
69 rowid = 10
70 if varlist and vallist:
71 for row in range(rowid, total_rows):
72 index = row - rowid
73 self.line_widgets[varlist[index]] = self.text2label(varlist[index], vallist[index])
74 self.table.attach(self.line_widgets[varlist[index]], colid, 20, row, row + 1)
75 # pack the button on the right
76 if button:
77 self.bbox = gtk.VBox()
78 self.bbox.pack_start(button, expand=True, fill=False)
79 if button2:
80 self.bbox.pack_start(button2, expand=True, fill=False)
81 self.bbox.set_size_request(150,-1)
82 self.row.attach(self.bbox, 1, 2, 0, 1, xoptions=gtk.FILL, yoptions=gtk.EXPAND)
83
84 def update_line_widgets(self, variable, value):
85 if len(self.line_widgets) == 0:
86 return
87 if not isinstance(self.line_widgets[variable], gtk.Label):
88 return
89 self.line_widgets[variable].set_markup(self.format_line(variable, value))
90
91 def wrap_line(self, inputs):
92 # wrap the long text of inputs
93 wrap_width_chars = 75
94 outputs = ""
95 tmps = inputs
96 less_chars = len(inputs)
97 while (less_chars - wrap_width_chars) > 0:
98 less_chars -= wrap_width_chars
99 outputs += tmps[:wrap_width_chars] + "\n "
100 tmps = inputs[less_chars:]
101 outputs += tmps
102 return outputs
103
104 def format_line(self, variable, value):
105 wraped_value = self.wrap_line(value)
106 markup = "<span weight=\'bold\'>%s</span>" % variable
107 markup += "<span weight=\'normal\' foreground=\'#1c1c1c\' font_desc=\'14px\'>%s</span>" % wraped_value
108 return markup
109
110 def text2label(self, variable, value):
111 # append the name:value to the left box
112 # such as "Name: hob-core-minimal-variant-2011-12-15-beagleboard"
113 label = gtk.Label()
114 label.set_alignment(0.0, 0.5)
115 label.set_markup(self.format_line(variable, value))
116 return label
117
118 class BuildDetailBox (gtk.EventBox):
119 def __init__(self, varlist = None, vallist = None, icon = None, color = HobColors.LIGHT_GRAY):
120 gtk.EventBox.__init__(self)
121
122 # set color
123 style = self.get_style().copy()
124 style.bg[gtk.STATE_NORMAL] = self.get_colormap().alloc_color(color, False, False)
125 self.set_style(style)
126
127 self.hbox = gtk.HBox()
128 self.hbox.set_border_width(10)
129 self.add(self.hbox)
130
131 total_rows = 0
132 if varlist and vallist:
133 # pack the icon and the text on the left
134 total_rows += len(varlist)
135 self.table = gtk.Table(total_rows, 20, True)
136 self.table.set_row_spacings(6)
137 self.table.set_size_request(100, -1)
138 self.hbox.pack_start(self.table, expand=True, fill=True, padding=15)
139
140 colid = 0
141 rowid = 0
142 self.line_widgets = {}
143 if icon:
144 self.table.attach(icon, colid, colid + 2, 0, 1)
145 colid = colid + 2
146 if varlist and vallist:
147 for row in range(rowid, total_rows):
148 index = row - rowid
149 self.line_widgets[varlist[index]] = self.text2label(varlist[index], vallist[index])
150 self.table.attach(self.line_widgets[varlist[index]], colid, 20, row, row + 1)
151
152 def update_line_widgets(self, variable, value):
153 if len(self.line_widgets) == 0:
154 return
155 if not isinstance(self.line_widgets[variable], gtk.Label):
156 return
157 self.line_widgets[variable].set_markup(self.format_line(variable, value))
158
159 def wrap_line(self, inputs):
160 # wrap the long text of inputs
161 wrap_width_chars = 75
162 outputs = ""
163 tmps = inputs
164 less_chars = len(inputs)
165 while (less_chars - wrap_width_chars) > 0:
166 less_chars -= wrap_width_chars
167 outputs += tmps[:wrap_width_chars] + "\n "
168 tmps = inputs[less_chars:]
169 outputs += tmps
170 return outputs
171
172 def format_line(self, variable, value):
173 wraped_value = self.wrap_line(value)
174 markup = "<span weight=\'bold\'>%s</span>" % variable
175 markup += "<span weight=\'normal\' foreground=\'#1c1c1c\' font_desc=\'14px\'>%s</span>" % wraped_value
176 return markup
177
178 def text2label(self, variable, value):
179 # append the name:value to the left box
180 # such as "Name: hob-core-minimal-variant-2011-12-15-beagleboard"
181 label = gtk.Label()
182 label.set_alignment(0.0, 0.5)
183 label.set_markup(self.format_line(variable, value))
184 return label
185
186 def __init__(self, builder):
187 super(ImageDetailsPage, self).__init__(builder, "Image details")
188
189 self.image_store = []
190 self.button_ids = {}
191 self.details_bottom_buttons = gtk.HBox(False, 6)
192 self.image_saved = False
193 self.create_visual_elements()
194 self.name_field_template = ""
195 self.description_field_template = ""
196
197 def create_visual_elements(self):
198 # create visual elements
199 # create the toolbar
200 self.toolbar = gtk.Toolbar()
201 self.toolbar.set_orientation(gtk.ORIENTATION_HORIZONTAL)
202 self.toolbar.set_style(gtk.TOOLBAR_BOTH)
203
204 my_images_button = self.append_toolbar_button(self.toolbar,
205 "Images",
206 hic.ICON_IMAGES_DISPLAY_FILE,
207 hic.ICON_IMAGES_HOVER_FILE,
208 "Open previously built images",
209 self.my_images_button_clicked_cb)
210 settings_button = self.append_toolbar_button(self.toolbar,
211 "Settings",
212 hic.ICON_SETTINGS_DISPLAY_FILE,
213 hic.ICON_SETTINGS_HOVER_FILE,
214 "View additional build settings",
215 self.settings_button_clicked_cb)
216
217 self.details_top_buttons = self.add_onto_top_bar(self.toolbar)
218
219 def _remove_all_widget(self):
220 children = self.get_children() or []
221 for child in children:
222 self.remove(child)
223 children = self.box_group_area.get_children() or []
224 for child in children:
225 self.box_group_area.remove(child)
226 children = self.details_bottom_buttons.get_children() or []
227 for child in children:
228 self.details_bottom_buttons.remove(child)
229
230 def show_page(self, step):
231 self.build_succeeded = (step == self.builder.IMAGE_GENERATED)
232 image_addr = self.builder.parameters.image_addr
233 image_names = self.builder.parameters.image_names
234 if self.build_succeeded:
235 machine = self.builder.configuration.curr_mach
236 base_image = self.builder.recipe_model.get_selected_image()
237 layers = self.builder.configuration.layers
238 pkg_num = "%s" % len(self.builder.package_model.get_selected_packages())
239 log_file = self.builder.current_logfile
240 else:
241 pkg_num = "N/A"
242 log_file = None
243
244 # remove
245 for button_id, button in self.button_ids.items():
246 button.disconnect(button_id)
247 self._remove_all_widget()
248
249 # repack
250 self.pack_start(self.details_top_buttons, expand=False, fill=False)
251 self.pack_start(self.group_align, expand=True, fill=True)
252
253 self.build_result = None
254 if self.image_saved or (self.build_succeeded and self.builder.current_step == self.builder.IMAGE_GENERATING):
255 # building is the previous step
256 icon = gtk.Image()
257 pixmap_path = hic.ICON_INDI_CONFIRM_FILE
258 color = HobColors.RUNNING
259 pix_buffer = gtk.gdk.pixbuf_new_from_file(pixmap_path)
260 icon.set_from_pixbuf(pix_buffer)
261 varlist = [""]
262 if self.image_saved:
263 vallist = ["Your image recipe has been saved"]
264 else:
265 vallist = ["Your image is ready"]
266 self.build_result = self.BuildDetailBox(varlist=varlist, vallist=vallist, icon=icon, color=color)
267 self.box_group_area.pack_start(self.build_result, expand=False, fill=False)
268
269 self.buttonlist = ["Build new image", "Save image recipe", "Run image", "Deploy image"]
270
271 # Name
272 self.image_store = []
273 self.toggled_image = ""
274 default_image_size = 0
275 self.num_toggled = 0
276 i = 0
277 for image_name in image_names:
278 image_size = HobPage._size_to_string(os.stat(os.path.join(image_addr, image_name)).st_size)
279
280 image_attr = ("run" if (self.test_type_runnable(image_name) and self.test_mach_runnable(image_name)) else \
281 ("deploy" if self.test_deployable(image_name) else ""))
282 is_toggled = (image_attr != "")
283
284 if not self.toggled_image:
285 if i == (len(image_names) - 1):
286 is_toggled = True
287 if is_toggled:
288 default_image_size = image_size
289 self.toggled_image = image_name
290
291 split_stuff = image_name.split('.')
292 if "rootfs" in split_stuff:
293 image_type = image_name[(len(split_stuff[0]) + len(".rootfs") + 1):]
294 else:
295 image_type = image_name[(len(split_stuff[0]) + 1):]
296
297 self.image_store.append({'name': image_name,
298 'type': image_type,
299 'size': image_size,
300 'is_toggled': is_toggled,
301 'action_attr': image_attr,})
302
303 i = i + 1
304 self.num_toggled += is_toggled
305
306 is_runnable = self.create_bottom_buttons(self.buttonlist, self.toggled_image)
307
308 # Generated image files info
309 varlist = ["Name: ", "Files created: ", "Directory: "]
310 vallist = []
311
312 vallist.append(image_name.split('.')[0])
313 vallist.append(', '.join(fileitem['type'] for fileitem in self.image_store))
314 vallist.append(image_addr)
315
316 view_files_button = HobAltButton("View files")
317 view_files_button.connect("clicked", self.view_files_clicked_cb, image_addr)
318 view_files_button.set_tooltip_text("Open the directory containing the image files")
319 open_log_button = None
320 if log_file:
321 open_log_button = HobAltButton("Open log")
322 open_log_button.connect("clicked", self.open_log_clicked_cb, log_file)
323 open_log_button.set_tooltip_text("Open the build's log file")
324 self.image_detail = self.DetailBox(varlist=varlist, vallist=vallist, button=view_files_button, button2=open_log_button)
325 self.box_group_area.pack_start(self.image_detail, expand=False, fill=True)
326
327 # The default kernel box for the qemu images
328 self.sel_kernel = ""
329 self.kernel_detail = None
330 if 'qemu' in image_name:
331 self.sel_kernel = self.get_kernel_file_name()
332
333 # varlist = ["Kernel: "]
334 # vallist = []
335 # vallist.append(self.sel_kernel)
336
337 # change_kernel_button = HobAltButton("Change")
338 # change_kernel_button.connect("clicked", self.change_kernel_cb)
339 # change_kernel_button.set_tooltip_text("Change qemu kernel file")
340 # self.kernel_detail = self.DetailBox(varlist=varlist, vallist=vallist, button=change_kernel_button)
341 # self.box_group_area.pack_start(self.kernel_detail, expand=True, fill=True)
342
343 # Machine, Image recipe and Layers
344 layer_num_limit = 15
345 varlist = ["Machine: ", "Image recipe: ", "Layers: "]
346 vallist = []
347 self.setting_detail = None
348 if self.build_succeeded:
349 vallist.append(machine)
350 if self.builder.recipe_model.is_custom_image():
351 if self.builder.configuration.initial_selected_image == self.builder.recipe_model.__custom_image__:
352 base_image ="New image recipe"
353 else:
354 base_image = self.builder.configuration.initial_selected_image + " (edited)"
355 vallist.append(base_image)
356 i = 0
357 for layer in layers:
358 if i > layer_num_limit:
359 break
360 varlist.append(" - ")
361 i += 1
362 vallist.append("")
363 i = 0
364 for layer in layers:
365 if i > layer_num_limit:
366 break
367 elif i == layer_num_limit:
368 vallist.append("and more...")
369 else:
370 vallist.append(layer)
371 i += 1
372
373 edit_config_button = HobAltButton("Edit configuration")
374 edit_config_button.set_tooltip_text("Edit machine and image recipe")
375 edit_config_button.connect("clicked", self.edit_config_button_clicked_cb)
376 self.setting_detail = self.DetailBox(varlist=varlist, vallist=vallist, button=edit_config_button)
377 self.box_group_area.pack_start(self.setting_detail, expand=True, fill=True)
378
379 # Packages included, and Total image size
380 varlist = ["Packages included: ", "Total image size: "]
381 vallist = []
382 vallist.append(pkg_num)
383 vallist.append(default_image_size)
384 self.builder.configuration.image_size = default_image_size
385 self.builder.configuration.image_packages = self.builder.configuration.selected_packages
386 if self.build_succeeded:
387 edit_packages_button = HobAltButton("Edit packages")
388 edit_packages_button.set_tooltip_text("Edit the packages included in your image")
389 edit_packages_button.connect("clicked", self.edit_packages_button_clicked_cb)
390 else: # get to this page from "My images"
391 edit_packages_button = None
392 self.package_detail = self.DetailBox(varlist=varlist, vallist=vallist, button=edit_packages_button)
393 self.box_group_area.pack_start(self.package_detail, expand=True, fill=True)
394
395 # pack the buttons at the bottom, at this time they are already created.
396 if self.build_succeeded:
397 self.box_group_area.pack_end(self.details_bottom_buttons, expand=False, fill=False)
398 else: # for "My images" page
399 self.details_separator = gtk.HSeparator()
400 self.box_group_area.pack_start(self.details_separator, expand=False, fill=False)
401 self.box_group_area.pack_start(self.details_bottom_buttons, expand=False, fill=False)
402
403 self.show_all()
404 if self.kernel_detail and (not is_runnable):
405 self.kernel_detail.hide()
406 self.image_saved = False
407
408 def view_files_clicked_cb(self, button, image_addr):
409 subprocess.call("xdg-open /%s" % image_addr, shell=True)
410
411 def open_log_clicked_cb(self, button, log_file):
412 if log_file:
413 log_file = "file:///" + log_file
414 gtk.show_uri(screen=button.get_screen(), uri=log_file, timestamp=0)
415
416 def refresh_package_detail_box(self, image_size):
417 self.package_detail.update_line_widgets("Total image size: ", image_size)
418
419 def test_type_runnable(self, image_name):
420 type_runnable = False
421 for t in self.builder.parameters.runnable_image_types:
422 if image_name.endswith(t):
423 type_runnable = True
424 break
425 return type_runnable
426
427 def test_mach_runnable(self, image_name):
428 mach_runnable = False
429 for t in self.builder.parameters.runnable_machine_patterns:
430 if t in image_name:
431 mach_runnable = True
432 break
433 return mach_runnable
434
435 def test_deployable(self, image_name):
436 if self.builder.configuration.curr_mach.startswith("qemu"):
437 return False
438 deployable = False
439 for t in self.builder.parameters.deployable_image_types:
440 if image_name.endswith(t):
441 deployable = True
442 break
443 return deployable
444
445 def get_kernel_file_name(self, kernel_addr=""):
446 kernel_name = ""
447
448 if not kernel_addr:
449 kernel_addr = self.builder.parameters.image_addr
450
451 files = [f for f in os.listdir(kernel_addr) if f[0] <> '.']
452 for check_file in files:
453 if check_file.endswith(".bin"):
454 name_splits = check_file.split(".")[0]
455 if self.builder.parameters.kernel_image_type in name_splits.split("-"):
456 kernel_name = check_file
457 break
458
459 return kernel_name
460
461 def show_builded_images_dialog(self, widget, primary_action=""):
462 title = primary_action if primary_action else "Your builded images"
463 dialog = CrumbsDialog(title, self.builder,
464 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
465 dialog.set_border_width(12)
466
467 label = gtk.Label()
468 label.set_use_markup(True)
469 label.set_alignment(0.0, 0.5)
470 label.set_padding(12,0)
471 if primary_action == "Run image":
472 label.set_markup("<span font_desc='12'>Select the image file you want to run:</span>")
473 elif primary_action == "Deploy image":
474 label.set_markup("<span font_desc='12'>Select the image file you want to deploy:</span>")
475 else:
476 label.set_markup("<span font_desc='12'>Select the image file you want to %s</span>" % primary_action)
477 dialog.vbox.pack_start(label, expand=False, fill=False)
478
479 # filter created images as action attribution (deploy or run)
480 action_attr = ""
481 action_images = []
482 for fileitem in self.image_store:
483 action_attr = fileitem['action_attr']
484 if (action_attr == 'run' and primary_action == "Run image") \
485 or (action_attr == 'deploy' and primary_action == "Deploy image"):
486 action_images.append(fileitem)
487
488 # pack the corresponding 'runnable' or 'deploy' radio_buttons, if there has no more than one file.
489 # assume that there does not both have 'deploy' and 'runnable' files in the same building result
490 # in possible as design.
491 curr_row = 0
492 rows = (len(action_images)) if len(action_images) < 10 else 10
493 table = gtk.Table(rows, 10, True)
494 table.set_row_spacings(6)
495 table.set_col_spacing(0, 12)
496 table.set_col_spacing(5, 12)
497
498 sel_parent_btn = None
499 for fileitem in action_images:
500 sel_btn = gtk.RadioButton(sel_parent_btn, fileitem['type'])
501 sel_parent_btn = sel_btn if not sel_parent_btn else sel_parent_btn
502 sel_btn.set_active(fileitem['is_toggled'])
503 sel_btn.connect('toggled', self.table_selected_cb, fileitem)
504 if curr_row < 10:
505 table.attach(sel_btn, 0, 4, curr_row, curr_row + 1, xpadding=24)
506 else:
507 table.attach(sel_btn, 5, 9, curr_row - 10, curr_row - 9, xpadding=24)
508 curr_row += 1
509
510 dialog.vbox.pack_start(table, expand=False, fill=False, padding=6)
511
512 button = dialog.add_button("Cancel", gtk.RESPONSE_CANCEL)
513 HobAltButton.style_button(button)
514
515 if primary_action:
516 button = dialog.add_button(primary_action, gtk.RESPONSE_YES)
517 HobButton.style_button(button)
518
519 dialog.show_all()
520
521 response = dialog.run()
522 dialog.destroy()
523
524 if response != gtk.RESPONSE_YES:
525 return
526
527 for fileitem in self.image_store:
528 if fileitem['is_toggled']:
529 if fileitem['action_attr'] == 'run':
530 self.builder.runqemu_image(fileitem['name'], self.sel_kernel)
531 elif fileitem['action_attr'] == 'deploy':
532 self.builder.deploy_image(fileitem['name'])
533
534 def table_selected_cb(self, tbutton, image):
535 image['is_toggled'] = tbutton.get_active()
536 if image['is_toggled']:
537 self.toggled_image = image['name']
538
539 def change_kernel_cb(self, widget):
540 kernel_path = self.builder.show_load_kernel_dialog()
541 if kernel_path and self.kernel_detail:
542 import os.path
543 self.sel_kernel = os.path.basename(kernel_path)
544 markup = self.kernel_detail.format_line("Kernel: ", self.sel_kernel)
545 label = ((self.kernel_detail.get_children()[0]).get_children()[0]).get_children()[0]
546 label.set_markup(markup)
547
548 def create_bottom_buttons(self, buttonlist, image_name):
549 # Create the buttons at the bottom
550 created = False
551 packed = False
552 self.button_ids = {}
553 is_runnable = False
554
555 # create button "Deploy image"
556 name = "Deploy image"
557 if name in buttonlist and self.test_deployable(image_name):
558 deploy_button = HobButton('Deploy image')
559 #deploy_button.set_size_request(205, 49)
560 deploy_button.set_tooltip_text("Burn a live image to a USB drive or flash memory")
561 deploy_button.set_flags(gtk.CAN_DEFAULT)
562 button_id = deploy_button.connect("clicked", self.deploy_button_clicked_cb)
563 self.button_ids[button_id] = deploy_button
564 self.details_bottom_buttons.pack_end(deploy_button, expand=False, fill=False)
565 created = True
566 packed = True
567
568 name = "Run image"
569 if name in buttonlist and self.test_type_runnable(image_name) and self.test_mach_runnable(image_name):
570 if created == True:
571 # separator
572 #label = gtk.Label(" or ")
573 #self.details_bottom_buttons.pack_end(label, expand=False, fill=False)
574
575 # create button "Run image"
576 run_button = HobAltButton("Run image")
577 else:
578 # create button "Run image" as the primary button
579 run_button = HobButton("Run image")
580 #run_button.set_size_request(205, 49)
581 run_button.set_flags(gtk.CAN_DEFAULT)
582 packed = True
583 run_button.set_tooltip_text("Start up an image with qemu emulator")
584 button_id = run_button.connect("clicked", self.run_button_clicked_cb)
585 self.button_ids[button_id] = run_button
586 self.details_bottom_buttons.pack_end(run_button, expand=False, fill=False)
587 created = True
588 is_runnable = True
589
590 name = "Save image recipe"
591 if name in buttonlist and self.builder.recipe_model.is_custom_image():
592 save_button = HobAltButton("Save image recipe")
593 save_button.set_tooltip_text("Keep your changes saving them as an image recipe")
594 save_button.set_sensitive(not self.image_saved)
595 button_id = save_button.connect("clicked", self.save_button_clicked_cb)
596 self.button_ids[button_id] = save_button
597 self.details_bottom_buttons.pack_end(save_button, expand=False, fill=False)
598
599 name = "Build new image"
600 if name in buttonlist:
601 # create button "Build new image"
602 if packed:
603 build_new_button = HobAltButton("Build new image")
604 else:
605 build_new_button = HobButton("Build new image")
606 build_new_button.set_flags(gtk.CAN_DEFAULT)
607 #build_new_button.set_size_request(205, 49)
608 self.details_bottom_buttons.pack_end(build_new_button, expand=False, fill=False)
609 build_new_button.set_tooltip_text("Create a new image from scratch")
610 button_id = build_new_button.connect("clicked", self.build_new_button_clicked_cb)
611 self.button_ids[button_id] = build_new_button
612
613 return is_runnable
614
615 def deploy_button_clicked_cb(self, button):
616 if self.toggled_image:
617 if self.num_toggled > 1:
618 self.set_sensitive(False)
619 self.show_builded_images_dialog(None, "Deploy image")
620 self.set_sensitive(True)
621 else:
622 self.builder.deploy_image(self.toggled_image)
623
624 def run_button_clicked_cb(self, button):
625 if self.toggled_image:
626 if self.num_toggled > 1:
627 self.set_sensitive(False)
628 self.show_builded_images_dialog(None, "Run image")
629 self.set_sensitive(True)
630 else:
631 self.builder.runqemu_image(self.toggled_image, self.sel_kernel)
632
633 def save_button_clicked_cb(self, button):
634 topdir = self.builder.get_topdir()
635 images_dir = topdir + "/recipes/images/custom/"
636 self.builder.ensure_dir(images_dir)
637
638 self.name_field_template = self.builder.image_configuration_page.custom_image_selected
639 if self.name_field_template:
640 image_path = self.builder.recipe_model.pn_path[self.name_field_template]
641 image_iter = self.builder.recipe_model.get_iter(image_path)
642 self.description_field_template = self.builder.recipe_model.get_value(image_iter, self.builder.recipe_model.COL_DESC)
643 else:
644 self.name_field_template = ""
645
646 dialog = SaveImageDialog(images_dir, self.name_field_template, self.description_field_template,
647 "Save image recipe", self.builder, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
648 response = dialog.run()
649 dialog.destroy()
650
651 def build_new_button_clicked_cb(self, button):
652 self.builder.initiate_new_build_async()
653
654 def edit_config_button_clicked_cb(self, button):
655 self.builder.show_configuration()
656
657 def edit_packages_button_clicked_cb(self, button):
658 self.builder.show_packages()
659
660 def my_images_button_clicked_cb(self, button):
661 self.builder.show_load_my_images_dialog()
662
663 def settings_button_clicked_cb(self, button):
664 # Create an advanced settings dialog
665 response, settings_changed = self.builder.show_simple_settings_dialog()
666 if not response:
667 return
668 if settings_changed:
669 self.builder.reparse_post_adv_settings()
diff --git a/bitbake/lib/bb/ui/crumbs/packageselectionpage.py b/bitbake/lib/bb/ui/crumbs/packageselectionpage.py
new file mode 100755
index 0000000000..7c62b36e6b
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/packageselectionpage.py
@@ -0,0 +1,355 @@
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
23import gtk
24import glib
25from bb.ui.crumbs.hobcolor import HobColors
26from bb.ui.crumbs.hobwidget import HobViewTable, HobNotebook, HobAltButton, HobButton
27from bb.ui.crumbs.hoblistmodel import PackageListModel
28from bb.ui.crumbs.hobpages import HobPage
29
30#
31# PackageSelectionPage
32#
33class PackageSelectionPage (HobPage):
34
35 pages = [
36 {
37 'name' : 'Included packages',
38 'tooltip' : 'The packages currently included for your image',
39 'filter' : { PackageListModel.COL_INC : [True] },
40 'search' : 'Search packages by name',
41 'searchtip' : 'Enter a package name to find it',
42 'columns' : [{
43 'col_name' : 'Package name',
44 'col_id' : PackageListModel.COL_NAME,
45 'col_style': 'text',
46 'col_min' : 100,
47 'col_max' : 300,
48 'expand' : 'True'
49 }, {
50 'col_name' : 'Size',
51 'col_id' : PackageListModel.COL_SIZE,
52 'col_style': 'text',
53 'col_min' : 100,
54 'col_max' : 300,
55 'expand' : 'True'
56 }, {
57 'col_name' : 'Recipe',
58 'col_id' : PackageListModel.COL_RCP,
59 'col_style': 'text',
60 'col_min' : 100,
61 'col_max' : 250,
62 'expand' : 'True'
63 }, {
64 'col_name' : 'Brought in by (+others)',
65 'col_id' : PackageListModel.COL_BINB,
66 'col_style': 'binb',
67 'col_min' : 100,
68 'col_max' : 350,
69 'expand' : 'True'
70 }, {
71 'col_name' : 'Included',
72 'col_id' : PackageListModel.COL_INC,
73 'col_style': 'check toggle',
74 'col_min' : 100,
75 'col_max' : 100
76 }]
77 }, {
78 'name' : 'All packages',
79 'tooltip' : 'All packages that have been built',
80 'filter' : {},
81 'search' : 'Search packages by name',
82 'searchtip' : 'Enter a package name to find it',
83 'columns' : [{
84 'col_name' : 'Package name',
85 'col_id' : PackageListModel.COL_NAME,
86 'col_style': 'text',
87 'col_min' : 100,
88 'col_max' : 400,
89 'expand' : 'True'
90 }, {
91 'col_name' : 'Size',
92 'col_id' : PackageListModel.COL_SIZE,
93 'col_style': 'text',
94 'col_min' : 100,
95 'col_max' : 500,
96 'expand' : 'True'
97 }, {
98 'col_name' : 'Recipe',
99 'col_id' : PackageListModel.COL_RCP,
100 'col_style': 'text',
101 'col_min' : 100,
102 'col_max' : 250,
103 'expand' : 'True'
104 }, {
105 'col_name' : 'Included',
106 'col_id' : PackageListModel.COL_INC,
107 'col_style': 'check toggle',
108 'col_min' : 100,
109 'col_max' : 100
110 }]
111 }
112 ]
113
114 (INCLUDED,
115 ALL) = range(2)
116
117 def __init__(self, builder):
118 super(PackageSelectionPage, self).__init__(builder, "Edit packages")
119
120 # set invisible members
121 self.recipe_model = self.builder.recipe_model
122 self.package_model = self.builder.package_model
123
124 # create visual elements
125 self.create_visual_elements()
126
127 def included_clicked_cb(self, button):
128 self.ins.set_current_page(self.INCLUDED)
129
130 def create_visual_elements(self):
131 self.label = gtk.Label("Packages included: 0\nSelected packages size: 0 MB")
132 self.eventbox = self.add_onto_top_bar(self.label, 73)
133 self.pack_start(self.eventbox, expand=False, fill=False)
134 self.pack_start(self.group_align, expand=True, fill=True)
135
136 # set visible members
137 self.ins = HobNotebook()
138 self.tables = [] # we need to modify table when the dialog is shown
139
140 search_names = []
141 search_tips = []
142 # append the tab
143 for page in self.pages:
144 columns = page['columns']
145 name = page['name']
146 tab = HobViewTable(columns, name)
147 search_names.append(page['search'])
148 search_tips.append(page['searchtip'])
149 filter = page['filter']
150 sort_model = self.package_model.tree_model(filter, initial=True)
151 tab.set_model(sort_model)
152 tab.connect("toggled", self.table_toggled_cb, name)
153 tab.connect("button-release-event", self.button_click_cb)
154 tab.connect("cell-fadeinout-stopped", self.after_fadeout_checkin_include, filter)
155 self.ins.append_page(tab, page['name'], page['tooltip'])
156 self.tables.append(tab)
157
158 self.ins.set_entry(search_names, search_tips)
159 self.ins.search.connect("changed", self.search_entry_changed)
160
161 # add all into the dialog
162 self.box_group_area.pack_start(self.ins, expand=True, fill=True)
163
164 self.button_box = gtk.HBox(False, 6)
165 self.box_group_area.pack_start(self.button_box, expand=False, fill=False)
166
167 self.build_image_button = HobButton('Build image')
168 #self.build_image_button.set_size_request(205, 49)
169 self.build_image_button.set_tooltip_text("Build target image")
170 self.build_image_button.set_flags(gtk.CAN_DEFAULT)
171 self.build_image_button.grab_default()
172 self.build_image_button.connect("clicked", self.build_image_clicked_cb)
173 self.button_box.pack_end(self.build_image_button, expand=False, fill=False)
174
175 self.back_button = HobAltButton('Cancel')
176 self.back_button.connect("clicked", self.back_button_clicked_cb)
177 self.button_box.pack_end(self.back_button, expand=False, fill=False)
178
179 def search_entry_changed(self, entry):
180 text = entry.get_text()
181 if self.ins.search_focus:
182 self.ins.search_focus = False
183 elif self.ins.page_changed:
184 self.ins.page_change = False
185 self.filter_search(entry)
186 elif text not in self.ins.search_names:
187 self.filter_search(entry)
188
189 def filter_search(self, entry):
190 text = entry.get_text()
191 current_tab = self.ins.get_current_page()
192 filter = self.pages[current_tab]['filter']
193 filter[PackageListModel.COL_NAME] = text
194 self.tables[current_tab].set_model(self.package_model.tree_model(filter, search_data=text))
195 if self.package_model.filtered_nb == 0:
196 if not self.ins.get_nth_page(current_tab).top_bar:
197 self.ins.get_nth_page(current_tab).add_no_result_bar(entry)
198 self.ins.get_nth_page(current_tab).top_bar.set_no_show_all(True)
199 self.ins.get_nth_page(current_tab).top_bar.show()
200 self.ins.get_nth_page(current_tab).scroll.hide()
201 else:
202 if self.ins.get_nth_page(current_tab).top_bar:
203 self.ins.get_nth_page(current_tab).top_bar.hide()
204 self.ins.get_nth_page(current_tab).scroll.show()
205 if entry.get_text() == '':
206 entry.set_icon_sensitive(gtk.ENTRY_ICON_SECONDARY, False)
207 else:
208 entry.set_icon_sensitive(gtk.ENTRY_ICON_SECONDARY, True)
209
210 def button_click_cb(self, widget, event):
211 path, col = widget.table_tree.get_cursor()
212 tree_model = widget.table_tree.get_model()
213 if path and col.get_title() != 'Included': # else activation is likely a removal
214 properties = {'binb': '' , 'name': '', 'size':'', 'recipe':'', 'files_list':''}
215 properties['binb'] = tree_model.get_value(tree_model.get_iter(path), PackageListModel.COL_BINB)
216 properties['name'] = tree_model.get_value(tree_model.get_iter(path), PackageListModel.COL_NAME)
217 properties['size'] = tree_model.get_value(tree_model.get_iter(path), PackageListModel.COL_SIZE)
218 properties['recipe'] = tree_model.get_value(tree_model.get_iter(path), PackageListModel.COL_RCP)
219 properties['files_list'] = tree_model.get_value(tree_model.get_iter(path), PackageListModel.COL_FLIST)
220
221 self.builder.show_recipe_property_dialog(properties)
222
223 def open_log_clicked_cb(self, button, log_file):
224 if log_file:
225 log_file = "file:///" + log_file
226 gtk.show_uri(screen=button.get_screen(), uri=log_file, timestamp=0)
227
228 def show_page(self, log_file):
229 children = self.button_box.get_children() or []
230 for child in children:
231 self.button_box.remove(child)
232 # re-packed the buttons as request, add the 'open log' button if build success
233 self.button_box.pack_end(self.build_image_button, expand=False, fill=False)
234 if log_file:
235 open_log_button = HobAltButton("Open log")
236 open_log_button.connect("clicked", self.open_log_clicked_cb, log_file)
237 open_log_button.set_tooltip_text("Open the build's log file")
238 self.button_box.pack_end(open_log_button, expand=False, fill=False)
239 self.button_box.pack_end(self.back_button, expand=False, fill=False)
240 self.show_all()
241
242 def build_image_clicked_cb(self, button):
243 self.builder.parsing_warnings = []
244 self.builder.build_image()
245
246 def refresh_tables(self):
247 self.ins.reset_entry(self.ins.search, 0)
248 for tab in self.tables:
249 index = self.tables.index(tab)
250 filter = self.pages[index]['filter']
251 tab.set_model(self.package_model.tree_model(filter, initial=True))
252
253 def back_button_clicked_cb(self, button):
254 if self.builder.previous_step == self.builder.IMAGE_GENERATED:
255 self.builder.restore_initial_selected_packages()
256 self.refresh_selection()
257 self.builder.show_image_details()
258 else:
259 self.builder.show_configuration()
260 self.refresh_tables()
261
262 def refresh_selection(self):
263 self.builder.configuration.selected_packages = self.package_model.get_selected_packages()
264 self.builder.configuration.user_selected_packages = self.package_model.get_user_selected_packages()
265 selected_packages_num = len(self.builder.configuration.selected_packages)
266 selected_packages_size = self.package_model.get_packages_size()
267 selected_packages_size_str = HobPage._size_to_string(selected_packages_size)
268
269 if self.builder.configuration.image_packages == self.builder.configuration.selected_packages:
270 image_total_size_str = self.builder.configuration.image_size
271 else:
272 image_overhead_factor = self.builder.configuration.image_overhead_factor
273 image_rootfs_size = self.builder.configuration.image_rootfs_size / 1024 # image_rootfs_size is KB
274 image_extra_size = self.builder.configuration.image_extra_size / 1024 # image_extra_size is KB
275 base_size = image_overhead_factor * selected_packages_size
276 image_total_size = max(base_size, image_rootfs_size) + image_extra_size
277 if "zypper" in self.builder.configuration.selected_packages:
278 image_total_size += (51200 * 1024)
279 image_total_size_str = HobPage._size_to_string(image_total_size)
280
281 self.label.set_label("Packages included: %s\nSelected packages size: %s\nEstimated image size: %s" %
282 (selected_packages_num, selected_packages_size_str, image_total_size_str))
283 self.ins.show_indicator_icon("Included packages", selected_packages_num)
284
285 def toggle_item_idle_cb(self, path, view_tree, cell, pagename):
286 if not self.package_model.path_included(path):
287 self.package_model.include_item(item_path=path, binb="User Selected")
288 else:
289 self.pre_fadeout_checkout_include(view_tree)
290 self.package_model.exclude_item(item_path=path)
291 self.render_fadeout(view_tree, cell)
292
293 self.refresh_selection()
294 if not self.builder.customized:
295 self.builder.customized = True
296 self.builder.set_base_image()
297 self.builder.configuration.selected_image = self.recipe_model.__custom_image__
298 self.builder.rcppkglist_populated()
299
300 self.builder.window_sensitive(True)
301 view_model = view_tree.get_model()
302 vpath = self.package_model.convert_path_to_vpath(view_model, path)
303 view_tree.set_cursor(vpath)
304
305 def table_toggled_cb(self, table, cell, view_path, toggled_columnid, view_tree, pagename):
306 # Click to include a package
307 self.builder.window_sensitive(False)
308 view_model = view_tree.get_model()
309 path = self.package_model.convert_vpath_to_path(view_model, view_path)
310 glib.idle_add(self.toggle_item_idle_cb, path, view_tree, cell, pagename)
311
312 def pre_fadeout_checkout_include(self, tree):
313 #after the fadeout the table will be sorted as before
314 self.sort_column_id = self.package_model.sort_column_id
315 self.sort_order = self.package_model.sort_order
316
317 self.package_model.resync_fadeout_column(self.package_model.get_iter_first())
318 # Check out a model which base on the column COL_FADE_INC,
319 # it's save the prev state of column COL_INC before do exclude_item
320 filter = { PackageListModel.COL_FADE_INC : [True]}
321 new_model = self.package_model.tree_model(filter, excluded_items_ahead=True)
322 tree.set_model(new_model)
323 tree.expand_all()
324
325 def get_excluded_rows(self, to_render_cells, model, it):
326 while it:
327 path = model.get_path(it)
328 prev_cell_is_active = model.get_value(it, PackageListModel.COL_FADE_INC)
329 curr_cell_is_active = model.get_value(it, PackageListModel.COL_INC)
330 if (prev_cell_is_active == True) and (curr_cell_is_active == False):
331 to_render_cells.append(path)
332 if model.iter_has_child(it):
333 self.get_excluded_rows(to_render_cells, model, model.iter_children(it))
334 it = model.iter_next(it)
335
336 return to_render_cells
337
338 def render_fadeout(self, tree, cell):
339 if (not cell) or (not tree):
340 return
341 to_render_cells = []
342 view_model = tree.get_model()
343 self.get_excluded_rows(to_render_cells, view_model, view_model.get_iter_first())
344
345 cell.fadeout(tree, 1000, to_render_cells)
346
347 def after_fadeout_checkin_include(self, table, ctrl, cell, tree, filter):
348 self.package_model.sort_column_id = self.sort_column_id
349 self.package_model.sort_order = self.sort_order
350 tree.set_model(self.package_model.tree_model(filter))
351 tree.expand_all()
352
353 def set_packages_curr_tab(self, curr_page):
354 self.ins.set_current_page(curr_page)
355
diff --git a/bitbake/lib/bb/ui/crumbs/persistenttooltip.py b/bitbake/lib/bb/ui/crumbs/persistenttooltip.py
new file mode 100644
index 0000000000..927c194292
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/persistenttooltip.py
@@ -0,0 +1,186 @@
1#
2# BitBake Graphical GTK User Interface
3#
4# Copyright (C) 2012 Intel Corporation
5#
6# Authored by Joshua Lock <josh@linux.intel.com>
7#
8# 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
10# published by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21import gobject
22import gtk
23try:
24 import gconf
25except:
26 pass
27
28class PersistentTooltip(gtk.Window):
29 """
30 A tooltip which persists once shown until the user dismisses it with the Esc
31 key or by clicking the close button.
32
33 # FIXME: the PersistentTooltip should be disabled when the user clicks anywhere off
34 # it. We can't do this with focus-out-event becuase modal ensures we have focus?
35
36 markup: some Pango text markup to display in the tooltip
37 """
38 def __init__(self, markup, parent_win=None):
39 gtk.Window.__init__(self, gtk.WINDOW_POPUP)
40
41 # Inherit the system theme for a tooltip
42 style = gtk.rc_get_style_by_paths(gtk.settings_get_default(),
43 'gtk-tooltip', 'gtk-tooltip', gobject.TYPE_NONE)
44 self.set_style(style)
45
46 # The placement of the close button on the tip should reflect how the
47 # window manager of the users system places close buttons. Try to read
48 # the metacity gconf key to determine whether the close button is on the
49 # left or the right.
50 # In the case that we can't determine the users configuration we default
51 # to close buttons being on the right.
52 __button_right = True
53 try:
54 client = gconf.client_get_default()
55 order = client.get_string("/apps/metacity/general/button_layout")
56 if order and order.endswith(":"):
57 __button_right = False
58 except NameError:
59 pass
60
61 # We need to ensure we're only shown once
62 self.shown = False
63
64 # We don't want any WM decorations
65 self.set_decorated(False)
66 # We don't want to show in the taskbar or window switcher
67 self.set_skip_pager_hint(True)
68 self.set_skip_taskbar_hint(True)
69 # We must be modal to ensure we grab focus when presented from a gtk.Dialog
70 self.set_modal(True)
71
72 self.set_border_width(0)
73 self.set_position(gtk.WIN_POS_MOUSE)
74 self.set_opacity(0.95)
75
76 # Ensure a reasonable minimum size
77 self.set_geometry_hints(self, 100, 50)
78
79 # Set this window as a transient window for parent(main window)
80 if parent_win:
81 self.set_transient_for(parent_win)
82 self.set_destroy_with_parent(True)
83 # Draw our label and close buttons
84 hbox = gtk.HBox(False, 0)
85 hbox.show()
86 self.add(hbox)
87
88 img = gtk.Image()
89 img.set_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_BUTTON)
90
91 self.button = gtk.Button()
92 self.button.set_image(img)
93 self.button.connect("clicked", self._dismiss_cb)
94 self.button.set_flags(gtk.CAN_DEFAULT)
95 self.button.grab_focus()
96 self.button.show()
97 vbox = gtk.VBox(False, 0)
98 vbox.show()
99 vbox.pack_start(self.button, False, False, 0)
100 if __button_right:
101 hbox.pack_end(vbox, True, True, 0)
102 else:
103 hbox.pack_start(vbox, True, True, 0)
104
105 self.set_default(self.button)
106
107 bin = gtk.HBox(True, 6)
108 bin.set_border_width(6)
109 bin.show()
110 self.label = gtk.Label()
111 self.label.set_line_wrap(True)
112 # We want to match the colours of the normal tooltips, as dictated by
113 # the users gtk+-2.0 theme, wherever possible - on some systems this
114 # requires explicitly setting a fg_color for the label which matches the
115 # tooltip_fg_color
116 settings = gtk.settings_get_default()
117 colours = settings.get_property('gtk-color-scheme').split('\n')
118 # remove any empty lines, there's likely to be a trailing one after
119 # calling split on a dictionary-like string
120 colours = filter(None, colours)
121 for col in colours:
122 item, val = col.split(': ')
123 if item == 'tooltip_fg_color':
124 style = self.label.get_style()
125 style.fg[gtk.STATE_NORMAL] = gtk.gdk.color_parse(val)
126 self.label.set_style(style)
127 break # we only care for the tooltip_fg_color
128
129 self.label.set_markup(markup)
130 self.label.show()
131 bin.add(self.label)
132 hbox.pack_end(bin, True, True, 6)
133
134 # add the original URL display for user reference
135 if 'a href' in markup:
136 hbox.set_tooltip_text(self.get_markup_url(markup))
137 hbox.show()
138
139 self.connect("key-press-event", self._catch_esc_cb)
140
141 """
142 Callback when the PersistentTooltip's close button is clicked.
143 Hides the PersistentTooltip.
144 """
145 def _dismiss_cb(self, button):
146 self.hide()
147 return True
148
149 """
150 Callback when the Esc key is detected. Hides the PersistentTooltip.
151 """
152 def _catch_esc_cb(self, widget, event):
153 keyname = gtk.gdk.keyval_name(event.keyval)
154 if keyname == "Escape":
155 self.hide()
156 return True
157
158 """
159 Called to present the PersistentTooltip.
160 Overrides the superclasses show() method to include state tracking.
161 """
162 def show(self):
163 if not self.shown:
164 self.shown = True
165 gtk.Window.show(self)
166
167 """
168 Called to hide the PersistentTooltip.
169 Overrides the superclasses hide() method to include state tracking.
170 """
171 def hide(self):
172 self.shown = False
173 gtk.Window.hide(self)
174
175 """
176 Called to get the hyperlink URL from markup text.
177 """
178 def get_markup_url(self, markup):
179 url = "http:"
180 if markup and type(markup) == str:
181 s = markup
182 if 'http:' in s:
183 import re
184 url = re.search('(http:[^,\\ "]+)', s).group(0)
185
186 return url
diff --git a/bitbake/lib/bb/ui/crumbs/progress.py b/bitbake/lib/bb/ui/crumbs/progress.py
new file mode 100644
index 0000000000..1d28a111b3
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/progress.py
@@ -0,0 +1,23 @@
1import gtk
2
3class ProgressBar(gtk.Dialog):
4 def __init__(self, parent):
5
6 gtk.Dialog.__init__(self, flags=(gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT))
7 self.set_title("Parsing metadata, please wait...")
8 self.set_default_size(500, 0)
9 self.set_transient_for(parent)
10 self.progress = gtk.ProgressBar()
11 self.vbox.pack_start(self.progress)
12 self.show_all()
13
14 def set_text(self, msg):
15 self.progress.set_text(msg)
16
17 def update(self, x, y):
18 self.progress.set_fraction(float(x)/float(y))
19 self.progress.set_text("%2d %%" % (x*100/y))
20
21 def pulse(self):
22 self.progress.set_text("Loading...")
23 self.progress.pulse()
diff --git a/bitbake/lib/bb/ui/crumbs/progressbar.py b/bitbake/lib/bb/ui/crumbs/progressbar.py
new file mode 100644
index 0000000000..3e2c660e4a
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/progressbar.py
@@ -0,0 +1,59 @@
1# BitBake Graphical GTK User Interface
2#
3# Copyright (C) 2011 Intel Corporation
4#
5# Authored by Shane Wang <shane.wang@intel.com>
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License version 2 as
9# published by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program; if not, write to the Free Software Foundation, Inc.,
18# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20import gtk
21from bb.ui.crumbs.hobcolor import HobColors
22
23class HobProgressBar (gtk.ProgressBar):
24 def __init__(self):
25 gtk.ProgressBar.__init__(self)
26 self.set_rcstyle(True)
27 self.percentage = 0
28
29 def set_rcstyle(self, status):
30 rcstyle = gtk.RcStyle()
31 rcstyle.fg[2] = gtk.gdk.Color(HobColors.BLACK)
32 if status == "stop":
33 rcstyle.bg[3] = gtk.gdk.Color(HobColors.WARNING)
34 elif status == "fail":
35 rcstyle.bg[3] = gtk.gdk.Color(HobColors.ERROR)
36 else:
37 rcstyle.bg[3] = gtk.gdk.Color(HobColors.RUNNING)
38 self.modify_style(rcstyle)
39
40 def set_title(self, text=None):
41 if not text:
42 text = ""
43 text += " %.0f%%" % self.percentage
44 self.set_text(text)
45
46 def set_stop_title(self, text=None):
47 if not text:
48 text = ""
49 self.set_text(text)
50
51 def reset(self):
52 self.set_fraction(0)
53 self.set_text("")
54 self.set_rcstyle(True)
55 self.percentage = 0
56
57 def update(self, fraction):
58 self.percentage = int(fraction * 100)
59 self.set_fraction(fraction)
diff --git a/bitbake/lib/bb/ui/crumbs/puccho.glade b/bitbake/lib/bb/ui/crumbs/puccho.glade
new file mode 100644
index 0000000000..d7553a6e14
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/puccho.glade
@@ -0,0 +1,606 @@
1<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
3<!--Generated with glade3 3.4.5 on Mon Nov 10 12:24:12 2008 -->
4<glade-interface>
5 <widget class="GtkDialog" id="build_dialog">
6 <property name="title" translatable="yes">Start a build</property>
7 <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
8 <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
9 <property name="has_separator">False</property>
10 <child internal-child="vbox">
11 <widget class="GtkVBox" id="dialog-vbox1">
12 <property name="visible">True</property>
13 <property name="spacing">2</property>
14 <child>
15 <widget class="GtkTable" id="build_table">
16 <property name="visible">True</property>
17 <property name="border_width">6</property>
18 <property name="n_rows">7</property>
19 <property name="n_columns">3</property>
20 <property name="column_spacing">5</property>
21 <property name="row_spacing">6</property>
22 <child>
23 <widget class="GtkAlignment" id="status_alignment">
24 <property name="visible">True</property>
25 <property name="left_padding">12</property>
26 <child>
27 <widget class="GtkHBox" id="status_hbox">
28 <property name="spacing">6</property>
29 <child>
30 <widget class="GtkImage" id="status_image">
31 <property name="visible">True</property>
32 <property name="no_show_all">True</property>
33 <property name="xalign">0</property>
34 <property name="stock">gtk-dialog-error</property>
35 </widget>
36 <packing>
37 <property name="expand">False</property>
38 <property name="fill">False</property>
39 </packing>
40 </child>
41 <child>
42 <widget class="GtkLabel" id="status_label">
43 <property name="visible">True</property>
44 <property name="xalign">0</property>
45 <property name="label" translatable="yes">If you see this text something is wrong...</property>
46 <property name="use_markup">True</property>
47 <property name="use_underline">True</property>
48 </widget>
49 <packing>
50 <property name="position">1</property>
51 </packing>
52 </child>
53 </widget>
54 </child>
55 </widget>
56 <packing>
57 <property name="right_attach">3</property>
58 <property name="top_attach">2</property>
59 <property name="bottom_attach">3</property>
60 </packing>
61 </child>
62 <child>
63 <widget class="GtkLabel" id="label2">
64 <property name="visible">True</property>
65 <property name="xalign">0</property>
66 <property name="label" translatable="yes">&lt;b&gt;Build configuration&lt;/b&gt;</property>
67 <property name="use_markup">True</property>
68 </widget>
69 <packing>
70 <property name="right_attach">3</property>
71 <property name="top_attach">3</property>
72 <property name="bottom_attach">4</property>
73 <property name="y_options"></property>
74 </packing>
75 </child>
76 <child>
77 <widget class="GtkComboBox" id="image_combo">
78 <property name="visible">True</property>
79 <property name="sensitive">False</property>
80 </widget>
81 <packing>
82 <property name="left_attach">1</property>
83 <property name="right_attach">2</property>
84 <property name="top_attach">6</property>
85 <property name="bottom_attach">7</property>
86 <property name="y_options"></property>
87 </packing>
88 </child>
89 <child>
90 <widget class="GtkLabel" id="image_label">
91 <property name="visible">True</property>
92 <property name="sensitive">False</property>
93 <property name="xalign">0</property>
94 <property name="xpad">12</property>
95 <property name="label" translatable="yes">Image:</property>
96 </widget>
97 <packing>
98 <property name="top_attach">6</property>
99 <property name="bottom_attach">7</property>
100 <property name="y_options"></property>
101 </packing>
102 </child>
103 <child>
104 <widget class="GtkComboBox" id="distribution_combo">
105 <property name="visible">True</property>
106 <property name="sensitive">False</property>
107 </widget>
108 <packing>
109 <property name="left_attach">1</property>
110 <property name="right_attach">2</property>
111 <property name="top_attach">5</property>
112 <property name="bottom_attach">6</property>
113 <property name="y_options"></property>
114 </packing>
115 </child>
116 <child>
117 <widget class="GtkLabel" id="distribution_label">
118 <property name="visible">True</property>
119 <property name="sensitive">False</property>
120 <property name="xalign">0</property>
121 <property name="xpad">12</property>
122 <property name="label" translatable="yes">Distribution:</property>
123 </widget>
124 <packing>
125 <property name="top_attach">5</property>
126 <property name="bottom_attach">6</property>
127 <property name="y_options"></property>
128 </packing>
129 </child>
130 <child>
131 <widget class="GtkComboBox" id="machine_combo">
132 <property name="visible">True</property>
133 <property name="sensitive">False</property>
134 </widget>
135 <packing>
136 <property name="left_attach">1</property>
137 <property name="right_attach">2</property>
138 <property name="top_attach">4</property>
139 <property name="bottom_attach">5</property>
140 <property name="y_options"></property>
141 </packing>
142 </child>
143 <child>
144 <widget class="GtkLabel" id="machine_label">
145 <property name="visible">True</property>
146 <property name="sensitive">False</property>
147 <property name="xalign">0</property>
148 <property name="xpad">12</property>
149 <property name="label" translatable="yes">Machine:</property>
150 </widget>
151 <packing>
152 <property name="top_attach">4</property>
153 <property name="bottom_attach">5</property>
154 <property name="y_options"></property>
155 </packing>
156 </child>
157 <child>
158 <widget class="GtkButton" id="refresh_button">
159 <property name="visible">True</property>
160 <property name="sensitive">False</property>
161 <property name="can_focus">True</property>
162 <property name="receives_default">True</property>
163 <property name="label" translatable="yes">gtk-refresh</property>
164 <property name="use_stock">True</property>
165 <property name="response_id">0</property>
166 </widget>
167 <packing>
168 <property name="left_attach">2</property>
169 <property name="right_attach">3</property>
170 <property name="top_attach">1</property>
171 <property name="bottom_attach">2</property>
172 <property name="y_options"></property>
173 </packing>
174 </child>
175 <child>
176 <widget class="GtkEntry" id="location_entry">
177 <property name="visible">True</property>
178 <property name="can_focus">True</property>
179 <property name="width_chars">32</property>
180 </widget>
181 <packing>
182 <property name="left_attach">1</property>
183 <property name="right_attach">2</property>
184 <property name="top_attach">1</property>
185 <property name="bottom_attach">2</property>
186 <property name="y_options"></property>
187 </packing>
188 </child>
189 <child>
190 <widget class="GtkLabel" id="label3">
191 <property name="visible">True</property>
192 <property name="xalign">0</property>
193 <property name="xpad">12</property>
194 <property name="label" translatable="yes">Location:</property>
195 </widget>
196 <packing>
197 <property name="top_attach">1</property>
198 <property name="bottom_attach">2</property>
199 <property name="y_options"></property>
200 </packing>
201 </child>
202 <child>
203 <widget class="GtkLabel" id="label1">
204 <property name="visible">True</property>
205 <property name="xalign">0</property>
206 <property name="label" translatable="yes">&lt;b&gt;Repository&lt;/b&gt;</property>
207 <property name="use_markup">True</property>
208 </widget>
209 <packing>
210 <property name="right_attach">3</property>
211 <property name="y_options"></property>
212 </packing>
213 </child>
214 <child>
215 <widget class="GtkAlignment" id="alignment1">
216 <property name="visible">True</property>
217 <child>
218 <placeholder/>
219 </child>
220 </widget>
221 <packing>
222 <property name="left_attach">2</property>
223 <property name="right_attach">3</property>
224 <property name="top_attach">4</property>
225 <property name="bottom_attach">5</property>
226 <property name="y_options"></property>
227 </packing>
228 </child>
229 <child>
230 <widget class="GtkAlignment" id="alignment2">
231 <property name="visible">True</property>
232 <child>
233 <placeholder/>
234 </child>
235 </widget>
236 <packing>
237 <property name="left_attach">2</property>
238 <property name="right_attach">3</property>
239 <property name="top_attach">5</property>
240 <property name="bottom_attach">6</property>
241 <property name="y_options"></property>
242 </packing>
243 </child>
244 <child>
245 <widget class="GtkAlignment" id="alignment3">
246 <property name="visible">True</property>
247 <child>
248 <placeholder/>
249 </child>
250 </widget>
251 <packing>
252 <property name="left_attach">2</property>
253 <property name="right_attach">3</property>
254 <property name="top_attach">6</property>
255 <property name="bottom_attach">7</property>
256 <property name="y_options"></property>
257 </packing>
258 </child>
259 </widget>
260 <packing>
261 <property name="position">1</property>
262 </packing>
263 </child>
264 <child internal-child="action_area">
265 <widget class="GtkHButtonBox" id="dialog-action_area1">
266 <property name="visible">True</property>
267 <property name="layout_style">GTK_BUTTONBOX_END</property>
268 <child>
269 <placeholder/>
270 </child>
271 <child>
272 <placeholder/>
273 </child>
274 <child>
275 <placeholder/>
276 </child>
277 </widget>
278 <packing>
279 <property name="expand">False</property>
280 <property name="pack_type">GTK_PACK_END</property>
281 </packing>
282 </child>
283 </widget>
284 </child>
285 </widget>
286 <widget class="GtkDialog" id="dialog2">
287 <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
288 <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
289 <property name="has_separator">False</property>
290 <child internal-child="vbox">
291 <widget class="GtkVBox" id="dialog-vbox2">
292 <property name="visible">True</property>
293 <property name="spacing">2</property>
294 <child>
295 <widget class="GtkTable" id="table2">
296 <property name="visible">True</property>
297 <property name="border_width">6</property>
298 <property name="n_rows">7</property>
299 <property name="n_columns">3</property>
300 <property name="column_spacing">6</property>
301 <property name="row_spacing">6</property>
302 <child>
303 <widget class="GtkLabel" id="label7">
304 <property name="visible">True</property>
305 <property name="xalign">0</property>
306 <property name="label" translatable="yes">&lt;b&gt;Repositories&lt;/b&gt;</property>
307 <property name="use_markup">True</property>
308 </widget>
309 <packing>
310 <property name="right_attach">3</property>
311 <property name="y_options"></property>
312 </packing>
313 </child>
314 <child>
315 <widget class="GtkAlignment" id="alignment4">
316 <property name="visible">True</property>
317 <property name="xalign">0</property>
318 <property name="left_padding">12</property>
319 <child>
320 <widget class="GtkScrolledWindow" id="scrolledwindow1">
321 <property name="visible">True</property>
322 <property name="can_focus">True</property>
323 <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
324 <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
325 <child>
326 <widget class="GtkTreeView" id="treeview1">
327 <property name="visible">True</property>
328 <property name="can_focus">True</property>
329 <property name="headers_clickable">True</property>
330 </widget>
331 </child>
332 </widget>
333 </child>
334 </widget>
335 <packing>
336 <property name="right_attach">3</property>
337 <property name="top_attach">2</property>
338 <property name="bottom_attach">3</property>
339 <property name="y_options"></property>
340 </packing>
341 </child>
342 <child>
343 <widget class="GtkEntry" id="entry1">
344 <property name="visible">True</property>
345 <property name="can_focus">True</property>
346 </widget>
347 <packing>
348 <property name="left_attach">1</property>
349 <property name="right_attach">3</property>
350 <property name="top_attach">1</property>
351 <property name="bottom_attach">2</property>
352 <property name="y_options"></property>
353 </packing>
354 </child>
355 <child>
356 <widget class="GtkLabel" id="label9">
357 <property name="visible">True</property>
358 <property name="xalign">0</property>
359 <property name="label" translatable="yes">&lt;b&gt;Additional packages&lt;/b&gt;</property>
360 <property name="use_markup">True</property>
361 </widget>
362 <packing>
363 <property name="right_attach">3</property>
364 <property name="top_attach">4</property>
365 <property name="bottom_attach">5</property>
366 <property name="y_options"></property>
367 </packing>
368 </child>
369 <child>
370 <widget class="GtkAlignment" id="alignment6">
371 <property name="visible">True</property>
372 <property name="xalign">0</property>
373 <property name="xscale">0</property>
374 <child>
375 <widget class="GtkLabel" id="label8">
376 <property name="visible">True</property>
377 <property name="xalign">0</property>
378 <property name="yalign">0</property>
379 <property name="xpad">12</property>
380 <property name="label" translatable="yes">Location: </property>
381 </widget>
382 </child>
383 </widget>
384 <packing>
385 <property name="top_attach">1</property>
386 <property name="bottom_attach">2</property>
387 <property name="y_options"></property>
388 </packing>
389 </child>
390 <child>
391 <widget class="GtkAlignment" id="alignment7">
392 <property name="visible">True</property>
393 <property name="xalign">1</property>
394 <property name="xscale">0</property>
395 <child>
396 <widget class="GtkHButtonBox" id="hbuttonbox1">
397 <property name="visible">True</property>
398 <property name="spacing">5</property>
399 <child>
400 <widget class="GtkButton" id="button7">
401 <property name="visible">True</property>
402 <property name="can_focus">True</property>
403 <property name="receives_default">True</property>
404 <property name="label" translatable="yes">gtk-remove</property>
405 <property name="use_stock">True</property>
406 <property name="response_id">0</property>
407 </widget>
408 </child>
409 <child>
410 <widget class="GtkButton" id="button6">
411 <property name="visible">True</property>
412 <property name="can_focus">True</property>
413 <property name="receives_default">True</property>
414 <property name="label" translatable="yes">gtk-edit</property>
415 <property name="use_stock">True</property>
416 <property name="response_id">0</property>
417 </widget>
418 <packing>
419 <property name="position">1</property>
420 </packing>
421 </child>
422 <child>
423 <widget class="GtkButton" id="button5">
424 <property name="visible">True</property>
425 <property name="can_focus">True</property>
426 <property name="receives_default">True</property>
427 <property name="label" translatable="yes">gtk-add</property>
428 <property name="use_stock">True</property>
429 <property name="response_id">0</property>
430 </widget>
431 <packing>
432 <property name="position">2</property>
433 </packing>
434 </child>
435 </widget>
436 </child>
437 </widget>
438 <packing>
439 <property name="left_attach">1</property>
440 <property name="right_attach">3</property>
441 <property name="top_attach">3</property>
442 <property name="bottom_attach">4</property>
443 <property name="y_options"></property>
444 </packing>
445 </child>
446 <child>
447 <widget class="GtkAlignment" id="alignment5">
448 <property name="visible">True</property>
449 <child>
450 <placeholder/>
451 </child>
452 </widget>
453 <packing>
454 <property name="top_attach">3</property>
455 <property name="bottom_attach">4</property>
456 <property name="y_options"></property>
457 </packing>
458 </child>
459 <child>
460 <widget class="GtkLabel" id="label10">
461 <property name="visible">True</property>
462 <property name="xalign">0</property>
463 <property name="yalign">0</property>
464 <property name="xpad">12</property>
465 <property name="label" translatable="yes">Search:</property>
466 </widget>
467 <packing>
468 <property name="top_attach">5</property>
469 <property name="bottom_attach">6</property>
470 <property name="y_options"></property>
471 </packing>
472 </child>
473 <child>
474 <widget class="GtkEntry" id="entry2">
475 <property name="visible">True</property>
476 <property name="can_focus">True</property>
477 </widget>
478 <packing>
479 <property name="left_attach">1</property>
480 <property name="right_attach">3</property>
481 <property name="top_attach">5</property>
482 <property name="bottom_attach">6</property>
483 <property name="y_options"></property>
484 </packing>
485 </child>
486 <child>
487 <widget class="GtkAlignment" id="alignment8">
488 <property name="visible">True</property>
489 <property name="xalign">0</property>
490 <property name="left_padding">12</property>
491 <child>
492 <widget class="GtkScrolledWindow" id="scrolledwindow2">
493 <property name="visible">True</property>
494 <property name="can_focus">True</property>
495 <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
496 <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
497 <child>
498 <widget class="GtkTreeView" id="treeview2">
499 <property name="visible">True</property>
500 <property name="can_focus">True</property>
501 <property name="headers_clickable">True</property>
502 </widget>
503 </child>
504 </widget>
505 </child>
506 </widget>
507 <packing>
508 <property name="right_attach">3</property>
509 <property name="top_attach">6</property>
510 <property name="bottom_attach">7</property>
511 <property name="y_options"></property>
512 </packing>
513 </child>
514 </widget>
515 <packing>
516 <property name="position">1</property>
517 </packing>
518 </child>
519 <child internal-child="action_area">
520 <widget class="GtkHButtonBox" id="dialog-action_area2">
521 <property name="visible">True</property>
522 <property name="layout_style">GTK_BUTTONBOX_END</property>
523 <child>
524 <widget class="GtkButton" id="button4">
525 <property name="visible">True</property>
526 <property name="can_focus">True</property>
527 <property name="receives_default">True</property>
528 <property name="label" translatable="yes">gtk-close</property>
529 <property name="use_stock">True</property>
530 <property name="response_id">0</property>
531 </widget>
532 </child>
533 </widget>
534 <packing>
535 <property name="expand">False</property>
536 <property name="pack_type">GTK_PACK_END</property>
537 </packing>
538 </child>
539 </widget>
540 </child>
541 </widget>
542 <widget class="GtkWindow" id="main_window">
543 <child>
544 <widget class="GtkVBox" id="main_window_vbox">
545 <property name="visible">True</property>
546 <child>
547 <widget class="GtkToolbar" id="main_toolbar">
548 <property name="visible">True</property>
549 <child>
550 <widget class="GtkToolButton" id="main_toolbutton_build">
551 <property name="visible">True</property>
552 <property name="label" translatable="yes">Build</property>
553 <property name="stock_id">gtk-execute</property>
554 </widget>
555 <packing>
556 <property name="expand">False</property>
557 </packing>
558 </child>
559 </widget>
560 <packing>
561 <property name="expand">False</property>
562 </packing>
563 </child>
564 <child>
565 <widget class="GtkVPaned" id="vpaned1">
566 <property name="visible">True</property>
567 <property name="can_focus">True</property>
568 <child>
569 <widget class="GtkScrolledWindow" id="results_scrolledwindow">
570 <property name="visible">True</property>
571 <property name="can_focus">True</property>
572 <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
573 <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
574 <child>
575 <placeholder/>
576 </child>
577 </widget>
578 <packing>
579 <property name="resize">False</property>
580 <property name="shrink">True</property>
581 </packing>
582 </child>
583 <child>
584 <widget class="GtkScrolledWindow" id="progress_scrolledwindow">
585 <property name="visible">True</property>
586 <property name="can_focus">True</property>
587 <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
588 <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
589 <child>
590 <placeholder/>
591 </child>
592 </widget>
593 <packing>
594 <property name="resize">True</property>
595 <property name="shrink">True</property>
596 </packing>
597 </child>
598 </widget>
599 <packing>
600 <property name="position">1</property>
601 </packing>
602 </child>
603 </widget>
604 </child>
605 </widget>
606</glade-interface>
diff --git a/bitbake/lib/bb/ui/crumbs/recipeselectionpage.py b/bitbake/lib/bb/ui/crumbs/recipeselectionpage.py
new file mode 100755
index 0000000000..58db43f706
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/recipeselectionpage.py
@@ -0,0 +1,335 @@
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
23import gtk
24import glib
25from bb.ui.crumbs.hobcolor import HobColors
26from bb.ui.crumbs.hobwidget import HobViewTable, HobNotebook, HobAltButton, HobButton
27from bb.ui.crumbs.hoblistmodel import RecipeListModel
28from bb.ui.crumbs.hobpages import HobPage
29
30#
31# RecipeSelectionPage
32#
33class RecipeSelectionPage (HobPage):
34 pages = [
35 {
36 'name' : 'Included recipes',
37 'tooltip' : 'The recipes currently included for your image',
38 'filter' : { RecipeListModel.COL_INC : [True],
39 RecipeListModel.COL_TYPE : ['recipe', 'packagegroup'] },
40 'search' : 'Search recipes by name',
41 'searchtip' : 'Enter a recipe name to find it',
42 'columns' : [{
43 'col_name' : 'Recipe name',
44 'col_id' : RecipeListModel.COL_NAME,
45 'col_style': 'text',
46 'col_min' : 100,
47 'col_max' : 400,
48 'expand' : 'True'
49 }, {
50 'col_name' : 'Group',
51 'col_id' : RecipeListModel.COL_GROUP,
52 'col_style': 'text',
53 'col_min' : 100,
54 'col_max' : 300,
55 'expand' : 'True'
56 }, {
57 'col_name' : 'Brought in by (+others)',
58 'col_id' : RecipeListModel.COL_BINB,
59 'col_style': 'binb',
60 'col_min' : 100,
61 'col_max' : 500,
62 'expand' : 'True'
63 }, {
64 'col_name' : 'Included',
65 'col_id' : RecipeListModel.COL_INC,
66 'col_style': 'check toggle',
67 'col_min' : 100,
68 'col_max' : 100
69 }]
70 }, {
71 'name' : 'All recipes',
72 'tooltip' : 'All recipes in your configured layers',
73 'filter' : { RecipeListModel.COL_TYPE : ['recipe'] },
74 'search' : 'Search recipes by name',
75 'searchtip' : 'Enter a recipe name to find it',
76 'columns' : [{
77 'col_name' : 'Recipe name',
78 'col_id' : RecipeListModel.COL_NAME,
79 'col_style': 'text',
80 'col_min' : 100,
81 'col_max' : 400,
82 'expand' : 'True'
83 }, {
84 'col_name' : 'Group',
85 'col_id' : RecipeListModel.COL_GROUP,
86 'col_style': 'text',
87 'col_min' : 100,
88 'col_max' : 400,
89 'expand' : 'True'
90 }, {
91 'col_name' : 'License',
92 'col_id' : RecipeListModel.COL_LIC,
93 'col_style': 'text',
94 'col_min' : 100,
95 'col_max' : 400,
96 'expand' : 'True'
97 }, {
98 'col_name' : 'Included',
99 'col_id' : RecipeListModel.COL_INC,
100 'col_style': 'check toggle',
101 'col_min' : 100,
102 'col_max' : 100
103 }]
104 }, {
105 'name' : 'Package Groups',
106 'tooltip' : 'All package groups in your configured layers',
107 'filter' : { RecipeListModel.COL_TYPE : ['packagegroup'] },
108 'search' : 'Search package groups by name',
109 'searchtip' : 'Enter a package group name to find it',
110 'columns' : [{
111 'col_name' : 'Package group name',
112 'col_id' : RecipeListModel.COL_NAME,
113 'col_style': 'text',
114 'col_min' : 100,
115 'col_max' : 400,
116 'expand' : 'True'
117 }, {
118 'col_name' : 'Included',
119 'col_id' : RecipeListModel.COL_INC,
120 'col_style': 'check toggle',
121 'col_min' : 100,
122 'col_max' : 100
123 }]
124 }
125 ]
126
127 (INCLUDED,
128 ALL,
129 TASKS) = range(3)
130
131 def __init__(self, builder = None):
132 super(RecipeSelectionPage, self).__init__(builder, "Step 1 of 2: Edit recipes")
133
134 # set invisible members
135 self.recipe_model = self.builder.recipe_model
136
137 # create visual elements
138 self.create_visual_elements()
139
140 def included_clicked_cb(self, button):
141 self.ins.set_current_page(self.INCLUDED)
142
143 def create_visual_elements(self):
144 self.eventbox = self.add_onto_top_bar(None, 73)
145 self.pack_start(self.eventbox, expand=False, fill=False)
146 self.pack_start(self.group_align, expand=True, fill=True)
147
148 # set visible members
149 self.ins = HobNotebook()
150 self.tables = [] # we need modify table when the dialog is shown
151
152 search_names = []
153 search_tips = []
154 # append the tabs in order
155 for page in self.pages:
156 columns = page['columns']
157 name = page['name']
158 tab = HobViewTable(columns, name)
159 search_names.append(page['search'])
160 search_tips.append(page['searchtip'])
161 filter = page['filter']
162 sort_model = self.recipe_model.tree_model(filter, initial=True)
163 tab.set_model(sort_model)
164 tab.connect("toggled", self.table_toggled_cb, name)
165 tab.connect("button-release-event", self.button_click_cb)
166 tab.connect("cell-fadeinout-stopped", self.after_fadeout_checkin_include, filter)
167 self.ins.append_page(tab, page['name'], page['tooltip'])
168 self.tables.append(tab)
169
170 self.ins.set_entry(search_names, search_tips)
171 self.ins.search.connect("changed", self.search_entry_changed)
172
173 # add all into the window
174 self.box_group_area.pack_start(self.ins, expand=True, fill=True)
175
176 button_box = gtk.HBox(False, 6)
177 self.box_group_area.pack_end(button_box, expand=False, fill=False)
178
179 self.build_packages_button = HobButton('Build packages')
180 #self.build_packages_button.set_size_request(205, 49)
181 self.build_packages_button.set_tooltip_text("Build selected recipes into packages")
182 self.build_packages_button.set_flags(gtk.CAN_DEFAULT)
183 self.build_packages_button.grab_default()
184 self.build_packages_button.connect("clicked", self.build_packages_clicked_cb)
185 button_box.pack_end(self.build_packages_button, expand=False, fill=False)
186
187 self.back_button = HobAltButton('Cancel')
188 self.back_button.connect("clicked", self.back_button_clicked_cb)
189 button_box.pack_end(self.back_button, expand=False, fill=False)
190
191 def search_entry_changed(self, entry):
192 text = entry.get_text()
193 if self.ins.search_focus:
194 self.ins.search_focus = False
195 elif self.ins.page_changed:
196 self.ins.page_change = False
197 self.filter_search(entry)
198 elif text not in self.ins.search_names:
199 self.filter_search(entry)
200
201 def filter_search(self, entry):
202 text = entry.get_text()
203 current_tab = self.ins.get_current_page()
204 filter = self.pages[current_tab]['filter']
205 filter[RecipeListModel.COL_NAME] = text
206 self.tables[current_tab].set_model(self.recipe_model.tree_model(filter, search_data=text))
207 if self.recipe_model.filtered_nb == 0:
208 if not self.ins.get_nth_page(current_tab).top_bar:
209 self.ins.get_nth_page(current_tab).add_no_result_bar(entry)
210 self.ins.get_nth_page(current_tab).top_bar.set_no_show_all(True)
211 self.ins.get_nth_page(current_tab).top_bar.show()
212 self.ins.get_nth_page(current_tab).scroll.hide()
213 else:
214 if self.ins.get_nth_page(current_tab).top_bar:
215 self.ins.get_nth_page(current_tab).top_bar.hide()
216 self.ins.get_nth_page(current_tab).scroll.show()
217 if entry.get_text() == '':
218 entry.set_icon_sensitive(gtk.ENTRY_ICON_SECONDARY, False)
219 else:
220 entry.set_icon_sensitive(gtk.ENTRY_ICON_SECONDARY, True)
221
222 def button_click_cb(self, widget, event):
223 path, col = widget.table_tree.get_cursor()
224 tree_model = widget.table_tree.get_model()
225 if path and col.get_title() != 'Included': # else activation is likely a removal
226 properties = {'summary': '', 'name': '', 'version': '', 'revision': '', 'binb': '', 'group': '', 'license': '', 'homepage': '', 'bugtracker': '', 'description': ''}
227 properties['summary'] = tree_model.get_value(tree_model.get_iter(path), RecipeListModel.COL_SUMMARY)
228 properties['name'] = tree_model.get_value(tree_model.get_iter(path), RecipeListModel.COL_NAME)
229 properties['version'] = tree_model.get_value(tree_model.get_iter(path), RecipeListModel.COL_VERSION)
230 properties['revision'] = tree_model.get_value(tree_model.get_iter(path), RecipeListModel.COL_REVISION)
231 properties['binb'] = tree_model.get_value(tree_model.get_iter(path), RecipeListModel.COL_BINB)
232 properties['group'] = tree_model.get_value(tree_model.get_iter(path), RecipeListModel.COL_GROUP)
233 properties['license'] = tree_model.get_value(tree_model.get_iter(path), RecipeListModel.COL_LIC)
234 properties['homepage'] = tree_model.get_value(tree_model.get_iter(path), RecipeListModel.COL_HOMEPAGE)
235 properties['bugtracker'] = tree_model.get_value(tree_model.get_iter(path), RecipeListModel.COL_BUGTRACKER)
236 properties['description'] = tree_model.get_value(tree_model.get_iter(path), RecipeListModel.COL_DESC)
237 self.builder.show_recipe_property_dialog(properties)
238
239 def build_packages_clicked_cb(self, button):
240 self.refresh_tables()
241 self.builder.build_packages()
242
243 def refresh_tables(self):
244 self.ins.reset_entry(self.ins.search, 0)
245 for tab in self.tables:
246 index = self.tables.index(tab)
247 filter = self.pages[index]['filter']
248 tab.set_model(self.recipe_model.tree_model(filter, search_data="", initial=True))
249
250 def back_button_clicked_cb(self, button):
251 self.builder.recipe_model.set_selected_image(self.builder.configuration.initial_selected_image)
252 self.builder.image_configuration_page.update_image_combo(self.builder.recipe_model, self.builder.configuration.initial_selected_image)
253 self.builder.image_configuration_page.update_image_desc()
254 self.builder.show_configuration()
255 self.refresh_tables()
256
257 def refresh_selection(self):
258 self.builder.configuration.selected_image = self.recipe_model.get_selected_image()
259 _, self.builder.configuration.selected_recipes = self.recipe_model.get_selected_recipes()
260 self.ins.show_indicator_icon("Included recipes", len(self.builder.configuration.selected_recipes))
261
262 def toggle_item_idle_cb(self, path, view_tree, cell, pagename):
263 if not self.recipe_model.path_included(path):
264 self.recipe_model.include_item(item_path=path, binb="User Selected", image_contents=False)
265 else:
266 self.pre_fadeout_checkout_include(view_tree, pagename)
267 self.recipe_model.exclude_item(item_path=path)
268 self.render_fadeout(view_tree, cell)
269
270 self.refresh_selection()
271 if not self.builder.customized:
272 self.builder.customized = True
273 self.builder.configuration.selected_image = self.recipe_model.__custom_image__
274 self.builder.rcppkglist_populated()
275
276 self.builder.window_sensitive(True)
277
278 view_model = view_tree.get_model()
279 vpath = self.recipe_model.convert_path_to_vpath(view_model, path)
280 view_tree.set_cursor(vpath)
281
282 def table_toggled_cb(self, table, cell, view_path, toggled_columnid, view_tree, pagename):
283 # Click to include a recipe
284 self.builder.window_sensitive(False)
285 view_model = view_tree.get_model()
286 path = self.recipe_model.convert_vpath_to_path(view_model, view_path)
287 glib.idle_add(self.toggle_item_idle_cb, path, view_tree, cell, pagename)
288
289 def pre_fadeout_checkout_include(self, tree, pagename):
290 #after the fadeout the table will be sorted as before
291 self.sort_column_id = self.recipe_model.sort_column_id
292 self.sort_order = self.recipe_model.sort_order
293
294 #resync the included items to a backup fade include column
295 it = self.recipe_model.get_iter_first()
296 while it:
297 active = self.recipe_model.get_value(it, self.recipe_model.COL_INC)
298 self.recipe_model.set(it, self.recipe_model.COL_FADE_INC, active)
299 it = self.recipe_model.iter_next(it)
300 # Check out a model which base on the column COL_FADE_INC,
301 # it's save the prev state of column COL_INC before do exclude_item
302 filter = { RecipeListModel.COL_FADE_INC:[True] }
303 if pagename == "Included recipes":
304 filter[RecipeListModel.COL_TYPE] = ['recipe', 'packagegroup']
305 elif pagename == "All recipes":
306 filter[RecipeListModel.COL_TYPE] = ['recipe']
307 else:
308 filter[RecipeListModel.COL_TYPE] = ['packagegroup']
309
310 new_model = self.recipe_model.tree_model(filter, excluded_items_ahead=True)
311 tree.set_model(new_model)
312
313 def render_fadeout(self, tree, cell):
314 if (not cell) or (not tree):
315 return
316 to_render_cells = []
317 model = tree.get_model()
318 it = model.get_iter_first()
319 while it:
320 path = model.get_path(it)
321 prev_cell_is_active = model.get_value(it, RecipeListModel.COL_FADE_INC)
322 curr_cell_is_active = model.get_value(it, RecipeListModel.COL_INC)
323 if (prev_cell_is_active == True) and (curr_cell_is_active == False):
324 to_render_cells.append(path)
325 it = model.iter_next(it)
326
327 cell.fadeout(tree, 1000, to_render_cells)
328
329 def after_fadeout_checkin_include(self, table, ctrl, cell, tree, filter):
330 self.recipe_model.sort_column_id = self.sort_column_id
331 self.recipe_model.sort_order = self.sort_order
332 tree.set_model(self.recipe_model.tree_model(filter))
333
334 def set_recipe_curr_tab(self, curr_page):
335 self.ins.set_current_page(curr_page)
diff --git a/bitbake/lib/bb/ui/crumbs/runningbuild.py b/bitbake/lib/bb/ui/crumbs/runningbuild.py
new file mode 100644
index 0000000000..16a955d2b1
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/runningbuild.py
@@ -0,0 +1,551 @@
1
2#
3# BitBake Graphical GTK User Interface
4#
5# Copyright (C) 2008 Intel Corporation
6#
7# Authored by Rob Bradford <rob@linux.intel.com>
8#
9# This program is free software; you can redistribute it and/or modify
10# it under the terms of the GNU General Public License version 2 as
11# published by the Free Software Foundation.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License along
19# with this program; if not, write to the Free Software Foundation, Inc.,
20# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
22import gtk
23import gobject
24import logging
25import time
26import urllib
27import urllib2
28import pango
29from bb.ui.crumbs.hobcolor import HobColors
30from bb.ui.crumbs.hobwidget import HobWarpCellRendererText, HobCellRendererPixbuf
31
32class RunningBuildModel (gtk.TreeStore):
33 (COL_LOG, COL_PACKAGE, COL_TASK, COL_MESSAGE, COL_ICON, COL_COLOR, COL_NUM_ACTIVE) = range(7)
34
35 def __init__ (self):
36 gtk.TreeStore.__init__ (self,
37 gobject.TYPE_STRING,
38 gobject.TYPE_STRING,
39 gobject.TYPE_STRING,
40 gobject.TYPE_STRING,
41 gobject.TYPE_STRING,
42 gobject.TYPE_STRING,
43 gobject.TYPE_INT)
44
45 def failure_model_filter(self, model, it):
46 color = model.get(it, self.COL_COLOR)[0]
47 if not color:
48 return False
49 if color == HobColors.ERROR or color == HobColors.WARNING:
50 return True
51 return False
52
53 def failure_model(self):
54 model = self.filter_new()
55 model.set_visible_func(self.failure_model_filter)
56 return model
57
58 def foreach_cell_func(self, model, path, iter, usr_data=None):
59 if model.get_value(iter, self.COL_ICON) == "gtk-execute":
60 model.set(iter, self.COL_ICON, "")
61
62 def close_task_refresh(self):
63 self.foreach(self.foreach_cell_func, None)
64
65class RunningBuild (gobject.GObject):
66 __gsignals__ = {
67 'build-started' : (gobject.SIGNAL_RUN_LAST,
68 gobject.TYPE_NONE,
69 ()),
70 'build-succeeded' : (gobject.SIGNAL_RUN_LAST,
71 gobject.TYPE_NONE,
72 ()),
73 'build-failed' : (gobject.SIGNAL_RUN_LAST,
74 gobject.TYPE_NONE,
75 ()),
76 'build-complete' : (gobject.SIGNAL_RUN_LAST,
77 gobject.TYPE_NONE,
78 ()),
79 'build-aborted' : (gobject.SIGNAL_RUN_LAST,
80 gobject.TYPE_NONE,
81 ()),
82 'task-started' : (gobject.SIGNAL_RUN_LAST,
83 gobject.TYPE_NONE,
84 (gobject.TYPE_PYOBJECT,)),
85 'log-error' : (gobject.SIGNAL_RUN_LAST,
86 gobject.TYPE_NONE,
87 ()),
88 'log-warning' : (gobject.SIGNAL_RUN_LAST,
89 gobject.TYPE_NONE,
90 ()),
91 'disk-full' : (gobject.SIGNAL_RUN_LAST,
92 gobject.TYPE_NONE,
93 ()),
94 'no-provider' : (gobject.SIGNAL_RUN_LAST,
95 gobject.TYPE_NONE,
96 (gobject.TYPE_PYOBJECT,)),
97 'log' : (gobject.SIGNAL_RUN_LAST,
98 gobject.TYPE_NONE,
99 (gobject.TYPE_STRING, gobject.TYPE_PYOBJECT,)),
100 }
101 pids_to_task = {}
102 tasks_to_iter = {}
103
104 def __init__ (self, sequential=False):
105 gobject.GObject.__init__ (self)
106 self.model = RunningBuildModel()
107 self.sequential = sequential
108 self.buildaborted = False
109
110 def reset (self):
111 self.pids_to_task.clear()
112 self.tasks_to_iter.clear()
113 self.model.clear()
114
115 def handle_event (self, event, pbar=None):
116 # Handle an event from the event queue, this may result in updating
117 # the model and thus the UI. Or it may be to tell us that the build
118 # has finished successfully (or not, as the case may be.)
119
120 parent = None
121 pid = 0
122 package = None
123 task = None
124
125 # If we have a pid attached to this message/event try and get the
126 # (package, task) pair for it. If we get that then get the parent iter
127 # for the message.
128 if hasattr(event, 'pid'):
129 pid = event.pid
130 if hasattr(event, 'process'):
131 pid = event.process
132
133 if pid and pid in self.pids_to_task:
134 (package, task) = self.pids_to_task[pid]
135 parent = self.tasks_to_iter[(package, task)]
136
137 if(isinstance(event, logging.LogRecord)):
138 if event.taskpid == 0 or event.levelno > logging.INFO:
139 self.emit("log", "handle", event)
140 # FIXME: this is a hack! More info in Yocto #1433
141 # http://bugzilla.pokylinux.org/show_bug.cgi?id=1433, temporarily
142 # mask the error message as it's not informative for the user.
143 if event.msg.startswith("Execution of event handler 'run_buildstats' failed"):
144 return
145
146 if (event.levelno < logging.INFO or
147 event.msg.startswith("Running task")):
148 return # don't add these to the list
149
150 if event.levelno >= logging.ERROR:
151 icon = "dialog-error"
152 color = HobColors.ERROR
153 self.emit("log-error")
154 elif event.levelno >= logging.WARNING:
155 icon = "dialog-warning"
156 color = HobColors.WARNING
157 self.emit("log-warning")
158 else:
159 icon = None
160 color = HobColors.OK
161
162 # if we know which package we belong to, we'll append onto its list.
163 # otherwise, we'll jump to the top of the master list
164 if self.sequential or not parent:
165 tree_add = self.model.append
166 else:
167 tree_add = self.model.prepend
168 tree_add(parent,
169 (None,
170 package,
171 task,
172 event.getMessage(),
173 icon,
174 color,
175 0))
176
177 # if there are warnings while processing a package
178 # (parent), mark the task with warning color;
179 # in case there are errors, the updates will be
180 # handled on TaskFailed.
181 if color == HobColors.WARNING and parent:
182 self.model.set(parent, self.model.COL_COLOR, color)
183 if task: #then we have a parent (package), and update it's color
184 self.model.set(self.tasks_to_iter[(package, None)], self.model.COL_COLOR, color)
185
186 elif isinstance(event, bb.build.TaskStarted):
187 (package, task) = (event._package, event._task)
188
189 # Save out this PID.
190 self.pids_to_task[pid] = (package, task)
191
192 # Check if we already have this package in our model. If so then
193 # that can be the parent for the task. Otherwise we create a new
194 # top level for the package.
195 if ((package, None) in self.tasks_to_iter):
196 parent = self.tasks_to_iter[(package, None)]
197 else:
198 if self.sequential:
199 add = self.model.append
200 else:
201 add = self.model.prepend
202 parent = add(None, (None,
203 package,
204 None,
205 "Package: %s" % (package),
206 None,
207 HobColors.OK,
208 0))
209 self.tasks_to_iter[(package, None)] = parent
210
211 # Because this parent package now has an active child mark it as
212 # such.
213 self.model.set(parent, self.model.COL_ICON, "gtk-execute")
214 parent_color = self.model.get(parent, self.model.COL_COLOR)[0]
215 if parent_color != HobColors.ERROR and parent_color != HobColors.WARNING:
216 self.model.set(parent, self.model.COL_COLOR, HobColors.RUNNING)
217
218 # Add an entry in the model for this task
219 i = self.model.append (parent, (None,
220 package,
221 task,
222 "Task: %s" % (task),
223 "gtk-execute",
224 HobColors.RUNNING,
225 0))
226
227 # update the parent's active task count
228 num_active = self.model.get(parent, self.model.COL_NUM_ACTIVE)[0] + 1
229 self.model.set(parent, self.model.COL_NUM_ACTIVE, num_active)
230
231 # Save out the iter so that we can find it when we have a message
232 # that we need to attach to a task.
233 self.tasks_to_iter[(package, task)] = i
234
235 elif isinstance(event, bb.build.TaskBase):
236 self.emit("log", "info", event._message)
237 current = self.tasks_to_iter[(package, task)]
238 parent = self.tasks_to_iter[(package, None)]
239
240 # remove this task from the parent's active count
241 num_active = self.model.get(parent, self.model.COL_NUM_ACTIVE)[0] - 1
242 self.model.set(parent, self.model.COL_NUM_ACTIVE, num_active)
243
244 if isinstance(event, bb.build.TaskFailed):
245 # Mark the task and parent as failed
246 icon = "dialog-error"
247 color = HobColors.ERROR
248
249 logfile = event.logfile
250 if logfile and os.path.exists(logfile):
251 with open(logfile) as f:
252 logdata = f.read()
253 self.model.append(current, ('pastebin', None, None, logdata, 'gtk-error', HobColors.OK, 0))
254
255 for i in (current, parent):
256 self.model.set(i, self.model.COL_ICON, icon,
257 self.model.COL_COLOR, color)
258 else:
259 # Mark the parent package and the task as inactive,
260 # but make sure to preserve error, warnings and active
261 # states
262 parent_color = self.model.get(parent, self.model.COL_COLOR)[0]
263 task_color = self.model.get(current, self.model.COL_COLOR)[0]
264
265 # Mark the task as inactive
266 self.model.set(current, self.model.COL_ICON, None)
267 if task_color != HobColors.ERROR:
268 if task_color == HobColors.WARNING:
269 self.model.set(current, self.model.COL_ICON, 'dialog-warning')
270 else:
271 self.model.set(current, self.model.COL_COLOR, HobColors.OK)
272
273 # Mark the parent as inactive
274 if parent_color != HobColors.ERROR:
275 if parent_color == HobColors.WARNING:
276 self.model.set(parent, self.model.COL_ICON, "dialog-warning")
277 else:
278 self.model.set(parent, self.model.COL_ICON, None)
279 if num_active == 0:
280 self.model.set(parent, self.model.COL_COLOR, HobColors.OK)
281
282 # Clear the iters and the pids since when the task goes away the
283 # pid will no longer be used for messages
284 del self.tasks_to_iter[(package, task)]
285 del self.pids_to_task[pid]
286
287 elif isinstance(event, bb.event.BuildStarted):
288
289 self.emit("build-started")
290 self.model.prepend(None, (None,
291 None,
292 None,
293 "Build Started (%s)" % time.strftime('%m/%d/%Y %H:%M:%S'),
294 None,
295 HobColors.OK,
296 0))
297 if pbar:
298 pbar.update(0, self.progress_total)
299 pbar.set_title(bb.event.getName(event))
300
301 elif isinstance(event, bb.event.BuildCompleted):
302 failures = int (event._failures)
303 self.model.prepend(None, (None,
304 None,
305 None,
306 "Build Completed (%s)" % time.strftime('%m/%d/%Y %H:%M:%S'),
307 None,
308 HobColors.OK,
309 0))
310
311 # Emit the appropriate signal depending on the number of failures
312 if self.buildaborted:
313 self.emit ("build-aborted")
314 self.buildaborted = False
315 elif (failures >= 1):
316 self.emit ("build-failed")
317 else:
318 self.emit ("build-succeeded")
319 # Emit a generic "build-complete" signal for things wishing to
320 # handle when the build is finished
321 self.emit("build-complete")
322 # reset the all cell's icon indicator
323 self.model.close_task_refresh()
324 if pbar:
325 pbar.set_text(event.msg)
326
327 elif isinstance(event, bb.event.DiskFull):
328 self.buildaborted = True
329 self.emit("disk-full")
330
331 elif isinstance(event, bb.command.CommandFailed):
332 self.emit("log", "error", "Command execution failed: %s" % (event.error))
333 if event.error.startswith("Exited with"):
334 # If the command fails with an exit code we're done, emit the
335 # generic signal for the UI to notify the user
336 self.emit("build-complete")
337 # reset the all cell's icon indicator
338 self.model.close_task_refresh()
339
340 elif isinstance(event, bb.event.CacheLoadStarted) and pbar:
341 pbar.set_title("Loading cache")
342 self.progress_total = event.total
343 pbar.update(0, self.progress_total)
344 elif isinstance(event, bb.event.CacheLoadProgress) and pbar:
345 pbar.update(event.current, self.progress_total)
346 elif isinstance(event, bb.event.CacheLoadCompleted) and pbar:
347 pbar.update(self.progress_total, self.progress_total)
348 pbar.hide()
349 elif isinstance(event, bb.event.ParseStarted) and pbar:
350 if event.total == 0:
351 return
352 pbar.set_title("Processing recipes")
353 self.progress_total = event.total
354 pbar.update(0, self.progress_total)
355 elif isinstance(event, bb.event.ParseProgress) and pbar:
356 pbar.update(event.current, self.progress_total)
357 elif isinstance(event, bb.event.ParseCompleted) and pbar:
358 pbar.hide()
359 #using runqueue events as many as possible to update the progress bar
360 elif isinstance(event, bb.runqueue.runQueueTaskFailed):
361 self.emit("log", "error", "Task %s (%s) failed with exit code '%s'" % (event.taskid, event.taskstring, event.exitcode))
362 elif isinstance(event, bb.runqueue.sceneQueueTaskFailed):
363 self.emit("log", "warn", "Setscene task %s (%s) failed with exit code '%s' - real task will be run instead" \
364 % (event.taskid, event.taskstring, event.exitcode))
365 elif isinstance(event, (bb.runqueue.runQueueTaskStarted, bb.runqueue.sceneQueueTaskStarted)):
366 if isinstance(event, bb.runqueue.sceneQueueTaskStarted):
367 self.emit("log", "info", "Running setscene task %d of %d (%s)" % \
368 (event.stats.completed + event.stats.active + event.stats.failed + 1,
369 event.stats.total, event.taskstring))
370 else:
371 if event.noexec:
372 tasktype = 'noexec task'
373 else:
374 tasktype = 'task'
375 self.emit("log", "info", "Running %s %s of %s (ID: %s, %s)" % \
376 (tasktype, event.stats.completed + event.stats.active + event.stats.failed + 1,
377 event.stats.total, event.taskid, event.taskstring))
378 message = {}
379 message["eventname"] = bb.event.getName(event)
380 num_of_completed = event.stats.completed + event.stats.failed
381 message["current"] = num_of_completed
382 message["total"] = event.stats.total
383 message["title"] = ""
384 message["task"] = event.taskstring
385 self.emit("task-started", message)
386 elif isinstance(event, bb.event.MultipleProviders):
387 self.emit("log", "info", "multiple providers are available for %s%s (%s)" \
388 % (event._is_runtime and "runtime " or "", event._item, ", ".join(event._candidates)))
389 self.emit("log", "info", "consider defining a PREFERRED_PROVIDER entry to match %s" % (event._item))
390 elif isinstance(event, bb.event.NoProvider):
391 msg = ""
392 if event._runtime:
393 r = "R"
394 else:
395 r = ""
396
397 extra = ''
398 if not event._reasons:
399 if event._close_matches:
400 extra = ". Close matches:\n %s" % '\n '.join(event._close_matches)
401
402 if event._dependees:
403 msg = "Nothing %sPROVIDES '%s' (but %s %sDEPENDS on or otherwise requires it)%s\n" % (r, event._item, ", ".join(event._dependees), r, extra)
404 else:
405 msg = "Nothing %sPROVIDES '%s'%s\n" % (r, event._item, extra)
406 if event._reasons:
407 for reason in event._reasons:
408 msg += ("%s\n" % reason)
409 self.emit("no-provider", msg)
410 self.emit("log", "error", msg)
411 elif isinstance(event, bb.event.LogExecTTY):
412 icon = "dialog-warning"
413 color = HobColors.WARNING
414 if self.sequential or not parent:
415 tree_add = self.model.append
416 else:
417 tree_add = self.model.prepend
418 tree_add(parent,
419 (None,
420 package,
421 task,
422 event.msg,
423 icon,
424 color,
425 0))
426 else:
427 if not isinstance(event, (bb.event.BuildBase,
428 bb.event.StampUpdate,
429 bb.event.ConfigParsed,
430 bb.event.RecipeParsed,
431 bb.event.RecipePreFinalise,
432 bb.runqueue.runQueueEvent,
433 bb.runqueue.runQueueExitWait,
434 bb.event.OperationStarted,
435 bb.event.OperationCompleted,
436 bb.event.OperationProgress)):
437 self.emit("log", "error", "Unknown event: %s" % (event.error if hasattr(event, 'error') else 'error'))
438
439 return
440
441
442def do_pastebin(text):
443 url = 'http://pastebin.com/api_public.php'
444 params = {'paste_code': text, 'paste_format': 'text'}
445
446 req = urllib2.Request(url, urllib.urlencode(params))
447 response = urllib2.urlopen(req)
448 paste_url = response.read()
449
450 return paste_url
451
452
453class RunningBuildTreeView (gtk.TreeView):
454 __gsignals__ = {
455 "button_press_event" : "override"
456 }
457 def __init__ (self, readonly=False, hob=False):
458 gtk.TreeView.__init__ (self)
459 self.readonly = readonly
460
461 # The icon that indicates whether we're building or failed.
462 # add 'hob' flag because there has not only hob to share this code
463 if hob:
464 renderer = HobCellRendererPixbuf ()
465 else:
466 renderer = gtk.CellRendererPixbuf()
467 col = gtk.TreeViewColumn ("Status", renderer)
468 col.add_attribute (renderer, "icon-name", 4)
469 self.append_column (col)
470
471 # The message of the build.
472 # add 'hob' flag because there has not only hob to share this code
473 if hob:
474 self.message_renderer = HobWarpCellRendererText (col_number=1)
475 else:
476 self.message_renderer = gtk.CellRendererText ()
477 self.message_column = gtk.TreeViewColumn ("Message", self.message_renderer, text=3)
478 self.message_column.add_attribute(self.message_renderer, 'background', 5)
479 self.message_renderer.set_property('editable', (not self.readonly))
480 self.append_column (self.message_column)
481
482 def do_button_press_event(self, event):
483 gtk.TreeView.do_button_press_event(self, event)
484
485 if event.button == 3:
486 selection = super(RunningBuildTreeView, self).get_selection()
487 (model, it) = selection.get_selected()
488 if it is not None:
489 can_paste = model.get(it, model.COL_LOG)[0]
490 if can_paste == 'pastebin':
491 # build a simple menu with a pastebin option
492 menu = gtk.Menu()
493 menuitem = gtk.MenuItem("Copy")
494 menu.append(menuitem)
495 menuitem.connect("activate", self.clipboard_handler, (model, it))
496 menuitem.show()
497 menuitem = gtk.MenuItem("Send log to pastebin")
498 menu.append(menuitem)
499 menuitem.connect("activate", self.pastebin_handler, (model, it))
500 menuitem.show()
501 menu.show()
502 menu.popup(None, None, None, event.button, event.time)
503
504 def _add_to_clipboard(self, clipping):
505 """
506 Add the contents of clipping to the system clipboard.
507 """
508 clipboard = gtk.clipboard_get()
509 clipboard.set_text(clipping)
510 clipboard.store()
511
512 def pastebin_handler(self, widget, data):
513 """
514 Send the log data to pastebin, then add the new paste url to the
515 clipboard.
516 """
517 (model, it) = data
518 paste_url = do_pastebin(model.get(it, model.COL_MESSAGE)[0])
519
520 # @todo Provide visual feedback to the user that it is done and that
521 # it worked.
522 print paste_url
523
524 self._add_to_clipboard(paste_url)
525
526 def clipboard_handler(self, widget, data):
527 """
528 """
529 (model, it) = data
530 message = model.get(it, model.COL_MESSAGE)[0]
531
532 self._add_to_clipboard(message)
533
534class BuildFailureTreeView(gtk.TreeView):
535
536 def __init__ (self):
537 gtk.TreeView.__init__(self)
538 self.set_rules_hint(False)
539 self.set_headers_visible(False)
540 self.get_selection().set_mode(gtk.SELECTION_SINGLE)
541
542 # The icon that indicates whether we're building or failed.
543 renderer = HobCellRendererPixbuf ()
544 col = gtk.TreeViewColumn ("Status", renderer)
545 col.add_attribute (renderer, "icon-name", RunningBuildModel.COL_ICON)
546 self.append_column (col)
547
548 # The message of the build.
549 self.message_renderer = HobWarpCellRendererText (col_number=1)
550 self.message_column = gtk.TreeViewColumn ("Message", self.message_renderer, text=RunningBuildModel.COL_MESSAGE, background=RunningBuildModel.COL_COLOR)
551 self.append_column (self.message_column)
diff --git a/bitbake/lib/bb/ui/crumbs/sanitycheckpage.py b/bitbake/lib/bb/ui/crumbs/sanitycheckpage.py
new file mode 100644
index 0000000000..76ce2ecc23
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/sanitycheckpage.py
@@ -0,0 +1,85 @@
1#!/usr/bin/env python
2#
3# BitBake Graphical GTK User Interface
4#
5# Copyright (C) 2012 Intel Corporation
6#
7# Authored by Bogdan Marinescu <bogdan.a.marinescu@intel.com>
8#
9# This program is free software; you can redistribute it and/or modify
10# it under the terms of the GNU General Public License version 2 as
11# published by the Free Software Foundation.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License along
19# with this program; if not, write to the Free Software Foundation, Inc.,
20# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
22import gtk, gobject
23from bb.ui.crumbs.progressbar import HobProgressBar
24from bb.ui.crumbs.hobwidget import hic
25from bb.ui.crumbs.hobpages import HobPage
26
27#
28# SanityCheckPage
29#
30class SanityCheckPage (HobPage):
31
32 def __init__(self, builder):
33 super(SanityCheckPage, self).__init__(builder)
34 self.running = False
35 self.create_visual_elements()
36 self.show_all()
37
38 def make_label(self, text, bold=True):
39 label = gtk.Label()
40 label.set_alignment(0.0, 0.5)
41 mark = "<span %s>%s</span>" % (self.span_tag('x-large', 'bold') if bold else self.span_tag('medium'), text)
42 label.set_markup(mark)
43 return label
44
45 def start(self):
46 if not self.running:
47 self.running = True
48 gobject.timeout_add(100, self.timer_func)
49
50 def stop(self):
51 self.running = False
52
53 def is_running(self):
54 return self.running
55
56 def timer_func(self):
57 self.progress_bar.pulse()
58 return self.running
59
60 def create_visual_elements(self):
61 # Table'd layout. 'rows' and 'cols' give the table size
62 rows, cols = 30, 50
63 self.table = gtk.Table(rows, cols, True)
64 self.pack_start(self.table, expand=False, fill=False)
65 sx, sy = 2, 2
66 # 'info' icon
67 image = gtk.Image()
68 image.set_from_file(hic.ICON_INFO_DISPLAY_FILE)
69 self.table.attach(image, sx, sx + 2, sy, sy + 3 )
70 image.show()
71 # 'Checking' message
72 label = self.make_label('Hob is checking for correct build system setup')
73 self.table.attach(label, sx + 2, cols, sy, sy + 3, xpadding=5 )
74 label.show()
75 # 'Shouldn't take long' message.
76 label = self.make_label("The check shouldn't take long.", False)
77 self.table.attach(label, sx + 2, cols, sy + 3, sy + 4, xpadding=5)
78 label.show()
79 # Progress bar
80 self.progress_bar = HobProgressBar()
81 self.table.attach(self.progress_bar, sx + 2, cols - 3, sy + 5, sy + 7, xpadding=5)
82 self.progress_bar.show()
83 # All done
84 self.table.show()
85
diff --git a/bitbake/lib/bb/ui/crumbs/utils.py b/bitbake/lib/bb/ui/crumbs/utils.py
new file mode 100644
index 0000000000..939864fa6f
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/utils.py
@@ -0,0 +1,34 @@
1#
2# BitBake UI Utils
3#
4# Copyright (C) 2012 Intel Corporation
5#
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License version 2 as
8# published by the Free Software Foundation.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License along
16# with this program; if not, write to the Free Software Foundation, Inc.,
17# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18
19# This utility method looks for xterm or vte and return the
20# frist to exist, currently we are keeping this simple, but
21# we will likely move the oe.terminal implementation into
22# bitbake which will allow more flexibility.
23
24import os
25import bb
26
27def which_terminal():
28 term = bb.utils.which(os.environ["PATH"], "xterm")
29 if term:
30 return term + " -e "
31 term = bb.utils.which(os.environ["PATH"], "vte")
32 if term:
33 return term + " -c "
34 return None