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.py436
-rwxr-xr-xbitbake/lib/bb/ui/crumbs/builder.py1476
-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.py340
-rw-r--r--bitbake/lib/bb/ui/crumbs/hig/crumbsdialog.py44
-rw-r--r--bitbake/lib/bb/ui/crumbs/hig/crumbsmessagedialog.py95
-rw-r--r--bitbake/lib/bb/ui/crumbs/hig/deployimagedialog.py215
-rw-r--r--bitbake/lib/bb/ui/crumbs/hig/imageselectiondialog.py172
-rw-r--r--bitbake/lib/bb/ui/crumbs/hig/layerselectiondialog.py296
-rw-r--r--bitbake/lib/bb/ui/crumbs/hig/parsingwarningsdialog.py163
-rw-r--r--bitbake/lib/bb/ui/crumbs/hig/propertydialog.py451
-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.py160
-rw-r--r--bitbake/lib/bb/ui/crumbs/hig/settingsuihelper.py122
-rw-r--r--bitbake/lib/bb/ui/crumbs/hig/simplesettingsdialog.py893
-rw-r--r--bitbake/lib/bb/ui/crumbs/hobcolor.py38
-rw-r--r--bitbake/lib/bb/ui/crumbs/hobeventhandler.py624
-rw-r--r--bitbake/lib/bb/ui/crumbs/hoblistmodel.py900
-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.py559
-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.py533
-rw-r--r--bitbake/lib/bb/ui/crumbs/sanitycheckpage.py85
-rw-r--r--bitbake/lib/bb/ui/crumbs/utils.py34
34 files changed, 11514 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..171a7a68ed
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/builddetailspage.py
@@ -0,0 +1,436 @@
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
189 def reset_issues(self):
190 self.num_of_issues = 0
191 self.notebook.hide_indicator_icon("Issues")
192
193 def _remove_all_widget(self):
194 children = self.vbox.get_children() or []
195 for child in children:
196 self.vbox.remove(child)
197 children = self.box_group_area.get_children() or []
198 for child in children:
199 self.box_group_area.remove(child)
200 children = self.get_children() or []
201 for child in children:
202 self.remove(child)
203
204 def add_build_fail_top_bar(self, actions, log_file=None):
205 primary_action = "Edit %s" % actions
206
207 color = HobColors.ERROR
208 build_fail_top = gtk.EventBox()
209 #build_fail_top.set_size_request(-1, 200)
210 build_fail_top.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(color))
211
212 build_fail_tab = gtk.Table(14, 46, True)
213 build_fail_top.add(build_fail_tab)
214
215 icon = gtk.Image()
216 icon_pix_buffer = gtk.gdk.pixbuf_new_from_file(hic.ICON_INDI_ERROR_FILE)
217 icon.set_from_pixbuf(icon_pix_buffer)
218 build_fail_tab.attach(icon, 1, 4, 0, 6)
219
220 label = gtk.Label()
221 label.set_alignment(0.0, 0.5)
222 label.set_markup("<span size='x-large'><b>%s</b></span>" % self.title)
223 build_fail_tab.attach(label, 4, 26, 0, 6)
224
225 label = gtk.Label()
226 label.set_alignment(0.0, 0.5)
227 # Ensure variable disk_full is defined
228 if not hasattr(self.builder, 'disk_full'):
229 self.builder.disk_full = False
230
231 if self.builder.disk_full:
232 markup = "<span size='medium'>There is no disk space left, so Hob cannot finish building your image. Free up some disk space\n"
233 markup += "and restart the build. Check the \"Issues\" tab for more details</span>"
234 label.set_markup(markup)
235 else:
236 label.set_markup("<span size='medium'>Check the \"Issues\" information for more details</span>")
237 build_fail_tab.attach(label, 4, 40, 4, 9)
238
239 # create button 'Edit packages'
240 action_button = HobButton(primary_action)
241 #action_button.set_size_request(-1, 40)
242 action_button.set_tooltip_text("Edit the %s parameters" % actions)
243 action_button.connect('clicked', self.failure_primary_action_button_clicked_cb, primary_action)
244
245 if log_file:
246 open_log_button = HobAltButton("Open log")
247 open_log_button.set_relief(gtk.RELIEF_HALF)
248 open_log_button.set_tooltip_text("Open the build's log file")
249 open_log_button.connect('clicked', self.open_log_button_clicked_cb, log_file)
250
251 attach_pos = (24 if log_file else 14)
252 file_bug_button = HobAltButton('File a bug')
253 file_bug_button.set_relief(gtk.RELIEF_HALF)
254 file_bug_button.set_tooltip_text("Open the Yocto Project bug tracking website")
255 file_bug_button.connect('clicked', self.failure_activate_file_bug_link_cb)
256
257 if not self.builder.disk_full:
258 build_fail_tab.attach(action_button, 4, 13, 9, 12)
259 if log_file:
260 build_fail_tab.attach(open_log_button, 14, 23, 9, 12)
261 build_fail_tab.attach(file_bug_button, attach_pos, attach_pos + 9, 9, 12)
262
263 else:
264 restart_build = HobButton("Restart the build")
265 restart_build.set_tooltip_text("Restart the build")
266 restart_build.connect('clicked', self.restart_build_button_clicked_cb)
267
268 build_fail_tab.attach(restart_build, 4, 13, 9, 12)
269 build_fail_tab.attach(action_button, 14, 23, 9, 12)
270 if log_file:
271 build_fail_tab.attach(open_log_button, attach_pos, attach_pos + 9, 9, 12)
272
273 self.builder.disk_full = False
274 return build_fail_top
275
276 def show_fail_page(self, title):
277 self._remove_all_widget()
278 self.title = "Hob cannot build your %s" % title
279
280 self.build_fail_bar = self.add_build_fail_top_bar(title, self.builder.current_logfile)
281
282 self.pack_start(self.group_align, expand=True, fill=True)
283 self.box_group_area.pack_start(self.build_fail_bar, expand=False, fill=False)
284 self.box_group_area.pack_start(self.vbox, expand=True, fill=True)
285
286 self.vbox.pack_start(self.notebook, expand=True, fill=True)
287 self.show_all()
288 self.notebook.set_page("Issues")
289 self.back_button.hide()
290
291 def add_build_stop_top_bar(self, action, log_file=None):
292 color = HobColors.LIGHT_GRAY
293 build_stop_top = gtk.EventBox()
294 #build_stop_top.set_size_request(-1, 200)
295 build_stop_top.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(color))
296 build_stop_top.set_flags(gtk.CAN_DEFAULT)
297 build_stop_top.grab_default()
298
299 build_stop_tab = gtk.Table(11, 46, True)
300 build_stop_top.add(build_stop_tab)
301
302 icon = gtk.Image()
303 icon_pix_buffer = gtk.gdk.pixbuf_new_from_file(hic.ICON_INFO_HOVER_FILE)
304 icon.set_from_pixbuf(icon_pix_buffer)
305 build_stop_tab.attach(icon, 1, 4, 0, 6)
306
307 label = gtk.Label()
308 label.set_alignment(0.0, 0.5)
309 label.set_markup("<span size='x-large'><b>%s</b></span>" % self.title)
310 build_stop_tab.attach(label, 4, 26, 0, 6)
311
312 action_button = HobButton("Edit %s" % action)
313 action_button.set_size_request(-1, 40)
314 if action == "image":
315 action_button.set_tooltip_text("Edit the image parameters")
316 elif action == "recipes":
317 action_button.set_tooltip_text("Edit the included recipes")
318 elif action == "packages":
319 action_button.set_tooltip_text("Edit the included packages")
320 action_button.connect('clicked', self.stop_primary_action_button_clicked_cb, action)
321 build_stop_tab.attach(action_button, 4, 13, 6, 9)
322
323 if log_file:
324 open_log_button = HobAltButton("Open log")
325 open_log_button.set_relief(gtk.RELIEF_HALF)
326 open_log_button.set_tooltip_text("Open the build's log file")
327 open_log_button.connect('clicked', self.open_log_button_clicked_cb, log_file)
328 build_stop_tab.attach(open_log_button, 14, 23, 6, 9)
329
330 attach_pos = (24 if log_file else 14)
331 build_button = HobAltButton("Build new image")
332 #build_button.set_size_request(-1, 40)
333 build_button.set_tooltip_text("Create a new image from scratch")
334 build_button.connect('clicked', self.new_image_button_clicked_cb)
335 build_stop_tab.attach(build_button, attach_pos, attach_pos + 9, 6, 9)
336
337 return build_stop_top, action_button
338
339 def show_stop_page(self, action):
340 self._remove_all_widget()
341 self.title = "Build stopped"
342 self.build_stop_bar, action_button = self.add_build_stop_top_bar(action, self.builder.current_logfile)
343
344 self.pack_start(self.group_align, expand=True, fill=True)
345 self.box_group_area.pack_start(self.build_stop_bar, expand=False, fill=False)
346 self.box_group_area.pack_start(self.vbox, expand=True, fill=True)
347
348 self.vbox.pack_start(self.notebook, expand=True, fill=True)
349 self.show_all()
350 self.back_button.hide()
351 return action_button
352
353 def show_page(self, step):
354 self._remove_all_widget()
355 if step == self.builder.PACKAGE_GENERATING or step == self.builder.FAST_IMAGE_GENERATING:
356 self.title = "Building packages ..."
357 else:
358 self.title = "Building image ..."
359 self.build_details_top = self.add_onto_top_bar(None)
360 self.pack_start(self.build_details_top, expand=False, fill=False)
361 self.pack_start(self.group_align, expand=True, fill=True)
362
363 self.box_group_area.pack_start(self.vbox, expand=True, fill=True)
364
365 self.progress_bar.reset()
366 self.config_tv.reset()
367 self.vbox.pack_start(self.progress_box, expand=False, fill=False)
368
369 self.vbox.pack_start(self.notebook, expand=True, fill=True)
370
371 self.box_group_area.pack_end(self.button_box, expand=False, fill=False)
372 self.show_all()
373 self.notebook.set_page("Log")
374 self.back_button.hide()
375
376 self.reset_build_status()
377 self.reset_issues()
378
379 def update_progress_bar(self, title, fraction, status=None):
380 self.progress_bar.update(fraction)
381 self.progress_bar.set_title(title)
382 self.progress_bar.set_rcstyle(status)
383
384 def back_button_clicked_cb(self, button):
385 self.builder.show_configuration()
386
387 def new_image_button_clicked_cb(self, button):
388 self.builder.reset()
389
390 def show_back_button(self):
391 self.back_button.show()
392
393 def stop_button_clicked_cb(self, button):
394 self.builder.stop_build()
395
396 def hide_stop_button(self):
397 self.stop_button.set_sensitive(False)
398 self.stop_button.hide()
399
400 def scroll_to_present_row(self, model, path, iter, v_adj, treeview):
401 if treeview and v_adj:
402 if path[0] > self.endpath[0]: # check the event is a new row append or not
403 self.endpath = path
404 # check the gtk.adjustment position is at end boundary or not
405 if (v_adj.upper <= v_adj.page_size) or (v_adj.value == v_adj.upper - v_adj.page_size):
406 treeview.scroll_to_cell(path)
407
408 def show_configurations(self, configurations, params):
409 self.config_tv.show(configurations, params)
410
411 def failure_primary_action_button_clicked_cb(self, button, action):
412 if "Edit recipes" in action:
413 self.builder.show_recipes()
414 elif "Edit packages" in action:
415 self.builder.show_packages()
416 elif "Edit image" in action:
417 self.builder.show_configuration()
418
419 def restart_build_button_clicked_cb(self, button):
420 self.builder.just_bake()
421
422 def stop_primary_action_button_clicked_cb(self, button, action):
423 if "recipes" in action:
424 self.builder.show_recipes()
425 elif "packages" in action:
426 self.builder.show_packages(ask=False)
427 elif "image" in action:
428 self.builder.show_configuration()
429
430 def open_log_button_clicked_cb(self, button, log_file):
431 if log_file:
432 log_file = "file:///" + log_file
433 gtk.show_uri(screen=button.get_screen(), uri=log_file, timestamp=0)
434
435 def failure_activate_file_bug_link_cb(self, button):
436 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..ab6750b741
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/builder.py
@@ -0,0 +1,1476 @@
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 handler.set_var_in_file("enable_proxy", self.enable_proxy, "local.conf")
231 handler.set_var_in_file("use_same_proxy", self.same_proxy, "local.conf")
232 handler.set_var_in_file("http_proxy", self.combine_proxy("http"), "local.conf")
233 handler.set_var_in_file("https_proxy", self.combine_proxy("https"), "local.conf")
234 handler.set_var_in_file("ftp_proxy", self.combine_proxy("ftp"), "local.conf")
235 handler.set_var_in_file("all_proxy", self.combine_proxy("socks"), "local.conf")
236 handler.set_var_in_file("CVS_PROXY_HOST", self.combine_host_only("cvs"), "local.conf")
237 handler.set_var_in_file("CVS_PROXY_PORT", self.combine_port_only("cvs"), "local.conf")
238
239 def __str__(self):
240 s = "VERSION: '%s', BBLAYERS: '%s', MACHINE: '%s', DISTRO: '%s', DL_DIR: '%s'," % \
241 (hobVer, " ".join(self.layers), self.curr_mach, self.curr_distro, self.dldir )
242 s += "SSTATE_DIR: '%s', SSTATE_MIRROR: '%s', PARALLEL_MAKE: '-j %s', BB_NUMBER_THREADS: '%s', PACKAGE_CLASSES: '%s', " % \
243 (self.sstatedir, self.sstatemirror, self.pmake, self.bbthread, " ".join(["package_" + i for i in self.curr_package_format.split()]))
244 s += "IMAGE_ROOTFS_SIZE: '%s', IMAGE_EXTRA_SPACE: '%s', INCOMPATIBLE_LICENSE: '%s', SDKMACHINE: '%s', CONF_VERSION: '%s', " % \
245 (self.image_rootfs_size, self.image_extra_size, self.incompat_license, self.curr_sdk_machine, self.conf_version)
246 s += "LCONF_VERSION: '%s', EXTRA_SETTING: '%s', TOOLCHAIN_BUILD: '%s', IMAGE_FSTYPES: '%s', __SELECTED_IMAGE__: '%s', " % \
247 (self.lconf_version, self.extra_setting, self.toolchain_build, self.image_fstypes, self.selected_image)
248 s += "DEPENDS: '%s', IMAGE_INSTALL: '%s', enable_proxy: '%s', use_same_proxy: '%s', http_proxy: '%s', " % \
249 (self.selected_recipes, self.user_selected_packages, self.enable_proxy, self.same_proxy, self.combine_proxy("http"))
250 s += "https_proxy: '%s', ftp_proxy: '%s', all_proxy: '%s', CVS_PROXY_HOST: '%s', CVS_PROXY_PORT: '%s'" % \
251 (self.combine_proxy("https"), self.combine_proxy("ftp"), self.combine_proxy("socks"),
252 self.combine_host_only("cvs"), self.combine_port_only("cvs"))
253 return s
254
255class Parameters:
256 '''Represents other variables like available machines, etc.'''
257
258 def __init__(self):
259 # Variables
260 self.max_threads = 65535
261 self.core_base = ""
262 self.image_addr = ""
263 self.image_types = []
264 self.runnable_image_types = []
265 self.runnable_machine_patterns = []
266 self.deployable_image_types = []
267 self.tmpdir = ""
268
269 self.all_machines = []
270 self.all_package_formats = []
271 self.all_distros = []
272 self.all_sdk_machines = []
273 self.all_layers = []
274 self.image_names = []
275 self.image_white_pattern = ""
276 self.image_black_pattern = ""
277
278 # for build log to show
279 self.bb_version = ""
280 self.target_arch = ""
281 self.target_os = ""
282 self.distro_version = ""
283 self.tune_pkgarch = ""
284
285 def update(self, params):
286 self.max_threads = params["max_threads"]
287 self.core_base = params["core_base"]
288 self.image_addr = params["image_addr"]
289 self.image_types = params["image_types"].split()
290 self.runnable_image_types = params["runnable_image_types"].split()
291 self.runnable_machine_patterns = params["runnable_machine_patterns"].split()
292 self.deployable_image_types = params["deployable_image_types"].split()
293 self.tmpdir = params["tmpdir"]
294 self.image_white_pattern = params["image_white_pattern"]
295 self.image_black_pattern = params["image_black_pattern"]
296 self.kernel_image_type = params["kernel_image_type"]
297 # for build log to show
298 self.bb_version = params["bb_version"]
299 self.target_arch = params["target_arch"]
300 self.target_os = params["target_os"]
301 self.distro_version = params["distro_version"]
302 self.tune_pkgarch = params["tune_pkgarch"]
303
304def hob_conf_filter(fn, data):
305 if fn.endswith("/local.conf"):
306 distro = data.getVar("DISTRO_HOB")
307 if distro:
308 if distro != "defaultsetup":
309 data.setVar("DISTRO", distro)
310 else:
311 data.delVar("DISTRO")
312
313 keys = ["MACHINE_HOB", "SDKMACHINE_HOB", "PACKAGE_CLASSES_HOB", \
314 "BB_NUMBER_THREADS_HOB", "PARALLEL_MAKE_HOB", "DL_DIR_HOB", \
315 "SSTATE_DIR_HOB", "SSTATE_MIRRORS_HOB", "INCOMPATIBLE_LICENSE_HOB"]
316 for key in keys:
317 var_hob = data.getVar(key)
318 if var_hob:
319 data.setVar(key.split("_HOB")[0], var_hob)
320 return
321
322 if fn.endswith("/bblayers.conf"):
323 layers = data.getVar("BBLAYERS_HOB")
324 if layers:
325 data.setVar("BBLAYERS", layers)
326 return
327
328class Builder(gtk.Window):
329
330 (INITIAL_CHECKS,
331 MACHINE_SELECTION,
332 RCPPKGINFO_POPULATING,
333 RCPPKGINFO_POPULATED,
334 BASEIMG_SELECTED,
335 RECIPE_SELECTION,
336 PACKAGE_GENERATING,
337 PACKAGE_GENERATED,
338 PACKAGE_SELECTION,
339 FAST_IMAGE_GENERATING,
340 IMAGE_GENERATING,
341 IMAGE_GENERATED,
342 MY_IMAGE_OPENED,
343 BACK,
344 END_NOOP) = range(15)
345
346 (SANITY_CHECK,
347 IMAGE_CONFIGURATION,
348 RECIPE_DETAILS,
349 BUILD_DETAILS,
350 PACKAGE_DETAILS,
351 IMAGE_DETAILS,
352 END_TAB) = range(7)
353
354 __step2page__ = {
355 INITIAL_CHECKS : SANITY_CHECK,
356 MACHINE_SELECTION : IMAGE_CONFIGURATION,
357 RCPPKGINFO_POPULATING : IMAGE_CONFIGURATION,
358 RCPPKGINFO_POPULATED : IMAGE_CONFIGURATION,
359 BASEIMG_SELECTED : IMAGE_CONFIGURATION,
360 RECIPE_SELECTION : RECIPE_DETAILS,
361 PACKAGE_GENERATING : BUILD_DETAILS,
362 PACKAGE_GENERATED : PACKAGE_DETAILS,
363 PACKAGE_SELECTION : PACKAGE_DETAILS,
364 FAST_IMAGE_GENERATING : BUILD_DETAILS,
365 IMAGE_GENERATING : BUILD_DETAILS,
366 IMAGE_GENERATED : IMAGE_DETAILS,
367 MY_IMAGE_OPENED : IMAGE_DETAILS,
368 END_NOOP : None,
369 }
370
371 SANITY_CHECK_MIN_DISPLAY_TIME = 5
372
373 def __init__(self, hobHandler, recipe_model, package_model):
374 super(Builder, self).__init__()
375
376 self.hob_image = "hob-image"
377 self.hob_toolchain = "hob-toolchain"
378
379 # handler
380 self.handler = hobHandler
381
382 # logger
383 self.logger = logging.getLogger("BitBake")
384 self.consolelog = None
385 self.current_logfile = None
386
387 # configuration and parameters
388 self.configuration = Configuration()
389 self.parameters = Parameters()
390
391 # build step
392 self.current_step = None
393 self.previous_step = None
394
395 self.stopping = False
396
397 # recipe model and package model
398 self.recipe_model = recipe_model
399 self.package_model = package_model
400
401 # Indicate whether user has customized the image
402 self.customized = False
403
404 # Indicate whether the UI is working
405 self.sensitive = True
406
407 # Indicate whether the sanity check ran
408 self.sanity_checked = False
409
410 # save parsing warnings
411 self.parsing_warnings = []
412
413 # create visual elements
414 self.create_visual_elements()
415
416 # connect the signals to functions
417 self.connect("delete-event", self.destroy_window_cb)
418 self.recipe_model.connect ("recipe-selection-changed", self.recipelist_changed_cb)
419 self.package_model.connect("package-selection-changed", self.packagelist_changed_cb)
420 self.handler.connect("config-updated", self.handler_config_updated_cb)
421 self.handler.connect("package-formats-updated", self.handler_package_formats_updated_cb)
422 self.handler.connect("parsing-started", self.handler_parsing_started_cb)
423 self.handler.connect("parsing", self.handler_parsing_cb)
424 self.handler.connect("parsing-completed", self.handler_parsing_completed_cb)
425 self.handler.build.connect("build-started", self.handler_build_started_cb)
426 self.handler.build.connect("build-succeeded", self.handler_build_succeeded_cb)
427 self.handler.build.connect("build-failed", self.handler_build_failed_cb)
428 self.handler.build.connect("build-aborted", self.handler_build_aborted_cb)
429 self.handler.build.connect("task-started", self.handler_task_started_cb)
430 self.handler.build.connect("disk-full", self.handler_disk_full_cb)
431 self.handler.build.connect("log-error", self.handler_build_failure_cb)
432 self.handler.build.connect("log-warning", self.handler_build_failure_cb)
433 self.handler.build.connect("log", self.handler_build_log_cb)
434 self.handler.build.connect("no-provider", self.handler_no_provider_cb)
435 self.handler.connect("generating-data", self.handler_generating_data_cb)
436 self.handler.connect("data-generated", self.handler_data_generated_cb)
437 self.handler.connect("command-succeeded", self.handler_command_succeeded_cb)
438 self.handler.connect("command-failed", self.handler_command_failed_cb)
439 self.handler.connect("parsing-warning", self.handler_parsing_warning_cb)
440 self.handler.connect("sanity-failed", self.handler_sanity_failed_cb)
441 self.handler.connect("recipe-populated", self.handler_recipe_populated_cb)
442 self.handler.connect("package-populated", self.handler_package_populated_cb)
443
444 self.handler.append_to_bbfiles("${TOPDIR}/recipes/images/*.bb")
445 self.initiate_new_build_async()
446
447 signal.signal(signal.SIGINT, self.event_handle_SIGINT)
448
449 def create_visual_elements(self):
450 self.set_title("Hob")
451 self.set_icon_name("applications-development")
452 self.set_resizable(True)
453
454 try:
455 window_width = self.get_screen().get_width()
456 window_height = self.get_screen().get_height()
457 except AttributeError:
458 print "Please set DISPLAY variable before running Hob."
459 sys.exit(1)
460
461 if window_width >= hwc.MAIN_WIN_WIDTH:
462 window_width = hwc.MAIN_WIN_WIDTH
463 window_height = hwc.MAIN_WIN_HEIGHT
464 self.set_size_request(window_width, window_height)
465
466 self.vbox = gtk.VBox(False, 0)
467 self.vbox.set_border_width(0)
468 self.add(self.vbox)
469
470 # create pages
471 self.image_configuration_page = ImageConfigurationPage(self)
472 self.recipe_details_page = RecipeSelectionPage(self)
473 self.build_details_page = BuildDetailsPage(self)
474 self.package_details_page = PackageSelectionPage(self)
475 self.image_details_page = ImageDetailsPage(self)
476 self.sanity_check_page = SanityCheckPage(self)
477 self.display_sanity_check = False
478 self.sanity_check_post_func = False
479 self.had_network_error = False
480
481 self.nb = gtk.Notebook()
482 self.nb.set_show_tabs(False)
483 self.nb.insert_page(self.sanity_check_page, None, self.SANITY_CHECK)
484 self.nb.insert_page(self.image_configuration_page, None, self.IMAGE_CONFIGURATION)
485 self.nb.insert_page(self.recipe_details_page, None, self.RECIPE_DETAILS)
486 self.nb.insert_page(self.build_details_page, None, self.BUILD_DETAILS)
487 self.nb.insert_page(self.package_details_page, None, self.PACKAGE_DETAILS)
488 self.nb.insert_page(self.image_details_page, None, self.IMAGE_DETAILS)
489 self.vbox.pack_start(self.nb, expand=True, fill=True)
490
491 self.show_all()
492 self.nb.set_current_page(0)
493
494 def sanity_check_timeout(self):
495 # The minimum time for showing the 'sanity check' page has passe
496 # If someone set the 'sanity_check_post_step' meanwhile, execute it now
497 self.display_sanity_check = False
498 if self.sanity_check_post_func:
499 temp = self.sanity_check_post_func
500 self.sanity_check_post_func = None
501 temp()
502 return False
503
504 def show_sanity_check_page(self):
505 # This window must stay on screen for at least 5 seconds, according to the design document
506 self.nb.set_current_page(self.SANITY_CHECK)
507 self.sanity_check_post_step = None
508 self.display_sanity_check = True
509 self.sanity_check_page.start()
510 gobject.timeout_add(self.SANITY_CHECK_MIN_DISPLAY_TIME * 1000, self.sanity_check_timeout)
511
512 def execute_after_sanity_check(self, func):
513 if not self.display_sanity_check:
514 func()
515 else:
516 self.sanity_check_post_func = func
517
518 def generate_configuration(self):
519 if not self.sanity_checked:
520 self.show_sanity_check_page()
521 self.handler.generate_configuration()
522
523 def initiate_new_build_async(self):
524 self.configuration.selected_image = None
525 self.switch_page(self.MACHINE_SELECTION)
526 self.handler.init_cooker()
527 self.handler.set_extra_inherit("image_types")
528 self.generate_configuration()
529
530 def update_config_async(self):
531 self.switch_page(self.MACHINE_SELECTION)
532 self.set_user_config()
533 self.generate_configuration()
534
535 def sanity_check(self):
536 self.handler.trigger_sanity_check()
537
538 def populate_recipe_package_info_async(self):
539 self.switch_page(self.RCPPKGINFO_POPULATING)
540 # Parse recipes
541 self.set_user_config()
542 self.handler.generate_recipes()
543
544 def generate_packages_async(self, log = False):
545 self.switch_page(self.PACKAGE_GENERATING)
546 if log:
547 self.current_logfile = self.handler.get_logfile()
548 self.do_log(self.current_logfile)
549 # Build packages
550 _, all_recipes = self.recipe_model.get_selected_recipes()
551 self.set_user_config()
552 self.handler.reset_build()
553 self.handler.generate_packages(all_recipes, self.configuration.default_task)
554
555 def restore_initial_selected_packages(self):
556 self.package_model.set_selected_packages(self.configuration.initial_user_selected_packages, True)
557 self.package_model.set_selected_packages(self.configuration.initial_selected_packages)
558 for package in self.configuration.selected_packages:
559 if package not in self.configuration.initial_selected_packages:
560 self.package_model.exclude_item(self.package_model.find_path_for_item(package))
561
562 def fast_generate_image_async(self, log = False):
563 self.switch_page(self.FAST_IMAGE_GENERATING)
564 if log:
565 self.current_logfile = self.handler.get_logfile()
566 self.do_log(self.current_logfile)
567 # Build packages
568 _, all_recipes = self.recipe_model.get_selected_recipes()
569 self.set_user_config()
570 self.handler.reset_build()
571 self.handler.generate_packages(all_recipes, self.configuration.default_task)
572
573 def generate_image_async(self, cont = False):
574 self.switch_page(self.IMAGE_GENERATING)
575 self.handler.reset_build()
576 if not cont:
577 self.current_logfile = self.handler.get_logfile()
578 self.do_log(self.current_logfile)
579 # Build image
580 self.set_user_config()
581 toolchain_packages = []
582 base_image = None
583 if self.configuration.toolchain_build:
584 toolchain_packages = self.package_model.get_selected_packages_toolchain()
585 if self.configuration.selected_image == self.recipe_model.__custom_image__:
586 packages = self.package_model.get_selected_packages()
587 image = self.hob_image
588 base_image = self.configuration.initial_selected_image
589 else:
590 packages = []
591 image = self.configuration.selected_image
592 self.handler.generate_image(image,
593 base_image,
594 self.hob_toolchain,
595 packages,
596 toolchain_packages,
597 self.configuration.default_task)
598
599 def generate_new_image(self, image, description):
600 base_image = self.configuration.initial_selected_image
601 if base_image == self.recipe_model.__custom_image__:
602 base_image = None
603 packages = self.package_model.get_selected_packages()
604 self.handler.generate_new_image(image, base_image, packages, description)
605
606 def ensure_dir(self, directory):
607 self.handler.ensure_dir(directory)
608
609 def get_parameters_sync(self):
610 return self.handler.get_parameters()
611
612 def request_package_info_async(self):
613 self.handler.request_package_info()
614
615 def cancel_build_sync(self, force=False):
616 self.handler.cancel_build(force)
617
618 def cancel_parse_sync(self):
619 self.handler.cancel_parse()
620
621 def switch_page(self, next_step):
622 # Main Workflow (Business Logic)
623 self.nb.set_current_page(self.__step2page__[next_step])
624
625 if next_step == self.MACHINE_SELECTION: # init step
626 self.image_configuration_page.show_machine()
627
628 elif next_step == self.RCPPKGINFO_POPULATING:
629 # MACHINE CHANGED action or SETTINGS CHANGED
630 # show the progress bar
631 self.image_configuration_page.show_info_populating()
632
633 elif next_step == self.RCPPKGINFO_POPULATED:
634 self.image_configuration_page.show_info_populated()
635
636 elif next_step == self.BASEIMG_SELECTED:
637 self.image_configuration_page.show_baseimg_selected()
638
639 elif next_step == self.RECIPE_SELECTION:
640 if self.recipe_model.get_selected_image() == self.recipe_model.__custom_image__:
641 self.recipe_details_page.set_recipe_curr_tab(self.recipe_details_page.ALL)
642 else:
643 self.recipe_details_page.set_recipe_curr_tab(self.recipe_details_page.INCLUDED)
644
645 elif next_step == self.PACKAGE_SELECTION:
646 self.configuration.initial_selected_packages = self.configuration.selected_packages
647 self.configuration.initial_user_selected_packages = self.configuration.user_selected_packages
648 self.package_details_page.set_title("Edit packages")
649 if self.recipe_model.get_selected_image() == self.recipe_model.__custom_image__:
650 self.package_details_page.set_packages_curr_tab(self.package_details_page.ALL)
651 else:
652 self.package_details_page.set_packages_curr_tab(self.package_details_page.INCLUDED)
653 self.package_details_page.show_page(self.current_logfile)
654
655
656 elif next_step == self.PACKAGE_GENERATING or next_step == self.FAST_IMAGE_GENERATING:
657 # both PACKAGE_GENERATING and FAST_IMAGE_GENERATING share the same page
658 self.build_details_page.show_page(next_step)
659
660 elif next_step == self.PACKAGE_GENERATED:
661 self.package_details_page.set_title("Step 2 of 2: Edit packages")
662 if self.recipe_model.get_selected_image() == self.recipe_model.__custom_image__:
663 self.package_details_page.set_packages_curr_tab(self.package_details_page.ALL)
664 else:
665 self.package_details_page.set_packages_curr_tab(self.package_details_page.INCLUDED)
666 self.package_details_page.show_page(self.current_logfile)
667
668 elif next_step == self.IMAGE_GENERATING:
669 # after packages are generated, selected_packages need to
670 # be updated in package_model per selected_image in recipe_model
671 self.build_details_page.show_page(next_step)
672
673 elif next_step == self.IMAGE_GENERATED:
674 self.image_details_page.show_page(next_step)
675
676 elif next_step == self.MY_IMAGE_OPENED:
677 self.image_details_page.show_page(next_step)
678
679 self.previous_step = self.current_step
680 self.current_step = next_step
681
682 def set_user_config_proxies(self):
683 if self.configuration.enable_proxy == True:
684 self.handler.set_http_proxy(self.configuration.combine_proxy("http"))
685 self.handler.set_https_proxy(self.configuration.combine_proxy("https"))
686 self.handler.set_ftp_proxy(self.configuration.combine_proxy("ftp"))
687 self.handler.set_socks_proxy(self.configuration.combine_proxy("socks"))
688 self.handler.set_cvs_proxy(self.configuration.combine_host_only("cvs"), self.configuration.combine_port_only("cvs"))
689 elif self.configuration.enable_proxy == False:
690 self.handler.set_http_proxy("")
691 self.handler.set_https_proxy("")
692 self.handler.set_ftp_proxy("")
693 self.handler.set_socks_proxy("")
694 self.handler.set_cvs_proxy("", "")
695
696 def set_user_config_extra(self):
697 self.handler.set_rootfs_size(self.configuration.image_rootfs_size)
698 self.handler.set_extra_size(self.configuration.image_extra_size)
699 self.handler.set_incompatible_license(self.configuration.incompat_license)
700 self.handler.set_sdk_machine(self.configuration.curr_sdk_machine)
701 self.handler.set_image_fstypes(self.configuration.image_fstypes)
702 self.handler.set_extra_config(self.configuration.extra_setting)
703 self.handler.set_extra_inherit("packageinfo image_types")
704 self.set_user_config_proxies()
705
706 def set_user_config(self):
707 self.handler.reset_cooker()
708 # set bb layers
709 self.handler.set_bblayers(self.configuration.layers)
710 # set local configuration
711 self.handler.set_machine(self.configuration.curr_mach)
712 self.handler.set_package_format(self.configuration.curr_package_format)
713 self.handler.set_distro(self.configuration.curr_distro)
714 self.handler.set_dl_dir(self.configuration.dldir)
715 self.handler.set_sstate_dir(self.configuration.sstatedir)
716 self.handler.set_sstate_mirrors(self.configuration.sstatemirror)
717 self.handler.set_pmake(self.configuration.pmake)
718 self.handler.set_bbthreads(self.configuration.bbthread)
719 self.set_user_config_extra()
720
721 def update_recipe_model(self, selected_image, selected_recipes):
722 self.recipe_model.set_selected_image(selected_image)
723 self.recipe_model.set_selected_recipes(selected_recipes)
724
725 def update_package_model(self, selected_packages, user_selected_packages=None):
726 if user_selected_packages:
727 left = self.package_model.set_selected_packages(user_selected_packages, True)
728 self.configuration.user_selected_packages += left
729 left = self.package_model.set_selected_packages(selected_packages)
730 self.configuration.selected_packages += left
731
732 def update_configuration_parameters(self, params):
733 if params:
734 self.configuration.update(params)
735 self.parameters.update(params)
736
737 def reset(self):
738 self.configuration.curr_mach = ""
739 self.configuration.clear_selection()
740 self.image_configuration_page.switch_machine_combo()
741 self.switch_page(self.MACHINE_SELECTION)
742
743 # Callback Functions
744 def handler_config_updated_cb(self, handler, which, values):
745 if which == "distro":
746 self.parameters.all_distros = values
747 elif which == "machine":
748 self.parameters.all_machines = values
749 self.image_configuration_page.update_machine_combo()
750 elif which == "machine-sdk":
751 self.parameters.all_sdk_machines = values
752
753 def handler_package_formats_updated_cb(self, handler, formats):
754 self.parameters.all_package_formats = formats
755
756 def switch_to_image_configuration_helper(self):
757 self.sanity_check_page.stop()
758 self.switch_page(self.IMAGE_CONFIGURATION)
759 self.image_configuration_page.switch_machine_combo()
760
761 def show_network_error_dialog_helper(self):
762 self.sanity_check_page.stop()
763 self.show_network_error_dialog()
764
765 def handler_command_succeeded_cb(self, handler, initcmd):
766 if initcmd == self.handler.GENERATE_CONFIGURATION:
767 if not self.configuration.curr_mach:
768 self.configuration.curr_mach = self.handler.runCommand(["getVariable", "HOB_MACHINE"]) or ""
769 self.update_configuration_parameters(self.get_parameters_sync())
770 if not self.sanity_checked:
771 self.sanity_check()
772 self.sanity_checked = True
773 elif initcmd == self.handler.SANITY_CHECK:
774 if self.had_network_error:
775 self.had_network_error = False
776 self.execute_after_sanity_check(self.show_network_error_dialog_helper)
777 else:
778 # Switch to the 'image configuration' page now, but we might need
779 # to wait for the minimum display time of the sanity check page
780 self.execute_after_sanity_check(self.switch_to_image_configuration_helper)
781 elif initcmd in [self.handler.GENERATE_RECIPES,
782 self.handler.GENERATE_PACKAGES,
783 self.handler.GENERATE_IMAGE]:
784 self.update_configuration_parameters(self.get_parameters_sync())
785 self.request_package_info_async()
786 elif initcmd == self.handler.POPULATE_PACKAGEINFO:
787 if self.current_step == self.RCPPKGINFO_POPULATING:
788 self.switch_page(self.RCPPKGINFO_POPULATED)
789 self.rcppkglist_populated()
790 return
791
792 self.rcppkglist_populated()
793 if self.current_step == self.FAST_IMAGE_GENERATING:
794 self.generate_image_async(True)
795
796 def show_error_dialog(self, msg):
797 lbl = "<b>Hob found an error</b>\n"
798 dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_ERROR, msg)
799 button = dialog.add_button("Close", gtk.RESPONSE_OK)
800 HobButton.style_button(button)
801 response = dialog.run()
802 dialog.destroy()
803
804 def show_warning_dialog(self):
805 dialog = ParsingWarningsDialog(title = "View warnings",
806 warnings = self.parsing_warnings,
807 parent = None,
808 flags = gtk.DIALOG_DESTROY_WITH_PARENT
809 | gtk.DIALOG_NO_SEPARATOR)
810 response = dialog.run()
811 dialog.destroy()
812
813 def show_network_error_dialog(self):
814 lbl = "<b>Hob cannot connect to the network</b>\n"
815 msg = "Please check your network connection. If you are using a proxy server, please make sure it is configured correctly."
816 lbl = lbl + "%s\n\n" % glib.markup_escape_text(msg)
817 dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_ERROR)
818 button = dialog.add_button("Close", gtk.RESPONSE_OK)
819 HobButton.style_button(button)
820 button = dialog.add_button("Proxy settings", gtk.RESPONSE_CANCEL)
821 HobButton.style_button(button)
822 res = dialog.run()
823 dialog.destroy()
824 if res == gtk.RESPONSE_CANCEL:
825 res, settings_changed = self.show_simple_settings_dialog(SimpleSettingsDialog.PROXIES_PAGE_ID)
826 if not res:
827 return
828 if settings_changed:
829 self.reparse_post_adv_settings()
830
831 def handler_command_failed_cb(self, handler, msg):
832 if msg:
833 self.show_error_dialog(msg)
834 self.reset()
835
836 def handler_parsing_warning_cb(self, handler, warn_msg):
837 self.parsing_warnings.append(warn_msg)
838
839 def handler_sanity_failed_cb(self, handler, msg, network_error):
840 self.reset()
841 if network_error:
842 # Mark this in an internal field. The "network error" dialog will be
843 # shown later, when a SanityCheckPassed event will be handled
844 # (as sent by sanity.bbclass)
845 self.had_network_error = True
846 else:
847 msg = msg.replace("your local.conf", "Settings")
848 self.show_error_dialog(msg)
849 self.reset()
850
851 def window_sensitive(self, sensitive):
852 self.image_configuration_page.machine_combo.set_sensitive(sensitive)
853 self.image_configuration_page.machine_combo.child.set_sensitive(sensitive)
854 self.image_configuration_page.image_combo.set_sensitive(sensitive)
855 self.image_configuration_page.image_combo.child.set_sensitive(sensitive)
856 self.image_configuration_page.layer_button.set_sensitive(sensitive)
857 self.image_configuration_page.layer_info_icon.set_sensitive(sensitive)
858 self.image_configuration_page.toolbar.set_sensitive(sensitive)
859 self.image_configuration_page.view_adv_configuration_button.set_sensitive(sensitive)
860 self.image_configuration_page.config_build_button.set_sensitive(sensitive)
861
862 self.recipe_details_page.set_sensitive(sensitive)
863 self.package_details_page.set_sensitive(sensitive)
864 self.build_details_page.set_sensitive(sensitive)
865 self.image_details_page.set_sensitive(sensitive)
866
867 if sensitive:
868 self.window.set_cursor(None)
869 else:
870 self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
871 self.sensitive = sensitive
872
873
874 def handler_generating_data_cb(self, handler):
875 self.window_sensitive(False)
876
877 def handler_data_generated_cb(self, handler):
878 self.window_sensitive(True)
879
880 def rcppkglist_populated(self):
881 selected_image = self.configuration.selected_image
882 selected_recipes = self.configuration.selected_recipes[:]
883 selected_packages = self.configuration.selected_packages[:]
884 user_selected_packages = self.configuration.user_selected_packages[:]
885
886 self.image_configuration_page.update_image_combo(self.recipe_model, selected_image)
887 self.image_configuration_page.update_image_desc()
888 self.update_recipe_model(selected_image, selected_recipes)
889 self.update_package_model(selected_packages, user_selected_packages)
890
891 def recipelist_changed_cb(self, recipe_model):
892 self.recipe_details_page.refresh_selection()
893
894 def packagelist_changed_cb(self, package_model):
895 self.package_details_page.refresh_selection()
896
897 def handler_recipe_populated_cb(self, handler):
898 self.image_configuration_page.update_progress_bar("Populating recipes", 0.99)
899
900 def handler_package_populated_cb(self, handler):
901 self.image_configuration_page.update_progress_bar("Populating packages", 1.0)
902
903 def handler_parsing_started_cb(self, handler, message):
904 if self.current_step != self.RCPPKGINFO_POPULATING:
905 return
906
907 fraction = 0
908 if message["eventname"] == "TreeDataPreparationStarted":
909 fraction = 0.6 + fraction
910 self.image_configuration_page.stop_button.set_sensitive(False)
911 self.image_configuration_page.update_progress_bar("Generating dependency tree", fraction)
912 else:
913 self.image_configuration_page.stop_button.set_sensitive(True)
914 self.image_configuration_page.update_progress_bar(message["title"], fraction)
915
916 def handler_parsing_cb(self, handler, message):
917 if self.current_step != self.RCPPKGINFO_POPULATING:
918 return
919
920 fraction = message["current"] * 1.0/message["total"]
921 if message["eventname"] == "TreeDataPreparationProgress":
922 fraction = 0.6 + 0.38 * fraction
923 self.image_configuration_page.update_progress_bar("Generating dependency tree", fraction)
924 else:
925 fraction = 0.6 * fraction
926 self.image_configuration_page.update_progress_bar(message["title"], fraction)
927
928 def handler_parsing_completed_cb(self, handler, message):
929 if self.current_step != self.RCPPKGINFO_POPULATING:
930 return
931
932 if message["eventname"] == "TreeDataPreparationCompleted":
933 fraction = 0.98
934 else:
935 fraction = 0.6
936 self.image_configuration_page.update_progress_bar("Generating dependency tree", fraction)
937
938 def handler_build_started_cb(self, running_build):
939 if self.current_step == self.FAST_IMAGE_GENERATING:
940 fraction = 0
941 elif self.current_step == self.IMAGE_GENERATING:
942 if self.previous_step == self.FAST_IMAGE_GENERATING:
943 fraction = 0.9
944 else:
945 fraction = 0
946 elif self.current_step == self.PACKAGE_GENERATING:
947 fraction = 0
948 self.build_details_page.update_progress_bar("Build Started: ", fraction)
949 self.build_details_page.show_configurations(self.configuration, self.parameters)
950
951 def build_succeeded(self):
952 if self.current_step == self.FAST_IMAGE_GENERATING:
953 fraction = 0.9
954 elif self.current_step == self.IMAGE_GENERATING:
955 fraction = 1.0
956 version = ""
957 self.parameters.image_names = []
958 selected_image = self.recipe_model.get_selected_image()
959 if selected_image == self.recipe_model.__custom_image__:
960 if self.configuration.initial_selected_image != selected_image:
961 version = self.recipe_model.get_custom_image_version()
962 linkname = 'hob-image' + version+ "-" + self.configuration.curr_mach
963 else:
964 linkname = selected_image + '-' + self.configuration.curr_mach
965 image_extension = self.get_image_extension()
966 for image_type in self.parameters.image_types:
967 if image_type in image_extension:
968 real_types = image_extension[image_type]
969 else:
970 real_types = [image_type]
971 for real_image_type in real_types:
972 linkpath = self.parameters.image_addr + '/' + linkname + '.' + real_image_type
973 if os.path.exists(linkpath):
974 self.parameters.image_names.append(os.readlink(linkpath))
975 elif self.current_step == self.PACKAGE_GENERATING:
976 fraction = 1.0
977 self.build_details_page.update_progress_bar("Build Completed: ", fraction)
978 self.handler.build_succeeded_async()
979 self.stopping = False
980
981 if self.current_step == self.PACKAGE_GENERATING:
982 self.switch_page(self.PACKAGE_GENERATED)
983 elif self.current_step == self.IMAGE_GENERATING:
984 self.switch_page(self.IMAGE_GENERATED)
985
986 def build_failed(self):
987 if self.stopping:
988 status = "stop"
989 message = "Build stopped: "
990 fraction = self.build_details_page.progress_bar.get_fraction()
991 stop_to_next_edit = ""
992 if self.current_step == self.FAST_IMAGE_GENERATING:
993 stop_to_next_edit = "image configuration"
994 elif self.current_step == self.IMAGE_GENERATING:
995 if self.previous_step == self.FAST_IMAGE_GENERATING:
996 stop_to_next_edit = "image configuration"
997 else:
998 stop_to_next_edit = "packages"
999 elif self.current_step == self.PACKAGE_GENERATING:
1000 stop_to_next_edit = "recipes"
1001 button = self.build_details_page.show_stop_page(stop_to_next_edit.split(' ')[0])
1002 self.set_default(button)
1003 else:
1004 fail_to_next_edit = ""
1005 if self.current_step == self.FAST_IMAGE_GENERATING:
1006 fail_to_next_edit = "image configuration"
1007 fraction = 0.9
1008 elif self.current_step == self.IMAGE_GENERATING:
1009 if self.previous_step == self.FAST_IMAGE_GENERATING:
1010 fail_to_next_edit = "image configuration"
1011 else:
1012 fail_to_next_edit = "packages"
1013 fraction = 1.0
1014 elif self.current_step == self.PACKAGE_GENERATING:
1015 fail_to_next_edit = "recipes"
1016 fraction = 1.0
1017 self.build_details_page.show_fail_page(fail_to_next_edit.split(' ')[0])
1018 status = "fail"
1019 message = "Build failed: "
1020 self.build_details_page.update_progress_bar(message, fraction, status)
1021 self.build_details_page.show_back_button()
1022 self.build_details_page.hide_stop_button()
1023 self.handler.build_failed_async()
1024 self.stopping = False
1025
1026 def handler_build_succeeded_cb(self, running_build):
1027 if not self.stopping:
1028 self.build_succeeded()
1029 else:
1030 self.build_failed()
1031
1032
1033 def handler_build_failed_cb(self, running_build):
1034 self.build_failed()
1035
1036 def handler_build_aborted_cb(self, running_build):
1037 self.build_failed()
1038
1039 def handler_no_provider_cb(self, running_build, msg):
1040 dialog = CrumbsMessageDialog(self, glib.markup_escape_text(msg), gtk.STOCK_DIALOG_INFO)
1041 button = dialog.add_button("Close", gtk.RESPONSE_OK)
1042 HobButton.style_button(button)
1043 dialog.run()
1044 dialog.destroy()
1045 self.build_failed()
1046
1047 def handler_task_started_cb(self, running_build, message):
1048 fraction = message["current"] * 1.0/message["total"]
1049 title = "Build packages"
1050 if self.current_step == self.FAST_IMAGE_GENERATING:
1051 if message["eventname"] == "sceneQueueTaskStarted":
1052 fraction = 0.27 * fraction
1053 elif message["eventname"] == "runQueueTaskStarted":
1054 fraction = 0.27 + 0.63 * fraction
1055 elif self.current_step == self.IMAGE_GENERATING:
1056 title = "Build image"
1057 if self.previous_step == self.FAST_IMAGE_GENERATING:
1058 if message["eventname"] == "sceneQueueTaskStarted":
1059 fraction = 0.27 + 0.63 + 0.03 * fraction
1060 elif message["eventname"] == "runQueueTaskStarted":
1061 fraction = 0.27 + 0.63 + 0.03 + 0.07 * fraction
1062 else:
1063 if message["eventname"] == "sceneQueueTaskStarted":
1064 fraction = 0.2 * fraction
1065 elif message["eventname"] == "runQueueTaskStarted":
1066 fraction = 0.2 + 0.8 * fraction
1067 elif self.current_step == self.PACKAGE_GENERATING:
1068 if message["eventname"] == "sceneQueueTaskStarted":
1069 fraction = 0.2 * fraction
1070 elif message["eventname"] == "runQueueTaskStarted":
1071 fraction = 0.2 + 0.8 * fraction
1072 self.build_details_page.update_progress_bar(title + ": ", fraction)
1073 self.build_details_page.update_build_status(message["current"], message["total"], message["task"])
1074
1075 def handler_disk_full_cb(self, running_build):
1076 self.disk_full = True
1077
1078 def handler_build_failure_cb(self, running_build):
1079 self.build_details_page.show_issues()
1080
1081 def handler_build_log_cb(self, running_build, func, obj):
1082 if hasattr(self.logger, func):
1083 getattr(self.logger, func)(obj)
1084
1085 def destroy_window_cb(self, widget, event):
1086 if not self.sensitive:
1087 return True
1088 elif self.handler.building:
1089 self.stop_build()
1090 return True
1091 else:
1092 gtk.main_quit()
1093
1094 def event_handle_SIGINT(self, signal, frame):
1095 for w in gtk.window_list_toplevels():
1096 if w.get_modal():
1097 w.response(gtk.RESPONSE_DELETE_EVENT)
1098 sys.exit(0)
1099
1100 def build_packages(self):
1101 _, all_recipes = self.recipe_model.get_selected_recipes()
1102 if not all_recipes:
1103 lbl = "<b>No selections made</b>\nYou have not made any selections"
1104 lbl = lbl + " so there isn't anything to bake at this time."
1105 dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
1106 button = dialog.add_button("Close", gtk.RESPONSE_OK)
1107 HobButton.style_button(button)
1108 dialog.run()
1109 dialog.destroy()
1110 return
1111 self.generate_packages_async(True)
1112
1113 def build_image(self):
1114 selected_packages = self.package_model.get_selected_packages()
1115 if not selected_packages:
1116 lbl = "<b>No selections made</b>\nYou have not made any selections"
1117 lbl = lbl + " so there isn't anything to bake at this time."
1118 dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
1119 button = dialog.add_button("Close", gtk.RESPONSE_OK)
1120 HobButton.style_button(button)
1121 dialog.run()
1122 dialog.destroy()
1123 return
1124 self.generate_image_async(True)
1125
1126 def just_bake(self):
1127 selected_image = self.recipe_model.get_selected_image()
1128 selected_packages = self.package_model.get_selected_packages() or []
1129
1130 # If no base image and no selected packages don't build anything
1131 if not (selected_packages or selected_image != self.recipe_model.__custom_image__):
1132 lbl = "<b>No selections made</b>\nYou have not made any selections"
1133 lbl = lbl + " so there isn't anything to bake at this time."
1134 dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
1135 button = dialog.add_button("Close", gtk.RESPONSE_OK)
1136 HobButton.style_button(button)
1137 dialog.run()
1138 dialog.destroy()
1139 return
1140
1141 self.fast_generate_image_async(True)
1142
1143 def show_recipe_property_dialog(self, properties):
1144 information = {}
1145 dialog = PropertyDialog(title = properties["name"] +' '+ "properties",
1146 parent = self,
1147 information = properties,
1148 flags = gtk.DIALOG_DESTROY_WITH_PARENT
1149 | gtk.DIALOG_NO_SEPARATOR)
1150
1151 dialog.set_modal(False)
1152
1153 button = dialog.add_button("Close", gtk.RESPONSE_NO)
1154 HobAltButton.style_button(button)
1155 button.connect("clicked", lambda w: dialog.destroy())
1156
1157 dialog.run()
1158
1159 def show_packages_property_dialog(self, properties):
1160 information = {}
1161 dialog = PropertyDialog(title = properties["name"] +' '+ "properties",
1162 parent = self,
1163 information = properties,
1164 flags = gtk.DIALOG_DESTROY_WITH_PARENT
1165 | gtk.DIALOG_NO_SEPARATOR)
1166
1167 dialog.set_modal(False)
1168
1169 button = dialog.add_button("Close", gtk.RESPONSE_NO)
1170 HobAltButton.style_button(button)
1171 button.connect("clicked", lambda w: dialog.destroy())
1172
1173 dialog.run()
1174
1175 def show_layer_selection_dialog(self):
1176 dialog = LayerSelectionDialog(title = "Layers",
1177 layers = copy.deepcopy(self.configuration.layers),
1178 layers_non_removable = copy.deepcopy(self.configuration.layers_non_removable),
1179 all_layers = self.parameters.all_layers,
1180 parent = self,
1181 flags = gtk.DIALOG_MODAL
1182 | gtk.DIALOG_DESTROY_WITH_PARENT
1183 | gtk.DIALOG_NO_SEPARATOR)
1184 button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
1185 HobAltButton.style_button(button)
1186 button = dialog.add_button("OK", gtk.RESPONSE_YES)
1187 HobButton.style_button(button)
1188 response = dialog.run()
1189 if response == gtk.RESPONSE_YES:
1190 self.configuration.layers = dialog.layers
1191 # DO refresh layers
1192 if dialog.layers_changed:
1193 self.update_config_async()
1194 dialog.destroy()
1195
1196 def get_image_extension(self):
1197 image_extension = {}
1198 for type in self.parameters.image_types:
1199 ext = self.handler.runCommand(["getVariable", "IMAGE_EXTENSION_%s" % type])
1200 if ext:
1201 image_extension[type] = ext.split(' ')
1202
1203 return image_extension
1204
1205 def show_load_my_images_dialog(self):
1206 image_extension = self.get_image_extension()
1207 dialog = ImageSelectionDialog(self.parameters.image_addr, self.parameters.image_types,
1208 "Open My Images", self,
1209 gtk.FILE_CHOOSER_ACTION_SAVE, None,
1210 image_extension)
1211 button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
1212 HobAltButton.style_button(button)
1213 button = dialog.add_button("Open", gtk.RESPONSE_YES)
1214 HobButton.style_button(button)
1215 response = dialog.run()
1216 if response == gtk.RESPONSE_YES:
1217 if not dialog.image_names:
1218 lbl = "<b>No selections made</b>\nYou have not made any selections"
1219 crumbs_dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
1220 button = crumbs_dialog.add_button("Close", gtk.RESPONSE_OK)
1221 HobButton.style_button(button)
1222 crumbs_dialog.run()
1223 crumbs_dialog.destroy()
1224 dialog.destroy()
1225 return
1226
1227 self.parameters.image_addr = dialog.image_folder
1228 self.parameters.image_names = dialog.image_names[:]
1229 self.switch_page(self.MY_IMAGE_OPENED)
1230
1231 dialog.destroy()
1232
1233 def show_adv_settings_dialog(self, tab=None):
1234 dialog = AdvancedSettingsDialog(title = "Advanced configuration",
1235 configuration = copy.deepcopy(self.configuration),
1236 all_image_types = self.parameters.image_types,
1237 all_package_formats = self.parameters.all_package_formats,
1238 all_distros = self.parameters.all_distros,
1239 all_sdk_machines = self.parameters.all_sdk_machines,
1240 max_threads = self.parameters.max_threads,
1241 parent = self,
1242 flags = gtk.DIALOG_MODAL
1243 | gtk.DIALOG_DESTROY_WITH_PARENT
1244 | gtk.DIALOG_NO_SEPARATOR)
1245 button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
1246 HobAltButton.style_button(button)
1247 button = dialog.add_button("Save", gtk.RESPONSE_YES)
1248 HobButton.style_button(button)
1249 dialog.set_save_button(button)
1250 response = dialog.run()
1251 settings_changed = False
1252 if response == gtk.RESPONSE_YES:
1253 self.configuration = dialog.configuration
1254 self.configuration.save(self.handler, True) # remember settings
1255 settings_changed = dialog.settings_changed
1256 dialog.destroy()
1257 return response == gtk.RESPONSE_YES, settings_changed
1258
1259 def show_simple_settings_dialog(self, tab=None):
1260 dialog = SimpleSettingsDialog(title = "Settings",
1261 configuration = copy.deepcopy(self.configuration),
1262 all_image_types = self.parameters.image_types,
1263 all_package_formats = self.parameters.all_package_formats,
1264 all_distros = self.parameters.all_distros,
1265 all_sdk_machines = self.parameters.all_sdk_machines,
1266 max_threads = self.parameters.max_threads,
1267 parent = self,
1268 flags = gtk.DIALOG_MODAL
1269 | gtk.DIALOG_DESTROY_WITH_PARENT
1270 | gtk.DIALOG_NO_SEPARATOR,
1271 handler = self.handler)
1272 button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
1273 HobAltButton.style_button(button)
1274 button = dialog.add_button("Save", gtk.RESPONSE_YES)
1275 HobButton.style_button(button)
1276 if tab:
1277 dialog.switch_to_page(tab)
1278 response = dialog.run()
1279 settings_changed = False
1280 if response == gtk.RESPONSE_YES:
1281 self.configuration = dialog.configuration
1282 self.configuration.save(self.handler, True) # remember settings
1283 settings_changed = dialog.settings_changed
1284 if dialog.proxy_settings_changed:
1285 self.set_user_config_proxies()
1286 elif dialog.proxy_test_ran:
1287 # The user might have modified the proxies in the "Proxy"
1288 # tab, which in turn made the proxy settings modify in bb.
1289 # If "Cancel" was pressed, restore the previous proxy
1290 # settings inside bb.
1291 self.set_user_config_proxies()
1292 dialog.destroy()
1293 return response == gtk.RESPONSE_YES, settings_changed
1294
1295 def reparse_post_adv_settings(self):
1296 if not self.configuration.curr_mach:
1297 self.update_config_async()
1298 else:
1299 self.configuration.clear_selection()
1300 # DO reparse recipes
1301 self.populate_recipe_package_info_async()
1302
1303 def deploy_image(self, image_name):
1304 if not image_name:
1305 lbl = "<b>Please select an image to deploy.</b>"
1306 dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
1307 button = dialog.add_button("Close", gtk.RESPONSE_OK)
1308 HobButton.style_button(button)
1309 dialog.run()
1310 dialog.destroy()
1311 return
1312
1313 image_path = os.path.join(self.parameters.image_addr, image_name)
1314 dialog = DeployImageDialog(title = "Usb Image Maker",
1315 image_path = image_path,
1316 parent = self,
1317 flags = gtk.DIALOG_MODAL
1318 | gtk.DIALOG_DESTROY_WITH_PARENT
1319 | gtk.DIALOG_NO_SEPARATOR)
1320 button = dialog.add_button("Close", gtk.RESPONSE_NO)
1321 HobAltButton.style_button(button)
1322 button = dialog.add_button("Make usb image", gtk.RESPONSE_YES)
1323 HobButton.style_button(button)
1324 response = dialog.run()
1325 dialog.destroy()
1326
1327 def show_load_kernel_dialog(self):
1328 dialog = gtk.FileChooserDialog("Load Kernel Files", self,
1329 gtk.FILE_CHOOSER_ACTION_SAVE)
1330 button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
1331 HobAltButton.style_button(button)
1332 button = dialog.add_button("Open", gtk.RESPONSE_YES)
1333 HobButton.style_button(button)
1334 filter = gtk.FileFilter()
1335 filter.set_name("Kernel Files")
1336 filter.add_pattern("*.bin")
1337 dialog.add_filter(filter)
1338
1339 dialog.set_current_folder(self.parameters.image_addr)
1340
1341 response = dialog.run()
1342 kernel_path = ""
1343 if response == gtk.RESPONSE_YES:
1344 kernel_path = dialog.get_filename()
1345
1346 dialog.destroy()
1347
1348 return kernel_path
1349
1350 def runqemu_image(self, image_name, kernel_name):
1351 if not image_name or not kernel_name:
1352 lbl = "<b>Please select an %s to launch in QEMU.</b>" % ("kernel" if image_name else "image")
1353 dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
1354 button = dialog.add_button("Close", gtk.RESPONSE_OK)
1355 HobButton.style_button(button)
1356 dialog.run()
1357 dialog.destroy()
1358 return
1359
1360 kernel_path = os.path.join(self.parameters.image_addr, kernel_name)
1361 image_path = os.path.join(self.parameters.image_addr, image_name)
1362
1363 source_env_path = os.path.join(self.parameters.core_base, "oe-init-build-env")
1364 tmp_path = self.parameters.tmpdir
1365 cmdline = bb.ui.crumbs.utils.which_terminal()
1366 if os.path.exists(image_path) and os.path.exists(kernel_path) \
1367 and os.path.exists(source_env_path) and os.path.exists(tmp_path) \
1368 and cmdline:
1369 cmdline += "\' bash -c \"export OE_TMPDIR=" + tmp_path + "; "
1370 cmdline += "source " + source_env_path + " " + os.getcwd() + "; "
1371 cmdline += "runqemu " + kernel_path + " " + image_path + "\"\'"
1372 subprocess.Popen(shlex.split(cmdline))
1373 else:
1374 lbl = "<b>Path error</b>\nOne of your paths is wrong,"
1375 lbl = lbl + " please make sure the following paths exist:\n"
1376 lbl = lbl + "image path:" + image_path + "\n"
1377 lbl = lbl + "kernel path:" + kernel_path + "\n"
1378 lbl = lbl + "source environment path:" + source_env_path + "\n"
1379 lbl = lbl + "tmp path: " + tmp_path + "."
1380 lbl = lbl + "You may be missing either xterm or vte for terminal services."
1381 dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_ERROR)
1382 button = dialog.add_button("Close", gtk.RESPONSE_OK)
1383 HobButton.style_button(button)
1384 dialog.run()
1385 dialog.destroy()
1386
1387 def show_packages(self, ask=True):
1388 _, selected_recipes = self.recipe_model.get_selected_recipes()
1389 if selected_recipes and ask:
1390 lbl = "<b>Package list may be incomplete!</b>\nDo you want to build selected recipes"
1391 lbl = lbl + " to get a full list or just view the existing packages?"
1392 dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
1393 button = dialog.add_button("View packages", gtk.RESPONSE_NO)
1394 HobAltButton.style_button(button)
1395 button = dialog.add_button("Build packages", gtk.RESPONSE_YES)
1396 HobButton.style_button(button)
1397 dialog.set_default_response(gtk.RESPONSE_YES)
1398 response = dialog.run()
1399 dialog.destroy()
1400 if response == gtk.RESPONSE_YES:
1401 self.generate_packages_async(True)
1402 else:
1403 self.switch_page(self.PACKAGE_SELECTION)
1404 else:
1405 self.switch_page(self.PACKAGE_SELECTION)
1406
1407 def show_recipes(self):
1408 self.switch_page(self.RECIPE_SELECTION)
1409
1410 def show_image_details(self):
1411 self.switch_page(self.IMAGE_GENERATED)
1412
1413 def show_configuration(self):
1414 self.switch_page(self.BASEIMG_SELECTED)
1415
1416 def stop_build(self):
1417 if self.stopping:
1418 lbl = "<b>Force Stop build?</b>\nYou've already selected Stop once,"
1419 lbl = lbl + " would you like to 'Force Stop' the build?\n\n"
1420 lbl = lbl + "This will stop the build as quickly as possible but may"
1421 lbl = lbl + " well leave your build directory in an unusable state"
1422 lbl = lbl + " that requires manual steps to fix.\n"
1423 dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_WARNING)
1424 button = dialog.add_button("Cancel", gtk.RESPONSE_CANCEL)
1425 HobAltButton.style_button(button)
1426 button = dialog.add_button("Force Stop", gtk.RESPONSE_YES)
1427 HobButton.style_button(button)
1428 else:
1429 lbl = "<b>Stop build?</b>\n\nAre you sure you want to stop this"
1430 lbl = lbl + " build?\n\n'Stop' will stop the build as soon as all in"
1431 lbl = lbl + " progress build tasks are finished. However if a"
1432 lbl = lbl + " lengthy compilation phase is in progress this may take"
1433 lbl = lbl + " some time.\n\n"
1434 lbl = lbl + "'Force Stop' will stop the build as quickly as"
1435 lbl = lbl + " possible but may well leave your build directory in an"
1436 lbl = lbl + " unusable state that requires manual steps to fix."
1437 dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_WARNING)
1438 button = dialog.add_button("Cancel", gtk.RESPONSE_CANCEL)
1439 HobAltButton.style_button(button)
1440 button = dialog.add_button("Force stop", gtk.RESPONSE_YES)
1441 HobAltButton.style_button(button)
1442 button = dialog.add_button("Stop", gtk.RESPONSE_OK)
1443 HobButton.style_button(button)
1444 response = dialog.run()
1445 dialog.destroy()
1446 if response != gtk.RESPONSE_CANCEL:
1447 self.stopping = True
1448 if response == gtk.RESPONSE_OK:
1449 self.build_details_page.progress_bar.set_stop_title("Stopping the build....")
1450 self.build_details_page.progress_bar.set_rcstyle("stop")
1451 self.cancel_build_sync()
1452 elif response == gtk.RESPONSE_YES:
1453 self.cancel_build_sync(True)
1454
1455 def do_log(self, consolelogfile = None):
1456 if consolelogfile:
1457 bb.utils.mkdirhier(os.path.dirname(consolelogfile))
1458 if self.consolelog:
1459 self.logger.removeHandler(self.consolelog)
1460 self.consolelog = None
1461 self.consolelog = logging.FileHandler(consolelogfile)
1462 bb.msg.addDefaultlogFilter(self.consolelog)
1463 format = bb.msg.BBLogFormatter("%(levelname)s: %(message)s")
1464 self.consolelog.setFormatter(format)
1465
1466 self.logger.addHandler(self.consolelog)
1467
1468 def get_topdir(self):
1469 return self.handler.get_topdir()
1470
1471 def wait(self, delay):
1472 time_start = time.time()
1473 time_end = time_start + delay
1474 while time_end > time.time():
1475 while gtk.events_pending():
1476 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..5542471c7b
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hig/advancedsettingsdialog.py
@@ -0,0 +1,340 @@
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>\n\nYou need to select at least one image type."
187 dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_WARNING)
188 button = dialog.add_button("OK", gtk.RESPONSE_OK)
189 HobButton.style_button(button)
190 response = dialog.run()
191 dialog.destroy()
192
193 def create_image_types_page(self):
194 main_vbox = gtk.VBox(False, 16)
195 main_vbox.set_border_width(6)
196
197 advanced_vbox = gtk.VBox(False, 6)
198 advanced_vbox.set_border_width(6)
199
200 distro_vbox = gtk.VBox(False, 6)
201 label = self.gen_label_widget("Distro:")
202 tooltip = "Selects the Yocto Project distribution you want"
203 try:
204 i = self.all_distros.index( "defaultsetup" )
205 except ValueError:
206 i = -1
207 if i != -1:
208 self.all_distros[ i ] = "Default"
209 if self.configuration.curr_distro == "defaultsetup":
210 self.configuration.curr_distro = "Default"
211 distro_widget, self.distro_combo = self.gen_combo_widget(self.configuration.curr_distro, self.all_distros,"<b>Distro</b>" + "*" + tooltip)
212 distro_vbox.pack_start(label, expand=False, fill=False)
213 distro_vbox.pack_start(distro_widget, expand=False, fill=False)
214 main_vbox.pack_start(distro_vbox, expand=False, fill=False)
215
216
217 rows = (len(self.image_types)+1)/3
218 table = gtk.Table(rows + 1, 10, True)
219 advanced_vbox.pack_start(table, expand=False, fill=False)
220
221 tooltip = "Image file system types you want."
222 info = HobInfoButton("<b>Image types</b>" + "*" + tooltip, self)
223 label = self.gen_label_widget("Image types:")
224 align = gtk.Alignment(0, 0.5, 0, 0)
225 table.attach(align, 0, 4, 0, 1)
226 align.add(label)
227 table.attach(info, 4, 5, 0, 1)
228
229 i = 1
230 j = 1
231 for image_type in sorted(self.image_types):
232 self.image_types_checkbuttons[image_type] = gtk.CheckButton(image_type)
233 self.image_types_checkbuttons[image_type].connect("toggled", self.image_type_checkbutton_clicked_cb)
234 article = ""
235 if image_type.startswith(("a", "e", "i", "o", "u")):
236 article = "n"
237 if image_type == "live":
238 self.image_types_checkbuttons[image_type].set_tooltip_text("Build iso and hddimg images")
239 else:
240 self.image_types_checkbuttons[image_type].set_tooltip_text("Build a%s %s image" % (article, image_type))
241 table.attach(self.image_types_checkbuttons[image_type], j - 1, j + 3, i, i + 1)
242 if image_type in self.configuration.image_fstypes.split():
243 self.image_types_checkbuttons[image_type].set_active(True)
244 i += 1
245 if i > rows:
246 i = 1
247 j = j + 4
248
249 main_vbox.pack_start(advanced_vbox, expand=False, fill=False)
250 self.set_save_button_state()
251
252 return main_vbox
253
254 def create_output_page(self):
255 advanced_vbox = gtk.VBox(False, 6)
256 advanced_vbox.set_border_width(6)
257
258 advanced_vbox.pack_start(self.gen_label_widget('<span weight="bold">Package format</span>'), expand=False, fill=False)
259 sub_vbox = gtk.VBox(False, 6)
260 advanced_vbox.pack_start(sub_vbox, expand=False, fill=False)
261 tooltip_combo = "Selects the package format used to generate rootfs."
262 tooltip_extra = "Selects extra package formats to build"
263 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)
264 sub_vbox.pack_start(pkgfmt_widget, expand=False, fill=False)
265
266 advanced_vbox.pack_start(self.gen_label_widget('<span weight="bold">Image size</span>'), expand=False, fill=False)
267 sub_vbox = gtk.VBox(False, 6)
268 advanced_vbox.pack_start(sub_vbox, expand=False, fill=False)
269 label = self.gen_label_widget("Image basic size (in MB)")
270 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>."
271 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)
272 sub_vbox.pack_start(label, expand=False, fill=False)
273 sub_vbox.pack_start(rootfs_size_widget, expand=False, fill=False)
274
275 sub_vbox = gtk.VBox(False, 6)
276 advanced_vbox.pack_start(sub_vbox, expand=False, fill=False)
277 label = self.gen_label_widget("Additional free space (in MB)")
278 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."
279 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)
280 sub_vbox.pack_start(label, expand=False, fill=False)
281 sub_vbox.pack_start(extra_size_widget, expand=False, fill=False)
282
283 advanced_vbox.pack_start(self.gen_label_widget('<span weight="bold">Licensing</span>'), expand=False, fill=False)
284 self.gplv3_checkbox = gtk.CheckButton("Exclude GPLv3 packages")
285 self.gplv3_checkbox.set_tooltip_text("Check this box to prevent GPLv3 packages from being included in your image")
286 if "GPLv3" in self.configuration.incompat_license.split():
287 self.gplv3_checkbox.set_active(True)
288 else:
289 self.gplv3_checkbox.set_active(False)
290 advanced_vbox.pack_start(self.gplv3_checkbox, expand=False, fill=False)
291
292 advanced_vbox.pack_start(self.gen_label_widget('<span weight="bold">SDK</span>'), expand=False, fill=False)
293 sub_hbox = gtk.HBox(False, 6)
294 advanced_vbox.pack_start(sub_hbox, expand=False, fill=False)
295 self.sdk_checkbox = gtk.CheckButton("Populate SDK")
296 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."
297 self.sdk_checkbox.set_tooltip_text(tooltip)
298 self.sdk_checkbox.set_active(self.configuration.toolchain_build)
299 sub_hbox.pack_start(self.sdk_checkbox, expand=False, fill=False)
300
301 tooltip = "Select the host platform for which you want to run the toolchain contained in the SDK tarball."
302 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)
303 sub_hbox.pack_start(sdk_machine_widget, expand=False, fill=False)
304
305 return advanced_vbox
306
307 def response_cb(self, dialog, response_id):
308 package_format = []
309 package_format.append(self.rootfs_combo.get_active_text())
310 for child in self.check_hbox:
311 if isinstance(child, gtk.CheckButton) and child.get_active():
312 package_format.append(child.get_label())
313 self.configuration.curr_package_format = " ".join(package_format)
314
315 distro = self.distro_combo.get_active_text()
316 if distro == "Default":
317 distro = "defaultsetup"
318 self.configuration.curr_distro = distro
319 self.configuration.image_rootfs_size = self.rootfs_size_spinner.get_value_as_int() * 1024
320 self.configuration.image_extra_size = self.extra_size_spinner.get_value_as_int() * 1024
321
322 self.configuration.image_fstypes = ""
323 for image_type in self.image_types:
324 if self.image_types_checkbuttons[image_type].get_active():
325 self.configuration.image_fstypes += (" " + image_type)
326 self.configuration.image_fstypes.strip()
327
328 if self.gplv3_checkbox.get_active():
329 if "GPLv3" not in self.configuration.incompat_license.split():
330 self.configuration.incompat_license += " GPLv3"
331 else:
332 if "GPLv3" in self.configuration.incompat_license.split():
333 self.configuration.incompat_license = self.configuration.incompat_license.split().remove("GPLv3")
334 self.configuration.incompat_license = " ".join(self.configuration.incompat_license or [])
335 self.configuration.incompat_license = self.configuration.incompat_license.strip()
336
337 self.configuration.toolchain_build = self.sdk_checkbox.get_active()
338 self.configuration.curr_sdk_machine = self.sdk_machine_combo.get_active_text()
339 md5 = self.config_md5()
340 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..097ce7b027
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hig/crumbsmessagedialog.py
@@ -0,0 +1,95 @@
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(CrumbsDialog):
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="", icon=gtk.STOCK_INFO, msg=""):
40 super(CrumbsMessageDialog, self).__init__("", parent, gtk.DIALOG_MODAL)
41
42 self.set_border_width(6)
43 self.vbox.set_property("spacing", 12)
44 self.action_area.set_property("spacing", 12)
45 self.action_area.set_property("border-width", 6)
46
47 first_column = gtk.HBox(spacing=12)
48 first_column.set_property("border-width", 6)
49 first_column.show()
50 self.vbox.add(first_column)
51
52 self.icon = gtk.Image()
53 # We have our own Info icon which should be used in preference of the stock icon
54 self.icon_chk = HobIconChecker()
55 self.icon.set_from_stock(self.icon_chk.check_stock_icon(icon), gtk.ICON_SIZE_DIALOG)
56 self.icon.set_property("yalign", 0.00)
57 self.icon.show()
58 first_column.pack_start(self.icon, expand=False, fill=True, padding=0)
59
60 if 0 <= len(msg) < 200:
61 lbl = label + "%s" % glib.markup_escape_text(msg)
62 self.label_short = gtk.Label()
63 self.label_short.set_use_markup(True)
64 self.label_short.set_line_wrap(True)
65 self.label_short.set_markup(lbl)
66 self.label_short.set_property("yalign", 0.00)
67 self.label_short.show()
68 first_column.add(self.label_short)
69 else:
70 second_row = gtk.VBox(spacing=12)
71 second_row.set_property("border-width", 6)
72 self.label_long = gtk.Label()
73 self.label_long.set_use_markup(True)
74 self.label_long.set_line_wrap(True)
75 self.label_long.set_markup(label)
76 self.label_long.set_alignment(0.0, 0.0)
77 second_row.pack_start(self.label_long, expand=False, fill=False, padding=0)
78 self.label_long.show()
79 self.textWindow = gtk.ScrolledWindow()
80 self.textWindow.set_shadow_type(gtk.SHADOW_IN)
81 self.textWindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
82 self.msgView = gtk.TextView()
83 self.msgView.set_editable(False)
84 self.msgView.set_wrap_mode(gtk.WRAP_WORD)
85 self.msgView.set_cursor_visible(False)
86 self.msgView.set_size_request(300, 300)
87 self.buf = gtk.TextBuffer()
88 self.buf.set_text(msg)
89 self.msgView.set_buffer(self.buf)
90 self.textWindow.add(self.msgView)
91 self.msgView.show()
92 second_row.add(self.textWindow)
93 self.textWindow.show()
94 first_column.add(second_row)
95 second_row.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..bc1efbbfaf
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hig/deployimagedialog.py
@@ -0,0 +1,215 @@
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 combo_item = self.usb_combo.get_active_text()
164 if combo_item and combo_item != self.__dummy_usb__ and self.image_path:
165 cmdline = bb.ui.crumbs.utils.which_terminal()
166 if cmdline:
167 tmpfile = tempfile.NamedTemporaryFile()
168 cmdline += "\"sudo dd if=" + self.image_path + \
169 " of=" + combo_item + "; echo $? > " + tmpfile.name + "\""
170 subprocess.call(shlex.split(cmdline))
171
172 if int(tmpfile.readline().strip()) == 0:
173 lbl = "<b>Deploy image successfully.</b>"
174 else:
175 lbl = "<b>Failed to deploy image.</b>\nPlease check image <b>%s</b> exists and USB device <b>%s</b> is writable." % (self.image_path, combo_item)
176 tmpfile.close()
177 else:
178 if not self.image_path:
179 lbl = "<b>No selection made.</b>\nYou have not selected an image to deploy."
180 else:
181 lbl = "<b>No selection made.</b>\nYou have not selected a USB device."
182 if len(lbl):
183 crumbs_dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
184 button = crumbs_dialog.add_button("Close", gtk.RESPONSE_OK)
185 HobButton.style_button(button)
186 crumbs_dialog.run()
187 crumbs_dialog.destroy()
188
189 def update_progress_bar(self, title, fraction, status=None):
190 self.progress_bar.update(fraction)
191 self.progress_bar.set_title(title)
192 self.progress_bar.set_rcstyle(status)
193
194 def write_file(self, ifile, ofile):
195 self.progress_bar.reset()
196 self.progress_bar.show()
197
198 f_from = os.open(ifile, os.O_RDONLY)
199 f_to = os.open(ofile, os.O_WRONLY)
200
201 total_size = os.stat(ifile).st_size
202 written_size = 0
203
204 while True:
205 buf = os.read(f_from, 1024*1024)
206 if not buf:
207 break
208 os.write(f_to, buf)
209 written_size += 1024*1024
210 self.update_progress_bar("Writing to usb:", written_size * 1.0/total_size)
211
212 self.update_progress_bar("Writing completed:", 1.0)
213 os.close(f_from)
214 os.close(f_to)
215 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..783ee73c7a
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hig/layerselectiondialog.py
@@ -0,0 +1,296 @@
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>\nUnable to load layer <i>%s</i> because " % path
96 if response == gtk.RESPONSE_YES:
97 import os
98 import os.path
99 layers = []
100 it = layer_store.get_iter_first()
101 while it:
102 layers.append(layer_store.get_value(it, 0))
103 it = layer_store.iter_next(it)
104
105 if not path:
106 lbl += "it is an invalid path."
107 elif not os.path.exists(path+"/conf/layer.conf"):
108 lbl += "there is no layer.conf inside the directory."
109 elif path in layers:
110 lbl += "it is already in loaded layers."
111 else:
112 layer_store.append([path])
113 return
114 dialog = CrumbsMessageDialog(parent, lbl)
115 dialog.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_OK)
116 response = dialog.run()
117 dialog.destroy()
118
119 def layer_widget_del_clicked_cb(self, action, tree_selection, layer_store):
120 model, iter = tree_selection.get_selected()
121 if iter:
122 layer_store.remove(iter)
123
124
125 def gen_layer_widget(self, layers, layers_avail, window, tooltip=""):
126 hbox = gtk.HBox(False, 6)
127
128 layer_tv = gtk.TreeView()
129 layer_tv.set_rules_hint(True)
130 layer_tv.set_headers_visible(False)
131 tree_selection = layer_tv.get_selection()
132 tree_selection.set_mode(gtk.SELECTION_SINGLE)
133
134 # Allow enable drag and drop of rows including row move
135 layer_tv.enable_model_drag_source( gtk.gdk.BUTTON1_MASK,
136 self.TARGETS,
137 gtk.gdk.ACTION_DEFAULT|
138 gtk.gdk.ACTION_MOVE)
139 layer_tv.enable_model_drag_dest(self.TARGETS,
140 gtk.gdk.ACTION_DEFAULT)
141 layer_tv.connect("drag_data_get", self.drag_data_get_cb)
142 layer_tv.connect("drag_data_received", self.drag_data_received_cb)
143
144 col0= gtk.TreeViewColumn('Path')
145 cell0 = gtk.CellRendererText()
146 cell0.set_padding(5,2)
147 col0.pack_start(cell0, True)
148 col0.set_cell_data_func(cell0, self.draw_layer_path_cb)
149 layer_tv.append_column(col0)
150
151 scroll = gtk.ScrolledWindow()
152 scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
153 scroll.set_shadow_type(gtk.SHADOW_IN)
154 scroll.add(layer_tv)
155
156 table_layer = gtk.Table(2, 10, False)
157 hbox.pack_start(table_layer, expand=True, fill=True)
158
159 table_layer.attach(scroll, 0, 10, 0, 1)
160
161 layer_store = gtk.ListStore(gobject.TYPE_STRING)
162 for layer in layers:
163 layer_store.append([layer])
164
165 col1 = gtk.TreeViewColumn('Enabled')
166 layer_tv.append_column(col1)
167
168 cell1 = CellRendererPixbufActivatable()
169 cell1.set_fixed_size(-1,35)
170 cell1.connect("clicked", self.del_cell_clicked_cb, layer_store)
171 col1.pack_start(cell1, True)
172 col1.set_cell_data_func(cell1, self.draw_delete_button_cb, layer_tv)
173
174 add_button = gtk.Button()
175 add_button.set_relief(gtk.RELIEF_NONE)
176 box = gtk.HBox(False, 6)
177 box.show()
178 add_button.add(box)
179 add_button.connect("enter-notify-event", self.add_hover_cb)
180 add_button.connect("leave-notify-event", self.add_leave_cb)
181 self.im = gtk.Image()
182 self.im.set_from_file(hic.ICON_INDI_ADD_FILE)
183 self.im.show()
184 box.pack_start(self.im, expand=False, fill=False, padding=6)
185 lbl = gtk.Label("Add layer")
186 lbl.set_alignment(0.0, 0.5)
187 lbl.show()
188 box.pack_start(lbl, expand=True, fill=True, padding=6)
189 add_button.connect("clicked", self.layer_widget_add_clicked_cb, layer_store, window)
190 table_layer.attach(add_button, 0, 10, 1, 2, gtk.EXPAND | gtk.FILL, 0, 0, 6)
191 layer_tv.set_model(layer_store)
192
193 hbox.show_all()
194
195 return hbox, layer_store
196
197 def drag_data_get_cb(self, treeview, context, selection, target_id, etime):
198 treeselection = treeview.get_selection()
199 model, iter = treeselection.get_selected()
200 data = model.get_value(iter, 0)
201 selection.set(selection.target, 8, data)
202
203 def drag_data_received_cb(self, treeview, context, x, y, selection, info, etime):
204 model = treeview.get_model()
205 data = selection.data
206 drop_info = treeview.get_dest_row_at_pos(x, y)
207 if drop_info:
208 path, position = drop_info
209 iter = model.get_iter(path)
210 if (position == gtk.TREE_VIEW_DROP_BEFORE or position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE):
211 model.insert_before(iter, [data])
212 else:
213 model.insert_after(iter, [data])
214 else:
215 model.append([data])
216 if context.action == gtk.gdk.ACTION_MOVE:
217 context.finish(True, True, etime)
218 return
219
220 def add_hover_cb(self, button, event):
221 self.im.set_from_file(hic.ICON_INDI_ADD_HOVER_FILE)
222
223 def add_leave_cb(self, button, event):
224 self.im.set_from_file(hic.ICON_INDI_ADD_FILE)
225
226 def __init__(self, title, layers, layers_non_removable, all_layers, parent, flags, buttons=None):
227 super(LayerSelectionDialog, self).__init__(title, parent, flags, buttons)
228
229 # class members from other objects
230 self.layers = layers
231 self.layers_non_removable = layers_non_removable
232 self.all_layers = all_layers
233 self.layers_changed = False
234
235 # icon for remove button in TreeView
236 im = gtk.Image()
237 im.set_from_file(hic.ICON_INDI_REMOVE_FILE)
238 self.rem_icon = im.get_pixbuf()
239
240 # class members for internal use
241 self.layer_store = None
242
243 # create visual elements on the dialog
244 self.create_visual_elements()
245 self.connect("response", self.response_cb)
246
247 def create_visual_elements(self):
248 layer_widget, self.layer_store = self.gen_layer_widget(self.layers, self.all_layers, self, None)
249 layer_widget.set_size_request(450, 250)
250 self.vbox.pack_start(layer_widget, expand=True, fill=True)
251 self.show_all()
252
253 def response_cb(self, dialog, response_id):
254 model = self.layer_store
255 it = model.get_iter_first()
256 layers = []
257 while it:
258 layers.append(model.get_value(it, 0))
259 it = model.iter_next(it)
260
261 self.layers_changed = (self.layers != layers)
262 self.layers = layers
263
264 """
265 A custom cell_data_func to draw a delete 'button' in the TreeView for layers
266 other than the meta layer. The deletion of which is prevented so that the
267 user can't shoot themselves in the foot too badly.
268 """
269 def draw_delete_button_cb(self, col, cell, model, it, tv):
270 path = model.get_value(it, 0)
271 if path in self.layers_non_removable:
272 cell.set_sensitive(False)
273 cell.set_property('pixbuf', None)
274 cell.set_property('mode', gtk.CELL_RENDERER_MODE_INERT)
275 else:
276 cell.set_property('pixbuf', self.rem_icon)
277 cell.set_sensitive(True)
278 cell.set_property('mode', gtk.CELL_RENDERER_MODE_ACTIVATABLE)
279
280 return True
281
282 """
283 A custom cell_data_func to write an extra message into the layer path cell
284 for the meta layer. We should inform the user that they can't remove it for
285 their own safety.
286 """
287 def draw_layer_path_cb(self, col, cell, model, it):
288 path = model.get_value(it, 0)
289 if path in self.layers_non_removable:
290 cell.set_property('markup', "<b>It cannot be removed</b>\n%s" % path)
291 else:
292 cell.set_property('text', path)
293
294 def del_cell_clicked_cb(self, cell, path, model):
295 it = model.get_iter_from_string(path)
296 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..bc40741bf2
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hig/propertydialog.py
@@ -0,0 +1,451 @@
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 name = self.properties['name']
109 binb = self.properties['binb']
110 size = self.properties['size']
111 recipe = self.properties['recipe']
112 file_list = self.properties['files_list']
113
114 file_list = file_list.strip("{}'")
115 files_temp = ''
116 paths_temp = ''
117 files_binb = []
118 paths_binb = []
119
120 self.tooltip_items = {}
121
122 self.set_resizable(False)
123
124 #cleaning out the recipe variable
125 recipe = recipe.split("+")[0]
126
127 vbox = gtk.VBox(True,spacing = 0)
128
129 ###################################### NAME ROW + COL #################################
130
131 self.label_short = gtk.Label()
132 self.label_short.set_size_request(300,-1)
133 self.label_short.set_selectable(True)
134 self.label_short.set_line_wrap(True)
135 self.label_short.set_markup("<span weight=\"bold\">Name: </span>" + name)
136 self.label_short.set_property("xalign", 0)
137
138 self.vbox.add(self.label_short)
139
140 ###################################### SIZE ROW + COL ######################################
141
142 self.label_short = gtk.Label()
143 self.label_short.set_size_request(300,-1)
144 self.label_short.set_selectable(True)
145 self.label_short.set_line_wrap(True)
146 self.label_short.set_markup("<span weight=\"bold\">Size: </span>" + size)
147 self.label_short.set_property("xalign", 0)
148
149 self.vbox.add(self.label_short)
150
151 ##################################### RECIPE ROW + COL #########################################
152
153 self.label_short = gtk.Label()
154 self.label_short.set_size_request(300,-1)
155 self.label_short.set_selectable(True)
156 self.label_short.set_line_wrap(True)
157 self.label_short.set_markup("<span weight=\"bold\">Recipe: </span>" + recipe)
158 self.label_short.set_property("xalign", 0)
159
160 self.vbox.add(self.label_short)
161
162 ##################################### BINB ROW + COL #######################################
163
164 if binb != '':
165 self.label_short = gtk.Label()
166 self.label_short.set_selectable(True)
167 self.label_short.set_line_wrap(True)
168 self.label_short.set_markup("<span weight=\"bold\">Brought in by: </span>")
169 self.label_short.set_property("xalign", 0)
170
171 self.label_info = gtk.Label()
172 self.label_info.set_size_request(300,-1)
173 self.label_info.set_selectable(True)
174 self.label_info.set_line_wrap(True)
175 self.label_info.set_markup(binb)
176 self.label_info.set_property("xalign", 0)
177
178 self.vbox.add(self.label_short)
179 self.vbox.add(self.label_info)
180
181 #################################### FILES BROUGHT BY PACKAGES ###################################
182
183 if file_list != '':
184
185 self.textWindow = gtk.ScrolledWindow()
186 self.textWindow.set_shadow_type(gtk.SHADOW_IN)
187 self.textWindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
188 self.textWindow.set_size_request(100, 170)
189
190 sstatemirrors_store = gtk.ListStore(str)
191
192 self.sstatemirrors_tv = gtk.TreeView()
193 self.sstatemirrors_tv.set_rules_hint(True)
194 self.sstatemirrors_tv.set_headers_visible(True)
195 self.textWindow.add(self.sstatemirrors_tv)
196
197 self.cell1 = gtk.CellRendererText()
198 col1 = gtk.TreeViewColumn('Package files', self.cell1)
199 col1.set_cell_data_func(self.cell1, self.regex_field)
200 self.sstatemirrors_tv.append_column(col1)
201
202 for items in file_list.split(']'):
203 if len(items) > 1:
204 paths_temp = items.split(":")[0]
205 paths_binb.append(paths_temp.strip(" ,'"))
206 files_temp = items.split(":")[1]
207 files_binb.append(files_temp.strip(" ['"))
208
209 unsorted_list = []
210
211 for items in range(len(paths_binb)):
212 if len(files_binb[items]) > 1:
213 for aduse in (files_binb[items].split(",")):
214 unsorted_list.append(paths_binb[items].split(name)[len(paths_binb[items].split(name))-1] + '/' + aduse.strip(" '"))
215
216
217 unsorted_list.sort()
218 for items in unsorted_list:
219 temp = items
220 while len(items) > 35:
221 items = items[:len(items)/2] + "" + items[len(items)/2+1:]
222 if len(items) == 35:
223 items = items[:len(items)/2] + "..." + items[len(items)/2+3:]
224 self.tooltip_items[items] = temp
225
226 sstatemirrors_store.append([str(items)])
227
228
229 self.sstatemirrors_tv.set_model(sstatemirrors_store)
230
231 tips = gtk.Tooltips()
232 tips.set_tip(self.sstatemirrors_tv, "")
233 self.sstatemirrors_tv.connect("motion-notify-event", self.treeViewTooltip, tips, 0)
234 self.sstatemirrors_tv.set_events(gtk.gdk.POINTER_MOTION_MASK)
235
236 self.vbox.add(self.textWindow)
237
238 self.vbox.show_all()
239
240
241 def regex_field(self, column, cell, model, iter):
242 cell.set_property('text', model.get_value(iter, 0))
243 return
244
245
246 def create_recipe_visual_elements(self):
247
248 summary = self.properties['summary']
249 name = self.properties['name']
250 version = self.properties['version']
251 revision = self.properties['revision']
252 binb = self.properties['binb']
253 group = self.properties['group']
254 license = self.properties['license']
255 homepage = self.properties['homepage']
256 bugtracker = self.properties['bugtracker']
257 description = self.properties['description']
258
259 self.set_resizable(False)
260
261 #cleaning out the version variable and also the summary
262 version = version.split(":")[1]
263 if len(version) > 30:
264 version = version.split("+")[0]
265 else:
266 version = version.split("-")[0]
267 license = license.replace("&" , "and")
268 if (homepage == ''):
269 homepage = 'unknown'
270 if (bugtracker == ''):
271 bugtracker = 'unknown'
272 summary = summary.split("+")[0]
273
274 #calculating the rows needed for the table
275 binb_items_count = len(binb.split(','))
276 binb_items = binb.split(',')
277
278 vbox = gtk.VBox(False,spacing = 0)
279
280 ######################################## SUMMARY LABEL #########################################
281
282 if summary != '':
283 self.label_short = gtk.Label()
284 self.label_short.set_width_chars(37)
285 self.label_short.set_selectable(True)
286 self.label_short.set_line_wrap(True)
287 self.label_short.set_markup("<b>" + summary + "</b>")
288 self.label_short.set_property("xalign", 0)
289
290 self.vbox.add(self.label_short)
291
292 ########################################## NAME ROW + COL #######################################
293
294 self.label_short = gtk.Label()
295 self.label_short.set_selectable(True)
296 self.label_short.set_line_wrap(True)
297 self.label_short.set_markup("<span weight=\"bold\">Name: </span>" + name)
298 self.label_short.set_property("xalign", 0)
299
300 self.vbox.add(self.label_short)
301
302 ####################################### VERSION ROW + COL ####################################
303
304 self.label_short = gtk.Label()
305 self.label_short.set_selectable(True)
306 self.label_short.set_line_wrap(True)
307 self.label_short.set_markup("<span weight=\"bold\">Version: </span>" + version)
308 self.label_short.set_property("xalign", 0)
309
310 self.vbox.add(self.label_short)
311
312 ##################################### REVISION ROW + COL #####################################
313
314 self.label_short = gtk.Label()
315 self.label_short.set_line_wrap(True)
316 self.label_short.set_selectable(True)
317 self.label_short.set_markup("<span weight=\"bold\">Revision: </span>" + revision)
318 self.label_short.set_property("xalign", 0)
319
320 self.vbox.add(self.label_short)
321
322 ################################## GROUP ROW + COL ############################################
323
324 self.label_short = gtk.Label()
325 self.label_short.set_selectable(True)
326 self.label_short.set_line_wrap(True)
327 self.label_short.set_markup("<span weight=\"bold\">Group: </span>" + group)
328 self.label_short.set_property("xalign", 0)
329
330 self.vbox.add(self.label_short)
331
332 ################################# HOMEPAGE ROW + COL ############################################
333
334 if homepage != 'unknown':
335 self.label_info = gtk.Label()
336 self.label_info.set_selectable(True)
337 self.label_info.set_line_wrap(True)
338 if len(homepage) > 35:
339 self.label_info.set_markup("<a href=\"" + homepage + "\">" + homepage[0:35] + "..." + "</a>")
340 else:
341 self.label_info.set_markup("<a href=\"" + homepage + "\">" + homepage[0:60] + "</a>")
342
343 self.label_info.set_property("xalign", 0)
344
345 self.label_short = gtk.Label()
346 self.label_short.set_selectable(True)
347 self.label_short.set_line_wrap(True)
348 self.label_short.set_markup("<b>Homepage: </b>")
349 self.label_short.set_property("xalign", 0)
350
351 self.vbox.add(self.label_short)
352 self.vbox.add(self.label_info)
353
354 ################################# BUGTRACKER ROW + COL ###########################################
355
356 if bugtracker != 'unknown':
357 self.label_info = gtk.Label()
358 self.label_info.set_selectable(True)
359 self.label_info.set_line_wrap(True)
360 if len(bugtracker) > 35:
361 self.label_info.set_markup("<a href=\"" + bugtracker + "\">" + bugtracker[0:35] + "..." + "</a>")
362 else:
363 self.label_info.set_markup("<a href=\"" + bugtracker + "\">" + bugtracker[0:60] + "</a>")
364 self.label_info.set_property("xalign", 0)
365
366 self.label_short = gtk.Label()
367 self.label_short.set_selectable(True)
368 self.label_short.set_line_wrap(True)
369 self.label_short.set_markup("<b>Bugtracker: </b>")
370 self.label_short.set_property("xalign", 0)
371
372 self.vbox.add(self.label_short)
373 self.vbox.add(self.label_info)
374
375 ################################# LICENSE ROW + COL ############################################
376
377 self.label_info = gtk.Label()
378 self.label_info.set_selectable(True)
379 self.label_info.set_line_wrap(True)
380 self.label_info.set_markup(license)
381 self.label_info.set_property("xalign", 0)
382
383 self.label_short = gtk.Label()
384 self.label_short.set_selectable(True)
385 self.label_short.set_line_wrap(True)
386 self.label_short.set_markup("<span weight=\"bold\">License: </span>")
387 self.label_short.set_property("xalign", 0)
388
389 self.vbox.add(self.label_short)
390 self.vbox.add(self.label_info)
391
392 ################################### BINB ROW+COL #############################################
393
394 if binb != '':
395 self.label_short = gtk.Label()
396 self.label_short.set_selectable(True)
397 self.label_short.set_line_wrap(True)
398 self.label_short.set_markup("<span weight=\"bold\">Brought in by: </span>")
399 self.label_short.set_property("xalign", 0)
400 self.vbox.add(self.label_short)
401 self.label_info = gtk.Label()
402 self.label_info.set_selectable(True)
403 self.label_info.set_width_chars(36)
404 if len(binb) > 200:
405 scrolled_window = gtk.ScrolledWindow()
406 scrolled_window.set_policy(gtk.POLICY_NEVER,gtk.POLICY_ALWAYS)
407 scrolled_window.set_size_request(100,100)
408 self.label_info.set_markup(binb)
409 self.label_info.set_padding(6,6)
410 self.label_info.set_alignment(0,0)
411 self.label_info.set_line_wrap(True)
412 scrolled_window.add_with_viewport(self.label_info)
413 self.vbox.add(scrolled_window)
414 else:
415 self.label_info.set_markup(binb)
416 self.label_info.set_property("xalign", 0)
417 self.label_info.set_line_wrap(True)
418 self.vbox.add(self.label_info)
419
420 ################################ DESCRIPTION TAG ROW #################################################
421
422 self.label_short = gtk.Label()
423 self.label_short.set_line_wrap(True)
424 self.label_short.set_markup("<span weight=\"bold\">Description </span>")
425 self.label_short.set_property("xalign", 0)
426 self.vbox.add(self.label_short)
427
428 ################################ DESCRIPTION INFORMATION ROW ##########################################
429
430 hbox = gtk.HBox(True,spacing = 0)
431
432 self.label_short = gtk.Label()
433 self.label_short.set_selectable(True)
434 self.label_short.set_width_chars(36)
435 if len(description) > 200:
436 scrolled_window = gtk.ScrolledWindow()
437 scrolled_window.set_policy(gtk.POLICY_NEVER,gtk.POLICY_ALWAYS)
438 scrolled_window.set_size_request(100,100)
439 self.label_short.set_markup(description)
440 self.label_short.set_padding(6,6)
441 self.label_short.set_alignment(0,0)
442 self.label_short.set_line_wrap(True)
443 scrolled_window.add_with_viewport(self.label_short)
444 self.vbox.add(scrolled_window)
445 else:
446 self.label_short.set_markup(description)
447 self.label_short.set_property("xalign", 0)
448 self.label_short.set_line_wrap(True)
449 self.vbox.add(self.label_short)
450
451 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..e940ceee43
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hig/saveimagedialog.py
@@ -0,0 +1,160 @@
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>\n"
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 lbl = lbl + "\n%s\n" % glib.markup_escape_text(msg)
154 dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_ERROR)
155 button = dialog.add_button("Close", gtk.RESPONSE_OK)
156 HobButton.style_button(button)
157
158 res = dialog.run()
159 self.name_entry.grab_focus()
160 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..de924b1206
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hig/simplesettingsdialog.py
@@ -0,0 +1,893 @@
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 # Check that all proxy entries have a corresponding port
215 for proxy, port in zip(self.all_proxy_addresses, self.all_proxy_ports):
216 if proxy.get_text() and not port.get_text():
217 lbl = "<b>Enter all port numbers</b>\n\n"
218 msg = "Proxy servers require a port number. Please make sure you have entered a port number for each proxy server."
219 dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_WARNING, msg)
220 button = dialog.add_button("Close", gtk.RESPONSE_OK)
221 HobButton.style_button(button)
222 response = dialog.run()
223 dialog.destroy()
224 self.emit_stop_by_name("response")
225 return
226
227 self.configuration.dldir = self.dldir_text.get_text()
228 self.configuration.sstatedir = self.sstatedir_text.get_text()
229 self.configuration.sstatemirror = ""
230 for mirror in self.sstatemirrors_list:
231 if mirror[1] != "" and mirror[2].startswith("file://"):
232 if mirror[1].endswith("\\1"):
233 smirror = mirror[2] + " " + mirror[1] + " \\n "
234 else:
235 smirror = mirror[2] + " " + mirror[1] + "\\1 \\n "
236 self.configuration.sstatemirror += smirror
237 self.configuration.bbthread = self.bb_spinner.get_value_as_int()
238 self.configuration.pmake = self.pmake_spinner.get_value_as_int()
239 self.save_proxy_data()
240 self.configuration.extra_setting = {}
241 it = self.setting_store.get_iter_first()
242 while it:
243 key = self.setting_store.get_value(it, 0)
244 value = self.setting_store.get_value(it, 1)
245 self.configuration.extra_setting[key] = value
246 it = self.setting_store.iter_next(it)
247
248 md5 = self.config_md5()
249 self.settings_changed = (self.md5 != md5)
250 self.proxy_settings_changed = (self.proxy_md5 != self.config_proxy_md5())
251
252 def create_build_environment_page(self):
253 advanced_vbox = gtk.VBox(False, 6)
254 advanced_vbox.set_border_width(6)
255
256 advanced_vbox.pack_start(self.gen_label_widget('<span weight="bold">Parallel threads</span>'), expand=False, fill=False)
257 sub_vbox = gtk.VBox(False, 6)
258 advanced_vbox.pack_start(sub_vbox, expand=False, fill=False)
259 label = self.gen_label_widget("BitBake parallel threads")
260 tooltip = "Sets the number of threads that BitBake tasks can simultaneously run. See the <a href=\""
261 tooltip += "http://www.yoctoproject.org/docs/current/poky-ref-manual/"
262 tooltip += "poky-ref-manual.html#var-BB_NUMBER_THREADS\">Poky reference manual</a> for information"
263 bbthread_widget, self.bb_spinner = self.gen_spinner_widget(self.configuration.bbthread, 1, self.max_threads,"<b>BitBake prallalel threads</b>" + "*" + tooltip)
264 sub_vbox.pack_start(label, expand=False, fill=False)
265 sub_vbox.pack_start(bbthread_widget, expand=False, fill=False)
266
267 sub_vbox = gtk.VBox(False, 6)
268 advanced_vbox.pack_start(sub_vbox, expand=False, fill=False)
269 label = self.gen_label_widget("Make parallel threads")
270 tooltip = "Sets the maximum number of threads the host can use during the build. See the <a href=\""
271 tooltip += "http://www.yoctoproject.org/docs/current/poky-ref-manual/"
272 tooltip += "poky-ref-manual.html#var-PARALLEL_MAKE\">Poky reference manual</a> for information"
273 pmake_widget, self.pmake_spinner = self.gen_spinner_widget(self.configuration.pmake, 1, self.max_threads,"<b>Make parallel threads</b>" + "*" + tooltip)
274 sub_vbox.pack_start(label, expand=False, fill=False)
275 sub_vbox.pack_start(pmake_widget, expand=False, fill=False)
276
277 advanced_vbox.pack_start(self.gen_label_widget('<span weight="bold">Downloaded source code</span>'), expand=False, fill=False)
278 sub_vbox = gtk.VBox(False, 6)
279 advanced_vbox.pack_start(sub_vbox, expand=False, fill=False)
280 label = self.gen_label_widget("Downloads directory")
281 tooltip = "Select a folder that caches the upstream project source code"
282 dldir_widget, self.dldir_text = self.gen_entry_widget(self.configuration.dldir, self,"<b>Downloaded source code</b>" + "*" + tooltip)
283 sub_vbox.pack_start(label, expand=False, fill=False)
284 sub_vbox.pack_start(dldir_widget, expand=False, fill=False)
285
286 return advanced_vbox
287
288 def create_shared_state_page(self):
289 advanced_vbox = gtk.VBox(False)
290 advanced_vbox.set_border_width(12)
291
292 sub_vbox = gtk.VBox(False)
293 advanced_vbox.pack_start(sub_vbox, expand=False, fill=False, padding=24)
294 content = "<span>Shared state directory</span>"
295 tooltip = "Select a folder that caches your prebuilt results"
296 label = self.gen_label_info_widget(content,"<b>Shared state directory</b>" + "*" + tooltip)
297 sstatedir_widget, self.sstatedir_text = self.gen_entry_widget(self.configuration.sstatedir, self)
298 sub_vbox.pack_start(label, expand=False, fill=False)
299 sub_vbox.pack_start(sstatedir_widget, expand=False, fill=False, padding=6)
300
301 content = "<span weight=\"bold\">Shared state mirrors</span>"
302 tooltip = "URLs pointing to pre-built mirrors that will speed your build. "
303 tooltip += "Select the \'Standard\' configuration if the structure of your "
304 tooltip += "mirror replicates the structure of your local shared state directory. "
305 tooltip += "For more information on shared state mirrors, check the <a href=\""
306 tooltip += "http://www.yoctoproject.org/docs/current/poky-ref-manual/"
307 tooltip += "poky-ref-manual.html#shared-state\">Yocto Project Reference Manual</a>."
308 table = self.gen_label_info_widget(content,"<b>Shared state mirrors</b>" + "*" + tooltip)
309 advanced_vbox.pack_start(table, expand=False, fill=False, padding=6)
310
311 sub_vbox = gtk.VBox(False)
312 advanced_vbox.pack_start(sub_vbox, gtk.TRUE, gtk.TRUE, 0)
313
314 if self.sstatemirrors_changed == 0:
315 self.sstatemirrors_changed = 1
316 sstatemirrors = self.configuration.sstatemirror
317 if sstatemirrors == "":
318 sm_list = ["Standard", "", "file://(.*)"]
319 self.sstatemirrors_list.append(sm_list)
320 else:
321 sstatemirrors = [x for x in sstatemirrors.split('\\n')]
322 for sstatemirror in sstatemirrors:
323 sstatemirror_fields = [x for x in sstatemirror.split(' ') if x.strip()]
324 if len(sstatemirror_fields) == 2:
325 if sstatemirror_fields[0] == "file://(.*)" or sstatemirror_fields[0] == "file://.*":
326 sm_list = ["Standard", sstatemirror_fields[1], sstatemirror_fields[0]]
327 else:
328 sm_list = ["Custom", sstatemirror_fields[1], sstatemirror_fields[0]]
329 self.sstatemirrors_list.append(sm_list)
330
331 sstatemirrors_widget, sstatemirrors_store = self.gen_shared_sstate_widget(self.sstatemirrors_list, self)
332 sub_vbox.pack_start(sstatemirrors_widget, expand=True, fill=True)
333
334 table = gtk.Table(1, 10, False)
335 table.set_col_spacings(6)
336 add_mirror_button = HobAltButton("Add mirror")
337 add_mirror_button.connect("clicked", self.add_mirror)
338 add_mirror_button.set_size_request(120,30)
339 table.attach(add_mirror_button, 1, 2, 0, 1, xoptions=gtk.SHRINK)
340
341 self.delete_button = HobAltButton("Delete mirror")
342 self.delete_button.connect("clicked", self.delete_cb)
343 self.delete_button.set_size_request(120, 30)
344 table.attach(self.delete_button, 3, 4, 0, 1, xoptions=gtk.SHRINK)
345
346 advanced_vbox.pack_start(table, expand=False, fill=False, padding=6)
347
348 return advanced_vbox
349
350 def gen_shared_sstate_widget(self, sstatemirrors_list, window):
351 hbox = gtk.HBox(False)
352
353 sstatemirrors_store = gtk.ListStore(str, str, str)
354 for sstatemirror in sstatemirrors_list:
355 sstatemirrors_store.append(sstatemirror)
356
357 self.sstatemirrors_tv = gtk.TreeView()
358 self.sstatemirrors_tv.set_rules_hint(True)
359 self.sstatemirrors_tv.set_headers_visible(True)
360 tree_selection = self.sstatemirrors_tv.get_selection()
361 tree_selection.set_mode(gtk.SELECTION_SINGLE)
362
363 # Allow enable drag and drop of rows including row move
364 self.sstatemirrors_tv.enable_model_drag_source( gtk.gdk.BUTTON1_MASK,
365 self.TARGETS,
366 gtk.gdk.ACTION_DEFAULT|
367 gtk.gdk.ACTION_MOVE)
368 self.sstatemirrors_tv.enable_model_drag_dest(self.TARGETS,
369 gtk.gdk.ACTION_DEFAULT)
370 self.sstatemirrors_tv.connect("drag_data_get", self.drag_data_get_cb)
371 self.sstatemirrors_tv.connect("drag_data_received", self.drag_data_received_cb)
372
373
374 self.scroll = gtk.ScrolledWindow()
375 self.scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
376 self.scroll.set_shadow_type(gtk.SHADOW_IN)
377 self.scroll.connect('size-allocate', self.scroll_changed)
378 self.scroll.add(self.sstatemirrors_tv)
379
380 #list store for cell renderer
381 m = gtk.ListStore(gobject.TYPE_STRING)
382 m.append(["Standard"])
383 m.append(["Custom"])
384
385 cell0 = gtk.CellRendererCombo()
386 cell0.set_property("model",m)
387 cell0.set_property("text-column", 0)
388 cell0.set_property("editable", True)
389 cell0.set_property("has-entry", False)
390 col0 = gtk.TreeViewColumn("Configuration")
391 col0.pack_start(cell0, False)
392 col0.add_attribute(cell0, "text", 0)
393 col0.set_cell_data_func(cell0, self.configuration_field)
394 self.sstatemirrors_tv.append_column(col0)
395
396 cell0.connect("edited", self.combo_changed, sstatemirrors_store)
397
398 self.cell1 = gtk.CellRendererText()
399 self.cell1.set_padding(5,2)
400 col1 = gtk.TreeViewColumn('Regex', self.cell1)
401 col1.set_cell_data_func(self.cell1, self.regex_field)
402 self.sstatemirrors_tv.append_column(col1)
403
404 self.cell1.connect("edited", self.regex_changed, sstatemirrors_store)
405
406 cell2 = gtk.CellRendererText()
407 cell2.set_padding(5,2)
408 cell2.set_property("editable", True)
409 col2 = gtk.TreeViewColumn('URL', cell2)
410 col2.set_cell_data_func(cell2, self.url_field)
411 self.sstatemirrors_tv.append_column(col2)
412
413 cell2.connect("edited", self.url_changed, sstatemirrors_store)
414
415 self.sstatemirrors_tv.set_model(sstatemirrors_store)
416 self.sstatemirrors_tv.set_cursor(self.selected_mirror_row)
417 hbox.pack_start(self.scroll, expand=True, fill=True)
418 hbox.show_all()
419
420 return hbox, sstatemirrors_store
421
422 def drag_data_get_cb(self, treeview, context, selection, target_id, etime):
423 treeselection = treeview.get_selection()
424 model, iter = treeselection.get_selected()
425 data = model.get_string_from_iter(iter)
426 selection.set(selection.target, 8, data)
427
428 def drag_data_received_cb(self, treeview, context, x, y, selection, info, etime):
429 model = treeview.get_model()
430 data = []
431 tree_iter = model.get_iter_from_string(selection.data)
432 data.append(model.get_value(tree_iter, 0))
433 data.append(model.get_value(tree_iter, 1))
434 data.append(model.get_value(tree_iter, 2))
435
436 drop_info = treeview.get_dest_row_at_pos(x, y)
437 if drop_info:
438 path, position = drop_info
439 iter = model.get_iter(path)
440 if (position == gtk.TREE_VIEW_DROP_BEFORE or position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE):
441 model.insert_before(iter, data)
442 else:
443 model.insert_after(iter, data)
444 else:
445 model.append(data)
446 if context.action == gtk.gdk.ACTION_MOVE:
447 context.finish(True, True, etime)
448 return
449
450 def delete_cb(self, button):
451 selection = self.sstatemirrors_tv.get_selection()
452 tree_model, tree_iter = selection.get_selected()
453 index = int(tree_model.get_string_from_iter(tree_iter))
454 if index == 0:
455 self.selected_mirror_row = index
456 else:
457 self.selected_mirror_row = index - 1
458 self.sstatemirrors_list.pop(index)
459 self.refresh_shared_state_page()
460 if not self.sstatemirrors_list:
461 self.delete_button.set_sensitive(False)
462
463 def add_mirror(self, button):
464 self.new_mirror = True
465 tooltip = "Select the pre-built mirror that will speed your build"
466 index = len(self.sstatemirrors_list)
467 self.selected_mirror_row = index
468 sm_list = ["Standard", "", "file://(.*)"]
469 self.sstatemirrors_list.append(sm_list)
470 self.refresh_shared_state_page()
471
472 def scroll_changed(self, widget, event, data=None):
473 if self.new_mirror == True:
474 adj = widget.get_vadjustment()
475 adj.set_value(adj.upper - adj.page_size)
476 self.new_mirror = False
477
478 def combo_changed(self, widget, path, text, model):
479 model[path][0] = text
480 selection = self.sstatemirrors_tv.get_selection()
481 tree_model, tree_iter = selection.get_selected()
482 index = int(tree_model.get_string_from_iter(tree_iter))
483 self.sstatemirrors_list[index][0] = text
484
485 def regex_changed(self, cell, path, new_text, user_data):
486 user_data[path][2] = new_text
487 selection = self.sstatemirrors_tv.get_selection()
488 tree_model, tree_iter = selection.get_selected()
489 index = int(tree_model.get_string_from_iter(tree_iter))
490 self.sstatemirrors_list[index][2] = new_text
491 return
492
493 def url_changed(self, cell, path, new_text, user_data):
494 if new_text!="Enter the mirror URL" and new_text!="Match regex and replace it with this URL":
495 user_data[path][1] = new_text
496 selection = self.sstatemirrors_tv.get_selection()
497 tree_model, tree_iter = selection.get_selected()
498 index = int(tree_model.get_string_from_iter(tree_iter))
499 self.sstatemirrors_list[index][1] = new_text
500 return
501
502 def configuration_field(self, column, cell, model, iter):
503 cell.set_property('text', model.get_value(iter, 0))
504 if model.get_value(iter, 0) == "Standard":
505 self.cell1.set_property("sensitive", False)
506 self.cell1.set_property("editable", False)
507 else:
508 self.cell1.set_property("sensitive", True)
509 self.cell1.set_property("editable", True)
510 return
511
512 def regex_field(self, column, cell, model, iter):
513 cell.set_property('text', model.get_value(iter, 2))
514 return
515
516 def url_field(self, column, cell, model, iter):
517 text = model.get_value(iter, 1)
518 if text == "":
519 if model.get_value(iter, 0) == "Standard":
520 text = "Enter the mirror URL"
521 else:
522 text = "Match regex and replace it with this URL"
523 cell.set_property('text', text)
524 return
525
526 def refresh_shared_state_page(self):
527 page_num = self.nb.get_current_page()
528 self.nb.remove_page(page_num);
529 self.nb.insert_page(self.create_shared_state_page(), gtk.Label("Shared state"),page_num)
530 self.show_all()
531 self.nb.set_current_page(page_num)
532
533 def test_proxy_ended(self, passed):
534 self.proxy_test_running = False
535 self.set_test_proxy_state(self.TEST_NETWORK_PASSED if passed else self.TEST_NETWORK_FAILED)
536 self.set_sensitive(True)
537 self.refresh_proxy_components()
538
539 def timer_func(self):
540 self.test_proxy_progress.pulse()
541 return self.proxy_test_running
542
543 def test_network_button_cb(self, b):
544 self.set_test_proxy_state(self.TEST_NETWORK_RUNNING)
545 self.set_sensitive(False)
546 self.save_proxy_data()
547 if self.configuration.enable_proxy == True:
548 self.handler.set_http_proxy(self.configuration.combine_proxy("http"))
549 self.handler.set_https_proxy(self.configuration.combine_proxy("https"))
550 self.handler.set_ftp_proxy(self.configuration.combine_proxy("ftp"))
551 self.handler.set_socks_proxy(self.configuration.combine_proxy("socks"))
552 self.handler.set_cvs_proxy(self.configuration.combine_host_only("cvs"), self.configuration.combine_port_only("cvs"))
553 elif self.configuration.enable_proxy == False:
554 self.handler.set_http_proxy("")
555 self.handler.set_https_proxy("")
556 self.handler.set_ftp_proxy("")
557 self.handler.set_socks_proxy("")
558 self.handler.set_cvs_proxy("", "")
559 self.proxy_test_ran = True
560 self.proxy_test_running = True
561 gobject.timeout_add(100, self.timer_func)
562 self.handler.trigger_network_test()
563
564 def test_proxy_focus_event(self, w, direction):
565 if self.test_proxy_state in [self.TEST_NETWORK_PASSED, self.TEST_NETWORK_FAILED]:
566 self.set_test_proxy_state(self.TEST_NETWORK_INITIAL)
567 return False
568
569 def http_proxy_changed(self, e):
570 if not self.configuration.same_proxy:
571 return
572 if e == self.http_proxy:
573 [w.set_text(self.http_proxy.get_text()) for w in self.same_proxy_addresses]
574 else:
575 [w.set_text(self.http_proxy_port.get_text()) for w in self.same_proxy_ports]
576
577 def proxy_address_focus_out_event(self, w, direction):
578 text = w.get_text()
579 if not text:
580 return False
581 if text.find("//") == -1:
582 w.set_text("http://" + text)
583 return False
584
585 def set_test_proxy_state(self, state):
586 if self.test_proxy_state == state:
587 return
588 [self.proxy_table.remove(w) for w in self.test_gui_elements]
589 if state == self.TEST_NETWORK_INITIAL:
590 self.proxy_table.attach(self.test_network_button, 1, 2, 5, 6)
591 self.test_network_button.show()
592 elif state == self.TEST_NETWORK_RUNNING:
593 self.test_proxy_progress.set_rcstyle("running")
594 self.test_proxy_progress.set_text("Testing network configuration")
595 self.proxy_table.attach(self.test_proxy_progress, 0, 5, 5, 6, xpadding=4)
596 self.test_proxy_progress.show()
597 else: # passed or failed
598 self.dummy_progress.update(1.0)
599 if state == self.TEST_NETWORK_PASSED:
600 self.dummy_progress.set_text("Your network is properly configured")
601 self.dummy_progress.set_rcstyle("running")
602 else:
603 self.dummy_progress.set_text("Network test failed")
604 self.dummy_progress.set_rcstyle("fail")
605 self.proxy_table.attach(self.dummy_progress, 0, 4, 5, 6)
606 self.proxy_table.attach(self.retest_network_button, 4, 5, 5, 6, xpadding=4)
607 self.dummy_progress.show()
608 self.retest_network_button.show()
609 self.test_proxy_state = state
610
611 def create_network_page(self):
612 advanced_vbox = gtk.VBox(False, 6)
613 advanced_vbox.set_border_width(6)
614 self.same_proxy_addresses = []
615 self.same_proxy_ports = []
616 self.all_proxy_ports = []
617 self.all_proxy_addresses = []
618
619 sub_vbox = gtk.VBox(False, 6)
620 advanced_vbox.pack_start(sub_vbox, expand=False, fill=False)
621 label = self.gen_label_widget("<span weight=\"bold\">Set the proxies used when fetching source code</span>")
622 tooltip = "Set the proxies used when fetching source code. A blank field uses a direct internet connection."
623 info = HobInfoButton("<span weight=\"bold\">Set the proxies used when fetching source code</span>" + "*" + tooltip, self)
624 hbox = gtk.HBox(False, 12)
625 hbox.pack_start(label, expand=True, fill=True)
626 hbox.pack_start(info, expand=False, fill=False)
627 sub_vbox.pack_start(hbox, expand=False, fill=False)
628
629 proxy_test_focus = []
630 self.direct_checkbox = gtk.RadioButton(None, "Direct network connection")
631 proxy_test_focus.append(self.direct_checkbox)
632 self.direct_checkbox.set_tooltip_text("Check this box to use a direct internet connection with no proxy")
633 self.direct_checkbox.set_active(not self.configuration.enable_proxy)
634 sub_vbox.pack_start(self.direct_checkbox, expand=False, fill=False)
635
636 self.proxy_checkbox = gtk.RadioButton(self.direct_checkbox, "Manual proxy configuration")
637 proxy_test_focus.append(self.proxy_checkbox)
638 self.proxy_checkbox.set_tooltip_text("Check this box to manually set up a specific proxy")
639 self.proxy_checkbox.set_active(self.configuration.enable_proxy)
640 sub_vbox.pack_start(self.proxy_checkbox, expand=False, fill=False)
641
642 self.same_checkbox = gtk.CheckButton("Use the HTTP proxy for all protocols")
643 proxy_test_focus.append(self.same_checkbox)
644 self.same_checkbox.set_tooltip_text("Check this box to use the HTTP proxy for all five proxies")
645 self.same_checkbox.set_active(self.configuration.same_proxy)
646 hbox = gtk.HBox(False, 12)
647 hbox.pack_start(self.same_checkbox, expand=False, fill=False, padding=24)
648 sub_vbox.pack_start(hbox, expand=False, fill=False)
649
650 self.proxy_table = gtk.Table(6, 5, False)
651 self.http_proxy, self.http_proxy_port, self.http_proxy_details = self.gen_proxy_entry_widget(
652 "http", self, True, 0)
653 proxy_test_focus +=[self.http_proxy, self.http_proxy_port]
654 self.http_proxy.connect("changed", self.http_proxy_changed)
655 self.http_proxy_port.connect("changed", self.http_proxy_changed)
656
657 self.https_proxy, self.https_proxy_port, self.https_proxy_details = self.gen_proxy_entry_widget(
658 "https", self, True, 1)
659 proxy_test_focus += [self.https_proxy, self.https_proxy_port]
660 self.same_proxy_addresses.append(self.https_proxy)
661 self.same_proxy_ports.append(self.https_proxy_port)
662
663 self.ftp_proxy, self.ftp_proxy_port, self.ftp_proxy_details = self.gen_proxy_entry_widget(
664 "ftp", self, True, 2)
665 proxy_test_focus += [self.ftp_proxy, self.ftp_proxy_port]
666 self.same_proxy_addresses.append(self.ftp_proxy)
667 self.same_proxy_ports.append(self.ftp_proxy_port)
668
669 self.socks_proxy, self.socks_proxy_port, self.socks_proxy_details = self.gen_proxy_entry_widget(
670 "socks", self, True, 3)
671 proxy_test_focus += [self.socks_proxy, self.socks_proxy_port]
672 self.same_proxy_addresses.append(self.socks_proxy)
673 self.same_proxy_ports.append(self.socks_proxy_port)
674
675 self.cvs_proxy, self.cvs_proxy_port, self.cvs_proxy_details = self.gen_proxy_entry_widget(
676 "cvs", self, True, 4)
677 proxy_test_focus += [self.cvs_proxy, self.cvs_proxy_port]
678 self.same_proxy_addresses.append(self.cvs_proxy)
679 self.same_proxy_ports.append(self.cvs_proxy_port)
680 self.all_proxy_ports = self.same_proxy_ports + [self.http_proxy_port]
681 self.all_proxy_addresses = self.same_proxy_addresses + [self.http_proxy]
682 sub_vbox.pack_start(self.proxy_table, expand=False, fill=False)
683 self.proxy_table.show_all()
684
685 # Create the graphical elements for the network test feature, but don't display them yet
686 self.test_network_button = HobAltButton("Test network configuration")
687 self.test_network_button.connect("clicked", self.test_network_button_cb)
688 self.test_proxy_progress = HobProgressBar()
689 self.dummy_progress = HobProgressBar()
690 self.retest_network_button = HobAltButton("Retest")
691 self.retest_network_button.connect("clicked", self.test_network_button_cb)
692 self.test_gui_elements = [self.test_network_button, self.test_proxy_progress, self.dummy_progress, self.retest_network_button]
693 # Initialize the network tester
694 self.test_proxy_state = self.TEST_NETWORK_NONE
695 self.set_test_proxy_state(self.TEST_NETWORK_INITIAL)
696 self.proxy_test_passed_id = self.handler.connect("network-passed", lambda h:self.test_proxy_ended(True))
697 self.proxy_test_failed_id = self.handler.connect("network-failed", lambda h:self.test_proxy_ended(False))
698 [w.connect("focus-in-event", self.test_proxy_focus_event) for w in proxy_test_focus]
699 [w.connect("focus-out-event", self.proxy_address_focus_out_event) for w in self.all_proxy_addresses]
700
701 self.direct_checkbox.connect("toggled", self.proxy_checkbox_toggled_cb)
702 self.proxy_checkbox.connect("toggled", self.proxy_checkbox_toggled_cb)
703 self.same_checkbox.connect("toggled", self.same_checkbox_toggled_cb)
704
705 self.refresh_proxy_components()
706 return advanced_vbox
707
708 def switch_to_page(self, page_id):
709 self.nb.set_current_page(page_id)
710
711 def details_cb(self, button, parent, protocol):
712 self.save_proxy_data()
713 dialog = ProxyDetailsDialog(title = protocol.upper() + " Proxy Details",
714 user = self.configuration.proxies[protocol][1],
715 passwd = self.configuration.proxies[protocol][2],
716 parent = parent,
717 flags = gtk.DIALOG_MODAL
718 | gtk.DIALOG_DESTROY_WITH_PARENT
719 | gtk.DIALOG_NO_SEPARATOR)
720 dialog.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_OK)
721 response = dialog.run()
722 if response == gtk.RESPONSE_OK:
723 self.configuration.proxies[protocol][1] = dialog.user
724 self.configuration.proxies[protocol][2] = dialog.passwd
725 self.refresh_proxy_components()
726 dialog.destroy()
727
728 def rootfs_combo_changed_cb(self, rootfs_combo, all_package_format, check_hbox):
729 combo_item = self.rootfs_combo.get_active_text()
730 for child in check_hbox.get_children():
731 if isinstance(child, gtk.CheckButton):
732 check_hbox.remove(child)
733 for format in all_package_format:
734 if format != combo_item:
735 check_button = gtk.CheckButton(format)
736 check_hbox.pack_start(check_button, expand=False, fill=False)
737 check_hbox.show_all()
738
739 def gen_pkgfmt_widget(self, curr_package_format, all_package_format, tooltip_combo="", tooltip_extra=""):
740 pkgfmt_hbox = gtk.HBox(False, 24)
741
742 rootfs_vbox = gtk.VBox(False, 6)
743 pkgfmt_hbox.pack_start(rootfs_vbox, expand=False, fill=False)
744
745 label = self.gen_label_widget("Root file system package format")
746 rootfs_vbox.pack_start(label, expand=False, fill=False)
747
748 rootfs_format = ""
749 if curr_package_format:
750 rootfs_format = curr_package_format.split()[0]
751
752 rootfs_format_widget, rootfs_combo = self.gen_combo_widget(rootfs_format, all_package_format, tooltip_combo)
753 rootfs_vbox.pack_start(rootfs_format_widget, expand=False, fill=False)
754
755 extra_vbox = gtk.VBox(False, 6)
756 pkgfmt_hbox.pack_start(extra_vbox, expand=False, fill=False)
757
758 label = self.gen_label_widget("Additional package formats")
759 extra_vbox.pack_start(label, expand=False, fill=False)
760
761 check_hbox = gtk.HBox(False, 12)
762 extra_vbox.pack_start(check_hbox, expand=False, fill=False)
763 for format in all_package_format:
764 if format != rootfs_format:
765 check_button = gtk.CheckButton(format)
766 is_active = (format in curr_package_format.split())
767 check_button.set_active(is_active)
768 check_hbox.pack_start(check_button, expand=False, fill=False)
769
770 info = HobInfoButton(tooltip_extra, self)
771 check_hbox.pack_end(info, expand=False, fill=False)
772
773 rootfs_combo.connect("changed", self.rootfs_combo_changed_cb, all_package_format, check_hbox)
774
775 pkgfmt_hbox.show_all()
776
777 return pkgfmt_hbox, rootfs_combo, check_hbox
778
779 def editable_settings_cell_edited(self, cell, path_string, new_text, model):
780 it = model.get_iter_from_string(path_string)
781 column = cell.get_data("column")
782 model.set(it, column, new_text)
783
784 def editable_settings_add_item_clicked(self, button, model):
785 new_item = ["##KEY##", "##VALUE##"]
786
787 iter = model.append()
788 model.set (iter,
789 0, new_item[0],
790 1, new_item[1],
791 )
792
793 def editable_settings_remove_item_clicked(self, button, treeview):
794 selection = treeview.get_selection()
795 model, iter = selection.get_selected()
796
797 if iter:
798 path = model.get_path(iter)[0]
799 model.remove(iter)
800
801 def gen_editable_settings(self, setting, tooltip=""):
802 setting_hbox = gtk.HBox(False, 12)
803
804 vbox = gtk.VBox(False, 12)
805 setting_hbox.pack_start(vbox, expand=True, fill=True)
806
807 setting_store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
808 for key in setting.keys():
809 setting_store.set(setting_store.append(), 0, key, 1, setting[key])
810
811 setting_tree = gtk.TreeView(setting_store)
812 setting_tree.set_headers_visible(True)
813 setting_tree.set_size_request(300, 100)
814
815 col = gtk.TreeViewColumn('Key')
816 col.set_min_width(100)
817 col.set_max_width(150)
818 col.set_resizable(True)
819 col1 = gtk.TreeViewColumn('Value')
820 col1.set_min_width(100)
821 col1.set_max_width(150)
822 col1.set_resizable(True)
823 setting_tree.append_column(col)
824 setting_tree.append_column(col1)
825 cell = gtk.CellRendererText()
826 cell.set_property('width-chars', 10)
827 cell.set_property('editable', True)
828 cell.set_data("column", 0)
829 cell.connect("edited", self.editable_settings_cell_edited, setting_store)
830 cell1 = gtk.CellRendererText()
831 cell1.set_property('width-chars', 10)
832 cell1.set_property('editable', True)
833 cell1.set_data("column", 1)
834 cell1.connect("edited", self.editable_settings_cell_edited, setting_store)
835 col.pack_start(cell, True)
836 col1.pack_end(cell1, True)
837 col.set_attributes(cell, text=0)
838 col1.set_attributes(cell1, text=1)
839
840 scroll = gtk.ScrolledWindow()
841 scroll.set_shadow_type(gtk.SHADOW_IN)
842 scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
843 scroll.add(setting_tree)
844 vbox.pack_start(scroll, expand=True, fill=True)
845
846 # some buttons
847 hbox = gtk.HBox(True, 6)
848 vbox.pack_start(hbox, False, False)
849
850 button = gtk.Button(stock=gtk.STOCK_ADD)
851 button.connect("clicked", self.editable_settings_add_item_clicked, setting_store)
852 hbox.pack_start(button)
853
854 button = gtk.Button(stock=gtk.STOCK_REMOVE)
855 button.connect("clicked", self.editable_settings_remove_item_clicked, setting_tree)
856 hbox.pack_start(button)
857
858 info = HobInfoButton(tooltip, self)
859 setting_hbox.pack_start(info, expand=False, fill=False)
860
861 return setting_hbox, setting_store
862
863 def create_others_page(self):
864 advanced_vbox = gtk.VBox(False, 6)
865 advanced_vbox.set_border_width(6)
866
867 sub_vbox = gtk.VBox(False, 6)
868 advanced_vbox.pack_start(sub_vbox, expand=True, fill=True)
869 label = self.gen_label_widget("<span weight=\"bold\">Add your own variables:</span>")
870 tooltip = "These are key/value pairs for your extra settings. Click \'Add\' and then directly edit the key and the value"
871 setting_widget, self.setting_store = self.gen_editable_settings(self.configuration.extra_setting,"<b>Add your own variables</b>" + "*" + tooltip)
872 sub_vbox.pack_start(label, expand=False, fill=False)
873 sub_vbox.pack_start(setting_widget, expand=True, fill=True)
874
875 return advanced_vbox
876
877 def create_visual_elements(self):
878 self.nb = gtk.Notebook()
879 self.nb.set_show_tabs(True)
880 self.nb.append_page(self.create_build_environment_page(), gtk.Label("Build environment"))
881 self.nb.append_page(self.create_shared_state_page(), gtk.Label("Shared state"))
882 self.nb.append_page(self.create_network_page(), gtk.Label("Network"))
883 self.nb.append_page(self.create_others_page(), gtk.Label("Others"))
884 self.nb.set_current_page(0)
885 self.vbox.pack_start(self.nb, expand=True, fill=True)
886 self.vbox.pack_end(gtk.HSeparator(), expand=True, fill=True)
887
888 self.show_all()
889
890 def destroy(self):
891 self.handler.disconnect(self.proxy_test_passed_id)
892 self.handler.disconnect(self.proxy_test_failed_id)
893 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..393c258e46
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hobeventhandler.py
@@ -0,0 +1,624 @@
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(["enableDataTracking"])
151 self.runCommand(["parseConfigurationFiles", "conf/.hob.conf", ""])
152 self.runCommand(["disableDataTracking"])
153 elif next_command == self.SUB_GNERATE_TGTS:
154 self.runCommand(["generateTargetsTree", "classes/image.bbclass", []])
155 elif next_command == self.SUB_GENERATE_PKGINFO:
156 self.runCommand(["triggerEvent", "bb.event.RequestPackageInfo()"])
157 elif next_command == self.SUB_SANITY_CHECK:
158 self.runCommand(["triggerEvent", "bb.event.SanityCheck()"])
159 elif next_command == self.SUB_NETWORK_TEST:
160 self.runCommand(["triggerEvent", "bb.event.NetworkTest()"])
161 elif next_command == self.SUB_BUILD_RECIPES:
162 self.clear_busy()
163 self.building = True
164 self.runCommand(["buildTargets", self.recipe_queue, self.default_task])
165 self.recipe_queue = []
166 elif next_command == self.SUB_BUILD_IMAGE:
167 self.clear_busy()
168 self.building = True
169 targets = [self.image]
170 if self.toolchain_packages:
171 self.set_var_in_file("TOOLCHAIN_TARGET_TASK", " ".join(self.toolchain_packages), "local.conf")
172 targets.append(self.toolchain)
173 if targets[0] == "hob-image":
174 self.set_var_in_file("LINGUAS_INSTALL", "", "local.conf")
175 hobImage = self.runCommand(["matchFile", "hob-image.bb"])
176 if self.base_image != "Start with an empty image recipe":
177 baseImage = self.runCommand(["matchFile", self.base_image + ".bb"])
178 version = self.runCommand(["generateNewImage", hobImage, baseImage, self.package_queue, True, ""])
179 targets[0] += version
180 self.recipe_model.set_custom_image_version(version)
181
182 self.runCommand(["buildTargets", targets, self.default_task])
183
184 def display_error(self):
185 self.clear_busy()
186 self.emit("command-failed", self.error_msg)
187 self.error_msg = ""
188 if self.building:
189 self.building = False
190
191 def handle_event(self, event):
192 if not event:
193 return
194 if self.building:
195 self.current_phase = "building"
196 self.build.handle_event(event)
197
198 if isinstance(event, bb.event.PackageInfo):
199 self.package_model.populate(event._pkginfolist)
200 self.emit("package-populated")
201 self.run_next_command()
202
203 elif isinstance(event, bb.event.SanityCheckPassed):
204 reparse = self.runCommand(["getVariable", "BB_INVALIDCONF"]) or None
205 if reparse is True:
206 self.set_var_in_file("BB_INVALIDCONF", False, "local.conf")
207 self.runCommand(["parseConfigurationFiles", "conf/.hob.conf", ""])
208 self.run_next_command()
209
210 elif isinstance(event, bb.event.SanityCheckFailed):
211 self.emit("sanity-failed", event._msg, event._network_error)
212
213 elif isinstance(event, logging.LogRecord):
214 if not self.building:
215 if event.levelno >= logging.ERROR:
216 formatter = bb.msg.BBLogFormatter()
217 msg = formatter.format(event)
218 self.error_msg += msg + '\n'
219 elif event.levelno >= logging.WARNING and self.parsing == True:
220 formatter = bb.msg.BBLogFormatter()
221 msg = formatter.format(event)
222 warn_msg = msg + '\n'
223 self.emit("parsing-warning", warn_msg)
224
225 elif isinstance(event, bb.event.TargetsTreeGenerated):
226 self.current_phase = "data generation"
227 if event._model:
228 self.recipe_model.populate(event._model)
229 self.emit("recipe-populated")
230 elif isinstance(event, bb.event.ConfigFilesFound):
231 self.current_phase = "configuration lookup"
232 var = event._variable
233 values = event._values
234 values.sort()
235 self.emit("config-updated", var, values)
236 elif isinstance(event, bb.event.ConfigFilePathFound):
237 self.current_phase = "configuration lookup"
238 elif isinstance(event, bb.event.FilesMatchingFound):
239 self.current_phase = "configuration lookup"
240 # FIXME: hard coding, should at least be a variable shared between
241 # here and the caller
242 if event._pattern == "rootfs_":
243 formats = []
244 for match in event._matches:
245 classname, sep, cls = match.rpartition(".")
246 fs, sep, format = classname.rpartition("_")
247 formats.append(format)
248 formats.sort()
249 self.emit("package-formats-updated", formats)
250 elif isinstance(event, bb.command.CommandCompleted):
251 self.current_phase = None
252 self.run_next_command()
253 elif isinstance(event, bb.command.CommandFailed):
254 self.commands_async = []
255 self.display_error()
256 elif isinstance(event, (bb.event.ParseStarted,
257 bb.event.CacheLoadStarted,
258 bb.event.TreeDataPreparationStarted,
259 )):
260 message = {}
261 message["eventname"] = bb.event.getName(event)
262 message["current"] = 0
263 message["total"] = None
264 message["title"] = "Parsing recipes"
265 self.emit("parsing-started", message)
266 if isinstance(event, bb.event.ParseStarted):
267 self.parsing = True
268 elif isinstance(event, (bb.event.ParseProgress,
269 bb.event.CacheLoadProgress,
270 bb.event.TreeDataPreparationProgress)):
271 message = {}
272 message["eventname"] = bb.event.getName(event)
273 message["current"] = event.current
274 message["total"] = event.total
275 message["title"] = "Parsing recipes"
276 self.emit("parsing", message)
277 elif isinstance(event, (bb.event.ParseCompleted,
278 bb.event.CacheLoadCompleted,
279 bb.event.TreeDataPreparationCompleted)):
280 message = {}
281 message["eventname"] = bb.event.getName(event)
282 message["current"] = event.total
283 message["total"] = event.total
284 message["title"] = "Parsing recipes"
285 self.emit("parsing-completed", message)
286 if isinstance(event, bb.event.ParseCompleted):
287 self.parsing = False
288 elif isinstance(event, bb.event.NetworkTestFailed):
289 self.emit("network-failed")
290 self.run_next_command()
291 elif isinstance(event, bb.event.NetworkTestPassed):
292 self.emit("network-passed")
293 self.run_next_command()
294
295 if self.error_msg and not self.commands_async:
296 self.display_error()
297
298 return
299
300 def init_cooker(self):
301 self.runCommand(["initCooker"])
302 self.runCommand(["createConfigFile", ".hob.conf"])
303
304 def reset_cooker(self):
305 self.runCommand(["enableDataTracking"])
306 self.runCommand(["resetCooker"])
307 self.runCommand(["disableDataTracking"])
308
309 def set_extra_inherit(self, bbclass):
310 inherits = self.runCommand(["getVariable", "INHERIT"]) or ""
311 inherits = inherits + " " + bbclass
312 self.set_var_in_file("INHERIT", inherits, ".hob.conf")
313
314 def set_bblayers(self, bblayers):
315 self.set_var_in_file("BBLAYERS", " ".join(bblayers), "bblayers.conf")
316
317 def set_machine(self, machine):
318 if machine:
319 self.early_assign_var_in_file("MACHINE", machine, "local.conf")
320
321 def set_sdk_machine(self, sdk_machine):
322 self.set_var_in_file("SDKMACHINE", sdk_machine, "local.conf")
323
324 def set_image_fstypes(self, image_fstypes):
325 self.set_var_in_file("IMAGE_FSTYPES", image_fstypes, "local.conf")
326
327 def set_distro(self, distro):
328 self.set_var_in_file("DISTRO", distro, "local.conf")
329
330 def set_package_format(self, format):
331 package_classes = ""
332 for pkgfmt in format.split():
333 package_classes += ("package_%s" % pkgfmt + " ")
334 self.set_var_in_file("PACKAGE_CLASSES", package_classes, "local.conf")
335
336 def set_bbthreads(self, threads):
337 self.set_var_in_file("BB_NUMBER_THREADS", threads, "local.conf")
338
339 def set_pmake(self, threads):
340 pmake = "-j %s" % threads
341 self.set_var_in_file("PARALLEL_MAKE", pmake, "local.conf")
342
343 def set_dl_dir(self, directory):
344 self.set_var_in_file("DL_DIR", directory, "local.conf")
345
346 def set_sstate_dir(self, directory):
347 self.set_var_in_file("SSTATE_DIR", directory, "local.conf")
348
349 def set_sstate_mirrors(self, url):
350 self.set_var_in_file("SSTATE_MIRRORS", url, "local.conf")
351
352 def set_extra_size(self, image_extra_size):
353 self.set_var_in_file("IMAGE_ROOTFS_EXTRA_SPACE", str(image_extra_size), "local.conf")
354
355 def set_rootfs_size(self, image_rootfs_size):
356 self.set_var_in_file("IMAGE_ROOTFS_SIZE", str(image_rootfs_size), "local.conf")
357
358 def set_incompatible_license(self, incompat_license):
359 self.set_var_in_file("INCOMPATIBLE_LICENSE", incompat_license, "local.conf")
360
361 def set_extra_setting(self, extra_setting):
362 self.set_var_in_file("EXTRA_SETTING", extra_setting, "local.conf")
363
364 def set_extra_config(self, extra_setting):
365 old_extra_setting = ast.literal_eval(self.runCommand(["getVariable", "EXTRA_SETTING"]) or "{}")
366 if extra_setting:
367 self.set_var_in_file("EXTRA_SETTING", extra_setting, "local.conf")
368 else:
369 self.remove_var_from_file("EXTRA_SETTING")
370
371 #remove not needed settings from conf
372 for key in old_extra_setting:
373 if key not in extra_setting:
374 self.remove_var_from_file(key)
375 for key in extra_setting.keys():
376 value = extra_setting[key]
377 self.set_var_in_file(key, value, "local.conf")
378
379 def set_http_proxy(self, http_proxy):
380 self.set_var_in_file("http_proxy", http_proxy, "local.conf")
381
382 def set_https_proxy(self, https_proxy):
383 self.set_var_in_file("https_proxy", https_proxy, "local.conf")
384
385 def set_ftp_proxy(self, ftp_proxy):
386 self.set_var_in_file("ftp_proxy", ftp_proxy, "local.conf")
387
388 def set_socks_proxy(self, socks_proxy):
389 self.set_var_in_file("all_proxy", socks_proxy, "local.conf")
390
391 def set_cvs_proxy(self, host, port):
392 self.set_var_in_file("CVS_PROXY_HOST", host, "local.conf")
393 self.set_var_in_file("CVS_PROXY_PORT", port, "local.conf")
394
395 def request_package_info(self):
396 self.commands_async.append(self.SUB_GENERATE_PKGINFO)
397 self.run_next_command(self.POPULATE_PACKAGEINFO)
398
399 def trigger_sanity_check(self):
400 self.commands_async.append(self.SUB_SANITY_CHECK)
401 self.run_next_command(self.SANITY_CHECK)
402
403 def trigger_network_test(self):
404 self.commands_async.append(self.SUB_NETWORK_TEST)
405 self.run_next_command(self.NETWORK_TEST)
406
407 def generate_configuration(self):
408 self.commands_async.append(self.SUB_PARSE_CONFIG)
409 self.commands_async.append(self.SUB_PATH_LAYERS)
410 self.commands_async.append(self.SUB_FILES_DISTRO)
411 self.commands_async.append(self.SUB_FILES_MACH)
412 self.commands_async.append(self.SUB_FILES_SDKMACH)
413 self.commands_async.append(self.SUB_MATCH_CLASS)
414 self.run_next_command(self.GENERATE_CONFIGURATION)
415
416 def generate_recipes(self):
417 self.commands_async.append(self.SUB_PARSE_CONFIG)
418 self.commands_async.append(self.SUB_GNERATE_TGTS)
419 self.run_next_command(self.GENERATE_RECIPES)
420
421 def generate_packages(self, tgts, default_task="build"):
422 targets = []
423 targets.extend(tgts)
424 self.recipe_queue = targets
425 self.default_task = default_task
426 self.commands_async.append(self.SUB_PARSE_CONFIG)
427 self.commands_async.append(self.SUB_BUILD_RECIPES)
428 self.run_next_command(self.GENERATE_PACKAGES)
429
430 def generate_image(self, image, base_image, toolchain, image_packages=[], toolchain_packages=[], default_task="build"):
431 self.image = image
432 self.base_image = base_image
433 self.toolchain = toolchain
434 self.package_queue = image_packages
435 self.toolchain_packages = toolchain_packages
436 self.default_task = default_task
437 self.commands_async.append(self.SUB_PARSE_CONFIG)
438 self.commands_async.append(self.SUB_BUILD_IMAGE)
439 self.run_next_command(self.GENERATE_IMAGE)
440
441 def generate_new_image(self, image, base_image, package_queue, description):
442 base_image = self.runCommand(["matchFile", self.base_image + ".bb"])
443 self.runCommand(["generateNewImage", image, base_image, package_queue, False, description])
444
445 def ensure_dir(self, directory):
446 self.runCommand(["ensureDir", directory])
447
448 def build_succeeded_async(self):
449 self.building = False
450
451 def build_failed_async(self):
452 self.initcmd = None
453 self.commands_async = []
454 self.building = False
455
456 def cancel_parse(self):
457 self.runCommand(["stateForceShutdown"])
458
459 def cancel_build(self, force=False):
460 if force:
461 # Force the cooker to stop as quickly as possible
462 self.runCommand(["stateForceShutdown"])
463 else:
464 # Wait for tasks to complete before shutting down, this helps
465 # leave the workdir in a usable state
466 self.runCommand(["stateShutdown"])
467
468 def reset_build(self):
469 self.build.reset()
470
471 def get_logfile(self):
472 return self.server.runCommand(["getVariable", "BB_CONSOLELOG"])[0]
473
474 def get_topdir(self):
475 return self.runCommand(["getVariable", "TOPDIR"]) or ""
476
477 def _remove_redundant(self, string):
478 ret = []
479 for i in string.split():
480 if i not in ret:
481 ret.append(i)
482 return " ".join(ret)
483
484 def set_var_in_file(self, var, val, default_file=None):
485 self.runCommand(["enableDataTracking"])
486 self.server.runCommand(["setVarFile", var, val, default_file, "set"])
487 self.runCommand(["disableDataTracking"])
488
489 def early_assign_var_in_file(self, var, val, default_file=None):
490 self.runCommand(["enableDataTracking"])
491 self.server.runCommand(["setVarFile", var, val, default_file, "earlyAssign"])
492 self.runCommand(["disableDataTracking"])
493
494 def remove_var_from_file(self, var):
495 self.server.runCommand(["removeVarFile", var])
496
497 def append_var_in_file(self, var, val, default_file=None):
498 self.server.runCommand(["setVarFile", var, val, default_file, "append"])
499
500 def append_to_bbfiles(self, val):
501 bbfiles = self.runCommand(["getVariable", "BBFILES", "False"]) or ""
502 bbfiles = bbfiles.split()
503 if val not in bbfiles:
504 self.append_var_in_file("BBFILES", val, "local.conf")
505
506 def get_parameters(self):
507 # retrieve the parameters from bitbake
508 params = {}
509 params["core_base"] = self.runCommand(["getVariable", "COREBASE"]) or ""
510 hob_layer = params["core_base"] + "/meta-hob"
511 params["layer"] = self.runCommand(["getVariable", "BBLAYERS"]) or ""
512 params["layers_non_removable"] = self.runCommand(["getVariable", "BBLAYERS_NON_REMOVABLE"]) or ""
513 if hob_layer not in params["layer"].split():
514 params["layer"] += (" " + hob_layer)
515 if hob_layer not in params["layers_non_removable"].split():
516 params["layers_non_removable"] += (" " + hob_layer)
517 params["dldir"] = self.runCommand(["getVariable", "DL_DIR"]) or ""
518 params["machine"] = self.runCommand(["getVariable", "MACHINE"]) or ""
519 params["distro"] = self.runCommand(["getVariable", "DISTRO"]) or "defaultsetup"
520 params["pclass"] = self.runCommand(["getVariable", "PACKAGE_CLASSES"]) or ""
521 params["sstatedir"] = self.runCommand(["getVariable", "SSTATE_DIR"]) or ""
522 params["sstatemirror"] = self.runCommand(["getVariable", "SSTATE_MIRRORS"]) or ""
523
524 num_threads = self.runCommand(["getCpuCount"])
525 if not num_threads:
526 num_threads = 1
527 max_threads = 65536
528 else:
529 try:
530 num_threads = int(num_threads)
531 max_threads = 16 * num_threads
532 except:
533 num_threads = 1
534 max_threads = 65536
535 params["max_threads"] = max_threads
536
537 bbthread = self.runCommand(["getVariable", "BB_NUMBER_THREADS"])
538 if not bbthread:
539 bbthread = num_threads
540 else:
541 try:
542 bbthread = int(bbthread)
543 except:
544 bbthread = num_threads
545 params["bbthread"] = bbthread
546
547 pmake = self.runCommand(["getVariable", "PARALLEL_MAKE"])
548 if not pmake:
549 pmake = num_threads
550 elif isinstance(pmake, int):
551 pass
552 else:
553 try:
554 pmake = int(pmake.lstrip("-j "))
555 except:
556 pmake = num_threads
557 params["pmake"] = "-j %s" % pmake
558
559 params["image_addr"] = self.runCommand(["getVariable", "DEPLOY_DIR_IMAGE"]) or ""
560
561 image_extra_size = self.runCommand(["getVariable", "IMAGE_ROOTFS_EXTRA_SPACE"])
562 if not image_extra_size:
563 image_extra_size = 0
564 else:
565 try:
566 image_extra_size = int(image_extra_size)
567 except:
568 image_extra_size = 0
569 params["image_extra_size"] = image_extra_size
570
571 image_rootfs_size = self.runCommand(["getVariable", "IMAGE_ROOTFS_SIZE"])
572 if not image_rootfs_size:
573 image_rootfs_size = 0
574 else:
575 try:
576 image_rootfs_size = int(image_rootfs_size)
577 except:
578 image_rootfs_size = 0
579 params["image_rootfs_size"] = image_rootfs_size
580
581 image_overhead_factor = self.runCommand(["getVariable", "IMAGE_OVERHEAD_FACTOR"])
582 if not image_overhead_factor:
583 image_overhead_factor = 1
584 else:
585 try:
586 image_overhead_factor = float(image_overhead_factor)
587 except:
588 image_overhead_factor = 1
589 params['image_overhead_factor'] = image_overhead_factor
590
591 params["incompat_license"] = self._remove_redundant(self.runCommand(["getVariable", "INCOMPATIBLE_LICENSE"]) or "")
592 params["sdk_machine"] = self.runCommand(["getVariable", "SDKMACHINE"]) or self.runCommand(["getVariable", "SDK_ARCH"]) or ""
593
594 params["image_fstypes"] = self._remove_redundant(self.runCommand(["getVariable", "IMAGE_FSTYPES"]) or "")
595
596 params["image_types"] = self._remove_redundant(self.runCommand(["getVariable", "IMAGE_TYPES"]) or "")
597
598 params["conf_version"] = self.runCommand(["getVariable", "CONF_VERSION"]) or ""
599 params["lconf_version"] = self.runCommand(["getVariable", "LCONF_VERSION"]) or ""
600
601 params["runnable_image_types"] = self._remove_redundant(self.runCommand(["getVariable", "RUNNABLE_IMAGE_TYPES"]) or "")
602 params["runnable_machine_patterns"] = self._remove_redundant(self.runCommand(["getVariable", "RUNNABLE_MACHINE_PATTERNS"]) or "")
603 params["deployable_image_types"] = self._remove_redundant(self.runCommand(["getVariable", "DEPLOYABLE_IMAGE_TYPES"]) or "")
604 params["kernel_image_type"] = self.runCommand(["getVariable", "KERNEL_IMAGETYPE"]) or ""
605 params["tmpdir"] = self.runCommand(["getVariable", "TMPDIR"]) or ""
606 params["distro_version"] = self.runCommand(["getVariable", "DISTRO_VERSION"]) or ""
607 params["target_os"] = self.runCommand(["getVariable", "TARGET_OS"]) or ""
608 params["target_arch"] = self.runCommand(["getVariable", "TARGET_ARCH"]) or ""
609 params["tune_pkgarch"] = self.runCommand(["getVariable", "TUNE_PKGARCH"]) or ""
610 params["bb_version"] = self.runCommand(["getVariable", "BB_MIN_VERSION"]) or ""
611
612 params["default_task"] = self.runCommand(["getVariable", "BB_DEFAULT_TASK"]) or "build"
613
614 params["socks_proxy"] = self.runCommand(["getVariable", "all_proxy"]) or ""
615 params["http_proxy"] = self.runCommand(["getVariable", "http_proxy"]) or ""
616 params["ftp_proxy"] = self.runCommand(["getVariable", "ftp_proxy"]) or ""
617 params["https_proxy"] = self.runCommand(["getVariable", "https_proxy"]) or ""
618
619 params["cvs_proxy_host"] = self.runCommand(["getVariable", "CVS_PROXY_HOST"]) or ""
620 params["cvs_proxy_port"] = self.runCommand(["getVariable", "CVS_PROXY_PORT"]) or ""
621
622 params["image_white_pattern"] = self.runCommand(["getVariable", "BBUI_IMAGE_WHITE_PATTERN"]) or ""
623 params["image_black_pattern"] = self.runCommand(["getVariable", "BBUI_IMAGE_BLACK_PATTERN"]) or ""
624 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..b4d2a621b7
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hoblistmodel.py
@@ -0,0 +1,900 @@
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.startswith(user_data) and not val2.startswith(user_data):
203 return -1
204 elif not val1.startswith(user_data) and val2.startswith(user_data):
205 return 1
206 else:
207 return cmp(val1, val2)
208
209 def convert_vpath_to_path(self, view_model, view_path):
210 # view_model is the model sorted
211 # get the path of the model filtered
212 filtered_model_path = view_model.convert_path_to_child_path(view_path)
213 # get the model filtered
214 filtered_model = view_model.get_model()
215 # get the path of the original model
216 path = filtered_model.convert_path_to_child_path(filtered_model_path)
217 return path
218
219 def convert_path_to_vpath(self, view_model, path):
220 it = view_model.get_iter_first()
221 while it:
222 name = self.find_item_for_path(path)
223 view_name = view_model.get_value(it, PackageListModel.COL_NAME)
224 if view_name == name:
225 view_path = view_model.get_path(it)
226 return view_path
227 it = view_model.iter_next(it)
228 return None
229
230 """
231 The populate() function takes as input the data from a
232 bb.event.PackageInfo event and populates the package list.
233 """
234 def populate(self, pkginfolist):
235 # First clear the model, in case repopulating
236 self.clear()
237
238 def getpkgvalue(pkgdict, key, pkgname, defaultval = None):
239 value = pkgdict.get('%s_%s' % (key, pkgname), None)
240 if not value:
241 value = pkgdict.get(key, defaultval)
242 return value
243
244 for pkginfo in pkginfolist:
245 pn = pkginfo['PN']
246 pv = pkginfo['PV']
247 pr = pkginfo['PR']
248 pkg = pkginfo['PKG']
249 pkgv = getpkgvalue(pkginfo, 'PKGV', pkg)
250 pkgr = getpkgvalue(pkginfo, 'PKGR', pkg)
251 # PKGSIZE is artificial, will always be overridden with the package name if present
252 pkgsize = pkginfo.get('PKGSIZE_%s' % pkg, "0")
253 # PKG_%s is the renamed version
254 pkg_rename = pkginfo.get('PKG_%s' % pkg, "")
255 # The rest may be overridden or not
256 section = getpkgvalue(pkginfo, 'SECTION', pkg, "")
257 summary = getpkgvalue(pkginfo, 'SUMMARY', pkg, "")
258 rdep = getpkgvalue(pkginfo, 'RDEPENDS', pkg, "")
259 rrec = getpkgvalue(pkginfo, 'RRECOMMENDS', pkg, "")
260 rprov = getpkgvalue(pkginfo, 'RPROVIDES', pkg, "")
261 files_list = getpkgvalue(pkginfo, 'FILES_INFO', pkg, "")
262 for i in rprov.split():
263 self.rprov_pkg[i] = pkg
264
265 recipe = pn + '-' + pv + '-' + pr
266
267 allow_empty = getpkgvalue(pkginfo, 'ALLOW_EMPTY', pkg, "")
268
269 if pkgsize == "0" and not allow_empty:
270 continue
271
272 # pkgsize is in KB
273 size = HobPage._size_to_string(HobPage._string_to_size(pkgsize + ' KB'))
274 self.set(self.append(), self.COL_NAME, pkg, self.COL_VER, pkgv,
275 self.COL_REV, pkgr, self.COL_RNM, pkg_rename,
276 self.COL_SEC, section, self.COL_SUM, summary,
277 self.COL_RDEP, rdep + ' ' + rrec,
278 self.COL_RPROV, rprov, self.COL_SIZE, size,
279 self.COL_RCP, recipe, self.COL_BINB, "",
280 self.COL_INC, False, self.COL_FONT, '10', self.COL_FLIST, files_list)
281
282 self.pn_path = {}
283 it = self.get_iter_first()
284 while it:
285 pn = self.get_value(it, self.COL_NAME)
286 path = self.get_path(it)
287 self.pn_path[pn] = path
288 it = self.iter_next(it)
289
290 """
291 Update the model, send out the notification.
292 """
293 def selection_change_notification(self):
294 self.emit("package-selection-changed")
295
296 """
297 Check whether the item at item_path is included or not
298 """
299 def path_included(self, item_path):
300 return self[item_path][self.COL_INC]
301
302 """
303 Add this item, and any of its dependencies, to the image contents
304 """
305 def include_item(self, item_path, binb=""):
306 if self.path_included(item_path):
307 return
308
309 item_name = self[item_path][self.COL_NAME]
310 item_deps = self[item_path][self.COL_RDEP]
311
312 self[item_path][self.COL_INC] = True
313
314 item_bin = self[item_path][self.COL_BINB].split(', ')
315 if binb and not binb in item_bin:
316 item_bin.append(binb)
317 self[item_path][self.COL_BINB] = ', '.join(item_bin).lstrip(', ')
318
319 if item_deps:
320 # Ensure all of the items deps are included and, where appropriate,
321 # add this item to their COL_BINB
322 for dep in item_deps.split(" "):
323 if dep.startswith('('):
324 continue
325 # If the contents model doesn't already contain dep, add it
326 dep_path = self.find_path_for_item(dep)
327 if not dep_path:
328 continue
329 dep_included = self.path_included(dep_path)
330
331 if dep_included and not dep in item_bin:
332 # don't set the COL_BINB to this item if the target is an
333 # item in our own COL_BINB
334 dep_bin = self[dep_path][self.COL_BINB].split(', ')
335 if not item_name in dep_bin:
336 dep_bin.append(item_name)
337 self[dep_path][self.COL_BINB] = ', '.join(dep_bin).lstrip(', ')
338 elif not dep_included:
339 self.include_item(dep_path, binb=item_name)
340
341 def exclude_item(self, item_path):
342 if not self.path_included(item_path):
343 return
344
345 self[item_path][self.COL_INC] = False
346
347 item_name = self[item_path][self.COL_NAME]
348 item_deps = self[item_path][self.COL_RDEP]
349 if item_deps:
350 for dep in item_deps.split(" "):
351 if dep.startswith('('):
352 continue
353 dep_path = self.find_path_for_item(dep)
354 if not dep_path:
355 continue
356 dep_bin = self[dep_path][self.COL_BINB].split(', ')
357 if item_name in dep_bin:
358 dep_bin.remove(item_name)
359 self[dep_path][self.COL_BINB] = ', '.join(dep_bin).lstrip(', ')
360
361 item_bin = self[item_path][self.COL_BINB].split(', ')
362 if item_bin:
363 for binb in item_bin:
364 binb_path = self.find_path_for_item(binb)
365 if not binb_path:
366 continue
367 self.exclude_item(binb_path)
368
369 """
370 Empty self.contents by setting the include of each entry to None
371 """
372 def reset(self):
373 it = self.get_iter_first()
374 while it:
375 self.set(it,
376 self.COL_INC, False,
377 self.COL_BINB, "")
378 it = self.iter_next(it)
379
380 self.selection_change_notification()
381
382 def get_selected_packages(self):
383 packagelist = []
384
385 it = self.get_iter_first()
386 while it:
387 if self.get_value(it, self.COL_INC):
388 name = self.get_value(it, self.COL_NAME)
389 packagelist.append(name)
390 it = self.iter_next(it)
391
392 return packagelist
393
394 def get_user_selected_packages(self):
395 packagelist = []
396
397 it = self.get_iter_first()
398 while it:
399 if self.get_value(it, self.COL_INC):
400 binb = self.get_value(it, self.COL_BINB)
401 if binb == "User Selected":
402 name = self.get_value(it, self.COL_NAME)
403 packagelist.append(name)
404 it = self.iter_next(it)
405
406 return packagelist
407
408 def get_selected_packages_toolchain(self):
409 packagelist = []
410
411 it = self.get_iter_first()
412 while it:
413 if self.get_value(it, self.COL_INC):
414 name = self.get_value(it, self.COL_NAME)
415 if name.endswith("-dev") or name.endswith("-dbg"):
416 packagelist.append(name)
417 it = self.iter_next(it)
418
419 return list(set(packagelist + self.__toolchain_required_packages__));
420
421 """
422 Package model may be incomplete, therefore when calling the
423 set_selected_packages(), some packages will not be set included.
424 Return the un-set packages list.
425 """
426 def set_selected_packages(self, packagelist, user_selected=False):
427 left = []
428 binb = 'User Selected' if user_selected else ''
429 for pn in packagelist:
430 if pn in self.pn_path.keys():
431 path = self.pn_path[pn]
432 self.include_item(item_path=path, binb=binb)
433 else:
434 left.append(pn)
435
436 self.selection_change_notification()
437 return left
438
439 """
440 Return the selected package size, unit is B.
441 """
442 def get_packages_size(self):
443 packages_size = 0
444 it = self.get_iter_first()
445 while it:
446 if self.get_value(it, self.COL_INC):
447 str_size = self.get_value(it, self.COL_SIZE)
448 if not str_size:
449 continue
450
451 packages_size += HobPage._string_to_size(str_size)
452
453 it = self.iter_next(it)
454 return packages_size
455
456 """
457 Resync the state of included items to a backup column before performing the fadeout visible effect
458 """
459 def resync_fadeout_column(self, model_first_iter=None):
460 it = model_first_iter
461 while it:
462 active = self.get_value(it, self.COL_INC)
463 self.set(it, self.COL_FADE_INC, active)
464 it = self.iter_next(it)
465
466#
467# RecipeListModel
468#
469class RecipeListModel(gtk.ListStore):
470 """
471 This class defines an gtk.ListStore subclass which will convert the output
472 of the bb.event.TargetsTreeGenerated event into a gtk.ListStore whilst also
473 providing convenience functions to access gtk.TreeModel subclasses which
474 provide filtered views of the data.
475 """
476 (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,
477 COL_REVISION, COL_HOMEPAGE, COL_BUGTRACKER, COL_FILE) = range(18)
478
479 __custom_image__ = "Start with an empty image recipe"
480
481 __gsignals__ = {
482 "recipe-selection-changed" : (gobject.SIGNAL_RUN_LAST,
483 gobject.TYPE_NONE,
484 ()),
485 }
486
487 """
488 """
489 def __init__(self):
490 gtk.ListStore.__init__ (self,
491 gobject.TYPE_STRING,
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_BOOLEAN,
499 gobject.TYPE_BOOLEAN,
500 gobject.TYPE_STRING,
501 gobject.TYPE_STRING,
502 gobject.TYPE_BOOLEAN,
503 gobject.TYPE_STRING,
504 gobject.TYPE_STRING,
505 gobject.TYPE_STRING,
506 gobject.TYPE_STRING,
507 gobject.TYPE_STRING,
508 gobject.TYPE_STRING)
509 self.sort_column_id, self.sort_order = RecipeListModel.COL_NAME, gtk.SORT_ASCENDING
510
511 """
512 Find the model path for the item_name
513 Returns the path in the model or None
514 """
515 def find_path_for_item(self, item_name):
516 if self.non_target_name(item_name) or item_name not in self.pn_path.keys():
517 return None
518 else:
519 return self.pn_path[item_name]
520
521 def find_item_for_path(self, item_path):
522 return self[item_path][self.COL_NAME]
523
524 """
525 Helper method to determine whether name is a target pn
526 """
527 def non_target_name(self, name):
528 if name and ('-native' in name):
529 return True
530 return False
531
532 """
533 Helper function to determine whether an item is an item specified by filter
534 """
535 def tree_model_filter(self, model, it, filter):
536 name = model.get_value(it, self.COL_NAME)
537 if self.non_target_name(name):
538 return False
539
540 for key in filter.keys():
541 if key == self.COL_NAME:
542 if filter[key] != 'Search recipes by name' and filter[key] != 'Search package groups by name':
543 if filter[key] not in name:
544 return False
545 else:
546 if model.get_value(it, key) not in filter[key]:
547 return False
548 self.filtered_nb += 1
549
550 return True
551
552 def exclude_item_sort_func(self, model, iter1, iter2, user_data=None):
553 if user_data:
554 val1 = model.get_value(iter1, RecipeListModel.COL_NAME)
555 val2 = model.get_value(iter2, RecipeListModel.COL_NAME)
556 return self.cmp_vals(val1, val2, user_data)
557 else:
558 val1 = model.get_value(iter1, RecipeListModel.COL_FADE_INC)
559 val2 = model.get_value(iter2, RecipeListModel.COL_INC)
560 return ((val1 == True) and (val2 == False))
561
562 def include_item_sort_func(self, model, iter1, iter2, user_data=None):
563 if user_data:
564 val1 = model.get_value(iter1, RecipeListModel.COL_NAME)
565 val2 = model.get_value(iter2, RecipeListModel.COL_NAME)
566 return self.cmp_vals(val1, val2, user_data)
567 else:
568 val1 = model.get_value(iter1, RecipeListModel.COL_INC)
569 val2 = model.get_value(iter2, RecipeListModel.COL_INC)
570 return ((val1 == False) and (val2 == True))
571
572 def sort_func(self, model, iter1, iter2, user_data):
573 val1 = model.get_value(iter1, RecipeListModel.COL_NAME)
574 val2 = model.get_value(iter2, RecipeListModel.COL_NAME)
575 return self.cmp_vals(val1, val2, user_data)
576
577 def cmp_vals(self, val1, val2, user_data):
578 if val1.startswith(user_data) and not val2.startswith(user_data):
579 return -1
580 elif not val1.startswith(user_data) and val2.startswith(user_data):
581 return 1
582 else:
583 return cmp(val1, val2)
584
585 """
586 Create, if required, and return a filtered gtk.TreeModelSort
587 containing only the items specified by filter
588 """
589 def tree_model(self, filter, excluded_items_ahead=False, included_items_ahead=False, search_data=None, initial=False):
590 model = self.filter_new()
591 self.filtered_nb = 0
592 model.set_visible_func(self.tree_model_filter, filter)
593
594 sort = gtk.TreeModelSort(model)
595 sort.connect ('sort-column-changed', self.sort_column_changed_cb)
596 if initial:
597 sort.set_sort_column_id(RecipeListModel.COL_NAME, gtk.SORT_ASCENDING)
598 sort.set_default_sort_func(None)
599 elif excluded_items_ahead:
600 sort.set_default_sort_func(self.exclude_item_sort_func, search_data)
601 elif included_items_ahead:
602 sort.set_default_sort_func(self.include_item_sort_func, search_data)
603 else:
604 if search_data and search_data!='Search recipes by name' and search_data!='Search package groups by name':
605 sort.set_default_sort_func(self.sort_func, search_data)
606 else:
607 sort.set_sort_column_id(self.sort_column_id, self.sort_order)
608 sort.set_default_sort_func(None)
609
610 sort.set_sort_func(RecipeListModel.COL_INC, self.sort_column, RecipeListModel.COL_INC)
611 sort.set_sort_func(RecipeListModel.COL_GROUP, self.sort_column, RecipeListModel.COL_GROUP)
612 sort.set_sort_func(RecipeListModel.COL_BINB, self.sort_binb_column)
613 sort.set_sort_func(RecipeListModel.COL_LIC, self.sort_column, RecipeListModel.COL_LIC)
614 return sort
615
616 def sort_column_changed_cb (self, data):
617 self.sort_column_id, self.sort_order = data.get_sort_column_id ()
618
619 def sort_column(self, model, row1, row2, col):
620 value1 = model.get_value(row1, col)
621 value2 = model.get_value(row2, col)
622 cmp_res = cmp(value1, value2)
623 if cmp_res!=0:
624 if col==RecipeListModel.COL_INC:
625 return -cmp_res
626 else:
627 return cmp_res
628 else:
629 name1 = model.get_value(row1, RecipeListModel.COL_NAME)
630 name2 = model.get_value(row2, RecipeListModel.COL_NAME)
631 return cmp(name1,name2)
632
633 def sort_binb_column(self, model, row1, row2):
634 value1 = model.get_value(row1, RecipeListModel.COL_BINB)
635 value2 = model.get_value(row2, RecipeListModel.COL_BINB)
636 value1_list = value1.split(', ')
637 value2_list = value2.split(', ')
638
639 value1 = value1_list[0]
640 value2 = value2_list[0]
641
642 cmp_res = cmp(value1, value2)
643 if cmp_res==0:
644 cmp_size = cmp(len(value1_list), len(value2_list))
645 if cmp_size==0:
646 name1 = model.get_value(row1, RecipeListModel.COL_NAME)
647 name2 = model.get_value(row2, RecipeListModel.COL_NAME)
648 return cmp(name1,name2)
649 else:
650 return cmp_size
651 else:
652 return cmp_res
653
654 def convert_vpath_to_path(self, view_model, view_path):
655 filtered_model_path = view_model.convert_path_to_child_path(view_path)
656 filtered_model = view_model.get_model()
657
658 # get the path of the original model
659 path = filtered_model.convert_path_to_child_path(filtered_model_path)
660 return path
661
662 def convert_path_to_vpath(self, view_model, path):
663 it = view_model.get_iter_first()
664 while it:
665 name = self.find_item_for_path(path)
666 view_name = view_model.get_value(it, RecipeListModel.COL_NAME)
667 if view_name == name:
668 view_path = view_model.get_path(it)
669 return view_path
670 it = view_model.iter_next(it)
671 return None
672
673 """
674 The populate() function takes as input the data from a
675 bb.event.TargetsTreeGenerated event and populates the RecipeList.
676 """
677 def populate(self, event_model):
678 # First clear the model, in case repopulating
679 self.clear()
680
681 # dummy image for prompt
682 self.set_in_list(self.__custom_image__, "Use 'Edit image recipe' to customize recipes and packages " \
683 "to be included in your image ")
684
685 for item in event_model["pn"]:
686 name = item
687 desc = event_model["pn"][item]["description"]
688 lic = event_model["pn"][item]["license"]
689 group = event_model["pn"][item]["section"]
690 inherits = event_model["pn"][item]["inherits"]
691 summary = event_model["pn"][item]["summary"]
692 version = event_model["pn"][item]["version"]
693 revision = event_model["pn"][item]["prevision"]
694 homepage = event_model["pn"][item]["homepage"]
695 bugtracker = event_model["pn"][item]["bugtracker"]
696 filename = event_model["pn"][item]["filename"]
697 install = []
698
699 depends = event_model["depends"].get(item, []) + event_model["rdepends-pn"].get(item, [])
700
701 if ('packagegroup.bbclass' in " ".join(inherits)):
702 atype = 'packagegroup'
703 elif ('image.bbclass' in " ".join(inherits)):
704 if name != "hob-image":
705 atype = 'image'
706 install = event_model["rdepends-pkg"].get(item, []) + event_model["rrecs-pkg"].get(item, [])
707 elif ('meta-' in name):
708 atype = 'toolchain'
709 elif (name == 'dummy-image' or name == 'dummy-toolchain'):
710 atype = 'dummy'
711 else:
712 atype = 'recipe'
713
714 self.set(self.append(), self.COL_NAME, item, self.COL_DESC, desc,
715 self.COL_LIC, lic, self.COL_GROUP, group,
716 self.COL_DEPS, " ".join(depends), self.COL_BINB, "",
717 self.COL_TYPE, atype, self.COL_INC, False,
718 self.COL_IMG, False, self.COL_INSTALL, " ".join(install), self.COL_PN, item,
719 self.COL_SUMMARY, summary, self.COL_VERSION, version, self.COL_REVISION, revision,
720 self.COL_HOMEPAGE, homepage, self.COL_BUGTRACKER, bugtracker,
721 self.COL_FILE, filename)
722
723 self.pn_path = {}
724 it = self.get_iter_first()
725 while it:
726 pn = self.get_value(it, self.COL_NAME)
727 path = self.get_path(it)
728 self.pn_path[pn] = path
729 it = self.iter_next(it)
730
731 def set_in_list(self, item, desc):
732 self.set(self.append(), self.COL_NAME, item,
733 self.COL_DESC, desc,
734 self.COL_LIC, "", self.COL_GROUP, "",
735 self.COL_DEPS, "", self.COL_BINB, "",
736 self.COL_TYPE, "image", self.COL_INC, False,
737 self.COL_IMG, False, self.COL_INSTALL, "", self.COL_PN, item,
738 self.COL_SUMMARY, "", self.COL_VERSION, "", self.COL_REVISION, "",
739 self.COL_HOMEPAGE, "", self.COL_BUGTRACKER, "")
740 self.pn_path = {}
741 it = self.get_iter_first()
742 while it:
743 pn = self.get_value(it, self.COL_NAME)
744 path = self.get_path(it)
745 self.pn_path[pn] = path
746 it = self.iter_next(it)
747
748 """
749 Update the model, send out the notification.
750 """
751 def selection_change_notification(self):
752 self.emit("recipe-selection-changed")
753
754 def path_included(self, item_path):
755 return self[item_path][self.COL_INC]
756
757 """
758 Add this item, and any of its dependencies, to the image contents
759 """
760 def include_item(self, item_path, binb="", image_contents=False):
761 if self.path_included(item_path):
762 return
763
764 item_name = self[item_path][self.COL_NAME]
765 item_deps = self[item_path][self.COL_DEPS]
766
767 self[item_path][self.COL_INC] = True
768
769 item_bin = self[item_path][self.COL_BINB].split(', ')
770 if binb and not binb in item_bin:
771 item_bin.append(binb)
772 self[item_path][self.COL_BINB] = ', '.join(item_bin).lstrip(', ')
773
774 # We want to do some magic with things which are brought in by the
775 # base image so tag them as so
776 if image_contents:
777 self[item_path][self.COL_IMG] = True
778
779 if item_deps:
780 # Ensure all of the items deps are included and, where appropriate,
781 # add this item to their COL_BINB
782 for dep in item_deps.split(" "):
783 # If the contents model doesn't already contain dep, add it
784 dep_path = self.find_path_for_item(dep)
785 if not dep_path:
786 continue
787 dep_included = self.path_included(dep_path)
788
789 if dep_included and not dep in item_bin:
790 # don't set the COL_BINB to this item if the target is an
791 # item in our own COL_BINB
792 dep_bin = self[dep_path][self.COL_BINB].split(', ')
793 if not item_name in dep_bin:
794 dep_bin.append(item_name)
795 self[dep_path][self.COL_BINB] = ', '.join(dep_bin).lstrip(', ')
796 elif not dep_included:
797 self.include_item(dep_path, binb=item_name, image_contents=image_contents)
798 dep_bin = self[item_path][self.COL_BINB].split(', ')
799 if self[item_path][self.COL_NAME] in dep_bin:
800 dep_bin.remove(self[item_path][self.COL_NAME])
801 self[item_path][self.COL_BINB] = ', '.join(dep_bin).lstrip(', ')
802
803 def exclude_item(self, item_path):
804 if not self.path_included(item_path):
805 return
806
807 self[item_path][self.COL_INC] = False
808
809 item_name = self[item_path][self.COL_NAME]
810 item_deps = self[item_path][self.COL_DEPS]
811 if item_deps:
812 for dep in item_deps.split(" "):
813 dep_path = self.find_path_for_item(dep)
814 if not dep_path:
815 continue
816 dep_bin = self[dep_path][self.COL_BINB].split(', ')
817 if item_name in dep_bin:
818 dep_bin.remove(item_name)
819 self[dep_path][self.COL_BINB] = ', '.join(dep_bin).lstrip(', ')
820
821 item_bin = self[item_path][self.COL_BINB].split(', ')
822 if item_bin:
823 for binb in item_bin:
824 binb_path = self.find_path_for_item(binb)
825 if not binb_path:
826 continue
827 self.exclude_item(binb_path)
828
829 def reset(self):
830 it = self.get_iter_first()
831 while it:
832 self.set(it,
833 self.COL_INC, False,
834 self.COL_BINB, "",
835 self.COL_IMG, False)
836 it = self.iter_next(it)
837
838 self.selection_change_notification()
839
840 """
841 Returns two lists. One of user selected recipes and the other containing
842 all selected recipes
843 """
844 def get_selected_recipes(self):
845 allrecipes = []
846 userrecipes = []
847
848 it = self.get_iter_first()
849 while it:
850 if self.get_value(it, self.COL_INC):
851 name = self.get_value(it, self.COL_PN)
852 type = self.get_value(it, self.COL_TYPE)
853 if type != "image":
854 allrecipes.append(name)
855 sel = "User Selected" in self.get_value(it, self.COL_BINB)
856 if sel:
857 userrecipes.append(name)
858 it = self.iter_next(it)
859
860 return list(set(userrecipes)), list(set(allrecipes))
861
862 def set_selected_recipes(self, recipelist):
863 for pn in recipelist:
864 if pn in self.pn_path.keys():
865 path = self.pn_path[pn]
866 self.include_item(item_path=path,
867 binb="User Selected")
868 self.selection_change_notification()
869
870 def get_selected_image(self):
871 it = self.get_iter_first()
872 while it:
873 if self.get_value(it, self.COL_INC):
874 name = self.get_value(it, self.COL_PN)
875 type = self.get_value(it, self.COL_TYPE)
876 if type == "image":
877 sel = "User Selected" in self.get_value(it, self.COL_BINB)
878 if sel:
879 return name
880 it = self.iter_next(it)
881 return None
882
883 def set_selected_image(self, img):
884 if not img:
885 return
886 self.reset()
887 path = self.find_path_for_item(img)
888 self.include_item(item_path=path,
889 binb="User Selected",
890 image_contents=True)
891 self.selection_change_notification()
892
893 def set_custom_image_version(self, version):
894 self.custom_image_version = version
895
896 def get_custom_image_version(self):
897 return self.custom_image_version
898
899 def is_custom_image(self):
900 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..3707d6160d
--- /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 = self.step_angle
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..79709d0d97
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/imageconfigurationpage.py
@@ -0,0 +1,559 @@
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.builder.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
308 self.builder.wait(0.1) #wait for combo and cursor to update
309 self.stopping = False
310 self.builder.parsing_warnings = []
311 combo_item = machine_combo.get_active_text()
312 if not combo_item or combo_item == self.__dummy_machine__:
313 return
314
315 # remove __dummy_machine__ item from the store list after first user selection
316 # because it is no longer valid
317 combo_store = machine_combo.get_model()
318 if len(combo_store) and (combo_store[0][0] == self.__dummy_machine__):
319 machine_combo.remove_text(0)
320
321 self.builder.configuration.curr_mach = combo_item
322 if self.machine_combo_changed_by_manual:
323 self.builder.configuration.clear_selection()
324 # reset machine_combo_changed_by_manual
325 self.machine_combo_changed_by_manual = True
326
327 self.builder.configuration.selected_image = None
328
329 # Do reparse recipes
330 self.builder.populate_recipe_package_info_async()
331
332 glib.idle_add(self.machine_combo_changed_idle_cb)
333
334 def update_machine_combo(self):
335 self.disable_warnings_bar()
336 all_machines = [self.__dummy_machine__] + self.builder.parameters.all_machines
337
338 model = self.machine_combo.get_model()
339 model.clear()
340 for machine in all_machines:
341 self.machine_combo.append_text(machine)
342 self.machine_combo.set_active(0)
343
344 def switch_machine_combo(self):
345 self.disable_warnings_bar()
346 self.machine_combo_changed_by_manual = False
347 model = self.machine_combo.get_model()
348 active = 0
349 while active < len(model):
350 if model[active][0] == self.builder.configuration.curr_mach:
351 self.machine_combo.set_active(active)
352 return
353 active += 1
354
355 if model[0][0] != self.__dummy_machine__:
356 self.machine_combo.insert_text(0, self.__dummy_machine__)
357
358 self.machine_combo.set_active(0)
359
360 def update_image_desc(self):
361 desc = ""
362 selected_image = self.image_combo.get_active_text()
363 if selected_image and selected_image in self.builder.recipe_model.pn_path.keys():
364 image_path = self.builder.recipe_model.pn_path[selected_image]
365 image_iter = self.builder.recipe_model.get_iter(image_path)
366 desc = self.builder.recipe_model.get_value(image_iter, self.builder.recipe_model.COL_DESC)
367
368 mark = ("<span %s>%s</span>\n") % (self.span_tag('small'), desc)
369 self.image_desc.set_markup(mark)
370
371 def image_combo_changed_idle_cb(self, selected_image, selected_recipes, selected_packages):
372 self.builder.update_recipe_model(selected_image, selected_recipes)
373 self.builder.update_package_model(selected_packages)
374 self.builder.window_sensitive(True)
375
376 def image_combo_changed_cb(self, combo):
377 self.builder.window_sensitive(False)
378 selected_image = self.image_combo.get_active_text()
379 if selected_image == self.__custom_image__:
380 topdir = self.builder.get_topdir()
381 images_dir = topdir + "/recipes/images/"
382 self.builder.ensure_dir(images_dir)
383
384 dialog = RetrieveImageDialog(images_dir, "Select from my image recipes",
385 self.builder, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
386 response = dialog.run()
387 if response == gtk.RESPONSE_OK:
388 image_name = dialog.get_filename()
389 head, tail = os.path.split(image_name)
390 selected_image = os.path.splitext(tail)[0]
391 self.custom_image_selected = selected_image
392 self.update_image_combo(self.builder.recipe_model, selected_image)
393 else:
394 selected_image = self.__dummy_image__
395 self.update_image_combo(self.builder.recipe_model, None)
396 dialog.destroy()
397 else:
398 if self.custom_image_selected:
399 self.custom_image_selected = None
400 self.update_image_combo(self.builder.recipe_model, selected_image)
401
402 if not selected_image or (selected_image == self.__dummy_image__):
403 self.builder.window_sensitive(True)
404 self.just_bake_button.hide()
405 self.edit_image_button.hide()
406 return
407
408 # remove __dummy_image__ item from the store list after first user selection
409 # because it is no longer valid
410 combo_store = combo.get_model()
411 if len(combo_store) and (combo_store[0][0] == self.__dummy_image__):
412 combo.remove_text(0)
413
414 self.builder.customized = False
415
416 selected_recipes = []
417
418 image_path = self.builder.recipe_model.pn_path[selected_image]
419 image_iter = self.builder.recipe_model.get_iter(image_path)
420 selected_packages = self.builder.recipe_model.get_value(image_iter, self.builder.recipe_model.COL_INSTALL).split()
421 self.update_image_desc()
422
423 self.builder.recipe_model.reset()
424 self.builder.package_model.reset()
425
426 self.show_baseimg_selected()
427
428 if selected_image == self.builder.recipe_model.__custom_image__:
429 self.just_bake_button.hide()
430
431 glib.idle_add(self.image_combo_changed_idle_cb, selected_image, selected_recipes, selected_packages)
432
433 def _image_combo_connect_signal(self):
434 if not self.image_combo_id:
435 self.image_combo_id = self.image_combo.connect("changed", self.image_combo_changed_cb)
436
437 def _image_combo_disconnect_signal(self):
438 if self.image_combo_id:
439 self.image_combo.disconnect(self.image_combo_id)
440 self.image_combo_id = None
441
442 def update_image_combo(self, recipe_model, selected_image):
443 # Update the image combo according to the images in the recipe_model
444 # populate image combo
445 filter = {RecipeListModel.COL_TYPE : ['image']}
446 image_model = recipe_model.tree_model(filter)
447 image_model.set_sort_column_id(recipe_model.COL_NAME, gtk.SORT_ASCENDING)
448 active = 0
449 cnt = 0
450
451 white_pattern = []
452 if self.builder.parameters.image_white_pattern:
453 for i in self.builder.parameters.image_white_pattern.split():
454 white_pattern.append(re.compile(i))
455
456 black_pattern = []
457 if self.builder.parameters.image_black_pattern:
458 for i in self.builder.parameters.image_black_pattern.split():
459 black_pattern.append(re.compile(i))
460 black_pattern.append(re.compile("hob-image"))
461
462 it = image_model.get_iter_first()
463 self._image_combo_disconnect_signal()
464 model = self.image_combo.get_model()
465 model.clear()
466 # Set a indicator text to combo store when first open
467 if not selected_image:
468 self.image_combo.append_text(self.__dummy_image__)
469 cnt = cnt + 1
470
471 self.image_combo.append_text(self.__custom_image__)
472 self.image_combo.append_text("--Separator--")
473 cnt = cnt + 2
474
475 topdir = self.builder.get_topdir()
476 # append and set active
477 while it:
478 path = image_model.get_path(it)
479 it = image_model.iter_next(it)
480 image_name = image_model[path][recipe_model.COL_NAME]
481 if image_name == self.builder.recipe_model.__custom_image__:
482 continue
483
484 if black_pattern:
485 allow = True
486 for pattern in black_pattern:
487 if pattern.search(image_name):
488 allow = False
489 break
490 elif white_pattern:
491 allow = False
492 for pattern in white_pattern:
493 if pattern.search(image_name):
494 allow = True
495 break
496 else:
497 allow = True
498
499 file_name = image_model[path][recipe_model.COL_FILE]
500 if file_name and topdir in file_name:
501 allow = False
502
503 if allow:
504 self.image_combo.append_text(image_name)
505 if image_name == selected_image:
506 active = cnt
507 cnt = cnt + 1
508 self.image_combo.append_text(self.builder.recipe_model.__custom_image__)
509
510 if selected_image == self.builder.recipe_model.__custom_image__:
511 active = cnt
512
513 if self.custom_image_selected:
514 self.image_combo.append_text("--Separator--")
515 self.image_combo.append_text(self.custom_image_selected)
516 cnt = cnt + 2
517 if self.custom_image_selected == selected_image:
518 active = cnt
519
520 self.image_combo.set_active(active)
521
522 if active != 0:
523 self.show_baseimg_selected()
524
525 self._image_combo_connect_signal()
526
527 def layer_button_clicked_cb(self, button):
528 # Create a layer selection dialog
529 self.builder.show_layer_selection_dialog()
530
531 def view_adv_configuration_button_clicked_cb(self, button):
532 # Create an advanced settings dialog
533 response, settings_changed = self.builder.show_adv_settings_dialog()
534 if not response:
535 return
536 if settings_changed:
537 self.builder.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
538 self.builder.wait(0.1) #wait for adv_settings_dialog to terminate
539 self.builder.reparse_post_adv_settings()
540 self.builder.window.set_cursor(None)
541
542 def just_bake_button_clicked_cb(self, button):
543 self.builder.parsing_warnings = []
544 self.builder.just_bake()
545
546 def edit_image_button_clicked_cb(self, button):
547 self.builder.configuration.initial_selected_image = self.builder.configuration.selected_image
548 self.builder.show_recipes()
549
550 def my_images_button_clicked_cb(self, button):
551 self.builder.show_load_my_images_dialog()
552
553 def settings_button_clicked_cb(self, button):
554 # Create an advanced settings dialog
555 response, settings_changed = self.builder.show_simple_settings_dialog()
556 if not response:
557 return
558 if settings_changed:
559 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..b5d9660218
--- /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 varlist.append(" - ")
359 if i > layer_num_limit:
360 break
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/"
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(ask=False)
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..780f026470
--- /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.configuration.initial_selected_image = self.builder.configuration.selected_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..abd3300149
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/runningbuild.py
@@ -0,0 +1,533 @@
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 elif isinstance(event, bb.build.TaskStarted):
178 (package, task) = (event._package, event._task)
179
180 # Save out this PID.
181 self.pids_to_task[pid] = (package, task)
182
183 # Check if we already have this package in our model. If so then
184 # that can be the parent for the task. Otherwise we create a new
185 # top level for the package.
186 if ((package, None) in self.tasks_to_iter):
187 parent = self.tasks_to_iter[(package, None)]
188 else:
189 if self.sequential:
190 add = self.model.append
191 else:
192 add = self.model.prepend
193 parent = add(None, (None,
194 package,
195 None,
196 "Package: %s" % (package),
197 None,
198 HobColors.OK,
199 0))
200 self.tasks_to_iter[(package, None)] = parent
201
202 # Because this parent package now has an active child mark it as
203 # such.
204 # @todo if parent is already in error, don't mark it green
205 self.model.set(parent, self.model.COL_ICON, "gtk-execute",
206 self.model.COL_COLOR, HobColors.RUNNING)
207
208 # Add an entry in the model for this task
209 i = self.model.append (parent, (None,
210 package,
211 task,
212 "Task: %s" % (task),
213 "gtk-execute",
214 HobColors.RUNNING,
215 0))
216
217 # update the parent's active task count
218 num_active = self.model.get(parent, self.model.COL_NUM_ACTIVE)[0] + 1
219 self.model.set(parent, self.model.COL_NUM_ACTIVE, num_active)
220
221 # Save out the iter so that we can find it when we have a message
222 # that we need to attach to a task.
223 self.tasks_to_iter[(package, task)] = i
224
225 elif isinstance(event, bb.build.TaskBase):
226 self.emit("log", "info", event._message)
227 current = self.tasks_to_iter[(package, task)]
228 parent = self.tasks_to_iter[(package, None)]
229
230 # remove this task from the parent's active count
231 num_active = self.model.get(parent, self.model.COL_NUM_ACTIVE)[0] - 1
232 self.model.set(parent, self.model.COL_NUM_ACTIVE, num_active)
233
234 if isinstance(event, bb.build.TaskFailed):
235 # Mark the task and parent as failed
236 icon = "dialog-error"
237 color = HobColors.ERROR
238
239 logfile = event.logfile
240 if logfile and os.path.exists(logfile):
241 with open(logfile) as f:
242 logdata = f.read()
243 self.model.append(current, ('pastebin', None, None, logdata, 'gtk-error', HobColors.OK, 0))
244
245 for i in (current, parent):
246 self.model.set(i, self.model.COL_ICON, icon,
247 self.model.COL_COLOR, color)
248 else:
249 icon = None
250 color = HobColors.OK
251
252 # Mark the task as inactive
253 self.model.set(current, self.model.COL_ICON, icon,
254 self.model.COL_COLOR, color)
255
256 # Mark the parent package as inactive, but make sure to
257 # preserve error and active states
258 i = self.tasks_to_iter[(package, None)]
259 if self.model.get(parent, self.model.COL_ICON) != 'dialog-error':
260 self.model.set(parent, self.model.COL_ICON, icon)
261 if num_active == 0:
262 self.model.set(parent, self.model.COL_COLOR, HobColors.OK)
263
264 # Clear the iters and the pids since when the task goes away the
265 # pid will no longer be used for messages
266 del self.tasks_to_iter[(package, task)]
267 del self.pids_to_task[pid]
268
269 elif isinstance(event, bb.event.BuildStarted):
270
271 self.emit("build-started")
272 self.model.prepend(None, (None,
273 None,
274 None,
275 "Build Started (%s)" % time.strftime('%m/%d/%Y %H:%M:%S'),
276 None,
277 HobColors.OK,
278 0))
279 if pbar:
280 pbar.update(0, self.progress_total)
281 pbar.set_title(bb.event.getName(event))
282
283 elif isinstance(event, bb.event.BuildCompleted):
284 failures = int (event._failures)
285 self.model.prepend(None, (None,
286 None,
287 None,
288 "Build Completed (%s)" % time.strftime('%m/%d/%Y %H:%M:%S'),
289 None,
290 HobColors.OK,
291 0))
292
293 # Emit the appropriate signal depending on the number of failures
294 if self.buildaborted:
295 self.emit ("build-aborted")
296 self.buildaborted = False
297 elif (failures >= 1):
298 self.emit ("build-failed")
299 else:
300 self.emit ("build-succeeded")
301 # Emit a generic "build-complete" signal for things wishing to
302 # handle when the build is finished
303 self.emit("build-complete")
304 # reset the all cell's icon indicator
305 self.model.close_task_refresh()
306 if pbar:
307 pbar.set_text(event.msg)
308
309 elif isinstance(event, bb.event.DiskFull):
310 self.buildaborted = True
311 self.emit("disk-full")
312
313 elif isinstance(event, bb.command.CommandFailed):
314 self.emit("log", "error", "Command execution failed: %s" % (event.error))
315 if event.error.startswith("Exited with"):
316 # If the command fails with an exit code we're done, emit the
317 # generic signal for the UI to notify the user
318 self.emit("build-complete")
319 # reset the all cell's icon indicator
320 self.model.close_task_refresh()
321
322 elif isinstance(event, bb.event.CacheLoadStarted) and pbar:
323 pbar.set_title("Loading cache")
324 self.progress_total = event.total
325 pbar.update(0, self.progress_total)
326 elif isinstance(event, bb.event.CacheLoadProgress) and pbar:
327 pbar.update(event.current, self.progress_total)
328 elif isinstance(event, bb.event.CacheLoadCompleted) and pbar:
329 pbar.update(self.progress_total, self.progress_total)
330 pbar.hide()
331 elif isinstance(event, bb.event.ParseStarted) and pbar:
332 if event.total == 0:
333 return
334 pbar.set_title("Processing recipes")
335 self.progress_total = event.total
336 pbar.update(0, self.progress_total)
337 elif isinstance(event, bb.event.ParseProgress) and pbar:
338 pbar.update(event.current, self.progress_total)
339 elif isinstance(event, bb.event.ParseCompleted) and pbar:
340 pbar.hide()
341 #using runqueue events as many as possible to update the progress bar
342 elif isinstance(event, bb.runqueue.runQueueTaskFailed):
343 self.emit("log", "error", "Task %s (%s) failed with exit code '%s'" % (event.taskid, event.taskstring, event.exitcode))
344 elif isinstance(event, bb.runqueue.sceneQueueTaskFailed):
345 self.emit("log", "warn", "Setscene task %s (%s) failed with exit code '%s' - real task will be run instead" \
346 % (event.taskid, event.taskstring, event.exitcode))
347 elif isinstance(event, (bb.runqueue.runQueueTaskStarted, bb.runqueue.sceneQueueTaskStarted)):
348 if isinstance(event, bb.runqueue.sceneQueueTaskStarted):
349 self.emit("log", "info", "Running setscene task %d of %d (%s)" % \
350 (event.stats.completed + event.stats.active + event.stats.failed + 1,
351 event.stats.total, event.taskstring))
352 else:
353 if event.noexec:
354 tasktype = 'noexec task'
355 else:
356 tasktype = 'task'
357 self.emit("log", "info", "Running %s %s of %s (ID: %s, %s)" % \
358 (tasktype, event.stats.completed + event.stats.active + event.stats.failed + 1,
359 event.stats.total, event.taskid, event.taskstring))
360 message = {}
361 message["eventname"] = bb.event.getName(event)
362 num_of_completed = event.stats.completed + event.stats.failed
363 message["current"] = num_of_completed
364 message["total"] = event.stats.total
365 message["title"] = ""
366 message["task"] = event.taskstring
367 self.emit("task-started", message)
368 elif isinstance(event, bb.event.MultipleProviders):
369 self.emit("log", "info", "multiple providers are available for %s%s (%s)" \
370 % (event._is_runtime and "runtime " or "", event._item, ", ".join(event._candidates)))
371 self.emit("log", "info", "consider defining a PREFERRED_PROVIDER entry to match %s" % (event._item))
372 elif isinstance(event, bb.event.NoProvider):
373 msg = ""
374 if event._runtime:
375 r = "R"
376 else:
377 r = ""
378
379 extra = ''
380 if not event._reasons:
381 if event._close_matches:
382 extra = ". Close matches:\n %s" % '\n '.join(event._close_matches)
383
384 if event._dependees:
385 msg = "Nothing %sPROVIDES '%s' (but %s %sDEPENDS on or otherwise requires it)%s\n" % (r, event._item, ", ".join(event._dependees), r, extra)
386 else:
387 msg = "Nothing %sPROVIDES '%s'%s\n" % (r, event._item, extra)
388 if event._reasons:
389 for reason in event._reasons:
390 msg += ("%s\n" % reason)
391 self.emit("no-provider", msg)
392 self.emit("log", "error", msg)
393 elif isinstance(event, bb.event.LogExecTTY):
394 icon = "dialog-warning"
395 color = HobColors.WARNING
396 if self.sequential or not parent:
397 tree_add = self.model.append
398 else:
399 tree_add = self.model.prepend
400 tree_add(parent,
401 (None,
402 package,
403 task,
404 event.msg,
405 icon,
406 color,
407 0))
408 else:
409 if not isinstance(event, (bb.event.BuildBase,
410 bb.event.StampUpdate,
411 bb.event.ConfigParsed,
412 bb.event.RecipeParsed,
413 bb.event.RecipePreFinalise,
414 bb.runqueue.runQueueEvent,
415 bb.runqueue.runQueueExitWait,
416 bb.event.OperationStarted,
417 bb.event.OperationCompleted,
418 bb.event.OperationProgress)):
419 self.emit("log", "error", "Unknown event: %s" % (event.error if hasattr(event, 'error') else 'error'))
420
421 return
422
423
424def do_pastebin(text):
425 url = 'http://pastebin.com/api_public.php'
426 params = {'paste_code': text, 'paste_format': 'text'}
427
428 req = urllib2.Request(url, urllib.urlencode(params))
429 response = urllib2.urlopen(req)
430 paste_url = response.read()
431
432 return paste_url
433
434
435class RunningBuildTreeView (gtk.TreeView):
436 __gsignals__ = {
437 "button_press_event" : "override"
438 }
439 def __init__ (self, readonly=False, hob=False):
440 gtk.TreeView.__init__ (self)
441 self.readonly = readonly
442
443 # The icon that indicates whether we're building or failed.
444 # add 'hob' flag because there has not only hob to share this code
445 if hob:
446 renderer = HobCellRendererPixbuf ()
447 else:
448 renderer = gtk.CellRendererPixbuf()
449 col = gtk.TreeViewColumn ("Status", renderer)
450 col.add_attribute (renderer, "icon-name", 4)
451 self.append_column (col)
452
453 # The message of the build.
454 # add 'hob' flag because there has not only hob to share this code
455 if hob:
456 self.message_renderer = HobWarpCellRendererText (col_number=1)
457 else:
458 self.message_renderer = gtk.CellRendererText ()
459 self.message_column = gtk.TreeViewColumn ("Message", self.message_renderer, text=3)
460 self.message_column.add_attribute(self.message_renderer, 'background', 5)
461 self.message_renderer.set_property('editable', (not self.readonly))
462 self.append_column (self.message_column)
463
464 def do_button_press_event(self, event):
465 gtk.TreeView.do_button_press_event(self, event)
466
467 if event.button == 3:
468 selection = super(RunningBuildTreeView, self).get_selection()
469 (model, it) = selection.get_selected()
470 if it is not None:
471 can_paste = model.get(it, model.COL_LOG)[0]
472 if can_paste == 'pastebin':
473 # build a simple menu with a pastebin option
474 menu = gtk.Menu()
475 menuitem = gtk.MenuItem("Copy")
476 menu.append(menuitem)
477 menuitem.connect("activate", self.clipboard_handler, (model, it))
478 menuitem.show()
479 menuitem = gtk.MenuItem("Send log to pastebin")
480 menu.append(menuitem)
481 menuitem.connect("activate", self.pastebin_handler, (model, it))
482 menuitem.show()
483 menu.show()
484 menu.popup(None, None, None, event.button, event.time)
485
486 def _add_to_clipboard(self, clipping):
487 """
488 Add the contents of clipping to the system clipboard.
489 """
490 clipboard = gtk.clipboard_get()
491 clipboard.set_text(clipping)
492 clipboard.store()
493
494 def pastebin_handler(self, widget, data):
495 """
496 Send the log data to pastebin, then add the new paste url to the
497 clipboard.
498 """
499 (model, it) = data
500 paste_url = do_pastebin(model.get(it, model.COL_MESSAGE)[0])
501
502 # @todo Provide visual feedback to the user that it is done and that
503 # it worked.
504 print paste_url
505
506 self._add_to_clipboard(paste_url)
507
508 def clipboard_handler(self, widget, data):
509 """
510 """
511 (model, it) = data
512 message = model.get(it, model.COL_MESSAGE)[0]
513
514 self._add_to_clipboard(message)
515
516class BuildFailureTreeView(gtk.TreeView):
517
518 def __init__ (self):
519 gtk.TreeView.__init__(self)
520 self.set_rules_hint(False)
521 self.set_headers_visible(False)
522 self.get_selection().set_mode(gtk.SELECTION_SINGLE)
523
524 # The icon that indicates whether we're building or failed.
525 renderer = HobCellRendererPixbuf ()
526 col = gtk.TreeViewColumn ("Status", renderer)
527 col.add_attribute (renderer, "icon-name", RunningBuildModel.COL_ICON)
528 self.append_column (col)
529
530 # The message of the build.
531 self.message_renderer = HobWarpCellRendererText (col_number=1)
532 self.message_column = gtk.TreeViewColumn ("Message", self.message_renderer, text=RunningBuildModel.COL_MESSAGE, background=RunningBuildModel.COL_COLOR)
533 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