From c527fd1f14c27855a37f2e8ac5346ce8d940ced2 Mon Sep 17 00:00:00 2001 From: Tudor Florea Date: Thu, 16 Oct 2014 03:05:19 +0200 Subject: initial commit for Enea Linux 4.0-140929 Migrated from the internal git server on the daisy-enea-point-release branch Signed-off-by: Tudor Florea --- bitbake/lib/bb/ui/crumbs/__init__.py | 17 + bitbake/lib/bb/ui/crumbs/builddetailspage.py | 437 ++++++ bitbake/lib/bb/ui/crumbs/builder.py | 1475 ++++++++++++++++++++ bitbake/lib/bb/ui/crumbs/buildmanager.py | 455 ++++++ bitbake/lib/bb/ui/crumbs/hig/__init__.py | 0 .../lib/bb/ui/crumbs/hig/advancedsettingsdialog.py | 341 +++++ bitbake/lib/bb/ui/crumbs/hig/crumbsdialog.py | 44 + .../lib/bb/ui/crumbs/hig/crumbsmessagedialog.py | 70 + bitbake/lib/bb/ui/crumbs/hig/deployimagedialog.py | 219 +++ .../lib/bb/ui/crumbs/hig/imageselectiondialog.py | 172 +++ .../lib/bb/ui/crumbs/hig/layerselectiondialog.py | 298 ++++ .../lib/bb/ui/crumbs/hig/parsingwarningsdialog.py | 163 +++ bitbake/lib/bb/ui/crumbs/hig/propertydialog.py | 437 ++++++ bitbake/lib/bb/ui/crumbs/hig/proxydetailsdialog.py | 90 ++ .../lib/bb/ui/crumbs/hig/retrieveimagedialog.py | 51 + bitbake/lib/bb/ui/crumbs/hig/saveimagedialog.py | 159 +++ bitbake/lib/bb/ui/crumbs/hig/settingsuihelper.py | 122 ++ .../lib/bb/ui/crumbs/hig/simplesettingsdialog.py | 894 ++++++++++++ bitbake/lib/bb/ui/crumbs/hobcolor.py | 38 + bitbake/lib/bb/ui/crumbs/hobeventhandler.py | 639 +++++++++ bitbake/lib/bb/ui/crumbs/hoblistmodel.py | 903 ++++++++++++ bitbake/lib/bb/ui/crumbs/hobpages.py | 128 ++ bitbake/lib/bb/ui/crumbs/hobwidget.py | 904 ++++++++++++ bitbake/lib/bb/ui/crumbs/imageconfigurationpage.py | 561 ++++++++ bitbake/lib/bb/ui/crumbs/imagedetailspage.py | 669 +++++++++ bitbake/lib/bb/ui/crumbs/packageselectionpage.py | 355 +++++ bitbake/lib/bb/ui/crumbs/persistenttooltip.py | 186 +++ bitbake/lib/bb/ui/crumbs/progress.py | 23 + bitbake/lib/bb/ui/crumbs/progressbar.py | 59 + bitbake/lib/bb/ui/crumbs/puccho.glade | 606 ++++++++ bitbake/lib/bb/ui/crumbs/recipeselectionpage.py | 335 +++++ bitbake/lib/bb/ui/crumbs/runningbuild.py | 551 ++++++++ bitbake/lib/bb/ui/crumbs/sanitycheckpage.py | 85 ++ bitbake/lib/bb/ui/crumbs/utils.py | 34 + 34 files changed, 11520 insertions(+) create mode 100644 bitbake/lib/bb/ui/crumbs/__init__.py create mode 100755 bitbake/lib/bb/ui/crumbs/builddetailspage.py create mode 100755 bitbake/lib/bb/ui/crumbs/builder.py create mode 100644 bitbake/lib/bb/ui/crumbs/buildmanager.py create mode 100644 bitbake/lib/bb/ui/crumbs/hig/__init__.py create mode 100644 bitbake/lib/bb/ui/crumbs/hig/advancedsettingsdialog.py create mode 100644 bitbake/lib/bb/ui/crumbs/hig/crumbsdialog.py create mode 100644 bitbake/lib/bb/ui/crumbs/hig/crumbsmessagedialog.py create mode 100644 bitbake/lib/bb/ui/crumbs/hig/deployimagedialog.py create mode 100644 bitbake/lib/bb/ui/crumbs/hig/imageselectiondialog.py create mode 100644 bitbake/lib/bb/ui/crumbs/hig/layerselectiondialog.py create mode 100644 bitbake/lib/bb/ui/crumbs/hig/parsingwarningsdialog.py create mode 100644 bitbake/lib/bb/ui/crumbs/hig/propertydialog.py create mode 100644 bitbake/lib/bb/ui/crumbs/hig/proxydetailsdialog.py create mode 100644 bitbake/lib/bb/ui/crumbs/hig/retrieveimagedialog.py create mode 100644 bitbake/lib/bb/ui/crumbs/hig/saveimagedialog.py create mode 100644 bitbake/lib/bb/ui/crumbs/hig/settingsuihelper.py create mode 100644 bitbake/lib/bb/ui/crumbs/hig/simplesettingsdialog.py create mode 100644 bitbake/lib/bb/ui/crumbs/hobcolor.py create mode 100644 bitbake/lib/bb/ui/crumbs/hobeventhandler.py create mode 100644 bitbake/lib/bb/ui/crumbs/hoblistmodel.py create mode 100755 bitbake/lib/bb/ui/crumbs/hobpages.py create mode 100644 bitbake/lib/bb/ui/crumbs/hobwidget.py create mode 100644 bitbake/lib/bb/ui/crumbs/imageconfigurationpage.py create mode 100755 bitbake/lib/bb/ui/crumbs/imagedetailspage.py create mode 100755 bitbake/lib/bb/ui/crumbs/packageselectionpage.py create mode 100644 bitbake/lib/bb/ui/crumbs/persistenttooltip.py create mode 100644 bitbake/lib/bb/ui/crumbs/progress.py create mode 100644 bitbake/lib/bb/ui/crumbs/progressbar.py create mode 100644 bitbake/lib/bb/ui/crumbs/puccho.glade create mode 100755 bitbake/lib/bb/ui/crumbs/recipeselectionpage.py create mode 100644 bitbake/lib/bb/ui/crumbs/runningbuild.py create mode 100644 bitbake/lib/bb/ui/crumbs/sanitycheckpage.py create mode 100644 bitbake/lib/bb/ui/crumbs/utils.py (limited to 'bitbake/lib/bb/ui/crumbs') 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 @@ +# +# Gtk+ UI pieces for BitBake +# +# Copyright (C) 2006-2007 Richard Purdie +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. diff --git a/bitbake/lib/bb/ui/crumbs/builddetailspage.py b/bitbake/lib/bb/ui/crumbs/builddetailspage.py new file mode 100755 index 0000000000..7fc690e2fa --- /dev/null +++ b/bitbake/lib/bb/ui/crumbs/builddetailspage.py @@ -0,0 +1,437 @@ +#!/usr/bin/env python +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2012 Intel Corporation +# +# Authored by Dongxiao Xu +# Authored by Shane Wang +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import gtk +import pango +import gobject +import bb.process +from bb.ui.crumbs.progressbar import HobProgressBar +from bb.ui.crumbs.hobwidget import hic, HobNotebook, HobAltButton, HobWarpCellRendererText, HobButton, HobInfoButton +from bb.ui.crumbs.runningbuild import RunningBuildTreeView +from bb.ui.crumbs.runningbuild import BuildFailureTreeView +from bb.ui.crumbs.hobpages import HobPage +from bb.ui.crumbs.hobcolor import HobColors + +class BuildConfigurationTreeView(gtk.TreeView): + def __init__ (self): + gtk.TreeView.__init__(self) + self.set_rules_hint(False) + self.set_headers_visible(False) + self.set_property("hover-expand", True) + self.get_selection().set_mode(gtk.SELECTION_SINGLE) + + # The icon that indicates whether we're building or failed. + renderer0 = gtk.CellRendererText() + renderer0.set_property('font-desc', pango.FontDescription('courier bold 12')) + col0 = gtk.TreeViewColumn ("Name", renderer0, text=0) + self.append_column (col0) + + # The message of configuration. + renderer1 = HobWarpCellRendererText(col_number=1) + col1 = gtk.TreeViewColumn ("Values", renderer1, text=1) + self.append_column (col1) + + def set_vars(self, key="", var=[""]): + d = {} + if type(var) == str: + d = {key: [var]} + elif type(var) == list and len(var) > 1: + #create the sub item line + l = [] + text = "" + for item in var: + text = " - " + item + l.append(text) + d = {key: var} + + return d + + def set_config_model(self, show_vars): + listmodel = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_STRING) + parent = None + for var in show_vars: + for subitem in var.items(): + name = subitem[0] + is_parent = True + for value in subitem[1]: + if is_parent: + parent = listmodel.append(parent, (name, value)) + is_parent = False + else: + listmodel.append(parent, (None, value)) + name = " - " + parent = None + # renew the tree model after get the configuration messages + self.set_model(listmodel) + + def show(self, src_config_info, src_params): + vars = [] + vars.append(self.set_vars("BB version:", src_params.bb_version)) + vars.append(self.set_vars("Target arch:", src_params.target_arch)) + vars.append(self.set_vars("Target OS:", src_params.target_os)) + vars.append(self.set_vars("Machine:", src_config_info.curr_mach)) + vars.append(self.set_vars("Distro:", src_config_info.curr_distro)) + vars.append(self.set_vars("Distro version:", src_params.distro_version)) + vars.append(self.set_vars("SDK machine:", src_config_info.curr_sdk_machine)) + vars.append(self.set_vars("Tune features:", src_params.tune_pkgarch)) + vars.append(self.set_vars("Layers:", src_config_info.layers)) + + for path in src_config_info.layers: + import os, os.path + if os.path.exists(path): + branch = bb.process.run('cd %s; git branch | grep "^* " | tr -d "* "' % path)[0] + if branch.startswith("fatal:"): + branch = "(unknown)" + if branch: + branch = branch.strip('\n') + vars.append(self.set_vars("Branch:", branch)) + break + + self.set_config_model(vars) + + def reset(self): + self.set_model(None) + +# +# BuildDetailsPage +# + +class BuildDetailsPage (HobPage): + + def __init__(self, builder): + super(BuildDetailsPage, self).__init__(builder, "Building ...") + + self.num_of_issues = 0 + self.endpath = (0,) + # create visual elements + self.create_visual_elements() + + def create_visual_elements(self): + # create visual elements + self.vbox = gtk.VBox(False, 12) + + self.progress_box = gtk.VBox(False, 12) + self.task_status = gtk.Label("\n") # to ensure layout is correct + self.task_status.set_alignment(0.0, 0.5) + self.progress_box.pack_start(self.task_status, expand=False, fill=False) + self.progress_hbox = gtk.HBox(False, 6) + self.progress_box.pack_end(self.progress_hbox, expand=True, fill=True) + self.progress_bar = HobProgressBar() + self.progress_hbox.pack_start(self.progress_bar, expand=True, fill=True) + self.stop_button = HobAltButton("Stop") + self.stop_button.connect("clicked", self.stop_button_clicked_cb) + self.stop_button.set_sensitive(False) + self.progress_hbox.pack_end(self.stop_button, expand=False, fill=False) + + self.notebook = HobNotebook() + self.config_tv = BuildConfigurationTreeView() + self.scrolled_view_config = gtk.ScrolledWindow () + self.scrolled_view_config.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS) + self.scrolled_view_config.add(self.config_tv) + self.notebook.append_page(self.scrolled_view_config, "Build configuration") + + self.failure_tv = BuildFailureTreeView() + self.failure_model = self.builder.handler.build.model.failure_model() + self.failure_tv.set_model(self.failure_model) + self.scrolled_view_failure = gtk.ScrolledWindow () + self.scrolled_view_failure.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS) + self.scrolled_view_failure.add(self.failure_tv) + self.notebook.append_page(self.scrolled_view_failure, "Issues") + + self.build_tv = RunningBuildTreeView(readonly=True, hob=True) + self.build_tv.set_model(self.builder.handler.build.model) + self.scrolled_view_build = gtk.ScrolledWindow () + self.scrolled_view_build.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS) + self.scrolled_view_build.add(self.build_tv) + self.notebook.append_page(self.scrolled_view_build, "Log") + + self.builder.handler.build.model.connect_after("row-changed", self.scroll_to_present_row, self.scrolled_view_build.get_vadjustment(), self.build_tv) + + self.button_box = gtk.HBox(False, 6) + self.back_button = HobAltButton('<< Back') + self.back_button.connect("clicked", self.back_button_clicked_cb) + self.button_box.pack_start(self.back_button, expand=False, fill=False) + + def update_build_status(self, current, total, task): + recipe_path, recipe_task = task.split(", ") + recipe = os.path.basename(recipe_path).rstrip(".bb") + tsk_msg = "Running task %s of %s: %s\nRecipe: %s" % (current, total, recipe_task, recipe) + self.task_status.set_markup(tsk_msg) + self.stop_button.set_sensitive(True) + + def reset_build_status(self): + self.task_status.set_markup("\n") # to ensure layout is correct + self.endpath = (0,) + + def show_issues(self): + self.num_of_issues += 1 + self.notebook.show_indicator_icon("Issues", self.num_of_issues) + self.notebook.queue_draw() + + def reset_issues(self): + self.num_of_issues = 0 + self.notebook.hide_indicator_icon("Issues") + + def _remove_all_widget(self): + children = self.vbox.get_children() or [] + for child in children: + self.vbox.remove(child) + children = self.box_group_area.get_children() or [] + for child in children: + self.box_group_area.remove(child) + children = self.get_children() or [] + for child in children: + self.remove(child) + + def add_build_fail_top_bar(self, actions, log_file=None): + primary_action = "Edit %s" % actions + + color = HobColors.ERROR + build_fail_top = gtk.EventBox() + #build_fail_top.set_size_request(-1, 200) + build_fail_top.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(color)) + + build_fail_tab = gtk.Table(14, 46, True) + build_fail_top.add(build_fail_tab) + + icon = gtk.Image() + icon_pix_buffer = gtk.gdk.pixbuf_new_from_file(hic.ICON_INDI_ERROR_FILE) + icon.set_from_pixbuf(icon_pix_buffer) + build_fail_tab.attach(icon, 1, 4, 0, 6) + + label = gtk.Label() + label.set_alignment(0.0, 0.5) + label.set_markup("%s" % self.title) + build_fail_tab.attach(label, 4, 26, 0, 6) + + label = gtk.Label() + label.set_alignment(0.0, 0.5) + # Ensure variable disk_full is defined + if not hasattr(self.builder, 'disk_full'): + self.builder.disk_full = False + + if self.builder.disk_full: + markup = "There is no disk space left, so Hob cannot finish building your image. Free up some disk space\n" + markup += "and restart the build. Check the \"Issues\" tab for more details" + label.set_markup(markup) + else: + label.set_markup("Check the \"Issues\" information for more details") + build_fail_tab.attach(label, 4, 40, 4, 9) + + # create button 'Edit packages' + action_button = HobButton(primary_action) + #action_button.set_size_request(-1, 40) + action_button.set_tooltip_text("Edit the %s parameters" % actions) + action_button.connect('clicked', self.failure_primary_action_button_clicked_cb, primary_action) + + if log_file: + open_log_button = HobAltButton("Open log") + open_log_button.set_relief(gtk.RELIEF_HALF) + open_log_button.set_tooltip_text("Open the build's log file") + open_log_button.connect('clicked', self.open_log_button_clicked_cb, log_file) + + attach_pos = (24 if log_file else 14) + file_bug_button = HobAltButton('File a bug') + file_bug_button.set_relief(gtk.RELIEF_HALF) + file_bug_button.set_tooltip_text("Open the Yocto Project bug tracking website") + file_bug_button.connect('clicked', self.failure_activate_file_bug_link_cb) + + if not self.builder.disk_full: + build_fail_tab.attach(action_button, 4, 13, 9, 12) + if log_file: + build_fail_tab.attach(open_log_button, 14, 23, 9, 12) + build_fail_tab.attach(file_bug_button, attach_pos, attach_pos + 9, 9, 12) + + else: + restart_build = HobButton("Restart the build") + restart_build.set_tooltip_text("Restart the build") + restart_build.connect('clicked', self.restart_build_button_clicked_cb) + + build_fail_tab.attach(restart_build, 4, 13, 9, 12) + build_fail_tab.attach(action_button, 14, 23, 9, 12) + if log_file: + build_fail_tab.attach(open_log_button, attach_pos, attach_pos + 9, 9, 12) + + self.builder.disk_full = False + return build_fail_top + + def show_fail_page(self, title): + self._remove_all_widget() + self.title = "Hob cannot build your %s" % title + + self.build_fail_bar = self.add_build_fail_top_bar(title, self.builder.current_logfile) + + self.pack_start(self.group_align, expand=True, fill=True) + self.box_group_area.pack_start(self.build_fail_bar, expand=False, fill=False) + self.box_group_area.pack_start(self.vbox, expand=True, fill=True) + + self.vbox.pack_start(self.notebook, expand=True, fill=True) + self.show_all() + self.notebook.set_page("Issues") + self.back_button.hide() + + def add_build_stop_top_bar(self, action, log_file=None): + color = HobColors.LIGHT_GRAY + build_stop_top = gtk.EventBox() + #build_stop_top.set_size_request(-1, 200) + build_stop_top.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(color)) + build_stop_top.set_flags(gtk.CAN_DEFAULT) + build_stop_top.grab_default() + + build_stop_tab = gtk.Table(11, 46, True) + build_stop_top.add(build_stop_tab) + + icon = gtk.Image() + icon_pix_buffer = gtk.gdk.pixbuf_new_from_file(hic.ICON_INFO_HOVER_FILE) + icon.set_from_pixbuf(icon_pix_buffer) + build_stop_tab.attach(icon, 1, 4, 0, 6) + + label = gtk.Label() + label.set_alignment(0.0, 0.5) + label.set_markup("%s" % self.title) + build_stop_tab.attach(label, 4, 26, 0, 6) + + action_button = HobButton("Edit %s" % action) + action_button.set_size_request(-1, 40) + if action == "image": + action_button.set_tooltip_text("Edit the image parameters") + elif action == "recipes": + action_button.set_tooltip_text("Edit the included recipes") + elif action == "packages": + action_button.set_tooltip_text("Edit the included packages") + action_button.connect('clicked', self.stop_primary_action_button_clicked_cb, action) + build_stop_tab.attach(action_button, 4, 13, 6, 9) + + if log_file: + open_log_button = HobAltButton("Open log") + open_log_button.set_relief(gtk.RELIEF_HALF) + open_log_button.set_tooltip_text("Open the build's log file") + open_log_button.connect('clicked', self.open_log_button_clicked_cb, log_file) + build_stop_tab.attach(open_log_button, 14, 23, 6, 9) + + attach_pos = (24 if log_file else 14) + build_button = HobAltButton("Build new image") + #build_button.set_size_request(-1, 40) + build_button.set_tooltip_text("Create a new image from scratch") + build_button.connect('clicked', self.new_image_button_clicked_cb) + build_stop_tab.attach(build_button, attach_pos, attach_pos + 9, 6, 9) + + return build_stop_top, action_button + + def show_stop_page(self, action): + self._remove_all_widget() + self.title = "Build stopped" + self.build_stop_bar, action_button = self.add_build_stop_top_bar(action, self.builder.current_logfile) + + self.pack_start(self.group_align, expand=True, fill=True) + self.box_group_area.pack_start(self.build_stop_bar, expand=False, fill=False) + self.box_group_area.pack_start(self.vbox, expand=True, fill=True) + + self.vbox.pack_start(self.notebook, expand=True, fill=True) + self.show_all() + self.back_button.hide() + return action_button + + def show_page(self, step): + self._remove_all_widget() + if step == self.builder.PACKAGE_GENERATING or step == self.builder.FAST_IMAGE_GENERATING: + self.title = "Building packages ..." + else: + self.title = "Building image ..." + self.build_details_top = self.add_onto_top_bar(None) + self.pack_start(self.build_details_top, expand=False, fill=False) + self.pack_start(self.group_align, expand=True, fill=True) + + self.box_group_area.pack_start(self.vbox, expand=True, fill=True) + + self.progress_bar.reset() + self.config_tv.reset() + self.vbox.pack_start(self.progress_box, expand=False, fill=False) + + self.vbox.pack_start(self.notebook, expand=True, fill=True) + + self.box_group_area.pack_end(self.button_box, expand=False, fill=False) + self.show_all() + self.notebook.set_page("Log") + self.back_button.hide() + + self.reset_build_status() + self.reset_issues() + + def update_progress_bar(self, title, fraction, status=None): + self.progress_bar.update(fraction) + self.progress_bar.set_title(title) + self.progress_bar.set_rcstyle(status) + + def back_button_clicked_cb(self, button): + self.builder.show_configuration() + + def new_image_button_clicked_cb(self, button): + self.builder.reset() + + def show_back_button(self): + self.back_button.show() + + def stop_button_clicked_cb(self, button): + self.builder.stop_build() + + def hide_stop_button(self): + self.stop_button.set_sensitive(False) + self.stop_button.hide() + + def scroll_to_present_row(self, model, path, iter, v_adj, treeview): + if treeview and v_adj: + if path[0] > self.endpath[0]: # check the event is a new row append or not + self.endpath = path + # check the gtk.adjustment position is at end boundary or not + if (v_adj.upper <= v_adj.page_size) or (v_adj.value == v_adj.upper - v_adj.page_size): + treeview.scroll_to_cell(path) + + def show_configurations(self, configurations, params): + self.config_tv.show(configurations, params) + + def failure_primary_action_button_clicked_cb(self, button, action): + if "Edit recipes" in action: + self.builder.show_recipes() + elif "Edit packages" in action: + self.builder.show_packages() + elif "Edit image" in action: + self.builder.show_configuration() + + def restart_build_button_clicked_cb(self, button): + self.builder.just_bake() + + def stop_primary_action_button_clicked_cb(self, button, action): + if "recipes" in action: + self.builder.show_recipes() + elif "packages" in action: + self.builder.show_packages() + elif "image" in action: + self.builder.show_configuration() + + def open_log_button_clicked_cb(self, button, log_file): + if log_file: + log_file = "file:///" + log_file + gtk.show_uri(screen=button.get_screen(), uri=log_file, timestamp=0) + + def failure_activate_file_bug_link_cb(self, button): + button.child.emit('activate-link', "http://bugzilla.yoctoproject.org") diff --git a/bitbake/lib/bb/ui/crumbs/builder.py b/bitbake/lib/bb/ui/crumbs/builder.py new file mode 100755 index 0000000000..455af320e8 --- /dev/null +++ b/bitbake/lib/bb/ui/crumbs/builder.py @@ -0,0 +1,1475 @@ +#!/usr/bin/env python +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2011-2012 Intel Corporation +# +# Authored by Joshua Lock +# Authored by Dongxiao Xu +# Authored by Shane Wang +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import glib +import gtk, gobject +import copy +import os +import subprocess +import shlex +import re +import logging +import sys +import signal +import time +from bb.ui.crumbs.imageconfigurationpage import ImageConfigurationPage +from bb.ui.crumbs.recipeselectionpage import RecipeSelectionPage +from bb.ui.crumbs.packageselectionpage import PackageSelectionPage +from bb.ui.crumbs.builddetailspage import BuildDetailsPage +from bb.ui.crumbs.imagedetailspage import ImageDetailsPage +from bb.ui.crumbs.sanitycheckpage import SanityCheckPage +from bb.ui.crumbs.hobwidget import hwc, HobButton, HobAltButton +from bb.ui.crumbs.persistenttooltip import PersistentTooltip +import bb.ui.crumbs.utils +from bb.ui.crumbs.hig.crumbsmessagedialog import CrumbsMessageDialog +from bb.ui.crumbs.hig.simplesettingsdialog import SimpleSettingsDialog +from bb.ui.crumbs.hig.advancedsettingsdialog import AdvancedSettingsDialog +from bb.ui.crumbs.hig.deployimagedialog import DeployImageDialog +from bb.ui.crumbs.hig.layerselectiondialog import LayerSelectionDialog +from bb.ui.crumbs.hig.imageselectiondialog import ImageSelectionDialog +from bb.ui.crumbs.hig.parsingwarningsdialog import ParsingWarningsDialog +from bb.ui.crumbs.hig.propertydialog import PropertyDialog + +hobVer = 20120808 + +class Configuration: + '''Represents the data structure of configuration.''' + + @classmethod + def parse_proxy_string(cls, proxy): + pattern = "^\s*((http|https|ftp|socks|cvs)://)?((\S+):(\S+)@)?([^\s:]+)(:(\d+))?/?" + match = re.search(pattern, proxy) + if match: + return match.group(2), match.group(4), match.group(5), match.group(6), match.group(8) + else: + return None, None, None, "", "" + + @classmethod + def make_host_string(cls, prot, user, passwd, host, default_prot=""): + if host == None or host == "": + return "" + + passwd = passwd or "" + + if user != None and user != "": + if prot == None or prot == "": + prot = default_prot + return prot + "://" + user + ":" + passwd + "@" + host + else: + if prot == None or prot == "": + return host + else: + return prot + "://" + host + + @classmethod + def make_port_string(cls, port): + port = port or "" + return port + + @classmethod + def make_proxy_string(cls, prot, user, passwd, host, port, default_prot=""): + if host == None or host == "":# or port == None or port == "": + return "" + + return Configuration.make_host_string(prot, user, passwd, host, default_prot) + (":" + Configuration.make_port_string(port) if port else "") + + def __init__(self): + self.curr_mach = "" + self.selected_image = None + # settings + self.curr_distro = "" + self.dldir = self.sstatedir = self.sstatemirror = "" + self.pmake = self.bbthread = 0 + self.curr_package_format = "" + self.image_rootfs_size = self.image_extra_size = 0 + self.image_overhead_factor = 1 + self.incompat_license = "" + self.curr_sdk_machine = "" + self.conf_version = self.lconf_version = "" + self.extra_setting = {} + self.toolchain_build = False + self.image_fstypes = "" + self.image_size = None + self.image_packages = [] + # bblayers.conf + self.layers = [] + # image/recipes/packages + self.clear_selection() + + self.user_selected_packages = [] + + self.default_task = "build" + + # proxy settings + self.enable_proxy = None + self.same_proxy = False + self.proxies = { + "http" : [None, None, None, "", ""], # protocol : [prot, user, passwd, host, port] + "https" : [None, None, None, "", ""], + "ftp" : [None, None, None, "", ""], + "socks" : [None, None, None, "", ""], + "cvs" : [None, None, None, "", ""], + } + + def clear_selection(self): + self.selected_recipes = [] + self.selected_packages = [] + self.initial_selected_image = None + self.initial_selected_packages = [] + self.initial_user_selected_packages = [] + + def split_proxy(self, protocol, proxy): + entry = [] + prot, user, passwd, host, port = Configuration.parse_proxy_string(proxy) + entry.append(prot) + entry.append(user) + entry.append(passwd) + entry.append(host) + entry.append(port) + self.proxies[protocol] = entry + + def combine_proxy(self, protocol): + entry = self.proxies[protocol] + return Configuration.make_proxy_string(entry[0], entry[1], entry[2], entry[3], entry[4], protocol) + + def combine_host_only(self, protocol): + entry = self.proxies[protocol] + return Configuration.make_host_string(entry[0], entry[1], entry[2], entry[3], protocol) + + def combine_port_only(self, protocol): + entry = self.proxies[protocol] + return Configuration.make_port_string(entry[4]) + + def update(self, params): + # settings + self.curr_distro = params["distro"] + self.dldir = params["dldir"] + self.sstatedir = params["sstatedir"] + self.sstatemirror = params["sstatemirror"] + self.pmake = int(params["pmake"].split()[1]) + self.bbthread = params["bbthread"] + self.curr_package_format = " ".join(params["pclass"].split("package_")).strip() + self.image_rootfs_size = params["image_rootfs_size"] + self.image_extra_size = params["image_extra_size"] + self.image_overhead_factor = params['image_overhead_factor'] + self.incompat_license = params["incompat_license"] + self.curr_sdk_machine = params["sdk_machine"] + self.conf_version = params["conf_version"] + self.lconf_version = params["lconf_version"] + self.image_fstypes = params["image_fstypes"] + # self.extra_setting/self.toolchain_build + # bblayers.conf + self.layers = params["layer"].split() + self.layers_non_removable = params["layers_non_removable"].split() + self.default_task = params["default_task"] + + # proxy settings + self.enable_proxy = params["http_proxy"] != "" or params["https_proxy"] != "" \ + or params["ftp_proxy"] != "" or params["socks_proxy"] != "" \ + or params["cvs_proxy_host"] != "" or params["cvs_proxy_port"] != "" + self.split_proxy("http", params["http_proxy"]) + self.split_proxy("https", params["https_proxy"]) + self.split_proxy("ftp", params["ftp_proxy"]) + self.split_proxy("socks", params["socks_proxy"]) + self.split_proxy("cvs", params["cvs_proxy_host"] + ":" + params["cvs_proxy_port"]) + + def save(self, handler, defaults=False): + # bblayers.conf + handler.set_var_in_file("BBLAYERS", self.layers, "bblayers.conf") + # local.conf + if not defaults: + handler.early_assign_var_in_file("MACHINE", self.curr_mach, "local.conf") + handler.set_var_in_file("DISTRO", self.curr_distro, "local.conf") + handler.set_var_in_file("DL_DIR", self.dldir, "local.conf") + handler.set_var_in_file("SSTATE_DIR", self.sstatedir, "local.conf") + sstate_mirror_list = self.sstatemirror.split("\\n ") + sstate_mirror_list_modified = [] + for mirror in sstate_mirror_list: + if mirror != "": + mirror = mirror + "\\n" + sstate_mirror_list_modified.append(mirror) + handler.set_var_in_file("SSTATE_MIRRORS", sstate_mirror_list_modified, "local.conf") + handler.set_var_in_file("PARALLEL_MAKE", "-j %s" % self.pmake, "local.conf") + handler.set_var_in_file("BB_NUMBER_THREADS", self.bbthread, "local.conf") + handler.set_var_in_file("PACKAGE_CLASSES", " ".join(["package_" + i for i in self.curr_package_format.split()]), "local.conf") + handler.set_var_in_file("IMAGE_ROOTFS_SIZE", self.image_rootfs_size, "local.conf") + handler.set_var_in_file("IMAGE_EXTRA_SPACE", self.image_extra_size, "local.conf") + handler.set_var_in_file("INCOMPATIBLE_LICENSE", self.incompat_license, "local.conf") + handler.set_var_in_file("SDKMACHINE", self.curr_sdk_machine, "local.conf") + handler.set_var_in_file("CONF_VERSION", self.conf_version, "local.conf") + handler.set_var_in_file("LCONF_VERSION", self.lconf_version, "bblayers.conf") + handler.set_extra_config(self.extra_setting) + handler.set_var_in_file("TOOLCHAIN_BUILD", self.toolchain_build, "local.conf") + handler.set_var_in_file("IMAGE_FSTYPES", self.image_fstypes, "local.conf") + if not defaults: + # image/recipes/packages + handler.set_var_in_file("__SELECTED_IMAGE__", self.selected_image, "local.conf") + handler.set_var_in_file("DEPENDS", self.selected_recipes, "local.conf") + handler.set_var_in_file("IMAGE_INSTALL", self.user_selected_packages, "local.conf") + # proxy + if self.enable_proxy == True: + handler.set_var_in_file("http_proxy", self.combine_proxy("http"), "local.conf") + handler.set_var_in_file("https_proxy", self.combine_proxy("https"), "local.conf") + handler.set_var_in_file("ftp_proxy", self.combine_proxy("ftp"), "local.conf") + handler.set_var_in_file("all_proxy", self.combine_proxy("socks"), "local.conf") + handler.set_var_in_file("CVS_PROXY_HOST", self.combine_host_only("cvs"), "local.conf") + handler.set_var_in_file("CVS_PROXY_PORT", self.combine_port_only("cvs"), "local.conf") + else: + handler.set_var_in_file("http_proxy", "", "local.conf") + handler.set_var_in_file("https_proxy", "", "local.conf") + handler.set_var_in_file("ftp_proxy", "", "local.conf") + handler.set_var_in_file("all_proxy", "", "local.conf") + handler.set_var_in_file("CVS_PROXY_HOST", "", "local.conf") + handler.set_var_in_file("CVS_PROXY_PORT", "", "local.conf") + + def __str__(self): + s = "VERSION: '%s', BBLAYERS: '%s', MACHINE: '%s', DISTRO: '%s', DL_DIR: '%s'," % \ + (hobVer, " ".join(self.layers), self.curr_mach, self.curr_distro, self.dldir ) + s += "SSTATE_DIR: '%s', SSTATE_MIRROR: '%s', PARALLEL_MAKE: '-j %s', BB_NUMBER_THREADS: '%s', PACKAGE_CLASSES: '%s', " % \ + (self.sstatedir, self.sstatemirror, self.pmake, self.bbthread, " ".join(["package_" + i for i in self.curr_package_format.split()])) + s += "IMAGE_ROOTFS_SIZE: '%s', IMAGE_EXTRA_SPACE: '%s', INCOMPATIBLE_LICENSE: '%s', SDKMACHINE: '%s', CONF_VERSION: '%s', " % \ + (self.image_rootfs_size, self.image_extra_size, self.incompat_license, self.curr_sdk_machine, self.conf_version) + s += "LCONF_VERSION: '%s', EXTRA_SETTING: '%s', TOOLCHAIN_BUILD: '%s', IMAGE_FSTYPES: '%s', __SELECTED_IMAGE__: '%s', " % \ + (self.lconf_version, self.extra_setting, self.toolchain_build, self.image_fstypes, self.selected_image) + s += "DEPENDS: '%s', IMAGE_INSTALL: '%s', enable_proxy: '%s', use_same_proxy: '%s', http_proxy: '%s', " % \ + (self.selected_recipes, self.user_selected_packages, self.enable_proxy, self.same_proxy, self.combine_proxy("http")) + s += "https_proxy: '%s', ftp_proxy: '%s', all_proxy: '%s', CVS_PROXY_HOST: '%s', CVS_PROXY_PORT: '%s'" % \ + (self.combine_proxy("https"), self.combine_proxy("ftp"), self.combine_proxy("socks"), + self.combine_host_only("cvs"), self.combine_port_only("cvs")) + return s + +class Parameters: + '''Represents other variables like available machines, etc.''' + + def __init__(self): + # Variables + self.max_threads = 65535 + self.core_base = "" + self.image_addr = "" + self.image_types = [] + self.runnable_image_types = [] + self.runnable_machine_patterns = [] + self.deployable_image_types = [] + self.tmpdir = "" + + self.all_machines = [] + self.all_package_formats = [] + self.all_distros = [] + self.all_sdk_machines = [] + self.all_layers = [] + self.image_names = [] + self.image_white_pattern = "" + self.image_black_pattern = "" + + # for build log to show + self.bb_version = "" + self.target_arch = "" + self.target_os = "" + self.distro_version = "" + self.tune_pkgarch = "" + + def update(self, params): + self.max_threads = params["max_threads"] + self.core_base = params["core_base"] + self.image_addr = params["image_addr"] + self.image_types = params["image_types"].split() + self.runnable_image_types = params["runnable_image_types"].split() + self.runnable_machine_patterns = params["runnable_machine_patterns"].split() + self.deployable_image_types = params["deployable_image_types"].split() + self.tmpdir = params["tmpdir"] + self.image_white_pattern = params["image_white_pattern"] + self.image_black_pattern = params["image_black_pattern"] + self.kernel_image_type = params["kernel_image_type"] + # for build log to show + self.bb_version = params["bb_version"] + self.target_arch = params["target_arch"] + self.target_os = params["target_os"] + self.distro_version = params["distro_version"] + self.tune_pkgarch = params["tune_pkgarch"] + +def hob_conf_filter(fn, data): + if fn.endswith("/local.conf"): + distro = data.getVar("DISTRO_HOB") + if distro: + if distro != "defaultsetup": + data.setVar("DISTRO", distro) + else: + data.delVar("DISTRO") + + keys = ["MACHINE_HOB", "SDKMACHINE_HOB", "PACKAGE_CLASSES_HOB", \ + "BB_NUMBER_THREADS_HOB", "PARALLEL_MAKE_HOB", "DL_DIR_HOB", \ + "SSTATE_DIR_HOB", "SSTATE_MIRRORS_HOB", "INCOMPATIBLE_LICENSE_HOB"] + for key in keys: + var_hob = data.getVar(key) + if var_hob: + data.setVar(key.split("_HOB")[0], var_hob) + return + + if fn.endswith("/bblayers.conf"): + layers = data.getVar("BBLAYERS_HOB") + if layers: + data.setVar("BBLAYERS", layers) + return + +class Builder(gtk.Window): + + (INITIAL_CHECKS, + MACHINE_SELECTION, + RCPPKGINFO_POPULATING, + RCPPKGINFO_POPULATED, + BASEIMG_SELECTED, + RECIPE_SELECTION, + PACKAGE_GENERATING, + PACKAGE_GENERATED, + PACKAGE_SELECTION, + FAST_IMAGE_GENERATING, + IMAGE_GENERATING, + IMAGE_GENERATED, + MY_IMAGE_OPENED, + BACK, + END_NOOP) = range(15) + + (SANITY_CHECK, + IMAGE_CONFIGURATION, + RECIPE_DETAILS, + BUILD_DETAILS, + PACKAGE_DETAILS, + IMAGE_DETAILS, + END_TAB) = range(7) + + __step2page__ = { + INITIAL_CHECKS : SANITY_CHECK, + MACHINE_SELECTION : IMAGE_CONFIGURATION, + RCPPKGINFO_POPULATING : IMAGE_CONFIGURATION, + RCPPKGINFO_POPULATED : IMAGE_CONFIGURATION, + BASEIMG_SELECTED : IMAGE_CONFIGURATION, + RECIPE_SELECTION : RECIPE_DETAILS, + PACKAGE_GENERATING : BUILD_DETAILS, + PACKAGE_GENERATED : PACKAGE_DETAILS, + PACKAGE_SELECTION : PACKAGE_DETAILS, + FAST_IMAGE_GENERATING : BUILD_DETAILS, + IMAGE_GENERATING : BUILD_DETAILS, + IMAGE_GENERATED : IMAGE_DETAILS, + MY_IMAGE_OPENED : IMAGE_DETAILS, + END_NOOP : None, + } + + SANITY_CHECK_MIN_DISPLAY_TIME = 5 + + def __init__(self, hobHandler, recipe_model, package_model): + super(Builder, self).__init__() + + self.hob_image = "hob-image" + + # handler + self.handler = hobHandler + + # logger + self.logger = logging.getLogger("BitBake") + self.consolelog = None + self.current_logfile = None + + # configuration and parameters + self.configuration = Configuration() + self.parameters = Parameters() + + # build step + self.current_step = None + self.previous_step = None + + self.stopping = False + + # recipe model and package model + self.recipe_model = recipe_model + self.package_model = package_model + + # Indicate whether user has customized the image + self.customized = False + + # Indicate whether the UI is working + self.sensitive = True + + # Indicate whether the sanity check ran + self.sanity_checked = False + + # save parsing warnings + self.parsing_warnings = [] + + # create visual elements + self.create_visual_elements() + + # connect the signals to functions + self.connect("delete-event", self.destroy_window_cb) + self.recipe_model.connect ("recipe-selection-changed", self.recipelist_changed_cb) + self.package_model.connect("package-selection-changed", self.packagelist_changed_cb) + self.handler.connect("config-updated", self.handler_config_updated_cb) + self.handler.connect("package-formats-updated", self.handler_package_formats_updated_cb) + self.handler.connect("parsing-started", self.handler_parsing_started_cb) + self.handler.connect("parsing", self.handler_parsing_cb) + self.handler.connect("parsing-completed", self.handler_parsing_completed_cb) + self.handler.build.connect("build-started", self.handler_build_started_cb) + self.handler.build.connect("build-succeeded", self.handler_build_succeeded_cb) + self.handler.build.connect("build-failed", self.handler_build_failed_cb) + self.handler.build.connect("build-aborted", self.handler_build_aborted_cb) + self.handler.build.connect("task-started", self.handler_task_started_cb) + self.handler.build.connect("disk-full", self.handler_disk_full_cb) + self.handler.build.connect("log-error", self.handler_build_failure_cb) + self.handler.build.connect("log-warning", self.handler_build_failure_cb) + self.handler.build.connect("log", self.handler_build_log_cb) + self.handler.build.connect("no-provider", self.handler_no_provider_cb) + self.handler.connect("generating-data", self.handler_generating_data_cb) + self.handler.connect("data-generated", self.handler_data_generated_cb) + self.handler.connect("command-succeeded", self.handler_command_succeeded_cb) + self.handler.connect("command-failed", self.handler_command_failed_cb) + self.handler.connect("parsing-warning", self.handler_parsing_warning_cb) + self.handler.connect("sanity-failed", self.handler_sanity_failed_cb) + self.handler.connect("recipe-populated", self.handler_recipe_populated_cb) + self.handler.connect("package-populated", self.handler_package_populated_cb) + + self.handler.append_to_bbfiles("${TOPDIR}/recipes/images/custom/*.bb") + self.handler.append_to_bbfiles("${TOPDIR}/recipes/images/*.bb") + self.initiate_new_build_async() + + signal.signal(signal.SIGINT, self.event_handle_SIGINT) + + def create_visual_elements(self): + self.set_title("Hob") + self.set_icon_name("applications-development") + self.set_resizable(True) + + try: + window_width = self.get_screen().get_width() + window_height = self.get_screen().get_height() + except AttributeError: + print "Please set DISPLAY variable before running Hob." + sys.exit(1) + + if window_width >= hwc.MAIN_WIN_WIDTH: + window_width = hwc.MAIN_WIN_WIDTH + window_height = hwc.MAIN_WIN_HEIGHT + self.set_size_request(window_width, window_height) + + self.vbox = gtk.VBox(False, 0) + self.vbox.set_border_width(0) + self.add(self.vbox) + + # create pages + self.image_configuration_page = ImageConfigurationPage(self) + self.recipe_details_page = RecipeSelectionPage(self) + self.build_details_page = BuildDetailsPage(self) + self.package_details_page = PackageSelectionPage(self) + self.image_details_page = ImageDetailsPage(self) + self.sanity_check_page = SanityCheckPage(self) + self.display_sanity_check = False + self.sanity_check_post_func = False + self.had_network_error = False + + self.nb = gtk.Notebook() + self.nb.set_show_tabs(False) + self.nb.insert_page(self.sanity_check_page, None, self.SANITY_CHECK) + self.nb.insert_page(self.image_configuration_page, None, self.IMAGE_CONFIGURATION) + self.nb.insert_page(self.recipe_details_page, None, self.RECIPE_DETAILS) + self.nb.insert_page(self.build_details_page, None, self.BUILD_DETAILS) + self.nb.insert_page(self.package_details_page, None, self.PACKAGE_DETAILS) + self.nb.insert_page(self.image_details_page, None, self.IMAGE_DETAILS) + self.vbox.pack_start(self.nb, expand=True, fill=True) + + self.show_all() + self.nb.set_current_page(0) + + def sanity_check_timeout(self): + # The minimum time for showing the 'sanity check' page has passe + # If someone set the 'sanity_check_post_step' meanwhile, execute it now + self.display_sanity_check = False + if self.sanity_check_post_func: + temp = self.sanity_check_post_func + self.sanity_check_post_func = None + temp() + return False + + def show_sanity_check_page(self): + # This window must stay on screen for at least 5 seconds, according to the design document + self.nb.set_current_page(self.SANITY_CHECK) + self.sanity_check_post_step = None + self.display_sanity_check = True + self.sanity_check_page.start() + gobject.timeout_add(self.SANITY_CHECK_MIN_DISPLAY_TIME * 1000, self.sanity_check_timeout) + + def execute_after_sanity_check(self, func): + if not self.display_sanity_check: + func() + else: + self.sanity_check_post_func = func + + def generate_configuration(self): + if not self.sanity_checked: + self.show_sanity_check_page() + self.handler.generate_configuration() + + def initiate_new_build_async(self): + self.configuration.selected_image = None + self.switch_page(self.MACHINE_SELECTION) + self.handler.init_cooker() + self.handler.set_extra_inherit("image_types") + self.generate_configuration() + + def update_config_async(self): + self.set_user_config() + self.generate_configuration() + self.switch_page(self.MACHINE_SELECTION) + + def sanity_check(self): + self.handler.trigger_sanity_check() + + def populate_recipe_package_info_async(self): + self.switch_page(self.RCPPKGINFO_POPULATING) + # Parse recipes + self.set_user_config() + self.handler.generate_recipes() + + def generate_packages_async(self, log = False): + self.switch_page(self.PACKAGE_GENERATING) + if log: + self.current_logfile = self.handler.get_logfile() + self.do_log(self.current_logfile) + # Build packages + _, all_recipes = self.recipe_model.get_selected_recipes() + self.set_user_config() + self.handler.reset_build() + self.handler.generate_packages(all_recipes, self.configuration.default_task) + + def restore_initial_selected_packages(self): + self.package_model.set_selected_packages(self.configuration.initial_user_selected_packages, True) + self.package_model.set_selected_packages(self.configuration.initial_selected_packages) + for package in self.configuration.selected_packages: + if package not in self.configuration.initial_selected_packages: + self.package_model.exclude_item(self.package_model.find_path_for_item(package)) + + def fast_generate_image_async(self, log = False): + self.switch_page(self.FAST_IMAGE_GENERATING) + if log: + self.current_logfile = self.handler.get_logfile() + self.do_log(self.current_logfile) + # Build packages + _, all_recipes = self.recipe_model.get_selected_recipes() + self.set_user_config() + self.handler.reset_build() + self.handler.generate_packages(all_recipes, self.configuration.default_task) + + def generate_image_async(self, cont = False): + self.switch_page(self.IMAGE_GENERATING) + self.handler.reset_build() + if not cont: + self.current_logfile = self.handler.get_logfile() + self.do_log(self.current_logfile) + # Build image + self.set_user_config() + toolchain_packages = [] + base_image = None + if self.configuration.toolchain_build: + toolchain_packages = self.package_model.get_selected_packages_toolchain() + if self.configuration.selected_image == self.recipe_model.__custom_image__: + packages = self.package_model.get_selected_packages() + image = self.hob_image + base_image = self.configuration.initial_selected_image + else: + packages = [] + image = self.configuration.selected_image + self.handler.generate_image(image, + base_image, + packages, + toolchain_packages, + self.configuration.default_task) + + def generate_new_image(self, image, description): + base_image = self.configuration.initial_selected_image + if base_image == self.recipe_model.__custom_image__: + base_image = None + packages = self.package_model.get_selected_packages() + self.handler.generate_new_image(image, base_image, packages, description) + + def ensure_dir(self, directory): + self.handler.ensure_dir(directory) + + def get_parameters_sync(self): + return self.handler.get_parameters() + + def request_package_info_async(self): + self.handler.request_package_info() + + def cancel_build_sync(self, force=False): + self.handler.cancel_build(force) + + def cancel_parse_sync(self): + self.handler.cancel_parse() + + def switch_page(self, next_step): + # Main Workflow (Business Logic) + self.nb.set_current_page(self.__step2page__[next_step]) + + if next_step == self.MACHINE_SELECTION: # init step + self.image_configuration_page.show_machine() + + elif next_step == self.RCPPKGINFO_POPULATING: + # MACHINE CHANGED action or SETTINGS CHANGED + # show the progress bar + self.image_configuration_page.show_info_populating() + + elif next_step == self.RCPPKGINFO_POPULATED: + self.image_configuration_page.show_info_populated() + + elif next_step == self.BASEIMG_SELECTED: + self.image_configuration_page.show_baseimg_selected() + + elif next_step == self.RECIPE_SELECTION: + if self.recipe_model.get_selected_image() == self.recipe_model.__custom_image__: + self.recipe_details_page.set_recipe_curr_tab(self.recipe_details_page.ALL) + else: + self.recipe_details_page.set_recipe_curr_tab(self.recipe_details_page.INCLUDED) + + elif next_step == self.PACKAGE_SELECTION: + self.configuration.initial_selected_packages = self.configuration.selected_packages + self.configuration.initial_user_selected_packages = self.configuration.user_selected_packages + self.package_details_page.set_title("Edit packages") + if self.recipe_model.get_selected_image() == self.recipe_model.__custom_image__: + self.package_details_page.set_packages_curr_tab(self.package_details_page.ALL) + else: + self.package_details_page.set_packages_curr_tab(self.package_details_page.INCLUDED) + self.package_details_page.show_page(self.current_logfile) + + + elif next_step == self.PACKAGE_GENERATING or next_step == self.FAST_IMAGE_GENERATING: + # both PACKAGE_GENERATING and FAST_IMAGE_GENERATING share the same page + self.build_details_page.show_page(next_step) + + elif next_step == self.PACKAGE_GENERATED: + self.package_details_page.set_title("Step 2 of 2: Edit packages") + if self.recipe_model.get_selected_image() == self.recipe_model.__custom_image__: + self.package_details_page.set_packages_curr_tab(self.package_details_page.ALL) + else: + self.package_details_page.set_packages_curr_tab(self.package_details_page.INCLUDED) + self.package_details_page.show_page(self.current_logfile) + + elif next_step == self.IMAGE_GENERATING: + # after packages are generated, selected_packages need to + # be updated in package_model per selected_image in recipe_model + self.build_details_page.show_page(next_step) + + elif next_step == self.IMAGE_GENERATED: + self.image_details_page.show_page(next_step) + + elif next_step == self.MY_IMAGE_OPENED: + self.image_details_page.show_page(next_step) + + self.previous_step = self.current_step + self.current_step = next_step + + def set_user_config_proxies(self): + if self.configuration.enable_proxy == True: + self.handler.set_http_proxy(self.configuration.combine_proxy("http")) + self.handler.set_https_proxy(self.configuration.combine_proxy("https")) + self.handler.set_ftp_proxy(self.configuration.combine_proxy("ftp")) + self.handler.set_socks_proxy(self.configuration.combine_proxy("socks")) + self.handler.set_cvs_proxy(self.configuration.combine_host_only("cvs"), self.configuration.combine_port_only("cvs")) + elif self.configuration.enable_proxy == False: + self.handler.set_http_proxy("") + self.handler.set_https_proxy("") + self.handler.set_ftp_proxy("") + self.handler.set_socks_proxy("") + self.handler.set_cvs_proxy("", "") + + def set_user_config_extra(self): + self.handler.set_rootfs_size(self.configuration.image_rootfs_size) + self.handler.set_extra_size(self.configuration.image_extra_size) + self.handler.set_incompatible_license(self.configuration.incompat_license) + self.handler.set_sdk_machine(self.configuration.curr_sdk_machine) + self.handler.set_image_fstypes(self.configuration.image_fstypes) + self.handler.set_extra_config(self.configuration.extra_setting) + self.handler.set_extra_inherit("packageinfo image_types") + self.set_user_config_proxies() + + def set_user_config(self): + # set bb layers + self.handler.set_bblayers(self.configuration.layers) + # set local configuration + self.handler.set_machine(self.configuration.curr_mach) + self.handler.set_package_format(self.configuration.curr_package_format) + self.handler.set_distro(self.configuration.curr_distro) + self.handler.set_dl_dir(self.configuration.dldir) + self.handler.set_sstate_dir(self.configuration.sstatedir) + self.handler.set_sstate_mirrors(self.configuration.sstatemirror) + self.handler.set_pmake(self.configuration.pmake) + self.handler.set_bbthreads(self.configuration.bbthread) + self.set_user_config_extra() + + def update_recipe_model(self, selected_image, selected_recipes): + self.recipe_model.set_selected_image(selected_image) + self.recipe_model.set_selected_recipes(selected_recipes) + + def update_package_model(self, selected_packages, user_selected_packages=None): + if user_selected_packages: + left = self.package_model.set_selected_packages(user_selected_packages, True) + self.configuration.user_selected_packages += left + left = self.package_model.set_selected_packages(selected_packages) + self.configuration.selected_packages += left + + def update_configuration_parameters(self, params): + if params: + self.configuration.update(params) + self.parameters.update(params) + + def set_base_image(self): + self.configuration.initial_selected_image = self.configuration.selected_image + if self.configuration.selected_image != self.recipe_model.__custom_image__: + self.hob_image = self.configuration.selected_image + "-edited" + + def reset(self): + self.configuration.curr_mach = "" + self.configuration.clear_selection() + self.image_configuration_page.switch_machine_combo() + self.switch_page(self.MACHINE_SELECTION) + + # Callback Functions + def handler_config_updated_cb(self, handler, which, values): + if which == "distro": + self.parameters.all_distros = values + elif which == "machine": + self.parameters.all_machines = values + self.image_configuration_page.update_machine_combo() + elif which == "machine-sdk": + self.parameters.all_sdk_machines = values + + def handler_package_formats_updated_cb(self, handler, formats): + self.parameters.all_package_formats = formats + + def switch_to_image_configuration_helper(self): + self.sanity_check_page.stop() + self.switch_page(self.IMAGE_CONFIGURATION) + self.image_configuration_page.switch_machine_combo() + + def show_network_error_dialog_helper(self): + self.sanity_check_page.stop() + self.show_network_error_dialog() + + def handler_command_succeeded_cb(self, handler, initcmd): + if initcmd == self.handler.GENERATE_CONFIGURATION: + if not self.configuration.curr_mach: + self.configuration.curr_mach = self.handler.runCommand(["getVariable", "HOB_MACHINE"]) or "" + self.update_configuration_parameters(self.get_parameters_sync()) + if not self.sanity_checked: + self.sanity_check() + self.sanity_checked = True + elif initcmd == self.handler.SANITY_CHECK: + if self.had_network_error: + self.had_network_error = False + self.execute_after_sanity_check(self.show_network_error_dialog_helper) + else: + # Switch to the 'image configuration' page now, but we might need + # to wait for the minimum display time of the sanity check page + self.execute_after_sanity_check(self.switch_to_image_configuration_helper) + elif initcmd in [self.handler.GENERATE_RECIPES, + self.handler.GENERATE_PACKAGES, + self.handler.GENERATE_IMAGE]: + self.update_configuration_parameters(self.get_parameters_sync()) + self.request_package_info_async() + elif initcmd == self.handler.POPULATE_PACKAGEINFO: + if self.current_step == self.RCPPKGINFO_POPULATING: + self.switch_page(self.RCPPKGINFO_POPULATED) + self.rcppkglist_populated() + return + + self.rcppkglist_populated() + if self.current_step == self.FAST_IMAGE_GENERATING: + self.generate_image_async(True) + + def show_error_dialog(self, msg): + lbl = "Hob found an error" + dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_ERROR, msg) + button = dialog.add_button("Close", gtk.RESPONSE_OK) + HobButton.style_button(button) + response = dialog.run() + dialog.destroy() + + def show_warning_dialog(self): + dialog = ParsingWarningsDialog(title = "View warnings", + warnings = self.parsing_warnings, + parent = None, + flags = gtk.DIALOG_DESTROY_WITH_PARENT + | gtk.DIALOG_NO_SEPARATOR) + response = dialog.run() + dialog.destroy() + + def show_network_error_dialog(self): + lbl = "Hob cannot connect to the network" + msg = msg + "Please check your network connection. If you are using a proxy server, please make sure it is configured correctly." + dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_ERROR, msg) + button = dialog.add_button("Close", gtk.RESPONSE_OK) + HobButton.style_button(button) + button = dialog.add_button("Proxy settings", gtk.RESPONSE_CANCEL) + HobButton.style_button(button) + res = dialog.run() + dialog.destroy() + if res == gtk.RESPONSE_CANCEL: + res, settings_changed = self.show_simple_settings_dialog(SimpleSettingsDialog.PROXIES_PAGE_ID) + if not res: + return + if settings_changed: + self.reparse_post_adv_settings() + + def handler_command_failed_cb(self, handler, msg): + if msg: + self.show_error_dialog(msg) + self.reset() + + def handler_parsing_warning_cb(self, handler, warn_msg): + self.parsing_warnings.append(warn_msg) + + def handler_sanity_failed_cb(self, handler, msg, network_error): + self.reset() + if network_error: + # Mark this in an internal field. The "network error" dialog will be + # shown later, when a SanityCheckPassed event will be handled + # (as sent by sanity.bbclass) + self.had_network_error = True + else: + msg = msg.replace("your local.conf", "Settings") + self.show_error_dialog(msg) + self.reset() + + def window_sensitive(self, sensitive): + self.image_configuration_page.machine_combo.set_sensitive(sensitive) + self.image_configuration_page.machine_combo.child.set_sensitive(sensitive) + self.image_configuration_page.image_combo.set_sensitive(sensitive) + self.image_configuration_page.image_combo.child.set_sensitive(sensitive) + self.image_configuration_page.layer_button.set_sensitive(sensitive) + self.image_configuration_page.layer_info_icon.set_sensitive(sensitive) + self.image_configuration_page.toolbar.set_sensitive(sensitive) + self.image_configuration_page.view_adv_configuration_button.set_sensitive(sensitive) + self.image_configuration_page.config_build_button.set_sensitive(sensitive) + + self.recipe_details_page.set_sensitive(sensitive) + self.package_details_page.set_sensitive(sensitive) + self.build_details_page.set_sensitive(sensitive) + self.image_details_page.set_sensitive(sensitive) + + if sensitive: + self.window.set_cursor(None) + else: + self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) + self.sensitive = sensitive + + + def handler_generating_data_cb(self, handler): + self.window_sensitive(False) + + def handler_data_generated_cb(self, handler): + self.window_sensitive(True) + + def rcppkglist_populated(self): + selected_image = self.configuration.selected_image + selected_recipes = self.configuration.selected_recipes[:] + selected_packages = self.configuration.selected_packages[:] + user_selected_packages = self.configuration.user_selected_packages[:] + + self.image_configuration_page.update_image_combo(self.recipe_model, selected_image) + self.image_configuration_page.update_image_desc() + self.update_recipe_model(selected_image, selected_recipes) + self.update_package_model(selected_packages, user_selected_packages) + + def recipelist_changed_cb(self, recipe_model): + self.recipe_details_page.refresh_selection() + + def packagelist_changed_cb(self, package_model): + self.package_details_page.refresh_selection() + + def handler_recipe_populated_cb(self, handler): + self.image_configuration_page.update_progress_bar("Populating recipes", 0.99) + + def handler_package_populated_cb(self, handler): + self.image_configuration_page.update_progress_bar("Populating packages", 1.0) + + def handler_parsing_started_cb(self, handler, message): + if self.current_step != self.RCPPKGINFO_POPULATING: + return + + fraction = 0 + if message["eventname"] == "TreeDataPreparationStarted": + fraction = 0.6 + fraction + self.image_configuration_page.stop_button.set_sensitive(False) + self.image_configuration_page.update_progress_bar("Generating dependency tree", fraction) + else: + self.image_configuration_page.stop_button.set_sensitive(True) + self.image_configuration_page.update_progress_bar(message["title"], fraction) + + def handler_parsing_cb(self, handler, message): + if self.current_step != self.RCPPKGINFO_POPULATING: + return + + fraction = message["current"] * 1.0/message["total"] + if message["eventname"] == "TreeDataPreparationProgress": + fraction = 0.6 + 0.38 * fraction + self.image_configuration_page.update_progress_bar("Generating dependency tree", fraction) + else: + fraction = 0.6 * fraction + self.image_configuration_page.update_progress_bar(message["title"], fraction) + + def handler_parsing_completed_cb(self, handler, message): + if self.current_step != self.RCPPKGINFO_POPULATING: + return + + if message["eventname"] == "TreeDataPreparationCompleted": + fraction = 0.98 + else: + fraction = 0.6 + self.image_configuration_page.update_progress_bar("Generating dependency tree", fraction) + + def handler_build_started_cb(self, running_build): + if self.current_step == self.FAST_IMAGE_GENERATING: + fraction = 0 + elif self.current_step == self.IMAGE_GENERATING: + if self.previous_step == self.FAST_IMAGE_GENERATING: + fraction = 0.9 + else: + fraction = 0 + elif self.current_step == self.PACKAGE_GENERATING: + fraction = 0 + self.build_details_page.update_progress_bar("Build Started: ", fraction) + self.build_details_page.show_configurations(self.configuration, self.parameters) + + def build_succeeded(self): + if self.current_step == self.FAST_IMAGE_GENERATING: + fraction = 0.9 + elif self.current_step == self.IMAGE_GENERATING: + fraction = 1.0 + version = "" + self.parameters.image_names = [] + selected_image = self.recipe_model.get_selected_image() + if selected_image == self.recipe_model.__custom_image__: + if self.configuration.initial_selected_image != selected_image: + version = self.recipe_model.get_custom_image_version() + linkname = self.hob_image + version + "-" + self.configuration.curr_mach + else: + linkname = selected_image + '-' + self.configuration.curr_mach + image_extension = self.get_image_extension() + for image_type in self.parameters.image_types: + if image_type in image_extension: + real_types = image_extension[image_type] + else: + real_types = [image_type] + for real_image_type in real_types: + linkpath = self.parameters.image_addr + '/' + linkname + '.' + real_image_type + if os.path.exists(linkpath): + self.parameters.image_names.append(os.readlink(linkpath)) + elif self.current_step == self.PACKAGE_GENERATING: + fraction = 1.0 + self.build_details_page.update_progress_bar("Build Completed: ", fraction) + self.handler.build_succeeded_async() + self.stopping = False + + if self.current_step == self.PACKAGE_GENERATING: + self.switch_page(self.PACKAGE_GENERATED) + elif self.current_step == self.IMAGE_GENERATING: + self.switch_page(self.IMAGE_GENERATED) + + def build_failed(self): + if self.stopping: + status = "stop" + message = "Build stopped: " + fraction = self.build_details_page.progress_bar.get_fraction() + stop_to_next_edit = "" + if self.current_step == self.FAST_IMAGE_GENERATING: + stop_to_next_edit = "image configuration" + elif self.current_step == self.IMAGE_GENERATING: + if self.previous_step == self.FAST_IMAGE_GENERATING: + stop_to_next_edit = "image configuration" + else: + stop_to_next_edit = "packages" + elif self.current_step == self.PACKAGE_GENERATING: + stop_to_next_edit = "recipes" + button = self.build_details_page.show_stop_page(stop_to_next_edit.split(' ')[0]) + self.set_default(button) + else: + fail_to_next_edit = "" + if self.current_step == self.FAST_IMAGE_GENERATING: + fail_to_next_edit = "image configuration" + fraction = 0.9 + elif self.current_step == self.IMAGE_GENERATING: + if self.previous_step == self.FAST_IMAGE_GENERATING: + fail_to_next_edit = "image configuration" + else: + fail_to_next_edit = "packages" + fraction = 1.0 + elif self.current_step == self.PACKAGE_GENERATING: + fail_to_next_edit = "recipes" + fraction = 1.0 + self.build_details_page.show_fail_page(fail_to_next_edit.split(' ')[0]) + status = "fail" + message = "Build failed: " + self.build_details_page.update_progress_bar(message, fraction, status) + self.build_details_page.show_back_button() + self.build_details_page.hide_stop_button() + self.handler.build_failed_async() + self.stopping = False + + def handler_build_succeeded_cb(self, running_build): + if not self.stopping: + self.build_succeeded() + else: + self.build_failed() + + + def handler_build_failed_cb(self, running_build): + self.build_failed() + + def handler_build_aborted_cb(self, running_build): + self.build_failed() + + def handler_no_provider_cb(self, running_build, msg): + dialog = CrumbsMessageDialog(self, glib.markup_escape_text(msg), gtk.MESSAGE_INFO) + button = dialog.add_button("Close", gtk.RESPONSE_OK) + HobButton.style_button(button) + dialog.run() + dialog.destroy() + self.build_failed() + + def handler_task_started_cb(self, running_build, message): + fraction = message["current"] * 1.0/message["total"] + title = "Build packages" + if self.current_step == self.FAST_IMAGE_GENERATING: + if message["eventname"] == "sceneQueueTaskStarted": + fraction = 0.27 * fraction + elif message["eventname"] == "runQueueTaskStarted": + fraction = 0.27 + 0.63 * fraction + elif self.current_step == self.IMAGE_GENERATING: + title = "Build image" + if self.previous_step == self.FAST_IMAGE_GENERATING: + if message["eventname"] == "sceneQueueTaskStarted": + fraction = 0.27 + 0.63 + 0.03 * fraction + elif message["eventname"] == "runQueueTaskStarted": + fraction = 0.27 + 0.63 + 0.03 + 0.07 * fraction + else: + if message["eventname"] == "sceneQueueTaskStarted": + fraction = 0.2 * fraction + elif message["eventname"] == "runQueueTaskStarted": + fraction = 0.2 + 0.8 * fraction + elif self.current_step == self.PACKAGE_GENERATING: + if message["eventname"] == "sceneQueueTaskStarted": + fraction = 0.2 * fraction + elif message["eventname"] == "runQueueTaskStarted": + fraction = 0.2 + 0.8 * fraction + self.build_details_page.update_progress_bar(title + ": ", fraction) + self.build_details_page.update_build_status(message["current"], message["total"], message["task"]) + + def handler_disk_full_cb(self, running_build): + self.disk_full = True + + def handler_build_failure_cb(self, running_build): + self.build_details_page.show_issues() + + def handler_build_log_cb(self, running_build, func, obj): + if hasattr(self.logger, func): + getattr(self.logger, func)(obj) + + def destroy_window_cb(self, widget, event): + if not self.sensitive: + return True + elif self.handler.building: + self.stop_build() + return True + else: + gtk.main_quit() + + def event_handle_SIGINT(self, signal, frame): + for w in gtk.window_list_toplevels(): + if w.get_modal(): + w.response(gtk.RESPONSE_DELETE_EVENT) + sys.exit(0) + + def build_packages(self): + _, all_recipes = self.recipe_model.get_selected_recipes() + if not all_recipes: + lbl = "No selections made" + msg = "You have not made any selections" + msg = msg + " so there isn't anything to bake at this time." + dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_INFO, msg) + button = dialog.add_button("Close", gtk.RESPONSE_OK) + HobButton.style_button(button) + dialog.run() + dialog.destroy() + return + self.generate_packages_async(True) + + def build_image(self): + selected_packages = self.package_model.get_selected_packages() + if not selected_packages: + lbl = "No selections made" + msg = "You have not made any selections" + msg = msg + " so there isn't anything to bake at this time." + dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_INFO, msg) + button = dialog.add_button("Close", gtk.RESPONSE_OK) + HobButton.style_button(button) + dialog.run() + dialog.destroy() + return + self.generate_image_async(True) + + def just_bake(self): + selected_image = self.recipe_model.get_selected_image() + selected_packages = self.package_model.get_selected_packages() or [] + + # If no base image and no selected packages don't build anything + if not (selected_packages or selected_image != self.recipe_model.__custom_image__): + lbl = "No selections made" + msg = "You have not made any selections" + msg = msg + " so there isn't anything to bake at this time." + dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_INFO, msg) + button = dialog.add_button("Close", gtk.RESPONSE_OK) + HobButton.style_button(button) + dialog.run() + dialog.destroy() + return + + self.fast_generate_image_async(True) + + def show_recipe_property_dialog(self, properties): + information = {} + dialog = PropertyDialog(title = properties["name"] +' '+ "properties", + parent = self, + information = properties, + flags = gtk.DIALOG_DESTROY_WITH_PARENT + | gtk.DIALOG_NO_SEPARATOR) + + dialog.set_modal(False) + + button = dialog.add_button("Close", gtk.RESPONSE_NO) + HobAltButton.style_button(button) + button.connect("clicked", lambda w: dialog.destroy()) + + dialog.run() + + def show_packages_property_dialog(self, properties): + information = {} + dialog = PropertyDialog(title = properties["name"] +' '+ "properties", + parent = self, + information = properties, + flags = gtk.DIALOG_DESTROY_WITH_PARENT + | gtk.DIALOG_NO_SEPARATOR) + + dialog.set_modal(False) + + button = dialog.add_button("Close", gtk.RESPONSE_NO) + HobAltButton.style_button(button) + button.connect("clicked", lambda w: dialog.destroy()) + + dialog.run() + + def show_layer_selection_dialog(self): + dialog = LayerSelectionDialog(title = "Layers", + layers = copy.deepcopy(self.configuration.layers), + layers_non_removable = copy.deepcopy(self.configuration.layers_non_removable), + all_layers = self.parameters.all_layers, + parent = self, + flags = gtk.DIALOG_MODAL + | gtk.DIALOG_DESTROY_WITH_PARENT + | gtk.DIALOG_NO_SEPARATOR) + button = dialog.add_button("Cancel", gtk.RESPONSE_NO) + HobAltButton.style_button(button) + button = dialog.add_button("OK", gtk.RESPONSE_YES) + HobButton.style_button(button) + response = dialog.run() + if response == gtk.RESPONSE_YES: + self.configuration.layers = dialog.layers + # DO refresh layers + if dialog.layers_changed: + self.update_config_async() + dialog.destroy() + + def get_image_extension(self): + image_extension = {} + for type in self.parameters.image_types: + ext = self.handler.runCommand(["getVariable", "IMAGE_EXTENSION_%s" % type]) + if ext: + image_extension[type] = ext.split(' ') + + return image_extension + + def show_load_my_images_dialog(self): + image_extension = self.get_image_extension() + dialog = ImageSelectionDialog(self.parameters.image_addr, self.parameters.image_types, + "Open My Images", self, + gtk.FILE_CHOOSER_ACTION_SAVE, None, + image_extension) + button = dialog.add_button("Cancel", gtk.RESPONSE_NO) + HobAltButton.style_button(button) + button = dialog.add_button("Open", gtk.RESPONSE_YES) + HobButton.style_button(button) + response = dialog.run() + if response == gtk.RESPONSE_YES: + if not dialog.image_names: + lbl = "No selections made" + msg = "You have not made any selections" + crumbs_dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_INFO, msg) + button = crumbs_dialog.add_button("Close", gtk.RESPONSE_OK) + HobButton.style_button(button) + crumbs_dialog.run() + crumbs_dialog.destroy() + dialog.destroy() + return + + self.parameters.image_addr = dialog.image_folder + self.parameters.image_names = dialog.image_names[:] + self.switch_page(self.MY_IMAGE_OPENED) + + dialog.destroy() + + def show_adv_settings_dialog(self, tab=None): + dialog = AdvancedSettingsDialog(title = "Advanced configuration", + configuration = copy.deepcopy(self.configuration), + all_image_types = self.parameters.image_types, + all_package_formats = self.parameters.all_package_formats, + all_distros = self.parameters.all_distros, + all_sdk_machines = self.parameters.all_sdk_machines, + max_threads = self.parameters.max_threads, + parent = self, + flags = gtk.DIALOG_MODAL + | gtk.DIALOG_DESTROY_WITH_PARENT + | gtk.DIALOG_NO_SEPARATOR) + button = dialog.add_button("Cancel", gtk.RESPONSE_NO) + HobAltButton.style_button(button) + button = dialog.add_button("Save", gtk.RESPONSE_YES) + HobButton.style_button(button) + dialog.set_save_button(button) + response = dialog.run() + settings_changed = False + if response == gtk.RESPONSE_YES: + self.configuration = dialog.configuration + self.configuration.save(self.handler, True) # remember settings + settings_changed = dialog.settings_changed + dialog.destroy() + return response == gtk.RESPONSE_YES, settings_changed + + def show_simple_settings_dialog(self, tab=None): + dialog = SimpleSettingsDialog(title = "Settings", + configuration = copy.deepcopy(self.configuration), + all_image_types = self.parameters.image_types, + all_package_formats = self.parameters.all_package_formats, + all_distros = self.parameters.all_distros, + all_sdk_machines = self.parameters.all_sdk_machines, + max_threads = self.parameters.max_threads, + parent = self, + flags = gtk.DIALOG_MODAL + | gtk.DIALOG_DESTROY_WITH_PARENT + | gtk.DIALOG_NO_SEPARATOR, + handler = self.handler) + button = dialog.add_button("Cancel", gtk.RESPONSE_NO) + HobAltButton.style_button(button) + button = dialog.add_button("Save", gtk.RESPONSE_YES) + HobButton.style_button(button) + if tab: + dialog.switch_to_page(tab) + response = dialog.run() + settings_changed = False + if response == gtk.RESPONSE_YES: + self.configuration = dialog.configuration + self.configuration.save(self.handler, True) # remember settings + settings_changed = dialog.settings_changed + if dialog.proxy_settings_changed: + self.set_user_config_proxies() + elif dialog.proxy_test_ran: + # The user might have modified the proxies in the "Proxy" + # tab, which in turn made the proxy settings modify in bb. + # If "Cancel" was pressed, restore the previous proxy + # settings inside bb. + self.set_user_config_proxies() + dialog.destroy() + return response == gtk.RESPONSE_YES, settings_changed + + def reparse_post_adv_settings(self): + if not self.configuration.curr_mach: + self.update_config_async() + else: + self.configuration.clear_selection() + # DO reparse recipes + self.populate_recipe_package_info_async() + + def deploy_image(self, image_name): + if not image_name: + lbl = "Please select an image to deploy." + dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_INFO) + button = dialog.add_button("Close", gtk.RESPONSE_OK) + HobButton.style_button(button) + dialog.run() + dialog.destroy() + return + + image_path = os.path.join(self.parameters.image_addr, image_name) + dialog = DeployImageDialog(title = "Usb Image Maker", + image_path = image_path, + parent = self, + flags = gtk.DIALOG_MODAL + | gtk.DIALOG_DESTROY_WITH_PARENT + | gtk.DIALOG_NO_SEPARATOR) + button = dialog.add_button("Close", gtk.RESPONSE_NO) + HobAltButton.style_button(button) + button = dialog.add_button("Make usb image", gtk.RESPONSE_YES) + HobButton.style_button(button) + response = dialog.run() + dialog.destroy() + + def show_load_kernel_dialog(self): + dialog = gtk.FileChooserDialog("Load Kernel Files", self, + gtk.FILE_CHOOSER_ACTION_SAVE) + button = dialog.add_button("Cancel", gtk.RESPONSE_NO) + HobAltButton.style_button(button) + button = dialog.add_button("Open", gtk.RESPONSE_YES) + HobButton.style_button(button) + filter = gtk.FileFilter() + filter.set_name("Kernel Files") + filter.add_pattern("*.bin") + dialog.add_filter(filter) + + dialog.set_current_folder(self.parameters.image_addr) + + response = dialog.run() + kernel_path = "" + if response == gtk.RESPONSE_YES: + kernel_path = dialog.get_filename() + + dialog.destroy() + + return kernel_path + + def runqemu_image(self, image_name, kernel_name): + if not image_name or not kernel_name: + lbl = "Please select %s to launch in QEMU." % ("a kernel" if image_name else "an image") + dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_INFO) + button = dialog.add_button("Close", gtk.RESPONSE_OK) + HobButton.style_button(button) + dialog.run() + dialog.destroy() + return + + kernel_path = os.path.join(self.parameters.image_addr, kernel_name) + image_path = os.path.join(self.parameters.image_addr, image_name) + + source_env_path = os.path.join(self.parameters.core_base, "oe-init-build-env") + tmp_path = self.parameters.tmpdir + cmdline = bb.ui.crumbs.utils.which_terminal() + if os.path.exists(image_path) and os.path.exists(kernel_path) \ + and os.path.exists(source_env_path) and os.path.exists(tmp_path) \ + and cmdline: + cmdline += "\' bash -c \"export OE_TMPDIR=" + tmp_path + "; " + cmdline += "source " + source_env_path + " " + os.getcwd() + "; " + cmdline += "runqemu " + kernel_path + " " + image_path + "\"\'" + subprocess.Popen(shlex.split(cmdline)) + else: + lbl = "Path error" + msg = "One of your paths is wrong," + msg = msg + " please make sure the following paths exist:\n" + msg = msg + "image path:" + image_path + "\n" + msg = msg + "kernel path:" + kernel_path + "\n" + msg = msg + "source environment path:" + source_env_path + "\n" + msg = msg + "tmp path: " + tmp_path + "." + msg = msg + "You may be missing either xterm or vte for terminal services." + dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_ERROR, msg) + button = dialog.add_button("Close", gtk.RESPONSE_OK) + HobButton.style_button(button) + dialog.run() + dialog.destroy() + + def show_packages(self): + self.package_details_page.refresh_tables() + self.switch_page(self.PACKAGE_SELECTION) + + def show_recipes(self): + self.switch_page(self.RECIPE_SELECTION) + + def show_image_details(self): + self.switch_page(self.IMAGE_GENERATED) + + def show_configuration(self): + self.switch_page(self.BASEIMG_SELECTED) + + def stop_build(self): + if self.stopping: + lbl = "Force Stop build?" + msg = "You've already selected Stop once," + msg = msg + " would you like to 'Force Stop' the build?\n\n" + msg = msg + "This will stop the build as quickly as possible but may" + msg = msg + " well leave your build directory in an unusable state" + msg = msg + " that requires manual steps to fix." + dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_WARNING, msg) + button = dialog.add_button("Cancel", gtk.RESPONSE_CANCEL) + HobAltButton.style_button(button) + button = dialog.add_button("Force Stop", gtk.RESPONSE_YES) + HobButton.style_button(button) + else: + lbl = "Stop build?" + msg = "Are you sure you want to stop this" + msg = msg + " build?\n\n'Stop' will stop the build as soon as all in" + msg = msg + " progress build tasks are finished. However if a" + msg = msg + " lengthy compilation phase is in progress this may take" + msg = msg + " some time.\n\n" + msg = msg + "'Force Stop' will stop the build as quickly as" + msg = msg + " possible but may well leave your build directory in an" + msg = msg + " unusable state that requires manual steps to fix." + dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_WARNING, msg) + button = dialog.add_button("Cancel", gtk.RESPONSE_CANCEL) + HobAltButton.style_button(button) + button = dialog.add_button("Force stop", gtk.RESPONSE_YES) + HobAltButton.style_button(button) + button = dialog.add_button("Stop", gtk.RESPONSE_OK) + HobButton.style_button(button) + response = dialog.run() + dialog.destroy() + if response != gtk.RESPONSE_CANCEL: + self.stopping = True + if response == gtk.RESPONSE_OK: + self.build_details_page.progress_bar.set_stop_title("Stopping the build....") + self.build_details_page.progress_bar.set_rcstyle("stop") + self.cancel_build_sync() + elif response == gtk.RESPONSE_YES: + self.cancel_build_sync(True) + + def do_log(self, consolelogfile = None): + if consolelogfile: + bb.utils.mkdirhier(os.path.dirname(consolelogfile)) + if self.consolelog: + self.logger.removeHandler(self.consolelog) + self.consolelog = None + self.consolelog = logging.FileHandler(consolelogfile) + bb.msg.addDefaultlogFilter(self.consolelog) + format = bb.msg.BBLogFormatter("%(levelname)s: %(message)s") + self.consolelog.setFormatter(format) + + self.logger.addHandler(self.consolelog) + + def get_topdir(self): + return self.handler.get_topdir() + + def wait(self, delay): + time_start = time.time() + time_end = time_start + delay + while time_end > time.time(): + while gtk.events_pending(): + 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 @@ +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2008 Intel Corporation +# +# Authored by Rob Bradford +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import gtk +import gobject +import threading +import os +import datetime +import time + +class BuildConfiguration: + """ Represents a potential *or* historic *or* concrete build. It + encompasses all the things that we need to tell bitbake to do to make it + build what we want it to build. + + It also stored the metadata URL and the set of possible machines (and the + distros / images / uris for these. Apart from the metdata URL these are + not serialised to file (since they may be transient). In some ways this + functionality might be shifted to the loader class.""" + + def __init__ (self): + self.metadata_url = None + + # Tuple of (distros, image, urls) + self.machine_options = {} + + self.machine = None + self.distro = None + self.image = None + self.urls = [] + self.extra_urls = [] + self.extra_pkgs = [] + + def get_machines_model (self): + model = gtk.ListStore (gobject.TYPE_STRING) + for machine in self.machine_options.keys(): + model.append ([machine]) + + return model + + def get_distro_and_images_models (self, machine): + distro_model = gtk.ListStore (gobject.TYPE_STRING) + + for distro in self.machine_options[machine][0]: + distro_model.append ([distro]) + + image_model = gtk.ListStore (gobject.TYPE_STRING) + + for image in self.machine_options[machine][1]: + image_model.append ([image]) + + return (distro_model, image_model) + + def get_repos (self): + self.urls = self.machine_options[self.machine][2] + return self.urls + + # It might be a lot lot better if we stored these in like, bitbake conf + # file format. + @staticmethod + def load_from_file (filename): + + conf = BuildConfiguration() + with open(filename, "r") as f: + for line in f: + data = line.split (";")[1] + if (line.startswith ("metadata-url;")): + conf.metadata_url = data.strip() + continue + if (line.startswith ("url;")): + conf.urls += [data.strip()] + continue + if (line.startswith ("extra-url;")): + conf.extra_urls += [data.strip()] + continue + if (line.startswith ("machine;")): + conf.machine = data.strip() + continue + if (line.startswith ("distribution;")): + conf.distro = data.strip() + continue + if (line.startswith ("image;")): + conf.image = data.strip() + continue + + return conf + + # Serialise to a file. This is part of the build process and we use this + # to be able to repeat a given build (using the same set of parameters) + # but also so that we can include the details of the image / machine / + # distro in the build manager tree view. + def write_to_file (self, filename): + f = open (filename, "w") + + lines = [] + + if (self.metadata_url): + lines += ["metadata-url;%s\n" % (self.metadata_url)] + + for url in self.urls: + lines += ["url;%s\n" % (url)] + + for url in self.extra_urls: + lines += ["extra-url;%s\n" % (url)] + + if (self.machine): + lines += ["machine;%s\n" % (self.machine)] + + if (self.distro): + lines += ["distribution;%s\n" % (self.distro)] + + if (self.image): + lines += ["image;%s\n" % (self.image)] + + f.writelines (lines) + f.close () + +class BuildResult(gobject.GObject): + """ Represents an historic build. Perhaps not successful. But it includes + things such as the files that are in the directory (the output from the + build) as well as a deserialised BuildConfiguration file that is stored in + ".conf" in the directory for the build. + + This is GObject so that it can be included in the TreeStore.""" + + (STATE_COMPLETE, STATE_FAILED, STATE_ONGOING) = \ + (0, 1, 2) + + def __init__ (self, parent, identifier): + gobject.GObject.__init__ (self) + self.date = None + + self.files = [] + self.status = None + self.identifier = identifier + self.path = os.path.join (parent, identifier) + + # Extract the date, since the directory name is of the + # format build-- we can easily + # pull it out. + # TODO: Better to stat a file? + (_, date, revision) = identifier.split ("-") + print(date) + + year = int (date[0:4]) + month = int (date[4:6]) + day = int (date[6:8]) + + self.date = datetime.date (year, month, day) + + self.conf = None + + # By default builds are STATE_FAILED unless we find a "complete" file + # in which case they are STATE_COMPLETE + self.state = BuildResult.STATE_FAILED + for file in os.listdir (self.path): + if (file.startswith (".conf")): + conffile = os.path.join (self.path, file) + self.conf = BuildConfiguration.load_from_file (conffile) + elif (file.startswith ("complete")): + self.state = BuildResult.STATE_COMPLETE + else: + self.add_file (file) + + def add_file (self, file): + # Just add the file for now. Don't care about the type. + self.files += [(file, None)] + +class BuildManagerModel (gtk.TreeStore): + """ Model for the BuildManagerTreeView. This derives from gtk.TreeStore + but it abstracts nicely what the columns mean and the setup of the columns + in the model. """ + + (COL_IDENT, COL_DESC, COL_MACHINE, COL_DISTRO, COL_BUILD_RESULT, COL_DATE, COL_STATE) = \ + (0, 1, 2, 3, 4, 5, 6) + + def __init__ (self): + gtk.TreeStore.__init__ (self, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_OBJECT, + gobject.TYPE_INT64, + gobject.TYPE_INT) + +class BuildManager (gobject.GObject): + """ This class manages the historic builds that have been found in the + "results" directory but is also used for starting a new build.""" + + __gsignals__ = { + 'population-finished' : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + ()), + 'populate-error' : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + ()) + } + + def update_build_result (self, result, iter): + # Convert the date into something we can sort by. + date = long (time.mktime (result.date.timetuple())) + + # Add a top level entry for the build + + self.model.set (iter, + BuildManagerModel.COL_IDENT, result.identifier, + BuildManagerModel.COL_DESC, result.conf.image, + BuildManagerModel.COL_MACHINE, result.conf.machine, + BuildManagerModel.COL_DISTRO, result.conf.distro, + BuildManagerModel.COL_BUILD_RESULT, result, + BuildManagerModel.COL_DATE, date, + BuildManagerModel.COL_STATE, result.state) + + # And then we use the files in the directory as the children for the + # top level iter. + for file in result.files: + self.model.append (iter, (None, file[0], None, None, None, date, -1)) + + # This function is called as an idle by the BuildManagerPopulaterThread + def add_build_result (self, result): + gtk.gdk.threads_enter() + self.known_builds += [result] + + self.update_build_result (result, self.model.append (None)) + + gtk.gdk.threads_leave() + + def notify_build_finished (self): + # This is a bit of a hack. If we have a running build running then we + # will have a row in the model in STATE_ONGOING. Find it and make it + # as if it was a proper historic build (well, it is completed now....) + + # We need to use the iters here rather than the Python iterator + # interface to the model since we need to pass it into + # update_build_result + + iter = self.model.get_iter_first() + + while (iter): + (ident, state) = self.model.get(iter, + BuildManagerModel.COL_IDENT, + BuildManagerModel.COL_STATE) + + if state == BuildResult.STATE_ONGOING: + result = BuildResult (self.results_directory, ident) + self.update_build_result (result, iter) + iter = self.model.iter_next(iter) + + def notify_build_succeeded (self): + # Write the "complete" file so that when we create the BuildResult + # object we put into the model + + complete_file_path = os.path.join (self.cur_build_directory, "complete") + f = file (complete_file_path, "w") + f.close() + self.notify_build_finished() + + def notify_build_failed (self): + # Without a "complete" file then this will mark the build as failed: + self.notify_build_finished() + + # This function is called as an idle + def emit_population_finished_signal (self): + gtk.gdk.threads_enter() + self.emit ("population-finished") + gtk.gdk.threads_leave() + + class BuildManagerPopulaterThread (threading.Thread): + def __init__ (self, manager, directory): + threading.Thread.__init__ (self) + self.manager = manager + self.directory = directory + + def run (self): + # For each of the "build-<...>" directories .. + + if os.path.exists (self.directory): + for directory in os.listdir (self.directory): + + if not directory.startswith ("build-"): + continue + + build_result = BuildResult (self.directory, directory) + self.manager.add_build_result (build_result) + + gobject.idle_add (BuildManager.emit_population_finished_signal, + self.manager) + + def __init__ (self, server, results_directory): + gobject.GObject.__init__ (self) + + # The builds that we've found from walking the result directory + self.known_builds = [] + + # Save out the bitbake server, we need this for issuing commands to + # the cooker: + self.server = server + + # The TreeStore that we use + self.model = BuildManagerModel () + + # The results directory is where we create (and look for) the + # build-- directories. We need to populate ourselves from + # directory + self.results_directory = results_directory + self.populate_from_directory (self.results_directory) + + def populate_from_directory (self, directory): + thread = BuildManager.BuildManagerPopulaterThread (self, directory) + thread.start() + + # Come up with the name for the next build ident by combining "build-" + # with the date formatted as yyyymmdd and then an ordinal. We do this by + # an optimistic algorithm incrementing the ordinal if we find that it + # already exists. + def get_next_build_ident (self): + today = datetime.date.today () + datestr = str (today.year) + str (today.month) + str (today.day) + + revision = 0 + test_name = "build-%s-%d" % (datestr, revision) + test_path = os.path.join (self.results_directory, test_name) + + while (os.path.exists (test_path)): + revision += 1 + test_name = "build-%s-%d" % (datestr, revision) + test_path = os.path.join (self.results_directory, test_name) + + return test_name + + # Take a BuildConfiguration and then try and build it based on the + # parameters of that configuration. S + def do_build (self, conf): + server = self.server + + # Work out the build directory. Note we actually create the + # directories here since we need to write the ".conf" file. Otherwise + # we could have relied on bitbake's builder thread to actually make + # the directories as it proceeds with the build. + ident = self.get_next_build_ident () + build_directory = os.path.join (self.results_directory, + ident) + self.cur_build_directory = build_directory + os.makedirs (build_directory) + + conffile = os.path.join (build_directory, ".conf") + conf.write_to_file (conffile) + + # Add a row to the model representing this ongoing build. It's kinda a + # fake entry. If this build completes or fails then this gets updated + # with the real stuff like the historic builds + date = long (time.time()) + self.model.append (None, (ident, conf.image, conf.machine, conf.distro, + None, date, BuildResult.STATE_ONGOING)) + try: + server.runCommand(["setVariable", "BUILD_IMAGES_FROM_FEEDS", 1]) + server.runCommand(["setVariable", "MACHINE", conf.machine]) + server.runCommand(["setVariable", "DISTRO", conf.distro]) + server.runCommand(["setVariable", "PACKAGE_CLASSES", "package_ipk"]) + server.runCommand(["setVariable", "BBFILES", \ + """${OEROOT}/meta/packages/*/*.bb ${OEROOT}/meta-moblin/packages/*/*.bb"""]) + server.runCommand(["setVariable", "TMPDIR", "${OEROOT}/build/tmp"]) + server.runCommand(["setVariable", "IPK_FEED_URIS", \ + " ".join(conf.get_repos())]) + server.runCommand(["setVariable", "DEPLOY_DIR_IMAGE", + build_directory]) + server.runCommand(["buildTargets", [conf.image], "rootfs"]) + + except Exception as e: + print(e) + +class BuildManagerTreeView (gtk.TreeView): + """ The tree view for the build manager. This shows the historic builds + and so forth. """ + + # We use this function to control what goes in the cell since we store + # the date in the model as seconds since the epoch (for sorting) and so we + # need to make it human readable. + def date_format_custom_cell_data_func (self, col, cell, model, iter): + date = model.get (iter, BuildManagerModel.COL_DATE)[0] + datestr = time.strftime("%A %d %B %Y", time.localtime(date)) + cell.set_property ("text", datestr) + + # This format function controls what goes in the cell. We use this to map + # the integer state to a string and also to colourise the text + def state_format_custom_cell_data_fun (self, col, cell, model, iter): + state = model.get (iter, BuildManagerModel.COL_STATE)[0] + + if (state == BuildResult.STATE_ONGOING): + cell.set_property ("text", "Active") + cell.set_property ("foreground", "#000000") + elif (state == BuildResult.STATE_FAILED): + cell.set_property ("text", "Failed") + cell.set_property ("foreground", "#ff0000") + elif (state == BuildResult.STATE_COMPLETE): + cell.set_property ("text", "Complete") + cell.set_property ("foreground", "#00ff00") + else: + cell.set_property ("text", "") + + def __init__ (self): + gtk.TreeView.__init__(self) + + # Misc descriptiony thing + renderer = gtk.CellRendererText () + col = gtk.TreeViewColumn (None, renderer, + text=BuildManagerModel.COL_DESC) + self.append_column (col) + + # Machine + renderer = gtk.CellRendererText () + col = gtk.TreeViewColumn ("Machine", renderer, + text=BuildManagerModel.COL_MACHINE) + self.append_column (col) + + # distro + renderer = gtk.CellRendererText () + col = gtk.TreeViewColumn ("Distribution", renderer, + text=BuildManagerModel.COL_DISTRO) + self.append_column (col) + + # date (using a custom function for formatting the cell contents it + # takes epoch -> human readable string) + renderer = gtk.CellRendererText () + col = gtk.TreeViewColumn ("Date", renderer, + text=BuildManagerModel.COL_DATE) + self.append_column (col) + col.set_cell_data_func (renderer, + self.date_format_custom_cell_data_func) + + # For status. + renderer = gtk.CellRendererText () + col = gtk.TreeViewColumn ("Status", renderer, + text = BuildManagerModel.COL_STATE) + self.append_column (col) + col.set_cell_data_func (renderer, + 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 diff --git a/bitbake/lib/bb/ui/crumbs/hig/advancedsettingsdialog.py b/bitbake/lib/bb/ui/crumbs/hig/advancedsettingsdialog.py new file mode 100644 index 0000000000..e0b3553c2f --- /dev/null +++ b/bitbake/lib/bb/ui/crumbs/hig/advancedsettingsdialog.py @@ -0,0 +1,341 @@ +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2011-2012 Intel Corporation +# +# Authored by Joshua Lock +# Authored by Dongxiao Xu +# Authored by Shane Wang +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import gtk +import hashlib +from bb.ui.crumbs.hobwidget import HobInfoButton, HobButton +from bb.ui.crumbs.progressbar import HobProgressBar +from bb.ui.crumbs.hig.settingsuihelper import SettingsUIHelper +from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog +from bb.ui.crumbs.hig.crumbsmessagedialog import CrumbsMessageDialog +from bb.ui.crumbs.hig.proxydetailsdialog import ProxyDetailsDialog + +""" +The following are convenience classes for implementing GNOME HIG compliant +BitBake GUI's +In summary: spacing = 12px, border-width = 6px +""" + +class AdvancedSettingsDialog (CrumbsDialog, SettingsUIHelper): + + def details_cb(self, button, parent, protocol): + dialog = ProxyDetailsDialog(title = protocol.upper() + " Proxy Details", + user = self.configuration.proxies[protocol][1], + passwd = self.configuration.proxies[protocol][2], + parent = parent, + flags = gtk.DIALOG_MODAL + | gtk.DIALOG_DESTROY_WITH_PARENT + | gtk.DIALOG_NO_SEPARATOR) + dialog.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_OK) + response = dialog.run() + if response == gtk.RESPONSE_OK: + self.configuration.proxies[protocol][1] = dialog.user + self.configuration.proxies[protocol][2] = dialog.passwd + self.refresh_proxy_components() + dialog.destroy() + + def set_save_button(self, button): + self.save_button = button + + def rootfs_combo_changed_cb(self, rootfs_combo, all_package_format, check_hbox): + combo_item = self.rootfs_combo.get_active_text() + modified = False + for child in check_hbox.get_children(): + if isinstance(child, gtk.CheckButton): + check_hbox.remove(child) + modified = True + for format in all_package_format: + if format != combo_item: + check_button = gtk.CheckButton(format) + check_hbox.pack_start(check_button, expand=False, fill=False) + modified = True + if modified: + check_hbox.remove(self.pkgfmt_info) + check_hbox.pack_start(self.pkgfmt_info, expand=False, fill=False) + check_hbox.show_all() + + def gen_pkgfmt_widget(self, curr_package_format, all_package_format, tooltip_combo="", tooltip_extra=""): + pkgfmt_vbox = gtk.VBox(False, 6) + + label = self.gen_label_widget("Root file system package format") + pkgfmt_vbox.pack_start(label, expand=False, fill=False) + + rootfs_format = "" + if curr_package_format: + rootfs_format = curr_package_format.split()[0] + + rootfs_format_widget, rootfs_combo = self.gen_combo_widget(rootfs_format, all_package_format, tooltip_combo) + pkgfmt_vbox.pack_start(rootfs_format_widget, expand=False, fill=False) + + label = self.gen_label_widget("Additional package formats") + pkgfmt_vbox.pack_start(label, expand=False, fill=False) + + check_hbox = gtk.HBox(False, 12) + pkgfmt_vbox.pack_start(check_hbox, expand=False, fill=False) + for format in all_package_format: + if format != rootfs_format: + check_button = gtk.CheckButton(format) + is_active = (format in curr_package_format.split()) + check_button.set_active(is_active) + check_hbox.pack_start(check_button, expand=False, fill=False) + + self.pkgfmt_info = HobInfoButton(tooltip_extra, self) + check_hbox.pack_start(self.pkgfmt_info, expand=False, fill=False) + + rootfs_combo.connect("changed", self.rootfs_combo_changed_cb, all_package_format, check_hbox) + + pkgfmt_vbox.show_all() + + return pkgfmt_vbox, rootfs_combo, check_hbox + + def __init__(self, title, configuration, all_image_types, + all_package_formats, all_distros, all_sdk_machines, + max_threads, parent, flags, buttons=None): + super(AdvancedSettingsDialog, self).__init__(title, parent, flags, buttons) + + # class members from other objects + # bitbake settings from Builder.Configuration + self.configuration = configuration + self.image_types = all_image_types + self.all_package_formats = all_package_formats + self.all_distros = all_distros[:] + self.all_sdk_machines = all_sdk_machines + self.max_threads = max_threads + + # class members for internal use + self.distro_combo = None + self.dldir_text = None + self.sstatedir_text = None + self.sstatemirror_text = None + self.bb_spinner = None + self.pmake_spinner = None + self.rootfs_size_spinner = None + self.extra_size_spinner = None + self.gplv3_checkbox = None + self.sdk_checkbox = None + self.image_types_checkbuttons = {} + + self.md5 = self.config_md5() + self.settings_changed = False + + # create visual elements on the dialog + self.save_button = None + self.create_visual_elements() + self.connect("response", self.response_cb) + + def _get_sorted_value(self, var): + return " ".join(sorted(str(var).split())) + "\n" + + def config_md5(self): + data = "" + data += ("PACKAGE_CLASSES: " + self.configuration.curr_package_format + '\n') + data += ("DISTRO: " + self._get_sorted_value(self.configuration.curr_distro)) + data += ("IMAGE_ROOTFS_SIZE: " + self._get_sorted_value(self.configuration.image_rootfs_size)) + data += ("IMAGE_EXTRA_SIZE: " + self._get_sorted_value(self.configuration.image_extra_size)) + data += ("INCOMPATIBLE_LICENSE: " + self._get_sorted_value(self.configuration.incompat_license)) + data += ("SDK_MACHINE: " + self._get_sorted_value(self.configuration.curr_sdk_machine)) + data += ("TOOLCHAIN_BUILD: " + self._get_sorted_value(self.configuration.toolchain_build)) + data += ("IMAGE_FSTYPES: " + self._get_sorted_value(self.configuration.image_fstypes)) + return hashlib.md5(data).hexdigest() + + def create_visual_elements(self): + self.nb = gtk.Notebook() + self.nb.set_show_tabs(True) + self.nb.append_page(self.create_image_types_page(), gtk.Label("Image types")) + self.nb.append_page(self.create_output_page(), gtk.Label("Output")) + self.nb.set_current_page(0) + self.vbox.pack_start(self.nb, expand=True, fill=True) + self.vbox.pack_end(gtk.HSeparator(), expand=True, fill=True) + + self.show_all() + + def get_num_checked_image_types(self): + total = 0 + for b in self.image_types_checkbuttons.values(): + if b.get_active(): + total = total + 1 + return total + + def set_save_button_state(self): + if self.save_button: + self.save_button.set_sensitive(self.get_num_checked_image_types() > 0) + + def image_type_checkbutton_clicked_cb(self, button): + self.set_save_button_state() + if self.get_num_checked_image_types() == 0: + # Show an error dialog + lbl = "Select an image type" + msg = "You need to select at least one image type." + dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_WARNING, msg) + button = dialog.add_button("OK", gtk.RESPONSE_OK) + HobButton.style_button(button) + response = dialog.run() + dialog.destroy() + + def create_image_types_page(self): + main_vbox = gtk.VBox(False, 16) + main_vbox.set_border_width(6) + + advanced_vbox = gtk.VBox(False, 6) + advanced_vbox.set_border_width(6) + + distro_vbox = gtk.VBox(False, 6) + label = self.gen_label_widget("Distro:") + tooltip = "Selects the Yocto Project distribution you want" + try: + i = self.all_distros.index( "defaultsetup" ) + except ValueError: + i = -1 + if i != -1: + self.all_distros[ i ] = "Default" + if self.configuration.curr_distro == "defaultsetup": + self.configuration.curr_distro = "Default" + distro_widget, self.distro_combo = self.gen_combo_widget(self.configuration.curr_distro, self.all_distros,"Distro" + "*" + tooltip) + distro_vbox.pack_start(label, expand=False, fill=False) + distro_vbox.pack_start(distro_widget, expand=False, fill=False) + main_vbox.pack_start(distro_vbox, expand=False, fill=False) + + + rows = (len(self.image_types)+1)/3 + table = gtk.Table(rows + 1, 10, True) + advanced_vbox.pack_start(table, expand=False, fill=False) + + tooltip = "Image file system types you want." + info = HobInfoButton("Image types" + "*" + tooltip, self) + label = self.gen_label_widget("Image types:") + align = gtk.Alignment(0, 0.5, 0, 0) + table.attach(align, 0, 4, 0, 1) + align.add(label) + table.attach(info, 4, 5, 0, 1) + + i = 1 + j = 1 + for image_type in sorted(self.image_types): + self.image_types_checkbuttons[image_type] = gtk.CheckButton(image_type) + self.image_types_checkbuttons[image_type].connect("toggled", self.image_type_checkbutton_clicked_cb) + article = "" + if image_type.startswith(("a", "e", "i", "o", "u")): + article = "n" + if image_type == "live": + self.image_types_checkbuttons[image_type].set_tooltip_text("Build iso and hddimg images") + else: + self.image_types_checkbuttons[image_type].set_tooltip_text("Build a%s %s image" % (article, image_type)) + table.attach(self.image_types_checkbuttons[image_type], j - 1, j + 3, i, i + 1) + if image_type in self.configuration.image_fstypes.split(): + self.image_types_checkbuttons[image_type].set_active(True) + i += 1 + if i > rows: + i = 1 + j = j + 4 + + main_vbox.pack_start(advanced_vbox, expand=False, fill=False) + self.set_save_button_state() + + return main_vbox + + def create_output_page(self): + advanced_vbox = gtk.VBox(False, 6) + advanced_vbox.set_border_width(6) + + advanced_vbox.pack_start(self.gen_label_widget('Package format'), expand=False, fill=False) + sub_vbox = gtk.VBox(False, 6) + advanced_vbox.pack_start(sub_vbox, expand=False, fill=False) + tooltip_combo = "Selects the package format used to generate rootfs." + tooltip_extra = "Selects extra package formats to build" + pkgfmt_widget, self.rootfs_combo, self.check_hbox = self.gen_pkgfmt_widget(self.configuration.curr_package_format, self.all_package_formats,"Root file system package format" + "*" + tooltip_combo,"Additional package formats" + "*" + tooltip_extra) + sub_vbox.pack_start(pkgfmt_widget, expand=False, fill=False) + + advanced_vbox.pack_start(self.gen_label_widget('Image size'), expand=False, fill=False) + sub_vbox = gtk.VBox(False, 6) + advanced_vbox.pack_start(sub_vbox, expand=False, fill=False) + label = self.gen_label_widget("Image basic size (in MB)") + 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 Yocto Project Reference Manual." + rootfs_size_widget, self.rootfs_size_spinner = self.gen_spinner_widget(int(self.configuration.image_rootfs_size*1.0/1024), 0, 65536,"Image basic size" + "*" + tooltip) + sub_vbox.pack_start(label, expand=False, fill=False) + sub_vbox.pack_start(rootfs_size_widget, expand=False, fill=False) + + sub_vbox = gtk.VBox(False, 6) + advanced_vbox.pack_start(sub_vbox, expand=False, fill=False) + label = self.gen_label_widget("Additional free space (in MB)") + 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." + extra_size_widget, self.extra_size_spinner = self.gen_spinner_widget(int(self.configuration.image_extra_size*1.0/1024), 0, 65536,"Additional free space" + "*" + tooltip) + sub_vbox.pack_start(label, expand=False, fill=False) + sub_vbox.pack_start(extra_size_widget, expand=False, fill=False) + + advanced_vbox.pack_start(self.gen_label_widget('Licensing'), expand=False, fill=False) + self.gplv3_checkbox = gtk.CheckButton("Exclude GPLv3 packages") + self.gplv3_checkbox.set_tooltip_text("Check this box to prevent GPLv3 packages from being included in your image") + if "GPLv3" in self.configuration.incompat_license.split(): + self.gplv3_checkbox.set_active(True) + else: + self.gplv3_checkbox.set_active(False) + advanced_vbox.pack_start(self.gplv3_checkbox, expand=False, fill=False) + + advanced_vbox.pack_start(self.gen_label_widget('SDK'), expand=False, fill=False) + sub_hbox = gtk.HBox(False, 6) + advanced_vbox.pack_start(sub_hbox, expand=False, fill=False) + self.sdk_checkbox = gtk.CheckButton("Populate SDK") + 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." + self.sdk_checkbox.set_tooltip_text(tooltip) + self.sdk_checkbox.set_active(self.configuration.toolchain_build) + sub_hbox.pack_start(self.sdk_checkbox, expand=False, fill=False) + + tooltip = "Select the host platform for which you want to run the toolchain contained in the SDK tarball." + sdk_machine_widget, self.sdk_machine_combo = self.gen_combo_widget(self.configuration.curr_sdk_machine, self.all_sdk_machines,"Populate SDK" + "*" + tooltip) + sub_hbox.pack_start(sdk_machine_widget, expand=False, fill=False) + + return advanced_vbox + + def response_cb(self, dialog, response_id): + package_format = [] + package_format.append(self.rootfs_combo.get_active_text()) + for child in self.check_hbox: + if isinstance(child, gtk.CheckButton) and child.get_active(): + package_format.append(child.get_label()) + self.configuration.curr_package_format = " ".join(package_format) + + distro = self.distro_combo.get_active_text() + if distro == "Default": + distro = "defaultsetup" + self.configuration.curr_distro = distro + self.configuration.image_rootfs_size = self.rootfs_size_spinner.get_value_as_int() * 1024 + self.configuration.image_extra_size = self.extra_size_spinner.get_value_as_int() * 1024 + + self.configuration.image_fstypes = "" + for image_type in self.image_types: + if self.image_types_checkbuttons[image_type].get_active(): + self.configuration.image_fstypes += (" " + image_type) + self.configuration.image_fstypes.strip() + + if self.gplv3_checkbox.get_active(): + if "GPLv3" not in self.configuration.incompat_license.split(): + self.configuration.incompat_license += " GPLv3" + else: + if "GPLv3" in self.configuration.incompat_license.split(): + self.configuration.incompat_license = self.configuration.incompat_license.split().remove("GPLv3") + self.configuration.incompat_license = " ".join(self.configuration.incompat_license or []) + self.configuration.incompat_license = self.configuration.incompat_license.strip() + + self.configuration.toolchain_build = self.sdk_checkbox.get_active() + self.configuration.curr_sdk_machine = self.sdk_machine_combo.get_active_text() + md5 = self.config_md5() + 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 @@ +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2011-2012 Intel Corporation +# +# Authored by Joshua Lock +# Authored by Dongxiao Xu +# Authored by Shane Wang +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import gtk + +""" +The following are convenience classes for implementing GNOME HIG compliant +BitBake GUI's +In summary: spacing = 12px, border-width = 6px +""" + +class CrumbsDialog(gtk.Dialog): + """ + A GNOME HIG compliant dialog widget. + Add buttons with gtk.Dialog.add_button or gtk.Dialog.add_buttons + """ + def __init__(self, title="", parent=None, flags=0, buttons=None): + super(CrumbsDialog, self).__init__(title, parent, flags, buttons) + + self.set_property("has-separator", False) # note: deprecated in 2.22 + + self.set_border_width(6) + self.vbox.set_property("spacing", 12) + self.action_area.set_property("spacing", 12) + self.action_area.set_property("border-width", 6) diff --git a/bitbake/lib/bb/ui/crumbs/hig/crumbsmessagedialog.py b/bitbake/lib/bb/ui/crumbs/hig/crumbsmessagedialog.py new file mode 100644 index 0000000000..3b998e4637 --- /dev/null +++ b/bitbake/lib/bb/ui/crumbs/hig/crumbsmessagedialog.py @@ -0,0 +1,70 @@ +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2011-2012 Intel Corporation +# +# Authored by Joshua Lock +# Authored by Dongxiao Xu +# Authored by Shane Wang +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import glib +import gtk +from bb.ui.crumbs.hobwidget import HobIconChecker +from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog + +""" +The following are convenience classes for implementing GNOME HIG compliant +BitBake GUI's +In summary: spacing = 12px, border-width = 6px +""" + +class CrumbsMessageDialog(gtk.MessageDialog): + """ + A GNOME HIG compliant dialog widget. + Add buttons with gtk.Dialog.add_button or gtk.Dialog.add_buttons + """ + def __init__(self, parent = None, label="", dialog_type = gtk.MESSAGE_QUESTION, msg=""): + super(CrumbsMessageDialog, self).__init__(None, + gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, + dialog_type, + gtk.BUTTONS_NONE, + None) + + self.set_skip_taskbar_hint(False) + + self.set_markup(label) + + if 0 <= len(msg) < 300: + self.format_secondary_markup(msg) + else: + vbox = self.get_message_area() + vbox.set_border_width(1) + vbox.set_property("spacing", 12) + self.textWindow = gtk.ScrolledWindow() + self.textWindow.set_shadow_type(gtk.SHADOW_IN) + self.textWindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + self.msgView = gtk.TextView() + self.msgView.set_editable(False) + self.msgView.set_wrap_mode(gtk.WRAP_WORD) + self.msgView.set_cursor_visible(False) + self.msgView.set_size_request(300, 300) + self.buf = gtk.TextBuffer() + self.buf.set_text(msg) + self.msgView.set_buffer(self.buf) + self.textWindow.add(self.msgView) + self.msgView.show() + vbox.add(self.textWindow) + self.textWindow.show() diff --git a/bitbake/lib/bb/ui/crumbs/hig/deployimagedialog.py b/bitbake/lib/bb/ui/crumbs/hig/deployimagedialog.py new file mode 100644 index 0000000000..a13fff906a --- /dev/null +++ b/bitbake/lib/bb/ui/crumbs/hig/deployimagedialog.py @@ -0,0 +1,219 @@ +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2011-2012 Intel Corporation +# +# Authored by Joshua Lock +# Authored by Dongxiao Xu +# Authored by Shane Wang +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import glob +import gtk +import gobject +import os +import re +import shlex +import subprocess +import tempfile +from bb.ui.crumbs.hobwidget import hic, HobButton +from bb.ui.crumbs.progressbar import HobProgressBar +import bb.ui.crumbs.utils +import bb.process +from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog +from bb.ui.crumbs.hig.crumbsmessagedialog import CrumbsMessageDialog + +""" +The following are convenience classes for implementing GNOME HIG compliant +BitBake GUI's +In summary: spacing = 12px, border-width = 6px +""" + +class DeployImageDialog (CrumbsDialog): + + __dummy_usb__ = "--select a usb drive--" + + def __init__(self, title, image_path, parent, flags, buttons=None, standalone=False): + super(DeployImageDialog, self).__init__(title, parent, flags, buttons) + + self.image_path = image_path + self.standalone = standalone + + self.create_visual_elements() + self.connect("response", self.response_cb) + + def create_visual_elements(self): + self.set_size_request(600, 400) + label = gtk.Label() + label.set_alignment(0.0, 0.5) + markup = "The image to be written into usb drive:" + label.set_markup(markup) + self.vbox.pack_start(label, expand=False, fill=False, padding=2) + + table = gtk.Table(2, 10, False) + table.set_col_spacings(5) + table.set_row_spacings(5) + self.vbox.pack_start(table, expand=True, fill=True) + + scroll = gtk.ScrolledWindow() + scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + scroll.set_shadow_type(gtk.SHADOW_IN) + tv = gtk.TextView() + tv.set_editable(False) + tv.set_wrap_mode(gtk.WRAP_WORD) + tv.set_cursor_visible(False) + self.buf = gtk.TextBuffer() + self.buf.set_text(self.image_path) + tv.set_buffer(self.buf) + scroll.add(tv) + table.attach(scroll, 0, 10, 0, 1) + + # There are 2 ways to use DeployImageDialog + # One way is that called by HOB when the 'Deploy Image' button is clicked + # The other way is that called by a standalone script. + # Following block of codes handles the latter way. It adds a 'Select Image' button and + # emit a signal when the button is clicked. + if self.standalone: + gobject.signal_new("select_image_clicked", self, gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ()) + icon = gtk.Image() + pix_buffer = gtk.gdk.pixbuf_new_from_file(hic.ICON_IMAGES_DISPLAY_FILE) + icon.set_from_pixbuf(pix_buffer) + button = gtk.Button("Select Image") + button.set_image(icon) + #button.set_size_request(140, 50) + table.attach(button, 9, 10, 1, 2, gtk.FILL, 0, 0, 0) + button.connect("clicked", self.select_image_button_clicked_cb) + + separator = gtk.HSeparator() + self.vbox.pack_start(separator, expand=False, fill=False, padding=10) + + self.usb_desc = gtk.Label() + self.usb_desc.set_alignment(0.0, 0.5) + markup = "You haven't chosen any USB drive." + self.usb_desc.set_markup(markup) + + self.usb_combo = gtk.combo_box_new_text() + self.usb_combo.connect("changed", self.usb_combo_changed_cb) + model = self.usb_combo.get_model() + model.clear() + self.usb_combo.append_text(self.__dummy_usb__) + for usb in self.find_all_usb_devices(): + self.usb_combo.append_text("/dev/" + usb) + self.usb_combo.set_active(0) + self.vbox.pack_start(self.usb_combo, expand=False, fill=False) + self.vbox.pack_start(self.usb_desc, expand=False, fill=False, padding=2) + + self.progress_bar = HobProgressBar() + self.vbox.pack_start(self.progress_bar, expand=False, fill=False) + separator = gtk.HSeparator() + self.vbox.pack_start(separator, expand=False, fill=True, padding=10) + + self.vbox.show_all() + self.progress_bar.hide() + + def set_image_text_buffer(self, image_path): + self.buf.set_text(image_path) + + def set_image_path(self, image_path): + self.image_path = image_path + + def popen_read(self, cmd): + tmpout, errors = bb.process.run("%s" % cmd) + return tmpout.strip() + + def find_all_usb_devices(self): + usb_devs = [ os.readlink(u) + for u in glob.glob('/dev/disk/by-id/usb*') + if not re.search(r'part\d+', u) ] + return [ '%s' % u[u.rfind('/')+1:] for u in usb_devs ] + + def get_usb_info(self, dev): + return "%s %s" % \ + (self.popen_read('cat /sys/class/block/%s/device/vendor' % dev), + self.popen_read('cat /sys/class/block/%s/device/model' % dev)) + + def select_image_button_clicked_cb(self, button): + self.emit('select_image_clicked') + + def usb_combo_changed_cb(self, usb_combo): + combo_item = self.usb_combo.get_active_text() + if not combo_item or combo_item == self.__dummy_usb__: + markup = "You haven't chosen any USB drive." + self.usb_desc.set_markup(markup) + else: + markup = "" + self.get_usb_info(combo_item.lstrip("/dev/")) + "" + self.usb_desc.set_markup(markup) + + def response_cb(self, dialog, response_id): + if response_id == gtk.RESPONSE_YES: + lbl = '' + msg = '' + combo_item = self.usb_combo.get_active_text() + if combo_item and combo_item != self.__dummy_usb__ and self.image_path: + cmdline = bb.ui.crumbs.utils.which_terminal() + if cmdline: + tmpfile = tempfile.NamedTemporaryFile() + cmdline += "\"sudo dd if=" + self.image_path + \ + " of=" + combo_item + " && sync; echo $? > " + tmpfile.name + "\"" + subprocess.call(shlex.split(cmdline)) + + if int(tmpfile.readline().strip()) == 0: + lbl = "Deploy image successfully." + else: + lbl = "Failed to deploy image." + msg = "Please check image %s exists and USB device %s is writable." % (self.image_path, combo_item) + tmpfile.close() + else: + if not self.image_path: + lbl = "No selection made." + msg = "You have not selected an image to deploy." + else: + lbl = "No selection made." + msg = "You have not selected a USB device." + if len(lbl): + crumbs_dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_INFO, msg) + button = crumbs_dialog.add_button("Close", gtk.RESPONSE_OK) + HobButton.style_button(button) + crumbs_dialog.run() + crumbs_dialog.destroy() + + def update_progress_bar(self, title, fraction, status=None): + self.progress_bar.update(fraction) + self.progress_bar.set_title(title) + self.progress_bar.set_rcstyle(status) + + def write_file(self, ifile, ofile): + self.progress_bar.reset() + self.progress_bar.show() + + f_from = os.open(ifile, os.O_RDONLY) + f_to = os.open(ofile, os.O_WRONLY) + + total_size = os.stat(ifile).st_size + written_size = 0 + + while True: + buf = os.read(f_from, 1024*1024) + if not buf: + break + os.write(f_to, buf) + written_size += 1024*1024 + self.update_progress_bar("Writing to usb:", written_size * 1.0/total_size) + + self.update_progress_bar("Writing completed:", 1.0) + os.close(f_from) + os.close(f_to) + 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 @@ +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2011-2012 Intel Corporation +# +# Authored by Joshua Lock +# Authored by Dongxiao Xu +# Authored by Shane Wang +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import gtk +import gobject +import os +from bb.ui.crumbs.hobwidget import HobViewTable, HobInfoButton, HobButton, HobAltButton +from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog +from bb.ui.crumbs.hig.layerselectiondialog import LayerSelectionDialog + +""" +The following are convenience classes for implementing GNOME HIG compliant +BitBake GUI's +In summary: spacing = 12px, border-width = 6px +""" + +class ImageSelectionDialog (CrumbsDialog): + + __columns__ = [{ + 'col_name' : 'Image name', + 'col_id' : 0, + 'col_style': 'text', + 'col_min' : 400, + 'col_max' : 400 + }, { + 'col_name' : 'Select', + 'col_id' : 1, + 'col_style': 'radio toggle', + 'col_min' : 160, + 'col_max' : 160 + }] + + + def __init__(self, image_folder, image_types, title, parent, flags, buttons=None, image_extension = {}): + super(ImageSelectionDialog, self).__init__(title, parent, flags, buttons) + self.connect("response", self.response_cb) + + self.image_folder = image_folder + self.image_types = image_types + self.image_list = [] + self.image_names = [] + self.image_extension = image_extension + + # create visual elements on the dialog + self.create_visual_elements() + + self.image_store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_BOOLEAN) + self.fill_image_store() + + def create_visual_elements(self): + hbox = gtk.HBox(False, 6) + + self.vbox.pack_start(hbox, expand=False, fill=False) + + entry = gtk.Entry() + entry.set_text(self.image_folder) + table = gtk.Table(1, 10, True) + table.set_size_request(560, -1) + hbox.pack_start(table, expand=False, fill=False) + table.attach(entry, 0, 9, 0, 1) + image = gtk.Image() + image.set_from_stock(gtk.STOCK_OPEN, gtk.ICON_SIZE_BUTTON) + open_button = gtk.Button() + open_button.set_image(image) + open_button.connect("clicked", self.select_path_cb, self, entry) + table.attach(open_button, 9, 10, 0, 1) + + self.image_table = HobViewTable(self.__columns__, "Images") + self.image_table.set_size_request(-1, 300) + self.image_table.connect("toggled", self.toggled_cb) + self.image_table.connect_group_selection(self.table_selected_cb) + self.image_table.connect("row-activated", self.row_actived_cb) + self.vbox.pack_start(self.image_table, expand=True, fill=True) + + self.show_all() + + def change_image_cb(self, model, path, columnid): + if not model: + return + iter = model.get_iter_first() + while iter: + rowpath = model.get_path(iter) + model[rowpath][columnid] = False + iter = model.iter_next(iter) + + model[path][columnid] = True + + def toggled_cb(self, table, cell, path, columnid, tree): + model = tree.get_model() + self.change_image_cb(model, path, columnid) + + def table_selected_cb(self, selection): + model, paths = selection.get_selected_rows() + if paths: + self.change_image_cb(model, paths[0], 1) + + def row_actived_cb(self, tab, model, path): + self.change_image_cb(model, path, 1) + self.emit('response', gtk.RESPONSE_YES) + + def select_path_cb(self, action, parent, entry): + dialog = gtk.FileChooserDialog("", parent, + gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER) + text = entry.get_text() + dialog.set_current_folder(text if len(text) > 0 else os.getcwd()) + button = dialog.add_button("Cancel", gtk.RESPONSE_NO) + HobAltButton.style_button(button) + button = dialog.add_button("Open", gtk.RESPONSE_YES) + HobButton.style_button(button) + response = dialog.run() + if response == gtk.RESPONSE_YES: + path = dialog.get_filename() + entry.set_text(path) + self.image_folder = path + self.fill_image_store() + + dialog.destroy() + + def fill_image_store(self): + self.image_list = [] + self.image_store.clear() + imageset = set() + for root, dirs, files in os.walk(self.image_folder): + # ignore the sub directories + dirs[:] = [] + for f in files: + for image_type in self.image_types: + if image_type in self.image_extension: + real_types = self.image_extension[image_type] + else: + real_types = [image_type] + for real_image_type in real_types: + if f.endswith('.' + real_image_type): + imageset.add(f.rsplit('.' + real_image_type)[0].rsplit('.rootfs')[0]) + self.image_list.append(f) + + for image in imageset: + self.image_store.set(self.image_store.append(), 0, image, 1, False) + + self.image_table.set_model(self.image_store) + + def response_cb(self, dialog, response_id): + self.image_names = [] + if response_id == gtk.RESPONSE_YES: + iter = self.image_store.get_iter_first() + while iter: + path = self.image_store.get_path(iter) + if self.image_store[path][1]: + for f in self.image_list: + if f.startswith(self.image_store[path][0] + '.'): + self.image_names.append(f) + break + iter = self.image_store.iter_next(iter) diff --git a/bitbake/lib/bb/ui/crumbs/hig/layerselectiondialog.py b/bitbake/lib/bb/ui/crumbs/hig/layerselectiondialog.py new file mode 100644 index 0000000000..52d57b6738 --- /dev/null +++ b/bitbake/lib/bb/ui/crumbs/hig/layerselectiondialog.py @@ -0,0 +1,298 @@ +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2011-2012 Intel Corporation +# +# Authored by Joshua Lock +# Authored by Dongxiao Xu +# Authored by Shane Wang +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import gtk +import gobject +import os +import tempfile +from bb.ui.crumbs.hobwidget import hic, HobButton, HobAltButton +from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog +from bb.ui.crumbs.hig.crumbsmessagedialog import CrumbsMessageDialog + +""" +The following are convenience classes for implementing GNOME HIG compliant +BitBake GUI's +In summary: spacing = 12px, border-width = 6px +""" + +class CellRendererPixbufActivatable(gtk.CellRendererPixbuf): + """ + A custom CellRenderer implementation which is activatable + so that we can handle user clicks + """ + __gsignals__ = { 'clicked' : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_STRING,)), } + + def __init__(self): + gtk.CellRendererPixbuf.__init__(self) + self.set_property('mode', gtk.CELL_RENDERER_MODE_ACTIVATABLE) + self.set_property('follow-state', True) + + """ + Respond to a user click on a cell + """ + def do_activate(self, even, widget, path, background_area, cell_area, flags): + self.emit('clicked', path) + +# +# LayerSelectionDialog +# +class LayerSelectionDialog (CrumbsDialog): + + TARGETS = [ + ("MY_TREE_MODEL_ROW", gtk.TARGET_SAME_WIDGET, 0), + ("text/plain", 0, 1), + ("TEXT", 0, 2), + ("STRING", 0, 3), + ] + + def gen_label_widget(self, content): + label = gtk.Label() + label.set_alignment(0, 0) + label.set_markup(content) + label.show() + return label + + def layer_widget_toggled_cb(self, cell, path, layer_store): + name = layer_store[path][0] + toggle = not layer_store[path][1] + layer_store[path][1] = toggle + + def layer_widget_add_clicked_cb(self, action, layer_store, parent): + dialog = gtk.FileChooserDialog("Add new layer", parent, + gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER) + button = dialog.add_button("Cancel", gtk.RESPONSE_NO) + HobAltButton.style_button(button) + button = dialog.add_button("Open", gtk.RESPONSE_YES) + HobButton.style_button(button) + label = gtk.Label("Select the layer you wish to add") + label.show() + dialog.set_extra_widget(label) + response = dialog.run() + path = dialog.get_filename() + dialog.destroy() + + lbl = "Error" + msg = "Unable to load layer %s because " % path + if response == gtk.RESPONSE_YES: + import os + import os.path + layers = [] + it = layer_store.get_iter_first() + while it: + layers.append(layer_store.get_value(it, 0)) + it = layer_store.iter_next(it) + + if not path: + msg += "it is an invalid path." + elif not os.path.exists(path+"/conf/layer.conf"): + msg += "there is no layer.conf inside the directory." + elif path in layers: + msg += "it is already in loaded layers." + else: + layer_store.append([path]) + return + dialog = CrumbsMessageDialog(parent, lbl, gtk.MESSAGE_ERROR, msg) + dialog.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_OK) + response = dialog.run() + dialog.destroy() + + def layer_widget_del_clicked_cb(self, action, tree_selection, layer_store): + model, iter = tree_selection.get_selected() + if iter: + layer_store.remove(iter) + + + def gen_layer_widget(self, layers, layers_avail, window, tooltip=""): + hbox = gtk.HBox(False, 6) + + layer_tv = gtk.TreeView() + layer_tv.set_rules_hint(True) + layer_tv.set_headers_visible(False) + tree_selection = layer_tv.get_selection() + tree_selection.set_mode(gtk.SELECTION_SINGLE) + + # Allow enable drag and drop of rows including row move + dnd_internal_target = '' + dnd_targets = [(dnd_internal_target, gtk.TARGET_SAME_WIDGET, 0)] + layer_tv.enable_model_drag_source( gtk.gdk.BUTTON1_MASK, + dnd_targets, + gtk.gdk.ACTION_MOVE) + layer_tv.enable_model_drag_dest(dnd_targets, + gtk.gdk.ACTION_MOVE) + layer_tv.connect("drag_data_get", self.drag_data_get_cb) + layer_tv.connect("drag_data_received", self.drag_data_received_cb) + + col0= gtk.TreeViewColumn('Path') + cell0 = gtk.CellRendererText() + cell0.set_padding(5,2) + col0.pack_start(cell0, True) + col0.set_cell_data_func(cell0, self.draw_layer_path_cb) + layer_tv.append_column(col0) + + scroll = gtk.ScrolledWindow() + scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + scroll.set_shadow_type(gtk.SHADOW_IN) + scroll.add(layer_tv) + + table_layer = gtk.Table(2, 10, False) + hbox.pack_start(table_layer, expand=True, fill=True) + + table_layer.attach(scroll, 0, 10, 0, 1) + + layer_store = gtk.ListStore(gobject.TYPE_STRING) + for layer in layers: + layer_store.append([layer]) + + col1 = gtk.TreeViewColumn('Enabled') + layer_tv.append_column(col1) + + cell1 = CellRendererPixbufActivatable() + cell1.set_fixed_size(-1,35) + cell1.connect("clicked", self.del_cell_clicked_cb, layer_store) + col1.pack_start(cell1, True) + col1.set_cell_data_func(cell1, self.draw_delete_button_cb, layer_tv) + + add_button = gtk.Button() + add_button.set_relief(gtk.RELIEF_NONE) + box = gtk.HBox(False, 6) + box.show() + add_button.add(box) + add_button.connect("enter-notify-event", self.add_hover_cb) + add_button.connect("leave-notify-event", self.add_leave_cb) + self.im = gtk.Image() + self.im.set_from_file(hic.ICON_INDI_ADD_FILE) + self.im.show() + box.pack_start(self.im, expand=False, fill=False, padding=6) + lbl = gtk.Label("Add layer") + lbl.set_alignment(0.0, 0.5) + lbl.show() + box.pack_start(lbl, expand=True, fill=True, padding=6) + add_button.connect("clicked", self.layer_widget_add_clicked_cb, layer_store, window) + table_layer.attach(add_button, 0, 10, 1, 2, gtk.EXPAND | gtk.FILL, 0, 0, 6) + layer_tv.set_model(layer_store) + + hbox.show_all() + + return hbox, layer_store + + def drag_data_get_cb(self, treeview, context, selection, target_id, etime): + treeselection = treeview.get_selection() + model, iter = treeselection.get_selected() + data = model.get_value(iter, 0) + selection.set(selection.target, 8, data) + + def drag_data_received_cb(self, treeview, context, x, y, selection, info, etime): + model = treeview.get_model() + data = selection.data + drop_info = treeview.get_dest_row_at_pos(x, y) + if drop_info: + path, position = drop_info + iter = model.get_iter(path) + if (position == gtk.TREE_VIEW_DROP_BEFORE or position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE): + model.insert_before(iter, [data]) + else: + model.insert_after(iter, [data]) + else: + model.append([data]) + if context.action == gtk.gdk.ACTION_MOVE: + context.finish(True, True, etime) + return + + def add_hover_cb(self, button, event): + self.im.set_from_file(hic.ICON_INDI_ADD_HOVER_FILE) + + def add_leave_cb(self, button, event): + self.im.set_from_file(hic.ICON_INDI_ADD_FILE) + + def __init__(self, title, layers, layers_non_removable, all_layers, parent, flags, buttons=None): + super(LayerSelectionDialog, self).__init__(title, parent, flags, buttons) + + # class members from other objects + self.layers = layers + self.layers_non_removable = layers_non_removable + self.all_layers = all_layers + self.layers_changed = False + + # icon for remove button in TreeView + im = gtk.Image() + im.set_from_file(hic.ICON_INDI_REMOVE_FILE) + self.rem_icon = im.get_pixbuf() + + # class members for internal use + self.layer_store = None + + # create visual elements on the dialog + self.create_visual_elements() + self.connect("response", self.response_cb) + + def create_visual_elements(self): + layer_widget, self.layer_store = self.gen_layer_widget(self.layers, self.all_layers, self, None) + layer_widget.set_size_request(450, 250) + self.vbox.pack_start(layer_widget, expand=True, fill=True) + self.show_all() + + def response_cb(self, dialog, response_id): + model = self.layer_store + it = model.get_iter_first() + layers = [] + while it: + layers.append(model.get_value(it, 0)) + it = model.iter_next(it) + + self.layers_changed = (self.layers != layers) + self.layers = layers + + """ + A custom cell_data_func to draw a delete 'button' in the TreeView for layers + other than the meta layer. The deletion of which is prevented so that the + user can't shoot themselves in the foot too badly. + """ + def draw_delete_button_cb(self, col, cell, model, it, tv): + path = model.get_value(it, 0) + if path in self.layers_non_removable: + cell.set_sensitive(False) + cell.set_property('pixbuf', None) + cell.set_property('mode', gtk.CELL_RENDERER_MODE_INERT) + else: + cell.set_property('pixbuf', self.rem_icon) + cell.set_sensitive(True) + cell.set_property('mode', gtk.CELL_RENDERER_MODE_ACTIVATABLE) + + return True + + """ + A custom cell_data_func to write an extra message into the layer path cell + for the meta layer. We should inform the user that they can't remove it for + their own safety. + """ + def draw_layer_path_cb(self, col, cell, model, it): + path = model.get_value(it, 0) + if path in self.layers_non_removable: + cell.set_property('markup', "It cannot be removed\n%s" % path) + else: + cell.set_property('text', path) + + def del_cell_clicked_cb(self, cell, path, model): + it = model.get_iter_from_string(path) + 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 @@ +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2011-2012 Intel Corporation +# +# Authored by Cristiana Voicu +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import gtk +import gobject +from bb.ui.crumbs.hobwidget import HobAltButton +from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog + +""" +The following are convenience classes for implementing GNOME HIG compliant +BitBake GUI's +In summary: spacing = 12px, border-width = 6px +""" + +# +# ParsingWarningsDialog +# +class ParsingWarningsDialog (CrumbsDialog): + + def __init__(self, title, warnings, parent, flags, buttons=None): + super(ParsingWarningsDialog, self).__init__(title, parent, flags, buttons) + + self.warnings = warnings + self.warning_on = 0 + self.warn_nb = len(warnings) + + # create visual elements on the dialog + self.create_visual_elements() + + def cancel_button_cb(self, button): + self.destroy() + + def previous_button_cb(self, button): + self.warning_on = self.warning_on - 1 + self.refresh_components() + + def next_button_cb(self, button): + self.warning_on = self.warning_on + 1 + self.refresh_components() + + def refresh_components(self): + lbl = self.warnings[self.warning_on] + #when the warning text has more than 400 chars, it uses a scroll bar + if 0<= len(lbl) < 400: + self.warning_label.set_size_request(320, 230) + self.warning_label.set_use_markup(True) + self.warning_label.set_line_wrap(True) + self.warning_label.set_markup(lbl) + self.warning_label.set_property("yalign", 0.00) + else: + self.textWindow.set_shadow_type(gtk.SHADOW_IN) + self.textWindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + self.msgView = gtk.TextView() + self.msgView.set_editable(False) + self.msgView.set_wrap_mode(gtk.WRAP_WORD) + self.msgView.set_cursor_visible(False) + self.msgView.set_size_request(320, 230) + self.buf = gtk.TextBuffer() + self.buf.set_text(lbl) + self.msgView.set_buffer(self.buf) + self.textWindow.add(self.msgView) + self.msgView.show() + + if self.warning_on==0: + self.previous_button.set_sensitive(False) + else: + self.previous_button.set_sensitive(True) + + if self.warning_on==self.warn_nb-1: + self.next_button.set_sensitive(False) + else: + self.next_button.set_sensitive(True) + + if self.warn_nb>1: + self.heading = "Warning " + str(self.warning_on + 1) + " of " + str(self.warn_nb) + self.heading_label.set_markup('%s' % self.heading) + else: + self.heading = "Warning" + self.heading_label.set_markup('%s' % self.heading) + + self.show_all() + + if 0<= len(lbl) < 400: + self.textWindow.hide() + else: + self.warning_label.hide() + + def create_visual_elements(self): + self.set_size_request(350, 350) + self.heading_label = gtk.Label() + self.heading_label.set_alignment(0, 0) + self.warning_label = gtk.Label() + self.warning_label.set_selectable(True) + self.warning_label.set_alignment(0, 0) + self.textWindow = gtk.ScrolledWindow() + + table = gtk.Table(1, 10, False) + + cancel_button = gtk.Button() + cancel_button.set_label("Close") + cancel_button.connect("clicked", self.cancel_button_cb) + cancel_button.set_size_request(110, 30) + + self.previous_button = gtk.Button() + image1 = gtk.image_new_from_stock(gtk.STOCK_GO_BACK, gtk.ICON_SIZE_BUTTON) + image1.show() + box = gtk.HBox(False, 6) + box.show() + self.previous_button.add(box) + lbl = gtk.Label("Previous") + lbl.show() + box.pack_start(image1, expand=False, fill=False, padding=3) + box.pack_start(lbl, expand=True, fill=True, padding=3) + self.previous_button.connect("clicked", self.previous_button_cb) + self.previous_button.set_size_request(110, 30) + + self.next_button = gtk.Button() + image2 = gtk.image_new_from_stock(gtk.STOCK_GO_FORWARD, gtk.ICON_SIZE_BUTTON) + image2.show() + box = gtk.HBox(False, 6) + box.show() + self.next_button.add(box) + lbl = gtk.Label("Next") + lbl.show() + box.pack_start(lbl, expand=True, fill=True, padding=3) + box.pack_start(image2, expand=False, fill=False, padding=3) + self.next_button.connect("clicked", self.next_button_cb) + self.next_button.set_size_request(110, 30) + + #when there more than one warning, we need "previous" and "next" button + if self.warn_nb>1: + self.vbox.pack_start(self.heading_label, expand=False, fill=False) + self.vbox.pack_start(self.warning_label, expand=False, fill=False) + self.vbox.pack_start(self.textWindow, expand=False, fill=False) + table.attach(cancel_button, 6, 7, 0, 1, xoptions=gtk.SHRINK) + table.attach(self.previous_button, 7, 8, 0, 1, xoptions=gtk.SHRINK) + table.attach(self.next_button, 8, 9, 0, 1, xoptions=gtk.SHRINK) + self.vbox.pack_end(table, expand=False, fill=False) + else: + self.vbox.pack_start(self.heading_label, expand=False, fill=False) + self.vbox.pack_start(self.warning_label, expand=False, fill=False) + self.vbox.pack_start(self.textWindow, expand=False, fill=False) + cancel_button = self.add_button("Close", gtk.RESPONSE_CANCEL) + HobAltButton.style_button(cancel_button) + + self.refresh_components() diff --git a/bitbake/lib/bb/ui/crumbs/hig/propertydialog.py b/bitbake/lib/bb/ui/crumbs/hig/propertydialog.py new file mode 100644 index 0000000000..09b9ce6de3 --- /dev/null +++ b/bitbake/lib/bb/ui/crumbs/hig/propertydialog.py @@ -0,0 +1,437 @@ +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2011-2013 Intel Corporation +# +# Authored by Andrei Dinu +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import string +import gtk +import gobject +import os +import tempfile +import glib +from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog +from bb.ui.crumbs.hig.settingsuihelper import SettingsUIHelper +from bb.ui.crumbs.hig.crumbsmessagedialog import CrumbsMessageDialog +from bb.ui.crumbs.hig.layerselectiondialog import LayerSelectionDialog + +""" +The following are convenience classes for implementing GNOME HIG compliant +BitBake GUI's +In summary: spacing = 12px, border-width = 6px +""" + +class PropertyDialog(CrumbsDialog): + + def __init__(self, title, parent, information, flags, buttons=None): + + super(PropertyDialog, self).__init__(title, parent, flags, buttons) + + self.properties = information + + if len(self.properties) == 10: + self.create_recipe_visual_elements() + elif len(self.properties) == 5: + self.create_package_visual_elements() + else: + self.create_information_visual_elements() + + + def create_information_visual_elements(self): + + HOB_ICON_BASE_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), ("icons/")) + ICON_PACKAGES_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('info/info_display.png')) + + self.set_resizable(False) + + self.table = gtk.Table(1,1,False) + self.table.set_row_spacings(0) + self.table.set_col_spacings(0) + + self.image = gtk.Image() + self.image.set_from_file(ICON_PACKAGES_DISPLAY_FILE) + self.image.set_property("xalign",0) + #self.vbox.add(self.image) + + image_info = self.properties.split("*")[0] + info = self.properties.split("*")[1] + + vbox = gtk.VBox(True, spacing=30) + + self.label_short = gtk.Label() + self.label_short.set_line_wrap(False) + self.label_short.set_markup(image_info) + self.label_short.set_property("xalign", 0) + + self.info_label = gtk.Label() + self.info_label.set_line_wrap(True) + self.info_label.set_markup(info) + self.info_label.set_property("yalign", 0.5) + + self.table.attach(self.image, 0,1,0,1, xoptions=gtk.FILL|gtk.EXPAND, yoptions=gtk.FILL,xpadding=5,ypadding=5) + self.table.attach(self.label_short, 0,1,0,1, xoptions=gtk.FILL|gtk.EXPAND, yoptions=gtk.FILL,xpadding=40,ypadding=5) + self.table.attach(self.info_label, 0,1,1,2, xoptions=gtk.FILL|gtk.EXPAND, yoptions=gtk.FILL,xpadding=40,ypadding=10) + + self.vbox.add(self.table) + self.connect('delete-event', lambda w, e: self.destroy() or True) + + def treeViewTooltip( self, widget, e, tooltips, cell, emptyText="" ): + try: + (path,col,x,y) = widget.get_path_at_pos( int(e.x), int(e.y) ) + it = widget.get_model().get_iter(path) + value = widget.get_model().get_value(it,cell) + if value in self.tooltip_items: + tooltips.set_tip(widget, self.tooltip_items[value]) + tooltips.enable() + else: + tooltips.set_tip(widget, emptyText) + except: + tooltips.set_tip(widget, emptyText) + + + def create_package_visual_elements(self): + + import json + + name = self.properties['name'] + binb = self.properties['binb'] + size = self.properties['size'] + recipe = self.properties['recipe'] + file_list = json.loads(self.properties['files_list']) + + files_temp = '' + paths_temp = '' + files_binb = [] + paths_binb = [] + + self.tooltip_items = {} + + self.set_resizable(False) + + #cleaning out the recipe variable + recipe = recipe.split("+")[0] + + vbox = gtk.VBox(True,spacing = 0) + + ###################################### NAME ROW + COL ################################# + + self.label_short = gtk.Label() + self.label_short.set_size_request(300,-1) + self.label_short.set_selectable(True) + self.label_short.set_line_wrap(True) + self.label_short.set_markup("Name: " + name) + self.label_short.set_property("xalign", 0) + + self.vbox.add(self.label_short) + + ###################################### SIZE ROW + COL ###################################### + + self.label_short = gtk.Label() + self.label_short.set_size_request(300,-1) + self.label_short.set_selectable(True) + self.label_short.set_line_wrap(True) + self.label_short.set_markup("Size: " + size) + self.label_short.set_property("xalign", 0) + + self.vbox.add(self.label_short) + + ##################################### RECIPE ROW + COL ######################################### + + self.label_short = gtk.Label() + self.label_short.set_size_request(300,-1) + self.label_short.set_selectable(True) + self.label_short.set_line_wrap(True) + self.label_short.set_markup("Recipe: " + recipe) + self.label_short.set_property("xalign", 0) + + self.vbox.add(self.label_short) + + ##################################### BINB ROW + COL ####################################### + + if binb != '': + self.label_short = gtk.Label() + self.label_short.set_selectable(True) + self.label_short.set_line_wrap(True) + self.label_short.set_markup("Brought in by: ") + self.label_short.set_property("xalign", 0) + + self.label_info = gtk.Label() + self.label_info.set_size_request(300,-1) + self.label_info.set_selectable(True) + self.label_info.set_line_wrap(True) + self.label_info.set_markup(binb) + self.label_info.set_property("xalign", 0) + + self.vbox.add(self.label_short) + self.vbox.add(self.label_info) + + #################################### FILES BROUGHT BY PACKAGES ################################### + + if file_list: + + self.textWindow = gtk.ScrolledWindow() + self.textWindow.set_shadow_type(gtk.SHADOW_IN) + self.textWindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + self.textWindow.set_size_request(100, 170) + + packagefiles_store = gtk.ListStore(str) + + self.packagefiles_tv = gtk.TreeView() + self.packagefiles_tv.set_rules_hint(True) + self.packagefiles_tv.set_headers_visible(True) + self.textWindow.add(self.packagefiles_tv) + + self.cell1 = gtk.CellRendererText() + col1 = gtk.TreeViewColumn('Package files', self.cell1) + col1.set_cell_data_func(self.cell1, self.regex_field) + self.packagefiles_tv.append_column(col1) + + items = file_list.keys() + items.sort() + for item in items: + fullpath = item + while len(item) > 35: + item = item[:len(item)/2] + "" + item[len(item)/2+1:] + if len(item) == 35: + item = item[:len(item)/2] + "..." + item[len(item)/2+3:] + self.tooltip_items[item] = fullpath + + packagefiles_store.append([str(item)]) + + self.packagefiles_tv.set_model(packagefiles_store) + + tips = gtk.Tooltips() + tips.set_tip(self.packagefiles_tv, "") + self.packagefiles_tv.connect("motion-notify-event", self.treeViewTooltip, tips, 0) + self.packagefiles_tv.set_events(gtk.gdk.POINTER_MOTION_MASK) + + self.vbox.add(self.textWindow) + + self.vbox.show_all() + + + def regex_field(self, column, cell, model, iter): + cell.set_property('text', model.get_value(iter, 0)) + return + + + def create_recipe_visual_elements(self): + + summary = self.properties['summary'] + name = self.properties['name'] + version = self.properties['version'] + revision = self.properties['revision'] + binb = self.properties['binb'] + group = self.properties['group'] + license = self.properties['license'] + homepage = self.properties['homepage'] + bugtracker = self.properties['bugtracker'] + description = self.properties['description'] + + self.set_resizable(False) + + #cleaning out the version variable and also the summary + version = version.split(":")[1] + if len(version) > 30: + version = version.split("+")[0] + else: + version = version.split("-")[0] + license = license.replace("&" , "and") + if (homepage == ''): + homepage = 'unknown' + if (bugtracker == ''): + bugtracker = 'unknown' + summary = summary.split("+")[0] + + #calculating the rows needed for the table + binb_items_count = len(binb.split(',')) + binb_items = binb.split(',') + + vbox = gtk.VBox(False,spacing = 0) + + ######################################## SUMMARY LABEL ######################################### + + if summary != '': + self.label_short = gtk.Label() + self.label_short.set_width_chars(37) + self.label_short.set_selectable(True) + self.label_short.set_line_wrap(True) + self.label_short.set_markup("" + summary + "") + self.label_short.set_property("xalign", 0) + + self.vbox.add(self.label_short) + + ########################################## NAME ROW + COL ####################################### + + self.label_short = gtk.Label() + self.label_short.set_selectable(True) + self.label_short.set_line_wrap(True) + self.label_short.set_markup("Name: " + name) + self.label_short.set_property("xalign", 0) + + self.vbox.add(self.label_short) + + ####################################### VERSION ROW + COL #################################### + + self.label_short = gtk.Label() + self.label_short.set_selectable(True) + self.label_short.set_line_wrap(True) + self.label_short.set_markup("Version: " + version) + self.label_short.set_property("xalign", 0) + + self.vbox.add(self.label_short) + + ##################################### REVISION ROW + COL ##################################### + + self.label_short = gtk.Label() + self.label_short.set_line_wrap(True) + self.label_short.set_selectable(True) + self.label_short.set_markup("Revision: " + revision) + self.label_short.set_property("xalign", 0) + + self.vbox.add(self.label_short) + + ################################## GROUP ROW + COL ############################################ + + self.label_short = gtk.Label() + self.label_short.set_selectable(True) + self.label_short.set_line_wrap(True) + self.label_short.set_markup("Group: " + group) + self.label_short.set_property("xalign", 0) + + self.vbox.add(self.label_short) + + ################################# HOMEPAGE ROW + COL ############################################ + + if homepage != 'unknown': + self.label_info = gtk.Label() + self.label_info.set_selectable(True) + self.label_info.set_line_wrap(True) + if len(homepage) > 35: + self.label_info.set_markup("" + homepage[0:35] + "..." + "") + else: + self.label_info.set_markup("" + homepage[0:60] + "") + + self.label_info.set_property("xalign", 0) + + self.label_short = gtk.Label() + self.label_short.set_selectable(True) + self.label_short.set_line_wrap(True) + self.label_short.set_markup("Homepage: ") + self.label_short.set_property("xalign", 0) + + self.vbox.add(self.label_short) + self.vbox.add(self.label_info) + + ################################# BUGTRACKER ROW + COL ########################################### + + if bugtracker != 'unknown': + self.label_info = gtk.Label() + self.label_info.set_selectable(True) + self.label_info.set_line_wrap(True) + if len(bugtracker) > 35: + self.label_info.set_markup("" + bugtracker[0:35] + "..." + "") + else: + self.label_info.set_markup("" + bugtracker[0:60] + "") + self.label_info.set_property("xalign", 0) + + self.label_short = gtk.Label() + self.label_short.set_selectable(True) + self.label_short.set_line_wrap(True) + self.label_short.set_markup("Bugtracker: ") + self.label_short.set_property("xalign", 0) + + self.vbox.add(self.label_short) + self.vbox.add(self.label_info) + + ################################# LICENSE ROW + COL ############################################ + + self.label_info = gtk.Label() + self.label_info.set_selectable(True) + self.label_info.set_line_wrap(True) + self.label_info.set_markup(license) + self.label_info.set_property("xalign", 0) + + self.label_short = gtk.Label() + self.label_short.set_selectable(True) + self.label_short.set_line_wrap(True) + self.label_short.set_markup("License: ") + self.label_short.set_property("xalign", 0) + + self.vbox.add(self.label_short) + self.vbox.add(self.label_info) + + ################################### BINB ROW+COL ############################################# + + if binb != '': + self.label_short = gtk.Label() + self.label_short.set_selectable(True) + self.label_short.set_line_wrap(True) + self.label_short.set_markup("Brought in by: ") + self.label_short.set_property("xalign", 0) + self.vbox.add(self.label_short) + self.label_info = gtk.Label() + self.label_info.set_selectable(True) + self.label_info.set_width_chars(36) + if len(binb) > 200: + scrolled_window = gtk.ScrolledWindow() + scrolled_window.set_policy(gtk.POLICY_NEVER,gtk.POLICY_ALWAYS) + scrolled_window.set_size_request(100,100) + self.label_info.set_markup(binb) + self.label_info.set_padding(6,6) + self.label_info.set_alignment(0,0) + self.label_info.set_line_wrap(True) + scrolled_window.add_with_viewport(self.label_info) + self.vbox.add(scrolled_window) + else: + self.label_info.set_markup(binb) + self.label_info.set_property("xalign", 0) + self.label_info.set_line_wrap(True) + self.vbox.add(self.label_info) + + ################################ DESCRIPTION TAG ROW ################################################# + + self.label_short = gtk.Label() + self.label_short.set_line_wrap(True) + self.label_short.set_markup("Description ") + self.label_short.set_property("xalign", 0) + self.vbox.add(self.label_short) + + ################################ DESCRIPTION INFORMATION ROW ########################################## + + hbox = gtk.HBox(True,spacing = 0) + + self.label_short = gtk.Label() + self.label_short.set_selectable(True) + self.label_short.set_width_chars(36) + if len(description) > 200: + scrolled_window = gtk.ScrolledWindow() + scrolled_window.set_policy(gtk.POLICY_NEVER,gtk.POLICY_ALWAYS) + scrolled_window.set_size_request(100,100) + self.label_short.set_markup(description) + self.label_short.set_padding(6,6) + self.label_short.set_alignment(0,0) + self.label_short.set_line_wrap(True) + scrolled_window.add_with_viewport(self.label_short) + self.vbox.add(scrolled_window) + else: + self.label_short.set_markup(description) + self.label_short.set_property("xalign", 0) + self.label_short.set_line_wrap(True) + self.vbox.add(self.label_short) + + 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 @@ +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2011-2012 Intel Corporation +# +# Authored by Joshua Lock +# Authored by Dongxiao Xu +# Authored by Shane Wang +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import gtk +from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog + +""" +The following are convenience classes for implementing GNOME HIG compliant +BitBake GUI's +In summary: spacing = 12px, border-width = 6px +""" + +class ProxyDetailsDialog (CrumbsDialog): + + def __init__(self, title, user, passwd, parent, flags, buttons=None): + super(ProxyDetailsDialog, self).__init__(title, parent, flags, buttons) + self.connect("response", self.response_cb) + + self.auth = not (user == None or passwd == None or user == "") + self.user = user or "" + self.passwd = passwd or "" + + # create visual elements on the dialog + self.create_visual_elements() + + def create_visual_elements(self): + self.auth_checkbox = gtk.CheckButton("Use authentication") + self.auth_checkbox.set_tooltip_text("Check this box to set the username and the password") + self.auth_checkbox.set_active(self.auth) + self.auth_checkbox.connect("toggled", self.auth_checkbox_toggled_cb) + self.vbox.pack_start(self.auth_checkbox, expand=False, fill=False) + + hbox = gtk.HBox(False, 6) + self.user_label = gtk.Label("Username:") + self.user_text = gtk.Entry() + self.user_text.set_text(self.user) + hbox.pack_start(self.user_label, expand=False, fill=False) + hbox.pack_end(self.user_text, expand=False, fill=False) + self.vbox.pack_start(hbox, expand=False, fill=False) + + hbox = gtk.HBox(False, 6) + self.passwd_label = gtk.Label("Password:") + self.passwd_text = gtk.Entry() + self.passwd_text.set_text(self.passwd) + hbox.pack_start(self.passwd_label, expand=False, fill=False) + hbox.pack_end(self.passwd_text, expand=False, fill=False) + self.vbox.pack_start(hbox, expand=False, fill=False) + + self.refresh_auth_components() + self.show_all() + + def refresh_auth_components(self): + self.user_label.set_sensitive(self.auth) + self.user_text.set_editable(self.auth) + self.user_text.set_sensitive(self.auth) + self.passwd_label.set_sensitive(self.auth) + self.passwd_text.set_editable(self.auth) + self.passwd_text.set_sensitive(self.auth) + + def auth_checkbox_toggled_cb(self, button): + self.auth = self.auth_checkbox.get_active() + self.refresh_auth_components() + + def response_cb(self, dialog, response_id): + if response_id == gtk.RESPONSE_OK: + if self.auth: + self.user = self.user_text.get_text() + self.passwd = self.passwd_text.get_text() + else: + self.user = None + 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 @@ +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2013 Intel Corporation +# +# Authored by Cristiana Voicu +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import gtk + +class RetrieveImageDialog (gtk.FileChooserDialog): + """ + This class is used to create a dialog that permits to retrieve + a custom image saved previously from Hob. + """ + def __init__(self, directory,title, parent, flags, buttons=None): + super(RetrieveImageDialog, self).__init__(title, None, gtk.FILE_CHOOSER_ACTION_OPEN, + (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN, gtk.RESPONSE_OK)) + self.directory = directory + + # create visual elements on the dialog + self.create_visual_elements() + + def create_visual_elements(self): + self.set_show_hidden(True) + self.set_default_response(gtk.RESPONSE_OK) + self.set_current_folder(self.directory) + + vbox = self.get_children()[0].get_children()[0].get_children()[0] + for child in vbox.get_children()[0].get_children()[0].get_children()[0].get_children(): + vbox.get_children()[0].get_children()[0].get_children()[0].remove(child) + + label1 = gtk.Label() + label1.set_text("File system" + self.directory) + label1.show() + vbox.get_children()[0].get_children()[0].get_children()[0].pack_start(label1, expand=False, fill=False, padding=0) + vbox.get_children()[0].get_children()[1].get_children()[0].hide() + + self.get_children()[0].get_children()[1].get_children()[0].set_label("Select") diff --git a/bitbake/lib/bb/ui/crumbs/hig/saveimagedialog.py b/bitbake/lib/bb/ui/crumbs/hig/saveimagedialog.py new file mode 100644 index 0000000000..4195f70e1e --- /dev/null +++ b/bitbake/lib/bb/ui/crumbs/hig/saveimagedialog.py @@ -0,0 +1,159 @@ +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2013 Intel Corporation +# +# Authored by Cristiana Voicu +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import gtk +import glib +from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog +from bb.ui.crumbs.hig.crumbsmessagedialog import CrumbsMessageDialog +from bb.ui.crumbs.hobwidget import HobButton + +class SaveImageDialog (CrumbsDialog): + """ + This class is used to create a dialog that permits to save + a custom image in a predefined directory. + """ + def __init__(self, directory, name, description, title, parent, flags, buttons=None): + super(SaveImageDialog, self).__init__(title, parent, flags, buttons) + self.directory = directory + self.builder = parent + self.name_field = name + self.description_field = description + + # create visual elements on the dialog + self.create_visual_elements() + + def create_visual_elements(self): + self.set_default_response(gtk.RESPONSE_OK) + self.vbox.set_border_width(6) + + sub_vbox = gtk.VBox(False, 12) + self.vbox.pack_start(sub_vbox, expand=False, fill=False) + label = gtk.Label() + label.set_alignment(0, 0) + label.set_markup("Name") + sub_label = gtk.Label() + sub_label.set_alignment(0, 0) + content = "Image recipe names should be all lowercase and include only alphanumeric\n" + content += "characters. The only special character you can use is the ASCII hyphen (-)." + sub_label.set_markup(content) + self.name_entry = gtk.Entry() + self.name_entry.set_text(self.name_field) + self.name_entry.set_size_request(350,30) + self.name_entry.connect("changed", self.name_entry_changed) + sub_vbox.pack_start(label, expand=False, fill=False) + sub_vbox.pack_start(sub_label, expand=False, fill=False) + sub_vbox.pack_start(self.name_entry, expand=False, fill=False) + + sub_vbox = gtk.VBox(False, 12) + self.vbox.pack_start(sub_vbox, expand=False, fill=False) + label = gtk.Label() + label.set_alignment(0, 0) + label.set_markup("Description (optional)") + sub_label = gtk.Label() + sub_label.set_alignment(0, 0) + sub_label.set_markup("The description should be less than 150 characters long.") + self.description_entry = gtk.TextView() + description_buffer = self.description_entry.get_buffer() + description_buffer.set_text(self.description_field) + description_buffer.connect("insert-text", self.limit_description_length) + self.description_entry.set_wrap_mode(gtk.WRAP_WORD) + self.description_entry.set_size_request(350,50) + sub_vbox.pack_start(label, expand=False, fill=False) + sub_vbox.pack_start(sub_label, expand=False, fill=False) + sub_vbox.pack_start(self.description_entry, expand=False, fill=False) + + sub_vbox = gtk.VBox(False, 12) + self.vbox.pack_start(sub_vbox, expand=False, fill=False) + label = gtk.Label() + label.set_alignment(0, 0) + label.set_markup("Your image recipe will be saved to:") + sub_label = gtk.Label() + sub_label.set_alignment(0, 0) + sub_label.set_markup(self.directory) + sub_vbox.pack_start(label, expand=False, fill=False) + sub_vbox.pack_start(sub_label, expand=False, fill=False) + + table = gtk.Table(1, 4, True) + + cancel_button = gtk.Button() + cancel_button.set_label("Cancel") + cancel_button.connect("clicked", self.cancel_button_cb) + cancel_button.set_size_request(110, 30) + + self.save_button = gtk.Button() + self.save_button.set_label("Save") + self.save_button.connect("clicked", self.save_button_cb) + self.save_button.set_size_request(110, 30) + if self.name_entry.get_text() == '': + self.save_button.set_sensitive(False) + + table.attach(cancel_button, 2, 3, 0, 1) + table.attach(self.save_button, 3, 4, 0, 1) + self.vbox.pack_end(table, expand=False, fill=False) + + self.show_all() + + def limit_description_length(self, textbuffer, iter, text, length): + buffer_bounds = textbuffer.get_bounds() + entire_text = textbuffer.get_text(*buffer_bounds) + entire_text += text + if len(entire_text)>150 or text=="\n": + textbuffer.emit_stop_by_name("insert-text") + + def name_entry_changed(self, entry): + text = entry.get_text() + if text == '': + self.save_button.set_sensitive(False) + else: + self.save_button.set_sensitive(True) + + def cancel_button_cb(self, button): + self.destroy() + + def save_button_cb(self, button): + text = self.name_entry.get_text() + new_text = text.replace("-","") + description_buffer = self.description_entry.get_buffer() + description = description_buffer.get_text(description_buffer.get_start_iter(),description_buffer.get_end_iter()) + if new_text.islower() and new_text.isalnum(): + self.builder.image_details_page.image_saved = True + self.builder.customized = False + self.builder.generate_new_image(self.directory+text, description) + self.builder.recipe_model.set_in_list(text, description) + self.builder.recipe_model.set_selected_image(text) + self.builder.image_details_page.show_page(self.builder.IMAGE_GENERATED) + self.builder.image_details_page.name_field_template = text + self.builder.image_details_page.description_field_template = description + self.destroy() + else: + self.show_invalid_input_error_dialog() + + def show_invalid_input_error_dialog(self): + lbl = "Invalid characters in image recipe name" + msg = "Image recipe names should be all lowercase and\n" + msg += "include only alphanumeric characters. The only\n" + msg += "special character you can use is the ASCII hyphen (-)." + dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_ERROR, msg) + button = dialog.add_button("Close", gtk.RESPONSE_OK) + HobButton.style_button(button) + + res = dialog.run() + self.name_entry.grab_focus() + 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 @@ +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2011-2012 Intel Corporation +# +# Authored by Joshua Lock +# Authored by Dongxiao Xu +# Authored by Shane Wang +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import gtk +import os +from bb.ui.crumbs.hobwidget import HobInfoButton, HobButton, HobAltButton + +""" +The following are convenience classes for implementing GNOME HIG compliant +BitBake GUI's +In summary: spacing = 12px, border-width = 6px +""" + +class SettingsUIHelper(): + + def gen_label_widget(self, content): + label = gtk.Label() + label.set_alignment(0, 0) + label.set_markup(content) + label.show() + return label + + def gen_label_info_widget(self, content, tooltip): + table = gtk.Table(1, 10, False) + label = self.gen_label_widget(content) + info = HobInfoButton(tooltip, self) + table.attach(label, 0, 1, 0, 1, xoptions=gtk.FILL) + table.attach(info, 1, 2, 0, 1, xoptions=gtk.FILL, xpadding=10) + return table + + def gen_spinner_widget(self, content, lower, upper, tooltip=""): + hbox = gtk.HBox(False, 12) + adjust = gtk.Adjustment(value=content, lower=lower, upper=upper, step_incr=1) + spinner = gtk.SpinButton(adjustment=adjust, climb_rate=1, digits=0) + + spinner.set_value(content) + hbox.pack_start(spinner, expand=False, fill=False) + + info = HobInfoButton(tooltip, self) + hbox.pack_start(info, expand=False, fill=False) + + hbox.show_all() + return hbox, spinner + + def gen_combo_widget(self, curr_item, all_item, tooltip=""): + hbox = gtk.HBox(False, 12) + combo = gtk.combo_box_new_text() + hbox.pack_start(combo, expand=False, fill=False) + + index = 0 + for item in all_item or []: + combo.append_text(item) + if item == curr_item: + combo.set_active(index) + index += 1 + + info = HobInfoButton(tooltip, self) + hbox.pack_start(info, expand=False, fill=False) + + hbox.show_all() + return hbox, combo + + def entry_widget_select_path_cb(self, action, parent, entry): + dialog = gtk.FileChooserDialog("", parent, + gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER) + text = entry.get_text() + dialog.set_current_folder(text if len(text) > 0 else os.getcwd()) + button = dialog.add_button("Cancel", gtk.RESPONSE_NO) + HobAltButton.style_button(button) + button = dialog.add_button("Open", gtk.RESPONSE_YES) + HobButton.style_button(button) + response = dialog.run() + if response == gtk.RESPONSE_YES: + path = dialog.get_filename() + entry.set_text(path) + + dialog.destroy() + + def gen_entry_widget(self, content, parent, tooltip="", need_button=True): + hbox = gtk.HBox(False, 12) + entry = gtk.Entry() + entry.set_text(content) + entry.set_size_request(350,30) + + if need_button: + table = gtk.Table(1, 10, False) + hbox.pack_start(table, expand=True, fill=True) + table.attach(entry, 0, 9, 0, 1, xoptions=gtk.SHRINK) + image = gtk.Image() + image.set_from_stock(gtk.STOCK_OPEN,gtk.ICON_SIZE_BUTTON) + open_button = gtk.Button() + open_button.set_image(image) + open_button.connect("clicked", self.entry_widget_select_path_cb, parent, entry) + table.attach(open_button, 9, 10, 0, 1, xoptions=gtk.SHRINK) + else: + hbox.pack_start(entry, expand=True, fill=True) + + if tooltip != "": + info = HobInfoButton(tooltip, self) + hbox.pack_start(info, expand=False, fill=False) + + hbox.show_all() + return hbox, entry diff --git a/bitbake/lib/bb/ui/crumbs/hig/simplesettingsdialog.py b/bitbake/lib/bb/ui/crumbs/hig/simplesettingsdialog.py new file mode 100644 index 0000000000..ab5b614c8d --- /dev/null +++ b/bitbake/lib/bb/ui/crumbs/hig/simplesettingsdialog.py @@ -0,0 +1,894 @@ +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2011-2012 Intel Corporation +# +# Authored by Joshua Lock +# Authored by Dongxiao Xu +# Authored by Shane Wang +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import gtk +import gobject +import hashlib +from bb.ui.crumbs.hobwidget import hic, HobInfoButton, HobButton, HobAltButton +from bb.ui.crumbs.progressbar import HobProgressBar +from bb.ui.crumbs.hig.settingsuihelper import SettingsUIHelper +from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog +from bb.ui.crumbs.hig.crumbsmessagedialog import CrumbsMessageDialog +from bb.ui.crumbs.hig.proxydetailsdialog import ProxyDetailsDialog + +""" +The following are convenience classes for implementing GNOME HIG compliant +BitBake GUI's +In summary: spacing = 12px, border-width = 6px +""" + +class SimpleSettingsDialog (CrumbsDialog, SettingsUIHelper): + + (BUILD_ENV_PAGE_ID, + SHARED_STATE_PAGE_ID, + PROXIES_PAGE_ID, + OTHERS_PAGE_ID) = range(4) + + (TEST_NETWORK_NONE, + TEST_NETWORK_INITIAL, + TEST_NETWORK_RUNNING, + TEST_NETWORK_PASSED, + TEST_NETWORK_FAILED, + TEST_NETWORK_CANCELED) = range(6) + + TARGETS = [ + ("MY_TREE_MODEL_ROW", gtk.TARGET_SAME_WIDGET, 0), + ("text/plain", 0, 1), + ("TEXT", 0, 2), + ("STRING", 0, 3), + ] + + def __init__(self, title, configuration, all_image_types, + all_package_formats, all_distros, all_sdk_machines, + max_threads, parent, flags, handler, buttons=None): + super(SimpleSettingsDialog, self).__init__(title, parent, flags, buttons) + + # class members from other objects + # bitbake settings from Builder.Configuration + self.configuration = configuration + self.image_types = all_image_types + self.all_package_formats = all_package_formats + self.all_distros = all_distros + self.all_sdk_machines = all_sdk_machines + self.max_threads = max_threads + + # class members for internal use + self.dldir_text = None + self.sstatedir_text = None + self.sstatemirrors_list = [] + self.sstatemirrors_changed = 0 + self.bb_spinner = None + self.pmake_spinner = None + self.rootfs_size_spinner = None + self.extra_size_spinner = None + self.gplv3_checkbox = None + self.toolchain_checkbox = None + self.setting_store = None + self.image_types_checkbuttons = {} + + self.md5 = self.config_md5() + self.proxy_md5 = self.config_proxy_md5() + self.settings_changed = False + self.proxy_settings_changed = False + self.handler = handler + self.proxy_test_ran = False + self.selected_mirror_row = 0 + self.new_mirror = False + + # create visual elements on the dialog + self.create_visual_elements() + self.connect("response", self.response_cb) + + def _get_sorted_value(self, var): + return " ".join(sorted(str(var).split())) + "\n" + + def config_proxy_md5(self): + data = ("ENABLE_PROXY: " + self._get_sorted_value(self.configuration.enable_proxy)) + if self.configuration.enable_proxy: + for protocol in self.configuration.proxies.keys(): + data += (protocol + ": " + self._get_sorted_value(self.configuration.combine_proxy(protocol))) + return hashlib.md5(data).hexdigest() + + def config_md5(self): + data = "" + for key in self.configuration.extra_setting.keys(): + data += (key + ": " + self._get_sorted_value(self.configuration.extra_setting[key])) + return hashlib.md5(data).hexdigest() + + def gen_proxy_entry_widget(self, protocol, parent, need_button=True, line=0): + label = gtk.Label(protocol.upper() + " proxy") + self.proxy_table.attach(label, 0, 1, line, line+1, xpadding=24) + + proxy_entry = gtk.Entry() + proxy_entry.set_size_request(300, -1) + self.proxy_table.attach(proxy_entry, 1, 2, line, line+1, ypadding=4) + + self.proxy_table.attach(gtk.Label(":"), 2, 3, line, line+1, xpadding=12, ypadding=4) + + port_entry = gtk.Entry() + port_entry.set_size_request(60, -1) + self.proxy_table.attach(port_entry, 3, 4, line, line+1, ypadding=4) + + details_button = HobAltButton("Details") + details_button.connect("clicked", self.details_cb, parent, protocol) + self.proxy_table.attach(details_button, 4, 5, line, line+1, xpadding=4, yoptions=gtk.EXPAND) + + return proxy_entry, port_entry, details_button + + def refresh_proxy_components(self): + self.same_checkbox.set_sensitive(self.configuration.enable_proxy) + + self.http_proxy.set_text(self.configuration.combine_host_only("http")) + self.http_proxy.set_editable(self.configuration.enable_proxy) + self.http_proxy.set_sensitive(self.configuration.enable_proxy) + self.http_proxy_port.set_text(self.configuration.combine_port_only("http")) + self.http_proxy_port.set_editable(self.configuration.enable_proxy) + self.http_proxy_port.set_sensitive(self.configuration.enable_proxy) + self.http_proxy_details.set_sensitive(self.configuration.enable_proxy) + + self.https_proxy.set_text(self.configuration.combine_host_only("https")) + self.https_proxy.set_editable(self.configuration.enable_proxy and (not self.configuration.same_proxy)) + self.https_proxy.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy)) + self.https_proxy_port.set_text(self.configuration.combine_port_only("https")) + self.https_proxy_port.set_editable(self.configuration.enable_proxy and (not self.configuration.same_proxy)) + self.https_proxy_port.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy)) + self.https_proxy_details.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy)) + + self.ftp_proxy.set_text(self.configuration.combine_host_only("ftp")) + self.ftp_proxy.set_editable(self.configuration.enable_proxy and (not self.configuration.same_proxy)) + self.ftp_proxy.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy)) + self.ftp_proxy_port.set_text(self.configuration.combine_port_only("ftp")) + self.ftp_proxy_port.set_editable(self.configuration.enable_proxy and (not self.configuration.same_proxy)) + self.ftp_proxy_port.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy)) + self.ftp_proxy_details.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy)) + + self.socks_proxy.set_text(self.configuration.combine_host_only("socks")) + self.socks_proxy.set_editable(self.configuration.enable_proxy and (not self.configuration.same_proxy)) + self.socks_proxy.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy)) + self.socks_proxy_port.set_text(self.configuration.combine_port_only("socks")) + self.socks_proxy_port.set_editable(self.configuration.enable_proxy and (not self.configuration.same_proxy)) + self.socks_proxy_port.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy)) + self.socks_proxy_details.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy)) + + self.cvs_proxy.set_text(self.configuration.combine_host_only("cvs")) + self.cvs_proxy.set_editable(self.configuration.enable_proxy and (not self.configuration.same_proxy)) + self.cvs_proxy.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy)) + self.cvs_proxy_port.set_text(self.configuration.combine_port_only("cvs")) + self.cvs_proxy_port.set_editable(self.configuration.enable_proxy and (not self.configuration.same_proxy)) + self.cvs_proxy_port.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy)) + self.cvs_proxy_details.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy)) + + if self.configuration.same_proxy: + if self.http_proxy.get_text(): + [w.set_text(self.http_proxy.get_text()) for w in self.same_proxy_addresses] + if self.http_proxy_port.get_text(): + [w.set_text(self.http_proxy_port.get_text()) for w in self.same_proxy_ports] + + def proxy_checkbox_toggled_cb(self, button): + self.configuration.enable_proxy = self.proxy_checkbox.get_active() + if not self.configuration.enable_proxy: + self.configuration.same_proxy = False + self.same_checkbox.set_active(self.configuration.same_proxy) + self.save_proxy_data() + self.refresh_proxy_components() + + def same_checkbox_toggled_cb(self, button): + self.configuration.same_proxy = self.same_checkbox.get_active() + self.save_proxy_data() + self.refresh_proxy_components() + + def save_proxy_data(self): + self.configuration.split_proxy("http", self.http_proxy.get_text() + ":" + self.http_proxy_port.get_text()) + if self.configuration.same_proxy: + self.configuration.split_proxy("https", self.http_proxy.get_text() + ":" + self.http_proxy_port.get_text()) + self.configuration.split_proxy("ftp", self.http_proxy.get_text() + ":" + self.http_proxy_port.get_text()) + self.configuration.split_proxy("socks", self.http_proxy.get_text() + ":" + self.http_proxy_port.get_text()) + self.configuration.split_proxy("cvs", self.http_proxy.get_text() + ":" + self.http_proxy_port.get_text()) + else: + self.configuration.split_proxy("https", self.https_proxy.get_text() + ":" + self.https_proxy_port.get_text()) + self.configuration.split_proxy("ftp", self.ftp_proxy.get_text() + ":" + self.ftp_proxy_port.get_text()) + self.configuration.split_proxy("socks", self.socks_proxy.get_text() + ":" + self.socks_proxy_port.get_text()) + self.configuration.split_proxy("cvs", self.cvs_proxy.get_text() + ":" + self.cvs_proxy_port.get_text()) + + def response_cb(self, dialog, response_id): + if response_id == gtk.RESPONSE_YES: + if self.proxy_checkbox.get_active(): + # Check that all proxy entries have a corresponding port + for proxy, port in zip(self.all_proxy_addresses, self.all_proxy_ports): + if proxy.get_text() and not port.get_text(): + lbl = "Enter all port numbers" + msg = "Proxy servers require a port number. Please make sure you have entered a port number for each proxy server." + dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_WARNING, msg) + button = dialog.add_button("Close", gtk.RESPONSE_OK) + HobButton.style_button(button) + response = dialog.run() + dialog.destroy() + self.emit_stop_by_name("response") + return + + self.configuration.dldir = self.dldir_text.get_text() + self.configuration.sstatedir = self.sstatedir_text.get_text() + self.configuration.sstatemirror = "" + for mirror in self.sstatemirrors_list: + if mirror[1] != "" and mirror[2].startswith("file://"): + if mirror[1].endswith("\\1"): + smirror = mirror[2] + " " + mirror[1] + " \\n " + else: + smirror = mirror[2] + " " + mirror[1] + "\\1 \\n " + self.configuration.sstatemirror += smirror + self.configuration.bbthread = self.bb_spinner.get_value_as_int() + self.configuration.pmake = self.pmake_spinner.get_value_as_int() + self.save_proxy_data() + self.configuration.extra_setting = {} + it = self.setting_store.get_iter_first() + while it: + key = self.setting_store.get_value(it, 0) + value = self.setting_store.get_value(it, 1) + self.configuration.extra_setting[key] = value + it = self.setting_store.iter_next(it) + + md5 = self.config_md5() + self.settings_changed = (self.md5 != md5) + self.proxy_settings_changed = (self.proxy_md5 != self.config_proxy_md5()) + + def create_build_environment_page(self): + advanced_vbox = gtk.VBox(False, 6) + advanced_vbox.set_border_width(6) + + advanced_vbox.pack_start(self.gen_label_widget('Parallel threads'), expand=False, fill=False) + sub_vbox = gtk.VBox(False, 6) + advanced_vbox.pack_start(sub_vbox, expand=False, fill=False) + label = self.gen_label_widget("BitBake parallel threads") + tooltip = "Sets the number of threads that BitBake tasks can simultaneously run. See the Poky reference manual for information" + bbthread_widget, self.bb_spinner = self.gen_spinner_widget(self.configuration.bbthread, 1, self.max_threads,"BitBake prallalel threads" + "*" + tooltip) + sub_vbox.pack_start(label, expand=False, fill=False) + sub_vbox.pack_start(bbthread_widget, expand=False, fill=False) + + sub_vbox = gtk.VBox(False, 6) + advanced_vbox.pack_start(sub_vbox, expand=False, fill=False) + label = self.gen_label_widget("Make parallel threads") + tooltip = "Sets the maximum number of threads the host can use during the build. See the Poky reference manual for information" + pmake_widget, self.pmake_spinner = self.gen_spinner_widget(self.configuration.pmake, 1, self.max_threads,"Make parallel threads" + "*" + tooltip) + sub_vbox.pack_start(label, expand=False, fill=False) + sub_vbox.pack_start(pmake_widget, expand=False, fill=False) + + advanced_vbox.pack_start(self.gen_label_widget('Downloaded source code'), expand=False, fill=False) + sub_vbox = gtk.VBox(False, 6) + advanced_vbox.pack_start(sub_vbox, expand=False, fill=False) + label = self.gen_label_widget("Downloads directory") + tooltip = "Select a folder that caches the upstream project source code" + dldir_widget, self.dldir_text = self.gen_entry_widget(self.configuration.dldir, self,"Downloaded source code" + "*" + tooltip) + sub_vbox.pack_start(label, expand=False, fill=False) + sub_vbox.pack_start(dldir_widget, expand=False, fill=False) + + return advanced_vbox + + def create_shared_state_page(self): + advanced_vbox = gtk.VBox(False) + advanced_vbox.set_border_width(12) + + sub_vbox = gtk.VBox(False) + advanced_vbox.pack_start(sub_vbox, expand=False, fill=False, padding=24) + content = "Shared state directory" + tooltip = "Select a folder that caches your prebuilt results" + label = self.gen_label_info_widget(content,"Shared state directory" + "*" + tooltip) + sstatedir_widget, self.sstatedir_text = self.gen_entry_widget(self.configuration.sstatedir, self) + sub_vbox.pack_start(label, expand=False, fill=False) + sub_vbox.pack_start(sstatedir_widget, expand=False, fill=False, padding=6) + + content = "Shared state mirrors" + tooltip = "URLs pointing to pre-built mirrors that will speed your build. " + tooltip += "Select the \'Standard\' configuration if the structure of your " + tooltip += "mirror replicates the structure of your local shared state directory. " + tooltip += "For more information on shared state mirrors, check the Yocto Project Reference Manual." + table = self.gen_label_info_widget(content,"Shared state mirrors" + "*" + tooltip) + advanced_vbox.pack_start(table, expand=False, fill=False, padding=6) + + sub_vbox = gtk.VBox(False) + advanced_vbox.pack_start(sub_vbox, gtk.TRUE, gtk.TRUE, 0) + + if self.sstatemirrors_changed == 0: + self.sstatemirrors_changed = 1 + sstatemirrors = self.configuration.sstatemirror + if sstatemirrors == "": + sm_list = ["Standard", "", "file://(.*)"] + self.sstatemirrors_list.append(sm_list) + else: + sstatemirrors = [x for x in sstatemirrors.split('\\n')] + for sstatemirror in sstatemirrors: + sstatemirror_fields = [x for x in sstatemirror.split(' ') if x.strip()] + if len(sstatemirror_fields) == 2: + if sstatemirror_fields[0] == "file://(.*)" or sstatemirror_fields[0] == "file://.*": + sm_list = ["Standard", sstatemirror_fields[1], sstatemirror_fields[0]] + else: + sm_list = ["Custom", sstatemirror_fields[1], sstatemirror_fields[0]] + self.sstatemirrors_list.append(sm_list) + + sstatemirrors_widget, sstatemirrors_store = self.gen_shared_sstate_widget(self.sstatemirrors_list, self) + sub_vbox.pack_start(sstatemirrors_widget, expand=True, fill=True) + + table = gtk.Table(1, 10, False) + table.set_col_spacings(6) + add_mirror_button = HobAltButton("Add mirror") + add_mirror_button.connect("clicked", self.add_mirror) + add_mirror_button.set_size_request(120,30) + table.attach(add_mirror_button, 1, 2, 0, 1, xoptions=gtk.SHRINK) + + self.delete_button = HobAltButton("Delete mirror") + self.delete_button.connect("clicked", self.delete_cb) + self.delete_button.set_size_request(120, 30) + table.attach(self.delete_button, 3, 4, 0, 1, xoptions=gtk.SHRINK) + + advanced_vbox.pack_start(table, expand=False, fill=False, padding=6) + + return advanced_vbox + + def gen_shared_sstate_widget(self, sstatemirrors_list, window): + hbox = gtk.HBox(False) + + sstatemirrors_store = gtk.ListStore(str, str, str) + for sstatemirror in sstatemirrors_list: + sstatemirrors_store.append(sstatemirror) + + self.sstatemirrors_tv = gtk.TreeView() + self.sstatemirrors_tv.set_rules_hint(True) + self.sstatemirrors_tv.set_headers_visible(True) + tree_selection = self.sstatemirrors_tv.get_selection() + tree_selection.set_mode(gtk.SELECTION_SINGLE) + + # Allow enable drag and drop of rows including row move + self.sstatemirrors_tv.enable_model_drag_source( gtk.gdk.BUTTON1_MASK, + self.TARGETS, + gtk.gdk.ACTION_DEFAULT| + gtk.gdk.ACTION_MOVE) + self.sstatemirrors_tv.enable_model_drag_dest(self.TARGETS, + gtk.gdk.ACTION_DEFAULT) + self.sstatemirrors_tv.connect("drag_data_get", self.drag_data_get_cb) + self.sstatemirrors_tv.connect("drag_data_received", self.drag_data_received_cb) + + + self.scroll = gtk.ScrolledWindow() + self.scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + self.scroll.set_shadow_type(gtk.SHADOW_IN) + self.scroll.connect('size-allocate', self.scroll_changed) + self.scroll.add(self.sstatemirrors_tv) + + #list store for cell renderer + m = gtk.ListStore(gobject.TYPE_STRING) + m.append(["Standard"]) + m.append(["Custom"]) + + cell0 = gtk.CellRendererCombo() + cell0.set_property("model",m) + cell0.set_property("text-column", 0) + cell0.set_property("editable", True) + cell0.set_property("has-entry", False) + col0 = gtk.TreeViewColumn("Configuration") + col0.pack_start(cell0, False) + col0.add_attribute(cell0, "text", 0) + col0.set_cell_data_func(cell0, self.configuration_field) + self.sstatemirrors_tv.append_column(col0) + + cell0.connect("edited", self.combo_changed, sstatemirrors_store) + + self.cell1 = gtk.CellRendererText() + self.cell1.set_padding(5,2) + col1 = gtk.TreeViewColumn('Regex', self.cell1) + col1.set_cell_data_func(self.cell1, self.regex_field) + self.sstatemirrors_tv.append_column(col1) + + self.cell1.connect("edited", self.regex_changed, sstatemirrors_store) + + cell2 = gtk.CellRendererText() + cell2.set_padding(5,2) + cell2.set_property("editable", True) + col2 = gtk.TreeViewColumn('URL', cell2) + col2.set_cell_data_func(cell2, self.url_field) + self.sstatemirrors_tv.append_column(col2) + + cell2.connect("edited", self.url_changed, sstatemirrors_store) + + self.sstatemirrors_tv.set_model(sstatemirrors_store) + self.sstatemirrors_tv.set_cursor(self.selected_mirror_row) + hbox.pack_start(self.scroll, expand=True, fill=True) + hbox.show_all() + + return hbox, sstatemirrors_store + + def drag_data_get_cb(self, treeview, context, selection, target_id, etime): + treeselection = treeview.get_selection() + model, iter = treeselection.get_selected() + data = model.get_string_from_iter(iter) + selection.set(selection.target, 8, data) + + def drag_data_received_cb(self, treeview, context, x, y, selection, info, etime): + model = treeview.get_model() + data = [] + tree_iter = model.get_iter_from_string(selection.data) + data.append(model.get_value(tree_iter, 0)) + data.append(model.get_value(tree_iter, 1)) + data.append(model.get_value(tree_iter, 2)) + + drop_info = treeview.get_dest_row_at_pos(x, y) + if drop_info: + path, position = drop_info + iter = model.get_iter(path) + if (position == gtk.TREE_VIEW_DROP_BEFORE or position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE): + model.insert_before(iter, data) + else: + model.insert_after(iter, data) + else: + model.append(data) + if context.action == gtk.gdk.ACTION_MOVE: + context.finish(True, True, etime) + return + + def delete_cb(self, button): + selection = self.sstatemirrors_tv.get_selection() + tree_model, tree_iter = selection.get_selected() + index = int(tree_model.get_string_from_iter(tree_iter)) + if index == 0: + self.selected_mirror_row = index + else: + self.selected_mirror_row = index - 1 + self.sstatemirrors_list.pop(index) + self.refresh_shared_state_page() + if not self.sstatemirrors_list: + self.delete_button.set_sensitive(False) + + def add_mirror(self, button): + self.new_mirror = True + tooltip = "Select the pre-built mirror that will speed your build" + index = len(self.sstatemirrors_list) + self.selected_mirror_row = index + sm_list = ["Standard", "", "file://(.*)"] + self.sstatemirrors_list.append(sm_list) + self.refresh_shared_state_page() + + def scroll_changed(self, widget, event, data=None): + if self.new_mirror == True: + adj = widget.get_vadjustment() + adj.set_value(adj.upper - adj.page_size) + self.new_mirror = False + + def combo_changed(self, widget, path, text, model): + model[path][0] = text + selection = self.sstatemirrors_tv.get_selection() + tree_model, tree_iter = selection.get_selected() + index = int(tree_model.get_string_from_iter(tree_iter)) + self.sstatemirrors_list[index][0] = text + + def regex_changed(self, cell, path, new_text, user_data): + user_data[path][2] = new_text + selection = self.sstatemirrors_tv.get_selection() + tree_model, tree_iter = selection.get_selected() + index = int(tree_model.get_string_from_iter(tree_iter)) + self.sstatemirrors_list[index][2] = new_text + return + + def url_changed(self, cell, path, new_text, user_data): + if new_text!="Enter the mirror URL" and new_text!="Match regex and replace it with this URL": + user_data[path][1] = new_text + selection = self.sstatemirrors_tv.get_selection() + tree_model, tree_iter = selection.get_selected() + index = int(tree_model.get_string_from_iter(tree_iter)) + self.sstatemirrors_list[index][1] = new_text + return + + def configuration_field(self, column, cell, model, iter): + cell.set_property('text', model.get_value(iter, 0)) + if model.get_value(iter, 0) == "Standard": + self.cell1.set_property("sensitive", False) + self.cell1.set_property("editable", False) + else: + self.cell1.set_property("sensitive", True) + self.cell1.set_property("editable", True) + return + + def regex_field(self, column, cell, model, iter): + cell.set_property('text', model.get_value(iter, 2)) + return + + def url_field(self, column, cell, model, iter): + text = model.get_value(iter, 1) + if text == "": + if model.get_value(iter, 0) == "Standard": + text = "Enter the mirror URL" + else: + text = "Match regex and replace it with this URL" + cell.set_property('text', text) + return + + def refresh_shared_state_page(self): + page_num = self.nb.get_current_page() + self.nb.remove_page(page_num); + self.nb.insert_page(self.create_shared_state_page(), gtk.Label("Shared state"),page_num) + self.show_all() + self.nb.set_current_page(page_num) + + def test_proxy_ended(self, passed): + self.proxy_test_running = False + self.set_test_proxy_state(self.TEST_NETWORK_PASSED if passed else self.TEST_NETWORK_FAILED) + self.set_sensitive(True) + self.refresh_proxy_components() + + def timer_func(self): + self.test_proxy_progress.pulse() + return self.proxy_test_running + + def test_network_button_cb(self, b): + self.set_test_proxy_state(self.TEST_NETWORK_RUNNING) + self.set_sensitive(False) + self.save_proxy_data() + if self.configuration.enable_proxy == True: + self.handler.set_http_proxy(self.configuration.combine_proxy("http")) + self.handler.set_https_proxy(self.configuration.combine_proxy("https")) + self.handler.set_ftp_proxy(self.configuration.combine_proxy("ftp")) + self.handler.set_socks_proxy(self.configuration.combine_proxy("socks")) + self.handler.set_cvs_proxy(self.configuration.combine_host_only("cvs"), self.configuration.combine_port_only("cvs")) + elif self.configuration.enable_proxy == False: + self.handler.set_http_proxy("") + self.handler.set_https_proxy("") + self.handler.set_ftp_proxy("") + self.handler.set_socks_proxy("") + self.handler.set_cvs_proxy("", "") + self.proxy_test_ran = True + self.proxy_test_running = True + gobject.timeout_add(100, self.timer_func) + self.handler.trigger_network_test() + + def test_proxy_focus_event(self, w, direction): + if self.test_proxy_state in [self.TEST_NETWORK_PASSED, self.TEST_NETWORK_FAILED]: + self.set_test_proxy_state(self.TEST_NETWORK_INITIAL) + return False + + def http_proxy_changed(self, e): + if not self.configuration.same_proxy: + return + if e == self.http_proxy: + [w.set_text(self.http_proxy.get_text()) for w in self.same_proxy_addresses] + else: + [w.set_text(self.http_proxy_port.get_text()) for w in self.same_proxy_ports] + + def proxy_address_focus_out_event(self, w, direction): + text = w.get_text() + if not text: + return False + if text.find("//") == -1: + w.set_text("http://" + text) + return False + + def set_test_proxy_state(self, state): + if self.test_proxy_state == state: + return + [self.proxy_table.remove(w) for w in self.test_gui_elements] + if state == self.TEST_NETWORK_INITIAL: + self.proxy_table.attach(self.test_network_button, 1, 2, 5, 6) + self.test_network_button.show() + elif state == self.TEST_NETWORK_RUNNING: + self.test_proxy_progress.set_rcstyle("running") + self.test_proxy_progress.set_text("Testing network configuration") + self.proxy_table.attach(self.test_proxy_progress, 0, 5, 5, 6, xpadding=4) + self.test_proxy_progress.show() + else: # passed or failed + self.dummy_progress.update(1.0) + if state == self.TEST_NETWORK_PASSED: + self.dummy_progress.set_text("Your network is properly configured") + self.dummy_progress.set_rcstyle("running") + else: + self.dummy_progress.set_text("Network test failed") + self.dummy_progress.set_rcstyle("fail") + self.proxy_table.attach(self.dummy_progress, 0, 4, 5, 6) + self.proxy_table.attach(self.retest_network_button, 4, 5, 5, 6, xpadding=4) + self.dummy_progress.show() + self.retest_network_button.show() + self.test_proxy_state = state + + def create_network_page(self): + advanced_vbox = gtk.VBox(False, 6) + advanced_vbox.set_border_width(6) + self.same_proxy_addresses = [] + self.same_proxy_ports = [] + self.all_proxy_ports = [] + self.all_proxy_addresses = [] + + sub_vbox = gtk.VBox(False, 6) + advanced_vbox.pack_start(sub_vbox, expand=False, fill=False) + label = self.gen_label_widget("Set the proxies used when fetching source code") + tooltip = "Set the proxies used when fetching source code. A blank field uses a direct internet connection." + info = HobInfoButton("Set the proxies used when fetching source code" + "*" + tooltip, self) + hbox = gtk.HBox(False, 12) + hbox.pack_start(label, expand=True, fill=True) + hbox.pack_start(info, expand=False, fill=False) + sub_vbox.pack_start(hbox, expand=False, fill=False) + + proxy_test_focus = [] + self.direct_checkbox = gtk.RadioButton(None, "Direct network connection") + proxy_test_focus.append(self.direct_checkbox) + self.direct_checkbox.set_tooltip_text("Check this box to use a direct internet connection with no proxy") + self.direct_checkbox.set_active(not self.configuration.enable_proxy) + sub_vbox.pack_start(self.direct_checkbox, expand=False, fill=False) + + self.proxy_checkbox = gtk.RadioButton(self.direct_checkbox, "Manual proxy configuration") + proxy_test_focus.append(self.proxy_checkbox) + self.proxy_checkbox.set_tooltip_text("Check this box to manually set up a specific proxy") + self.proxy_checkbox.set_active(self.configuration.enable_proxy) + sub_vbox.pack_start(self.proxy_checkbox, expand=False, fill=False) + + self.same_checkbox = gtk.CheckButton("Use the HTTP proxy for all protocols") + proxy_test_focus.append(self.same_checkbox) + self.same_checkbox.set_tooltip_text("Check this box to use the HTTP proxy for all five proxies") + self.same_checkbox.set_active(self.configuration.same_proxy) + hbox = gtk.HBox(False, 12) + hbox.pack_start(self.same_checkbox, expand=False, fill=False, padding=24) + sub_vbox.pack_start(hbox, expand=False, fill=False) + + self.proxy_table = gtk.Table(6, 5, False) + self.http_proxy, self.http_proxy_port, self.http_proxy_details = self.gen_proxy_entry_widget( + "http", self, True, 0) + proxy_test_focus +=[self.http_proxy, self.http_proxy_port] + self.http_proxy.connect("changed", self.http_proxy_changed) + self.http_proxy_port.connect("changed", self.http_proxy_changed) + + self.https_proxy, self.https_proxy_port, self.https_proxy_details = self.gen_proxy_entry_widget( + "https", self, True, 1) + proxy_test_focus += [self.https_proxy, self.https_proxy_port] + self.same_proxy_addresses.append(self.https_proxy) + self.same_proxy_ports.append(self.https_proxy_port) + + self.ftp_proxy, self.ftp_proxy_port, self.ftp_proxy_details = self.gen_proxy_entry_widget( + "ftp", self, True, 2) + proxy_test_focus += [self.ftp_proxy, self.ftp_proxy_port] + self.same_proxy_addresses.append(self.ftp_proxy) + self.same_proxy_ports.append(self.ftp_proxy_port) + + self.socks_proxy, self.socks_proxy_port, self.socks_proxy_details = self.gen_proxy_entry_widget( + "socks", self, True, 3) + proxy_test_focus += [self.socks_proxy, self.socks_proxy_port] + self.same_proxy_addresses.append(self.socks_proxy) + self.same_proxy_ports.append(self.socks_proxy_port) + + self.cvs_proxy, self.cvs_proxy_port, self.cvs_proxy_details = self.gen_proxy_entry_widget( + "cvs", self, True, 4) + proxy_test_focus += [self.cvs_proxy, self.cvs_proxy_port] + self.same_proxy_addresses.append(self.cvs_proxy) + self.same_proxy_ports.append(self.cvs_proxy_port) + self.all_proxy_ports = self.same_proxy_ports + [self.http_proxy_port] + self.all_proxy_addresses = self.same_proxy_addresses + [self.http_proxy] + sub_vbox.pack_start(self.proxy_table, expand=False, fill=False) + self.proxy_table.show_all() + + # Create the graphical elements for the network test feature, but don't display them yet + self.test_network_button = HobAltButton("Test network configuration") + self.test_network_button.connect("clicked", self.test_network_button_cb) + self.test_proxy_progress = HobProgressBar() + self.dummy_progress = HobProgressBar() + self.retest_network_button = HobAltButton("Retest") + self.retest_network_button.connect("clicked", self.test_network_button_cb) + self.test_gui_elements = [self.test_network_button, self.test_proxy_progress, self.dummy_progress, self.retest_network_button] + # Initialize the network tester + self.test_proxy_state = self.TEST_NETWORK_NONE + self.set_test_proxy_state(self.TEST_NETWORK_INITIAL) + self.proxy_test_passed_id = self.handler.connect("network-passed", lambda h:self.test_proxy_ended(True)) + self.proxy_test_failed_id = self.handler.connect("network-failed", lambda h:self.test_proxy_ended(False)) + [w.connect("focus-in-event", self.test_proxy_focus_event) for w in proxy_test_focus] + [w.connect("focus-out-event", self.proxy_address_focus_out_event) for w in self.all_proxy_addresses] + + self.direct_checkbox.connect("toggled", self.proxy_checkbox_toggled_cb) + self.proxy_checkbox.connect("toggled", self.proxy_checkbox_toggled_cb) + self.same_checkbox.connect("toggled", self.same_checkbox_toggled_cb) + + self.refresh_proxy_components() + return advanced_vbox + + def switch_to_page(self, page_id): + self.nb.set_current_page(page_id) + + def details_cb(self, button, parent, protocol): + self.save_proxy_data() + dialog = ProxyDetailsDialog(title = protocol.upper() + " Proxy Details", + user = self.configuration.proxies[protocol][1], + passwd = self.configuration.proxies[protocol][2], + parent = parent, + flags = gtk.DIALOG_MODAL + | gtk.DIALOG_DESTROY_WITH_PARENT + | gtk.DIALOG_NO_SEPARATOR) + dialog.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_OK) + response = dialog.run() + if response == gtk.RESPONSE_OK: + self.configuration.proxies[protocol][1] = dialog.user + self.configuration.proxies[protocol][2] = dialog.passwd + self.refresh_proxy_components() + dialog.destroy() + + def rootfs_combo_changed_cb(self, rootfs_combo, all_package_format, check_hbox): + combo_item = self.rootfs_combo.get_active_text() + for child in check_hbox.get_children(): + if isinstance(child, gtk.CheckButton): + check_hbox.remove(child) + for format in all_package_format: + if format != combo_item: + check_button = gtk.CheckButton(format) + check_hbox.pack_start(check_button, expand=False, fill=False) + check_hbox.show_all() + + def gen_pkgfmt_widget(self, curr_package_format, all_package_format, tooltip_combo="", tooltip_extra=""): + pkgfmt_hbox = gtk.HBox(False, 24) + + rootfs_vbox = gtk.VBox(False, 6) + pkgfmt_hbox.pack_start(rootfs_vbox, expand=False, fill=False) + + label = self.gen_label_widget("Root file system package format") + rootfs_vbox.pack_start(label, expand=False, fill=False) + + rootfs_format = "" + if curr_package_format: + rootfs_format = curr_package_format.split()[0] + + rootfs_format_widget, rootfs_combo = self.gen_combo_widget(rootfs_format, all_package_format, tooltip_combo) + rootfs_vbox.pack_start(rootfs_format_widget, expand=False, fill=False) + + extra_vbox = gtk.VBox(False, 6) + pkgfmt_hbox.pack_start(extra_vbox, expand=False, fill=False) + + label = self.gen_label_widget("Additional package formats") + extra_vbox.pack_start(label, expand=False, fill=False) + + check_hbox = gtk.HBox(False, 12) + extra_vbox.pack_start(check_hbox, expand=False, fill=False) + for format in all_package_format: + if format != rootfs_format: + check_button = gtk.CheckButton(format) + is_active = (format in curr_package_format.split()) + check_button.set_active(is_active) + check_hbox.pack_start(check_button, expand=False, fill=False) + + info = HobInfoButton(tooltip_extra, self) + check_hbox.pack_end(info, expand=False, fill=False) + + rootfs_combo.connect("changed", self.rootfs_combo_changed_cb, all_package_format, check_hbox) + + pkgfmt_hbox.show_all() + + return pkgfmt_hbox, rootfs_combo, check_hbox + + def editable_settings_cell_edited(self, cell, path_string, new_text, model): + it = model.get_iter_from_string(path_string) + column = cell.get_data("column") + model.set(it, column, new_text) + + def editable_settings_add_item_clicked(self, button, model): + new_item = ["##KEY##", "##VALUE##"] + + iter = model.append() + model.set (iter, + 0, new_item[0], + 1, new_item[1], + ) + + def editable_settings_remove_item_clicked(self, button, treeview): + selection = treeview.get_selection() + model, iter = selection.get_selected() + + if iter: + path = model.get_path(iter)[0] + model.remove(iter) + + def gen_editable_settings(self, setting, tooltip=""): + setting_hbox = gtk.HBox(False, 12) + + vbox = gtk.VBox(False, 12) + setting_hbox.pack_start(vbox, expand=True, fill=True) + + setting_store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING) + for key in setting.keys(): + setting_store.set(setting_store.append(), 0, key, 1, setting[key]) + + setting_tree = gtk.TreeView(setting_store) + setting_tree.set_headers_visible(True) + setting_tree.set_size_request(300, 100) + + col = gtk.TreeViewColumn('Key') + col.set_min_width(100) + col.set_max_width(150) + col.set_resizable(True) + col1 = gtk.TreeViewColumn('Value') + col1.set_min_width(100) + col1.set_max_width(150) + col1.set_resizable(True) + setting_tree.append_column(col) + setting_tree.append_column(col1) + cell = gtk.CellRendererText() + cell.set_property('width-chars', 10) + cell.set_property('editable', True) + cell.set_data("column", 0) + cell.connect("edited", self.editable_settings_cell_edited, setting_store) + cell1 = gtk.CellRendererText() + cell1.set_property('width-chars', 10) + cell1.set_property('editable', True) + cell1.set_data("column", 1) + cell1.connect("edited", self.editable_settings_cell_edited, setting_store) + col.pack_start(cell, True) + col1.pack_end(cell1, True) + col.set_attributes(cell, text=0) + col1.set_attributes(cell1, text=1) + + scroll = gtk.ScrolledWindow() + scroll.set_shadow_type(gtk.SHADOW_IN) + scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + scroll.add(setting_tree) + vbox.pack_start(scroll, expand=True, fill=True) + + # some buttons + hbox = gtk.HBox(True, 6) + vbox.pack_start(hbox, False, False) + + button = gtk.Button(stock=gtk.STOCK_ADD) + button.connect("clicked", self.editable_settings_add_item_clicked, setting_store) + hbox.pack_start(button) + + button = gtk.Button(stock=gtk.STOCK_REMOVE) + button.connect("clicked", self.editable_settings_remove_item_clicked, setting_tree) + hbox.pack_start(button) + + info = HobInfoButton(tooltip, self) + setting_hbox.pack_start(info, expand=False, fill=False) + + return setting_hbox, setting_store + + def create_others_page(self): + advanced_vbox = gtk.VBox(False, 6) + advanced_vbox.set_border_width(6) + + sub_vbox = gtk.VBox(False, 6) + advanced_vbox.pack_start(sub_vbox, expand=True, fill=True) + label = self.gen_label_widget("Add your own variables:") + tooltip = "These are key/value pairs for your extra settings. Click \'Add\' and then directly edit the key and the value" + setting_widget, self.setting_store = self.gen_editable_settings(self.configuration.extra_setting,"Add your own variables" + "*" + tooltip) + sub_vbox.pack_start(label, expand=False, fill=False) + sub_vbox.pack_start(setting_widget, expand=True, fill=True) + + return advanced_vbox + + def create_visual_elements(self): + self.nb = gtk.Notebook() + self.nb.set_show_tabs(True) + self.nb.append_page(self.create_build_environment_page(), gtk.Label("Build environment")) + self.nb.append_page(self.create_shared_state_page(), gtk.Label("Shared state")) + self.nb.append_page(self.create_network_page(), gtk.Label("Network")) + self.nb.append_page(self.create_others_page(), gtk.Label("Others")) + self.nb.set_current_page(0) + self.vbox.pack_start(self.nb, expand=True, fill=True) + self.vbox.pack_end(gtk.HSeparator(), expand=True, fill=True) + + self.show_all() + + def destroy(self): + self.handler.disconnect(self.proxy_test_passed_id) + self.handler.disconnect(self.proxy_test_failed_id) + 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 @@ +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2012 Intel Corporation +# +# Authored by Shane Wang +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +class HobColors: + WHITE = "#ffffff" + PALE_GREEN = "#aaffaa" + ORANGE = "#eb8e68" + PALE_RED = "#ffaaaa" + GRAY = "#aaaaaa" + LIGHT_GRAY = "#dddddd" + SLIGHT_DARK = "#5f5f5f" + DARK = "#3c3b37" + BLACK = "#000000" + PALE_BLUE = "#53b8ff" + DEEP_RED = "#aa3e3e" + KHAKI = "#fff68f" + + OK = WHITE + RUNNING = PALE_GREEN + WARNING = ORANGE + ERROR = PALE_RED diff --git a/bitbake/lib/bb/ui/crumbs/hobeventhandler.py b/bitbake/lib/bb/ui/crumbs/hobeventhandler.py new file mode 100644 index 0000000000..43edb70b08 --- /dev/null +++ b/bitbake/lib/bb/ui/crumbs/hobeventhandler.py @@ -0,0 +1,639 @@ +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2011 Intel Corporation +# +# Authored by Joshua Lock +# Authored by Dongxiao Xu +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import gobject +import logging +import ast +from bb.ui.crumbs.runningbuild import RunningBuild + +class HobHandler(gobject.GObject): + + """ + This object does BitBake event handling for the hob gui. + """ + __gsignals__ = { + "package-formats-updated" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_PYOBJECT,)), + "config-updated" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_STRING, gobject.TYPE_PYOBJECT,)), + "command-succeeded" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_INT,)), + "command-failed" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_STRING,)), + "parsing-warning" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_STRING,)), + "sanity-failed" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_STRING, gobject.TYPE_INT)), + "generating-data" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + ()), + "data-generated" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + ()), + "parsing-started" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_PYOBJECT,)), + "parsing" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_PYOBJECT,)), + "parsing-completed" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_PYOBJECT,)), + "recipe-populated" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + ()), + "package-populated" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + ()), + "network-passed" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + ()), + "network-failed" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + ()), + } + + (GENERATE_CONFIGURATION, GENERATE_RECIPES, GENERATE_PACKAGES, GENERATE_IMAGE, POPULATE_PACKAGEINFO, SANITY_CHECK, NETWORK_TEST) = range(7) + (SUB_PATH_LAYERS, SUB_FILES_DISTRO, SUB_FILES_MACH, SUB_FILES_SDKMACH, SUB_MATCH_CLASS, SUB_PARSE_CONFIG, SUB_SANITY_CHECK, + SUB_GNERATE_TGTS, SUB_GENERATE_PKGINFO, SUB_BUILD_RECIPES, SUB_BUILD_IMAGE, SUB_NETWORK_TEST) = range(12) + + def __init__(self, server, recipe_model, package_model): + super(HobHandler, self).__init__() + + self.build = RunningBuild(sequential=True) + + self.recipe_model = recipe_model + self.package_model = package_model + + self.commands_async = [] + self.generating = False + self.current_phase = None + self.building = False + self.recipe_queue = [] + self.package_queue = [] + + self.server = server + self.error_msg = "" + self.initcmd = None + self.parsing = False + + def set_busy(self): + if not self.generating: + self.emit("generating-data") + self.generating = True + + def clear_busy(self): + if self.generating: + self.emit("data-generated") + self.generating = False + + def runCommand(self, commandline): + try: + result, error = self.server.runCommand(commandline) + if error: + raise Exception("Error running command '%s': %s" % (commandline, error)) + return result + except Exception as e: + self.commands_async = [] + self.clear_busy() + self.emit("command-failed", "Hob Exception - %s" % (str(e))) + return None + + def run_next_command(self, initcmd=None): + if initcmd != None: + self.initcmd = initcmd + + if self.commands_async: + self.set_busy() + next_command = self.commands_async.pop(0) + else: + self.clear_busy() + if self.initcmd != None: + self.emit("command-succeeded", self.initcmd) + return + + if next_command == self.SUB_PATH_LAYERS: + self.runCommand(["findConfigFilePath", "bblayers.conf"]) + elif next_command == self.SUB_FILES_DISTRO: + self.runCommand(["findConfigFiles", "DISTRO"]) + elif next_command == self.SUB_FILES_MACH: + self.runCommand(["findConfigFiles", "MACHINE"]) + elif next_command == self.SUB_FILES_SDKMACH: + self.runCommand(["findConfigFiles", "MACHINE-SDK"]) + elif next_command == self.SUB_MATCH_CLASS: + self.runCommand(["findFilesMatchingInDir", "rootfs_", "classes"]) + elif next_command == self.SUB_PARSE_CONFIG: + self.runCommand(["resetCooker"]) + elif next_command == self.SUB_GNERATE_TGTS: + self.runCommand(["generateTargetsTree", "classes/image.bbclass", []]) + elif next_command == self.SUB_GENERATE_PKGINFO: + self.runCommand(["triggerEvent", "bb.event.RequestPackageInfo()"]) + elif next_command == self.SUB_SANITY_CHECK: + self.runCommand(["triggerEvent", "bb.event.SanityCheck()"]) + elif next_command == self.SUB_NETWORK_TEST: + self.runCommand(["triggerEvent", "bb.event.NetworkTest()"]) + elif next_command == self.SUB_BUILD_RECIPES: + self.clear_busy() + self.building = True + self.runCommand(["buildTargets", self.recipe_queue, self.default_task]) + self.recipe_queue = [] + elif next_command == self.SUB_BUILD_IMAGE: + self.clear_busy() + self.building = True + target = self.image + + if self.base_image: + # Request the build of a custom image + self.generate_hob_base_image(target) + self.set_var_in_file("LINGUAS_INSTALL", "", "local.conf") + hobImage = self.runCommand(["matchFile", target + ".bb"]) + if self.base_image != self.recipe_model.__custom_image__: + baseImage = self.runCommand(["matchFile", self.base_image + ".bb"]) + version = self.runCommand(["generateNewImage", hobImage, baseImage, self.package_queue, True, ""]) + target += version + self.recipe_model.set_custom_image_version(version) + + targets = [target] + if self.toolchain_packages: + self.set_var_in_file("TOOLCHAIN_TARGET_TASK", " ".join(self.toolchain_packages), "local.conf") + targets.append(target + ":do_populate_sdk") + + self.runCommand(["buildTargets", targets, self.default_task]) + + def display_error(self): + self.clear_busy() + self.emit("command-failed", self.error_msg) + self.error_msg = "" + if self.building: + self.building = False + + def handle_event(self, event): + if not event: + return + if self.building: + self.current_phase = "building" + self.build.handle_event(event) + + if isinstance(event, bb.event.PackageInfo): + self.package_model.populate(event._pkginfolist) + self.emit("package-populated") + self.run_next_command() + + elif isinstance(event, bb.event.SanityCheckPassed): + reparse = self.runCommand(["getVariable", "BB_INVALIDCONF"]) or None + if reparse is True: + self.set_var_in_file("BB_INVALIDCONF", False, "local.conf") + self.runCommand(["setPrePostConfFiles", "conf/.hob.conf", ""]) + self.commands_async.prepend(self.SUB_PARSE_CONFIG) + self.run_next_command() + + elif isinstance(event, bb.event.SanityCheckFailed): + self.emit("sanity-failed", event._msg, event._network_error) + + elif isinstance(event, logging.LogRecord): + if not self.building: + if event.levelno >= logging.ERROR: + formatter = bb.msg.BBLogFormatter() + msg = formatter.format(event) + self.error_msg += msg + '\n' + elif event.levelno >= logging.WARNING and self.parsing == True: + formatter = bb.msg.BBLogFormatter() + msg = formatter.format(event) + warn_msg = msg + '\n' + self.emit("parsing-warning", warn_msg) + + elif isinstance(event, bb.event.TargetsTreeGenerated): + self.current_phase = "data generation" + if event._model: + self.recipe_model.populate(event._model) + self.emit("recipe-populated") + elif isinstance(event, bb.event.ConfigFilesFound): + self.current_phase = "configuration lookup" + var = event._variable + values = event._values + values.sort() + self.emit("config-updated", var, values) + elif isinstance(event, bb.event.ConfigFilePathFound): + self.current_phase = "configuration lookup" + elif isinstance(event, bb.event.FilesMatchingFound): + self.current_phase = "configuration lookup" + # FIXME: hard coding, should at least be a variable shared between + # here and the caller + if event._pattern == "rootfs_": + formats = [] + for match in event._matches: + classname, sep, cls = match.rpartition(".") + fs, sep, format = classname.rpartition("_") + formats.append(format) + formats.sort() + self.emit("package-formats-updated", formats) + elif isinstance(event, bb.command.CommandCompleted): + self.current_phase = None + self.run_next_command() + elif isinstance(event, bb.command.CommandFailed): + if event.error not in ("Forced shutdown", "Stopped build"): + self.error_msg += event.error + self.commands_async = [] + self.display_error() + elif isinstance(event, (bb.event.ParseStarted, + bb.event.CacheLoadStarted, + bb.event.TreeDataPreparationStarted, + )): + message = {} + message["eventname"] = bb.event.getName(event) + message["current"] = 0 + message["total"] = None + message["title"] = "Parsing recipes" + self.emit("parsing-started", message) + if isinstance(event, bb.event.ParseStarted): + self.parsing = True + elif isinstance(event, (bb.event.ParseProgress, + bb.event.CacheLoadProgress, + bb.event.TreeDataPreparationProgress)): + message = {} + message["eventname"] = bb.event.getName(event) + message["current"] = event.current + message["total"] = event.total + message["title"] = "Parsing recipes" + self.emit("parsing", message) + elif isinstance(event, (bb.event.ParseCompleted, + bb.event.CacheLoadCompleted, + bb.event.TreeDataPreparationCompleted)): + message = {} + message["eventname"] = bb.event.getName(event) + message["current"] = event.total + message["total"] = event.total + message["title"] = "Parsing recipes" + self.emit("parsing-completed", message) + if isinstance(event, bb.event.ParseCompleted): + self.parsing = False + elif isinstance(event, bb.event.NetworkTestFailed): + self.emit("network-failed") + self.run_next_command() + elif isinstance(event, bb.event.NetworkTestPassed): + self.emit("network-passed") + self.run_next_command() + + if self.error_msg and not self.commands_async: + self.display_error() + + return + + def init_cooker(self): + self.runCommand(["createConfigFile", ".hob.conf"]) + + def set_extra_inherit(self, bbclass): + self.append_var_in_file("INHERIT", bbclass, ".hob.conf") + + def set_bblayers(self, bblayers): + self.set_var_in_file("BBLAYERS", " ".join(bblayers), "bblayers.conf") + + def set_machine(self, machine): + if machine: + self.early_assign_var_in_file("MACHINE", machine, "local.conf") + + def set_sdk_machine(self, sdk_machine): + self.set_var_in_file("SDKMACHINE", sdk_machine, "local.conf") + + def set_image_fstypes(self, image_fstypes): + self.set_var_in_file("IMAGE_FSTYPES", image_fstypes, "local.conf") + + def set_distro(self, distro): + self.set_var_in_file("DISTRO", distro, "local.conf") + + def set_package_format(self, format): + package_classes = "" + for pkgfmt in format.split(): + package_classes += ("package_%s" % pkgfmt + " ") + self.set_var_in_file("PACKAGE_CLASSES", package_classes, "local.conf") + + def set_bbthreads(self, threads): + self.set_var_in_file("BB_NUMBER_THREADS", threads, "local.conf") + + def set_pmake(self, threads): + pmake = "-j %s" % threads + self.set_var_in_file("PARALLEL_MAKE", pmake, "local.conf") + + def set_dl_dir(self, directory): + self.set_var_in_file("DL_DIR", directory, "local.conf") + + def set_sstate_dir(self, directory): + self.set_var_in_file("SSTATE_DIR", directory, "local.conf") + + def set_sstate_mirrors(self, url): + self.set_var_in_file("SSTATE_MIRRORS", url, "local.conf") + + def set_extra_size(self, image_extra_size): + self.set_var_in_file("IMAGE_ROOTFS_EXTRA_SPACE", str(image_extra_size), "local.conf") + + def set_rootfs_size(self, image_rootfs_size): + self.set_var_in_file("IMAGE_ROOTFS_SIZE", str(image_rootfs_size), "local.conf") + + def set_incompatible_license(self, incompat_license): + self.set_var_in_file("INCOMPATIBLE_LICENSE", incompat_license, "local.conf") + + def set_extra_setting(self, extra_setting): + self.set_var_in_file("EXTRA_SETTING", extra_setting, "local.conf") + + def set_extra_config(self, extra_setting): + old_extra_setting = self.runCommand(["getVariable", "EXTRA_SETTING"]) or {} + old_extra_setting = str(old_extra_setting) + + old_extra_setting = ast.literal_eval(old_extra_setting) + if not type(old_extra_setting) == dict: + old_extra_setting = {} + + # settings not changed + if old_extra_setting == extra_setting: + return + + # remove the old EXTRA SETTING variable + self.remove_var_from_file("EXTRA_SETTING") + + # remove old settings from conf + for key in old_extra_setting.keys(): + if key not in extra_setting: + self.remove_var_from_file(key) + + # add new settings + for key, value in extra_setting.iteritems(): + self.set_var_in_file(key, value, "local.conf") + + if extra_setting: + self.set_var_in_file("EXTRA_SETTING", extra_setting, "local.conf") + + def set_http_proxy(self, http_proxy): + self.set_var_in_file("http_proxy", http_proxy, "local.conf") + + def set_https_proxy(self, https_proxy): + self.set_var_in_file("https_proxy", https_proxy, "local.conf") + + def set_ftp_proxy(self, ftp_proxy): + self.set_var_in_file("ftp_proxy", ftp_proxy, "local.conf") + + def set_socks_proxy(self, socks_proxy): + self.set_var_in_file("all_proxy", socks_proxy, "local.conf") + + def set_cvs_proxy(self, host, port): + self.set_var_in_file("CVS_PROXY_HOST", host, "local.conf") + self.set_var_in_file("CVS_PROXY_PORT", port, "local.conf") + + def request_package_info(self): + self.commands_async.append(self.SUB_GENERATE_PKGINFO) + self.run_next_command(self.POPULATE_PACKAGEINFO) + + def trigger_sanity_check(self): + self.commands_async.append(self.SUB_SANITY_CHECK) + self.run_next_command(self.SANITY_CHECK) + + def trigger_network_test(self): + self.commands_async.append(self.SUB_NETWORK_TEST) + self.run_next_command(self.NETWORK_TEST) + + def generate_configuration(self): + self.runCommand(["setPrePostConfFiles", "conf/.hob.conf", ""]) + self.commands_async.append(self.SUB_PARSE_CONFIG) + self.commands_async.append(self.SUB_PATH_LAYERS) + self.commands_async.append(self.SUB_FILES_DISTRO) + self.commands_async.append(self.SUB_FILES_MACH) + self.commands_async.append(self.SUB_FILES_SDKMACH) + self.commands_async.append(self.SUB_MATCH_CLASS) + self.run_next_command(self.GENERATE_CONFIGURATION) + + def generate_recipes(self): + self.runCommand(["setPrePostConfFiles", "conf/.hob.conf", ""]) + self.commands_async.append(self.SUB_PARSE_CONFIG) + self.commands_async.append(self.SUB_GNERATE_TGTS) + self.run_next_command(self.GENERATE_RECIPES) + + def generate_packages(self, tgts, default_task="build"): + targets = [] + targets.extend(tgts) + self.recipe_queue = targets + self.default_task = default_task + self.runCommand(["setPrePostConfFiles", "conf/.hob.conf", ""]) + self.commands_async.append(self.SUB_PARSE_CONFIG) + self.commands_async.append(self.SUB_BUILD_RECIPES) + self.run_next_command(self.GENERATE_PACKAGES) + + def generate_image(self, image, base_image, image_packages=[], toolchain_packages=[], default_task="build"): + self.image = image + self.base_image = base_image + self.package_queue = image_packages + self.toolchain_packages = toolchain_packages + self.default_task = default_task + self.runCommand(["setPrePostConfFiles", "conf/.hob.conf", ""]) + self.commands_async.append(self.SUB_PARSE_CONFIG) + self.commands_async.append(self.SUB_BUILD_IMAGE) + self.run_next_command(self.GENERATE_IMAGE) + + def generate_new_image(self, image, base_image, package_queue, description): + if base_image: + base_image = self.runCommand(["matchFile", self.base_image + ".bb"]) + self.runCommand(["generateNewImage", image, base_image, package_queue, False, description]) + + def generate_hob_base_image(self, hob_image): + image_dir = self.get_topdir() + "/recipes/images/" + recipe_name = hob_image + ".bb" + self.ensure_dir(image_dir) + self.generate_new_image(image_dir + recipe_name, None, [], "") + + def ensure_dir(self, directory): + self.runCommand(["ensureDir", directory]) + + def build_succeeded_async(self): + self.building = False + + def build_failed_async(self): + self.initcmd = None + self.commands_async = [] + self.building = False + + def cancel_parse(self): + self.runCommand(["stateForceShutdown"]) + + def cancel_build(self, force=False): + if force: + # Force the cooker to stop as quickly as possible + self.runCommand(["stateForceShutdown"]) + else: + # Wait for tasks to complete before shutting down, this helps + # leave the workdir in a usable state + self.runCommand(["stateShutdown"]) + + def reset_build(self): + self.build.reset() + + def get_logfile(self): + return self.server.runCommand(["getVariable", "BB_CONSOLELOG"])[0] + + def get_topdir(self): + return self.runCommand(["getVariable", "TOPDIR"]) or "" + + def _remove_redundant(self, string): + ret = [] + for i in string.split(): + if i not in ret: + ret.append(i) + return " ".join(ret) + + def set_var_in_file(self, var, val, default_file=None): + self.runCommand(["enableDataTracking"]) + self.server.runCommand(["setVarFile", var, val, default_file, "set"]) + self.runCommand(["disableDataTracking"]) + + def early_assign_var_in_file(self, var, val, default_file=None): + self.runCommand(["enableDataTracking"]) + self.server.runCommand(["setVarFile", var, val, default_file, "earlyAssign"]) + self.runCommand(["disableDataTracking"]) + + def remove_var_from_file(self, var): + self.server.runCommand(["removeVarFile", var]) + + def append_var_in_file(self, var, val, default_file=None): + self.server.runCommand(["setVarFile", var, val, default_file, "append"]) + + def append_to_bbfiles(self, val): + bbfiles = self.runCommand(["getVariable", "BBFILES", "False"]) or "" + bbfiles = bbfiles.split() + if val not in bbfiles: + self.append_var_in_file("BBFILES", val, "bblayers.conf") + + def get_parameters(self): + # retrieve the parameters from bitbake + params = {} + params["core_base"] = self.runCommand(["getVariable", "COREBASE"]) or "" + params["layer"] = self.runCommand(["getVariable", "BBLAYERS"]) or "" + params["layers_non_removable"] = self.runCommand(["getVariable", "BBLAYERS_NON_REMOVABLE"]) or "" + params["dldir"] = self.runCommand(["getVariable", "DL_DIR"]) or "" + params["machine"] = self.runCommand(["getVariable", "MACHINE"]) or "" + params["distro"] = self.runCommand(["getVariable", "DISTRO"]) or "defaultsetup" + params["pclass"] = self.runCommand(["getVariable", "PACKAGE_CLASSES"]) or "" + params["sstatedir"] = self.runCommand(["getVariable", "SSTATE_DIR"]) or "" + params["sstatemirror"] = self.runCommand(["getVariable", "SSTATE_MIRRORS"]) or "" + + num_threads = self.runCommand(["getCpuCount"]) + if not num_threads: + num_threads = 1 + max_threads = 65536 + else: + try: + num_threads = int(num_threads) + max_threads = 16 * num_threads + except: + num_threads = 1 + max_threads = 65536 + params["max_threads"] = max_threads + + bbthread = self.runCommand(["getVariable", "BB_NUMBER_THREADS"]) + if not bbthread: + bbthread = num_threads + else: + try: + bbthread = int(bbthread) + except: + bbthread = num_threads + params["bbthread"] = bbthread + + pmake = self.runCommand(["getVariable", "PARALLEL_MAKE"]) + if not pmake: + pmake = num_threads + elif isinstance(pmake, int): + pass + else: + try: + pmake = int(pmake.lstrip("-j ")) + except: + pmake = num_threads + params["pmake"] = "-j %s" % pmake + + params["image_addr"] = self.runCommand(["getVariable", "DEPLOY_DIR_IMAGE"]) or "" + + image_extra_size = self.runCommand(["getVariable", "IMAGE_ROOTFS_EXTRA_SPACE"]) + if not image_extra_size: + image_extra_size = 0 + else: + try: + image_extra_size = int(image_extra_size) + except: + image_extra_size = 0 + params["image_extra_size"] = image_extra_size + + image_rootfs_size = self.runCommand(["getVariable", "IMAGE_ROOTFS_SIZE"]) + if not image_rootfs_size: + image_rootfs_size = 0 + else: + try: + image_rootfs_size = int(image_rootfs_size) + except: + image_rootfs_size = 0 + params["image_rootfs_size"] = image_rootfs_size + + image_overhead_factor = self.runCommand(["getVariable", "IMAGE_OVERHEAD_FACTOR"]) + if not image_overhead_factor: + image_overhead_factor = 1 + else: + try: + image_overhead_factor = float(image_overhead_factor) + except: + image_overhead_factor = 1 + params['image_overhead_factor'] = image_overhead_factor + + params["incompat_license"] = self._remove_redundant(self.runCommand(["getVariable", "INCOMPATIBLE_LICENSE"]) or "") + params["sdk_machine"] = self.runCommand(["getVariable", "SDKMACHINE"]) or self.runCommand(["getVariable", "SDK_ARCH"]) or "" + + params["image_fstypes"] = self._remove_redundant(self.runCommand(["getVariable", "IMAGE_FSTYPES"]) or "") + + params["image_types"] = self._remove_redundant(self.runCommand(["getVariable", "IMAGE_TYPES"]) or "") + + params["conf_version"] = self.runCommand(["getVariable", "CONF_VERSION"]) or "" + params["lconf_version"] = self.runCommand(["getVariable", "LCONF_VERSION"]) or "" + + params["runnable_image_types"] = self._remove_redundant(self.runCommand(["getVariable", "RUNNABLE_IMAGE_TYPES"]) or "") + params["runnable_machine_patterns"] = self._remove_redundant(self.runCommand(["getVariable", "RUNNABLE_MACHINE_PATTERNS"]) or "") + params["deployable_image_types"] = self._remove_redundant(self.runCommand(["getVariable", "DEPLOYABLE_IMAGE_TYPES"]) or "") + params["kernel_image_type"] = self.runCommand(["getVariable", "KERNEL_IMAGETYPE"]) or "" + params["tmpdir"] = self.runCommand(["getVariable", "TMPDIR"]) or "" + params["distro_version"] = self.runCommand(["getVariable", "DISTRO_VERSION"]) or "" + params["target_os"] = self.runCommand(["getVariable", "TARGET_OS"]) or "" + params["target_arch"] = self.runCommand(["getVariable", "TARGET_ARCH"]) or "" + params["tune_pkgarch"] = self.runCommand(["getVariable", "TUNE_PKGARCH"]) or "" + params["bb_version"] = self.runCommand(["getVariable", "BB_MIN_VERSION"]) or "" + + params["default_task"] = self.runCommand(["getVariable", "BB_DEFAULT_TASK"]) or "build" + + params["socks_proxy"] = self.runCommand(["getVariable", "all_proxy"]) or "" + params["http_proxy"] = self.runCommand(["getVariable", "http_proxy"]) or "" + params["ftp_proxy"] = self.runCommand(["getVariable", "ftp_proxy"]) or "" + params["https_proxy"] = self.runCommand(["getVariable", "https_proxy"]) or "" + + params["cvs_proxy_host"] = self.runCommand(["getVariable", "CVS_PROXY_HOST"]) or "" + params["cvs_proxy_port"] = self.runCommand(["getVariable", "CVS_PROXY_PORT"]) or "" + + params["image_white_pattern"] = self.runCommand(["getVariable", "BBUI_IMAGE_WHITE_PATTERN"]) or "" + params["image_black_pattern"] = self.runCommand(["getVariable", "BBUI_IMAGE_BLACK_PATTERN"]) or "" + return params diff --git a/bitbake/lib/bb/ui/crumbs/hoblistmodel.py b/bitbake/lib/bb/ui/crumbs/hoblistmodel.py new file mode 100644 index 0000000000..50df156f4d --- /dev/null +++ b/bitbake/lib/bb/ui/crumbs/hoblistmodel.py @@ -0,0 +1,903 @@ +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2011 Intel Corporation +# +# Authored by Joshua Lock +# Authored by Dongxiao Xu +# Authored by Shane Wang +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import gtk +import gobject +from bb.ui.crumbs.hobpages import HobPage + +# +# PackageListModel +# +class PackageListModel(gtk.ListStore): + """ + This class defines an gtk.ListStore subclass which will convert the output + of the bb.event.TargetsTreeGenerated event into a gtk.ListStore whilst also + providing convenience functions to access gtk.TreeModel subclasses which + provide filtered views of the data. + """ + + (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) + + __gsignals__ = { + "package-selection-changed" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + ()), + } + + __toolchain_required_packages__ = ["packagegroup-core-standalone-sdk-target", "packagegroup-core-standalone-sdk-target-dbg"] + + def __init__(self): + self.rprov_pkg = {} + gtk.ListStore.__init__ (self, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_BOOLEAN, + gobject.TYPE_BOOLEAN, + gobject.TYPE_STRING, + gobject.TYPE_STRING) + self.sort_column_id, self.sort_order = PackageListModel.COL_NAME, gtk.SORT_ASCENDING + + """ + Find the model path for the item_name + Returns the path in the model or None + """ + def find_path_for_item(self, item_name): + pkg = item_name + if item_name not in self.pn_path.keys(): + if item_name not in self.rprov_pkg.keys(): + return None + pkg = self.rprov_pkg[item_name] + if pkg not in self.pn_path.keys(): + return None + + return self.pn_path[pkg] + + def find_item_for_path(self, item_path): + return self[item_path][self.COL_NAME] + + """ + Helper function to determine whether an item is an item specified by filter + """ + def tree_model_filter(self, model, it, filter): + name = model.get_value(it, self.COL_NAME) + + for key in filter.keys(): + if key == self.COL_NAME: + if filter[key] != 'Search packages by name': + if name and filter[key] not in name: + return False + else: + if model.get_value(it, key) not in filter[key]: + return False + self.filtered_nb += 1 + return True + + """ + Create, if required, and return a filtered gtk.TreeModelSort + containing only the items specified by filter + """ + def tree_model(self, filter, excluded_items_ahead=False, included_items_ahead=False, search_data=None, initial=False): + model = self.filter_new() + self.filtered_nb = 0 + model.set_visible_func(self.tree_model_filter, filter) + + sort = gtk.TreeModelSort(model) + sort.connect ('sort-column-changed', self.sort_column_changed_cb) + if initial: + sort.set_sort_column_id(PackageListModel.COL_NAME, gtk.SORT_ASCENDING) + sort.set_default_sort_func(None) + elif excluded_items_ahead: + sort.set_default_sort_func(self.exclude_item_sort_func, search_data) + elif included_items_ahead: + sort.set_default_sort_func(self.include_item_sort_func, search_data) + else: + if search_data and search_data!='Search recipes by name' and search_data!='Search package groups by name': + sort.set_default_sort_func(self.sort_func, search_data) + else: + sort.set_sort_column_id(self.sort_column_id, self.sort_order) + sort.set_default_sort_func(None) + + sort.set_sort_func(PackageListModel.COL_INC, self.sort_column, PackageListModel.COL_INC) + sort.set_sort_func(PackageListModel.COL_SIZE, self.sort_column, PackageListModel.COL_SIZE) + sort.set_sort_func(PackageListModel.COL_BINB, self.sort_binb_column) + sort.set_sort_func(PackageListModel.COL_RCP, self.sort_column, PackageListModel.COL_RCP) + return sort + + def sort_column_changed_cb (self, data): + self.sort_column_id, self.sort_order = data.get_sort_column_id () + + def sort_column(self, model, row1, row2, col): + value1 = model.get_value(row1, col) + value2 = model.get_value(row2, col) + if col==PackageListModel.COL_SIZE: + value1 = HobPage._string_to_size(value1) + value2 = HobPage._string_to_size(value2) + + cmp_res = cmp(value1, value2) + if cmp_res!=0: + if col==PackageListModel.COL_INC: + return -cmp_res + else: + return cmp_res + else: + name1 = model.get_value(row1, PackageListModel.COL_NAME) + name2 = model.get_value(row2, PackageListModel.COL_NAME) + return cmp(name1,name2) + + def sort_binb_column(self, model, row1, row2): + value1 = model.get_value(row1, PackageListModel.COL_BINB) + value2 = model.get_value(row2, PackageListModel.COL_BINB) + value1_list = value1.split(', ') + value2_list = value2.split(', ') + + value1 = value1_list[0] + value2 = value2_list[0] + + cmp_res = cmp(value1, value2) + if cmp_res==0: + cmp_size = cmp(len(value1_list), len(value2_list)) + if cmp_size==0: + name1 = model.get_value(row1, PackageListModel.COL_NAME) + name2 = model.get_value(row2, PackageListModel.COL_NAME) + return cmp(name1,name2) + else: + return cmp_size + else: + return cmp_res + + def exclude_item_sort_func(self, model, iter1, iter2, user_data=None): + if user_data: + val1 = model.get_value(iter1, PackageListModel.COL_NAME) + val2 = model.get_value(iter2, PackageListModel.COL_NAME) + return self.cmp_vals(val1, val2, user_data) + else: + val1 = model.get_value(iter1, PackageListModel.COL_FADE_INC) + val2 = model.get_value(iter2, PackageListModel.COL_INC) + return ((val1 == True) and (val2 == False)) + + def include_item_sort_func(self, model, iter1, iter2, user_data=None): + if user_data: + val1 = model.get_value(iter1, PackageListModel.COL_NAME) + val2 = model.get_value(iter2, PackageListModel.COL_NAME) + return self.cmp_vals(val1, val2, user_data) + else: + val1 = model.get_value(iter1, PackageListModel.COL_INC) + val2 = model.get_value(iter2, PackageListModel.COL_INC) + return ((val1 == False) and (val2 == True)) + + def sort_func(self, model, iter1, iter2, user_data): + val1 = model.get_value(iter1, PackageListModel.COL_NAME) + val2 = model.get_value(iter2, PackageListModel.COL_NAME) + return self.cmp_vals(val1, val2, user_data) + + def cmp_vals(self, val1, val2, user_data): + if val1 is None or val2 is None: + return 0 + elif val1.startswith(user_data) and not val2.startswith(user_data): + return -1 + elif not val1.startswith(user_data) and val2.startswith(user_data): + return 1 + else: + return cmp(val1, val2) + + def convert_vpath_to_path(self, view_model, view_path): + # view_model is the model sorted + # get the path of the model filtered + filtered_model_path = view_model.convert_path_to_child_path(view_path) + # get the model filtered + filtered_model = view_model.get_model() + # get the path of the original model + path = filtered_model.convert_path_to_child_path(filtered_model_path) + return path + + def convert_path_to_vpath(self, view_model, path): + it = view_model.get_iter_first() + while it: + name = self.find_item_for_path(path) + view_name = view_model.get_value(it, PackageListModel.COL_NAME) + if view_name == name: + view_path = view_model.get_path(it) + return view_path + it = view_model.iter_next(it) + return None + + """ + The populate() function takes as input the data from a + bb.event.PackageInfo event and populates the package list. + """ + def populate(self, pkginfolist): + # First clear the model, in case repopulating + self.clear() + + def getpkgvalue(pkgdict, key, pkgname, defaultval = None): + value = pkgdict.get('%s_%s' % (key, pkgname), None) + if not value: + value = pkgdict.get(key, defaultval) + return value + + for pkginfo in pkginfolist: + pn = pkginfo['PN'] + pv = pkginfo['PV'] + pr = pkginfo['PR'] + pkg = pkginfo['PKG'] + pkgv = getpkgvalue(pkginfo, 'PKGV', pkg) + pkgr = getpkgvalue(pkginfo, 'PKGR', pkg) + # PKGSIZE is artificial, will always be overridden with the package name if present + pkgsize = int(pkginfo.get('PKGSIZE_%s' % pkg, "0")) + # PKG_%s is the renamed version + pkg_rename = pkginfo.get('PKG_%s' % pkg, "") + # The rest may be overridden or not + section = getpkgvalue(pkginfo, 'SECTION', pkg, "") + summary = getpkgvalue(pkginfo, 'SUMMARY', pkg, "") + rdep = getpkgvalue(pkginfo, 'RDEPENDS', pkg, "") + rrec = getpkgvalue(pkginfo, 'RRECOMMENDS', pkg, "") + rprov = getpkgvalue(pkginfo, 'RPROVIDES', pkg, "") + files_list = getpkgvalue(pkginfo, 'FILES_INFO', pkg, "") + for i in rprov.split(): + self.rprov_pkg[i] = pkg + + recipe = pn + '-' + pv + '-' + pr + + allow_empty = getpkgvalue(pkginfo, 'ALLOW_EMPTY', pkg, "") + + if pkgsize == 0 and not allow_empty: + continue + + size = HobPage._size_to_string(pkgsize) + self.set(self.append(), self.COL_NAME, pkg, self.COL_VER, pkgv, + self.COL_REV, pkgr, self.COL_RNM, pkg_rename, + self.COL_SEC, section, self.COL_SUM, summary, + self.COL_RDEP, rdep + ' ' + rrec, + self.COL_RPROV, rprov, self.COL_SIZE, size, + self.COL_RCP, recipe, self.COL_BINB, "", + self.COL_INC, False, self.COL_FONT, '10', self.COL_FLIST, files_list) + + self.pn_path = {} + it = self.get_iter_first() + while it: + pn = self.get_value(it, self.COL_NAME) + path = self.get_path(it) + self.pn_path[pn] = path + it = self.iter_next(it) + + """ + Update the model, send out the notification. + """ + def selection_change_notification(self): + self.emit("package-selection-changed") + + """ + Check whether the item at item_path is included or not + """ + def path_included(self, item_path): + return self[item_path][self.COL_INC] + + """ + Add this item, and any of its dependencies, to the image contents + """ + def include_item(self, item_path, binb=""): + if self.path_included(item_path): + return + + item_name = self[item_path][self.COL_NAME] + item_deps = self[item_path][self.COL_RDEP] + + self[item_path][self.COL_INC] = True + + item_bin = self[item_path][self.COL_BINB].split(', ') + if binb and not binb in item_bin: + item_bin.append(binb) + self[item_path][self.COL_BINB] = ', '.join(item_bin).lstrip(', ') + + if item_deps: + # Ensure all of the items deps are included and, where appropriate, + # add this item to their COL_BINB + for dep in item_deps.split(" "): + if dep.startswith('('): + continue + # If the contents model doesn't already contain dep, add it + dep_path = self.find_path_for_item(dep) + if not dep_path: + continue + dep_included = self.path_included(dep_path) + + if dep_included and not dep in item_bin: + # don't set the COL_BINB to this item if the target is an + # item in our own COL_BINB + dep_bin = self[dep_path][self.COL_BINB].split(', ') + if not item_name in dep_bin: + dep_bin.append(item_name) + self[dep_path][self.COL_BINB] = ', '.join(dep_bin).lstrip(', ') + elif not dep_included: + self.include_item(dep_path, binb=item_name) + + def exclude_item(self, item_path): + if not self.path_included(item_path): + return + + self[item_path][self.COL_INC] = False + + item_name = self[item_path][self.COL_NAME] + item_deps = self[item_path][self.COL_RDEP] + if item_deps: + for dep in item_deps.split(" "): + if dep.startswith('('): + continue + dep_path = self.find_path_for_item(dep) + if not dep_path: + continue + dep_bin = self[dep_path][self.COL_BINB].split(', ') + if item_name in dep_bin: + dep_bin.remove(item_name) + self[dep_path][self.COL_BINB] = ', '.join(dep_bin).lstrip(', ') + + item_bin = self[item_path][self.COL_BINB].split(', ') + if item_bin: + for binb in item_bin: + binb_path = self.find_path_for_item(binb) + if not binb_path: + continue + self.exclude_item(binb_path) + + """ + Empty self.contents by setting the include of each entry to None + """ + def reset(self): + it = self.get_iter_first() + while it: + self.set(it, + self.COL_INC, False, + self.COL_BINB, "") + it = self.iter_next(it) + + self.selection_change_notification() + + def get_selected_packages(self): + packagelist = [] + + it = self.get_iter_first() + while it: + if self.get_value(it, self.COL_INC): + name = self.get_value(it, self.COL_NAME) + packagelist.append(name) + it = self.iter_next(it) + + return packagelist + + def get_user_selected_packages(self): + packagelist = [] + + it = self.get_iter_first() + while it: + if self.get_value(it, self.COL_INC): + binb = self.get_value(it, self.COL_BINB) + if binb == "User Selected": + name = self.get_value(it, self.COL_NAME) + packagelist.append(name) + it = self.iter_next(it) + + return packagelist + + def get_selected_packages_toolchain(self): + packagelist = [] + + it = self.get_iter_first() + while it: + if self.get_value(it, self.COL_INC): + name = self.get_value(it, self.COL_NAME) + if name.endswith("-dev") or name.endswith("-dbg"): + packagelist.append(name) + it = self.iter_next(it) + + return list(set(packagelist + self.__toolchain_required_packages__)); + + """ + Package model may be incomplete, therefore when calling the + set_selected_packages(), some packages will not be set included. + Return the un-set packages list. + """ + def set_selected_packages(self, packagelist, user_selected=False): + left = [] + binb = 'User Selected' if user_selected else '' + for pn in packagelist: + if pn in self.pn_path.keys(): + path = self.pn_path[pn] + self.include_item(item_path=path, binb=binb) + else: + left.append(pn) + + self.selection_change_notification() + return left + + """ + Return the selected package size, unit is B. + """ + def get_packages_size(self): + packages_size = 0 + it = self.get_iter_first() + while it: + if self.get_value(it, self.COL_INC): + str_size = self.get_value(it, self.COL_SIZE) + if not str_size: + continue + + packages_size += HobPage._string_to_size(str_size) + + it = self.iter_next(it) + return packages_size + + """ + Resync the state of included items to a backup column before performing the fadeout visible effect + """ + def resync_fadeout_column(self, model_first_iter=None): + it = model_first_iter + while it: + active = self.get_value(it, self.COL_INC) + self.set(it, self.COL_FADE_INC, active) + it = self.iter_next(it) + +# +# RecipeListModel +# +class RecipeListModel(gtk.ListStore): + """ + This class defines an gtk.ListStore subclass which will convert the output + of the bb.event.TargetsTreeGenerated event into a gtk.ListStore whilst also + providing convenience functions to access gtk.TreeModel subclasses which + provide filtered views of the data. + """ + (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, + COL_REVISION, COL_HOMEPAGE, COL_BUGTRACKER, COL_FILE) = range(18) + + __custom_image__ = "Start with an empty image recipe" + + __gsignals__ = { + "recipe-selection-changed" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + ()), + } + + """ + """ + def __init__(self): + gtk.ListStore.__init__ (self, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_BOOLEAN, + gobject.TYPE_BOOLEAN, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_BOOLEAN, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING) + self.sort_column_id, self.sort_order = RecipeListModel.COL_NAME, gtk.SORT_ASCENDING + + """ + Find the model path for the item_name + Returns the path in the model or None + """ + def find_path_for_item(self, item_name): + if self.non_target_name(item_name) or item_name not in self.pn_path.keys(): + return None + else: + return self.pn_path[item_name] + + def find_item_for_path(self, item_path): + return self[item_path][self.COL_NAME] + + """ + Helper method to determine whether name is a target pn + """ + def non_target_name(self, name): + if name and ('-native' in name): + return True + return False + + """ + Helper function to determine whether an item is an item specified by filter + """ + def tree_model_filter(self, model, it, filter): + name = model.get_value(it, self.COL_NAME) + if self.non_target_name(name): + return False + + for key in filter.keys(): + if key == self.COL_NAME: + if filter[key] != 'Search recipes by name' and filter[key] != 'Search package groups by name': + if filter[key] not in name: + return False + else: + if model.get_value(it, key) not in filter[key]: + return False + self.filtered_nb += 1 + + return True + + def exclude_item_sort_func(self, model, iter1, iter2, user_data=None): + if user_data: + val1 = model.get_value(iter1, RecipeListModel.COL_NAME) + val2 = model.get_value(iter2, RecipeListModel.COL_NAME) + return self.cmp_vals(val1, val2, user_data) + else: + val1 = model.get_value(iter1, RecipeListModel.COL_FADE_INC) + val2 = model.get_value(iter2, RecipeListModel.COL_INC) + return ((val1 == True) and (val2 == False)) + + def include_item_sort_func(self, model, iter1, iter2, user_data=None): + if user_data: + val1 = model.get_value(iter1, RecipeListModel.COL_NAME) + val2 = model.get_value(iter2, RecipeListModel.COL_NAME) + return self.cmp_vals(val1, val2, user_data) + else: + val1 = model.get_value(iter1, RecipeListModel.COL_INC) + val2 = model.get_value(iter2, RecipeListModel.COL_INC) + return ((val1 == False) and (val2 == True)) + + def sort_func(self, model, iter1, iter2, user_data): + val1 = model.get_value(iter1, RecipeListModel.COL_NAME) + val2 = model.get_value(iter2, RecipeListModel.COL_NAME) + return self.cmp_vals(val1, val2, user_data) + + def cmp_vals(self, val1, val2, user_data): + if val1 is None or val2 is None: + return 0 + elif val1.startswith(user_data) and not val2.startswith(user_data): + return -1 + elif not val1.startswith(user_data) and val2.startswith(user_data): + return 1 + else: + return cmp(val1, val2) + + """ + Create, if required, and return a filtered gtk.TreeModelSort + containing only the items specified by filter + """ + def tree_model(self, filter, excluded_items_ahead=False, included_items_ahead=False, search_data=None, initial=False): + model = self.filter_new() + self.filtered_nb = 0 + model.set_visible_func(self.tree_model_filter, filter) + + sort = gtk.TreeModelSort(model) + sort.connect ('sort-column-changed', self.sort_column_changed_cb) + if initial: + sort.set_sort_column_id(RecipeListModel.COL_NAME, gtk.SORT_ASCENDING) + sort.set_default_sort_func(None) + elif excluded_items_ahead: + sort.set_default_sort_func(self.exclude_item_sort_func, search_data) + elif included_items_ahead: + sort.set_default_sort_func(self.include_item_sort_func, search_data) + else: + if search_data and search_data!='Search recipes by name' and search_data!='Search package groups by name': + sort.set_default_sort_func(self.sort_func, search_data) + else: + sort.set_sort_column_id(self.sort_column_id, self.sort_order) + sort.set_default_sort_func(None) + + sort.set_sort_func(RecipeListModel.COL_INC, self.sort_column, RecipeListModel.COL_INC) + sort.set_sort_func(RecipeListModel.COL_GROUP, self.sort_column, RecipeListModel.COL_GROUP) + sort.set_sort_func(RecipeListModel.COL_BINB, self.sort_binb_column) + sort.set_sort_func(RecipeListModel.COL_LIC, self.sort_column, RecipeListModel.COL_LIC) + return sort + + def sort_column_changed_cb (self, data): + self.sort_column_id, self.sort_order = data.get_sort_column_id () + + def sort_column(self, model, row1, row2, col): + value1 = model.get_value(row1, col) + value2 = model.get_value(row2, col) + cmp_res = cmp(value1, value2) + if cmp_res!=0: + if col==RecipeListModel.COL_INC: + return -cmp_res + else: + return cmp_res + else: + name1 = model.get_value(row1, RecipeListModel.COL_NAME) + name2 = model.get_value(row2, RecipeListModel.COL_NAME) + return cmp(name1,name2) + + def sort_binb_column(self, model, row1, row2): + value1 = model.get_value(row1, RecipeListModel.COL_BINB) + value2 = model.get_value(row2, RecipeListModel.COL_BINB) + value1_list = value1.split(', ') + value2_list = value2.split(', ') + + value1 = value1_list[0] + value2 = value2_list[0] + + cmp_res = cmp(value1, value2) + if cmp_res==0: + cmp_size = cmp(len(value1_list), len(value2_list)) + if cmp_size==0: + name1 = model.get_value(row1, RecipeListModel.COL_NAME) + name2 = model.get_value(row2, RecipeListModel.COL_NAME) + return cmp(name1,name2) + else: + return cmp_size + else: + return cmp_res + + def convert_vpath_to_path(self, view_model, view_path): + filtered_model_path = view_model.convert_path_to_child_path(view_path) + filtered_model = view_model.get_model() + + # get the path of the original model + path = filtered_model.convert_path_to_child_path(filtered_model_path) + return path + + def convert_path_to_vpath(self, view_model, path): + it = view_model.get_iter_first() + while it: + name = self.find_item_for_path(path) + view_name = view_model.get_value(it, RecipeListModel.COL_NAME) + if view_name == name: + view_path = view_model.get_path(it) + return view_path + it = view_model.iter_next(it) + return None + + """ + The populate() function takes as input the data from a + bb.event.TargetsTreeGenerated event and populates the RecipeList. + """ + def populate(self, event_model): + # First clear the model, in case repopulating + self.clear() + + # dummy image for prompt + self.set_in_list(self.__custom_image__, "Use 'Edit image recipe' to customize recipes and packages " \ + "to be included in your image ") + + for item in event_model["pn"]: + name = item + desc = event_model["pn"][item]["description"] + lic = event_model["pn"][item]["license"] + group = event_model["pn"][item]["section"] + inherits = event_model["pn"][item]["inherits"] + summary = event_model["pn"][item]["summary"] + version = event_model["pn"][item]["version"] + revision = event_model["pn"][item]["prevision"] + homepage = event_model["pn"][item]["homepage"] + bugtracker = event_model["pn"][item]["bugtracker"] + filename = event_model["pn"][item]["filename"] + install = [] + + depends = event_model["depends"].get(item, []) + event_model["rdepends-pn"].get(item, []) + + if ('packagegroup.bbclass' in " ".join(inherits)): + atype = 'packagegroup' + elif ('/image.bbclass' in " ".join(inherits)): + if "edited" not in name: + atype = 'image' + install = event_model["rdepends-pkg"].get(item, []) + event_model["rrecs-pkg"].get(item, []) + elif ('meta-' in name): + atype = 'toolchain' + elif (name == 'dummy-image' or name == 'dummy-toolchain'): + atype = 'dummy' + else: + atype = 'recipe' + + self.set(self.append(), self.COL_NAME, item, self.COL_DESC, desc, + self.COL_LIC, lic, self.COL_GROUP, group, + self.COL_DEPS, " ".join(depends), self.COL_BINB, "", + self.COL_TYPE, atype, self.COL_INC, False, + self.COL_IMG, False, self.COL_INSTALL, " ".join(install), self.COL_PN, item, + self.COL_SUMMARY, summary, self.COL_VERSION, version, self.COL_REVISION, revision, + self.COL_HOMEPAGE, homepage, self.COL_BUGTRACKER, bugtracker, + self.COL_FILE, filename) + + self.pn_path = {} + it = self.get_iter_first() + while it: + pn = self.get_value(it, self.COL_NAME) + path = self.get_path(it) + self.pn_path[pn] = path + it = self.iter_next(it) + + def set_in_list(self, item, desc): + self.set(self.append(), self.COL_NAME, item, + self.COL_DESC, desc, + self.COL_LIC, "", self.COL_GROUP, "", + self.COL_DEPS, "", self.COL_BINB, "", + self.COL_TYPE, "image", self.COL_INC, False, + self.COL_IMG, False, self.COL_INSTALL, "", self.COL_PN, item, + self.COL_SUMMARY, "", self.COL_VERSION, "", self.COL_REVISION, "", + self.COL_HOMEPAGE, "", self.COL_BUGTRACKER, "") + self.pn_path = {} + it = self.get_iter_first() + while it: + pn = self.get_value(it, self.COL_NAME) + path = self.get_path(it) + self.pn_path[pn] = path + it = self.iter_next(it) + + """ + Update the model, send out the notification. + """ + def selection_change_notification(self): + self.emit("recipe-selection-changed") + + def path_included(self, item_path): + return self[item_path][self.COL_INC] + + """ + Add this item, and any of its dependencies, to the image contents + """ + def include_item(self, item_path, binb="", image_contents=False): + if self.path_included(item_path): + return + + item_name = self[item_path][self.COL_NAME] + item_deps = self[item_path][self.COL_DEPS] + + self[item_path][self.COL_INC] = True + + item_bin = self[item_path][self.COL_BINB].split(', ') + if binb and not binb in item_bin: + item_bin.append(binb) + self[item_path][self.COL_BINB] = ', '.join(item_bin).lstrip(', ') + + # We want to do some magic with things which are brought in by the + # base image so tag them as so + if image_contents: + self[item_path][self.COL_IMG] = True + + if item_deps: + # Ensure all of the items deps are included and, where appropriate, + # add this item to their COL_BINB + for dep in item_deps.split(" "): + # If the contents model doesn't already contain dep, add it + dep_path = self.find_path_for_item(dep) + if not dep_path: + continue + dep_included = self.path_included(dep_path) + + if dep_included and not dep in item_bin: + # don't set the COL_BINB to this item if the target is an + # item in our own COL_BINB + dep_bin = self[dep_path][self.COL_BINB].split(', ') + if not item_name in dep_bin: + dep_bin.append(item_name) + self[dep_path][self.COL_BINB] = ', '.join(dep_bin).lstrip(', ') + elif not dep_included: + self.include_item(dep_path, binb=item_name, image_contents=image_contents) + dep_bin = self[item_path][self.COL_BINB].split(', ') + if self[item_path][self.COL_NAME] in dep_bin: + dep_bin.remove(self[item_path][self.COL_NAME]) + self[item_path][self.COL_BINB] = ', '.join(dep_bin).lstrip(', ') + + def exclude_item(self, item_path): + if not self.path_included(item_path): + return + + self[item_path][self.COL_INC] = False + + item_name = self[item_path][self.COL_NAME] + item_deps = self[item_path][self.COL_DEPS] + if item_deps: + for dep in item_deps.split(" "): + dep_path = self.find_path_for_item(dep) + if not dep_path: + continue + dep_bin = self[dep_path][self.COL_BINB].split(', ') + if item_name in dep_bin: + dep_bin.remove(item_name) + self[dep_path][self.COL_BINB] = ', '.join(dep_bin).lstrip(', ') + + item_bin = self[item_path][self.COL_BINB].split(', ') + if item_bin: + for binb in item_bin: + binb_path = self.find_path_for_item(binb) + if not binb_path: + continue + self.exclude_item(binb_path) + + def reset(self): + it = self.get_iter_first() + while it: + self.set(it, + self.COL_INC, False, + self.COL_BINB, "", + self.COL_IMG, False) + it = self.iter_next(it) + + self.selection_change_notification() + + """ + Returns two lists. One of user selected recipes and the other containing + all selected recipes + """ + def get_selected_recipes(self): + allrecipes = [] + userrecipes = [] + + it = self.get_iter_first() + while it: + if self.get_value(it, self.COL_INC): + name = self.get_value(it, self.COL_PN) + type = self.get_value(it, self.COL_TYPE) + if type != "image": + allrecipes.append(name) + sel = "User Selected" in self.get_value(it, self.COL_BINB) + if sel: + userrecipes.append(name) + it = self.iter_next(it) + + return list(set(userrecipes)), list(set(allrecipes)) + + def set_selected_recipes(self, recipelist): + for pn in recipelist: + if pn in self.pn_path.keys(): + path = self.pn_path[pn] + self.include_item(item_path=path, + binb="User Selected") + self.selection_change_notification() + + def get_selected_image(self): + it = self.get_iter_first() + while it: + if self.get_value(it, self.COL_INC): + name = self.get_value(it, self.COL_PN) + type = self.get_value(it, self.COL_TYPE) + if type == "image": + sel = "User Selected" in self.get_value(it, self.COL_BINB) + if sel: + return name + it = self.iter_next(it) + return None + + def set_selected_image(self, img): + if not img: + return + self.reset() + path = self.find_path_for_item(img) + self.include_item(item_path=path, + binb="User Selected", + image_contents=True) + self.selection_change_notification() + + def set_custom_image_version(self, version): + self.custom_image_version = version + + def get_custom_image_version(self): + return self.custom_image_version + + def is_custom_image(self): + 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 @@ +#!/usr/bin/env python +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2012 Intel Corporation +# +# Authored by Dongxiao Xu +# Authored by Shane Wang +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import gtk +from bb.ui.crumbs.hobcolor import HobColors +from bb.ui.crumbs.hobwidget import hwc + +# +# HobPage: the super class for all Hob-related pages +# +class HobPage (gtk.VBox): + + def __init__(self, builder, title = None): + super(HobPage, self).__init__(False, 0) + self.builder = builder + self.builder_width, self.builder_height = self.builder.size_request() + + if not title: + self.title = "Hob -- Image Creator" + else: + self.title = title + self.title_label = gtk.Label() + + self.box_group_area = gtk.VBox(False, 12) + self.box_group_area.set_size_request(self.builder_width - 73 - 73, self.builder_height - 88 - 15 - 15) + self.group_align = gtk.Alignment(xalign = 0, yalign=0.5, xscale=1, yscale=1) + self.group_align.set_padding(15, 15, 73, 73) + self.group_align.add(self.box_group_area) + self.box_group_area.set_homogeneous(False) + + def set_title(self, title): + self.title = title + self.title_label.set_markup("%s" % self.title) + + def add_onto_top_bar(self, widget = None, padding = 0): + # the top button occupies 1/7 of the page height + # setup an event box + eventbox = gtk.EventBox() + style = eventbox.get_style().copy() + style.bg[gtk.STATE_NORMAL] = eventbox.get_colormap().alloc_color(HobColors.LIGHT_GRAY, False, False) + eventbox.set_style(style) + eventbox.set_size_request(-1, 88) + + hbox = gtk.HBox() + + self.title_label = gtk.Label() + self.title_label.set_markup("%s" % self.title) + hbox.pack_start(self.title_label, expand=False, fill=False, padding=20) + + if widget: + # add the widget in the event box + hbox.pack_end(widget, expand=False, fill=False, padding=padding) + eventbox.add(hbox) + + return eventbox + + def span_tag(self, size="medium", weight="normal", forground="#1c1c1c"): + span_tag = "weight='%s' foreground='%s' size='%s'" % (weight, forground, size) + return span_tag + + def append_toolbar_button(self, toolbar, buttonname, icon_disp, icon_hovor, tip, cb): + # Create a button and append it on the toolbar according to button name + icon = gtk.Image() + icon_display = icon_disp + icon_hover = icon_hovor + pix_buffer = gtk.gdk.pixbuf_new_from_file(icon_display) + icon.set_from_pixbuf(pix_buffer) + tip_text = tip + button = toolbar.append_item(buttonname, tip, None, icon, cb) + return button + + @staticmethod + def _size_to_string(size): + try: + if not size: + size_str = "0 B" + else: + if len(str(int(size))) > 6: + size_str = '%.1f' % (size*1.0/(1024*1024)) + ' MB' + elif len(str(int(size))) > 3: + size_str = '%.1f' % (size*1.0/1024) + ' KB' + else: + size_str = str(size) + ' B' + except: + size_str = "0 B" + return size_str + + @staticmethod + def _string_to_size(str_size): + try: + if not str_size: + size = 0 + else: + unit = str_size.split() + if len(unit) > 1: + if unit[1] == 'MB': + size = float(unit[0])*1024*1024 + elif unit[1] == 'KB': + size = float(unit[0])*1024 + elif unit[1] == 'B': + size = float(unit[0]) + else: + size = 0 + else: + size = float(unit[0]) + except: + size = 0 + return size + diff --git a/bitbake/lib/bb/ui/crumbs/hobwidget.py b/bitbake/lib/bb/ui/crumbs/hobwidget.py new file mode 100644 index 0000000000..2b969c146e --- /dev/null +++ b/bitbake/lib/bb/ui/crumbs/hobwidget.py @@ -0,0 +1,904 @@ +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2011-2012 Intel Corporation +# +# Authored by Dongxiao Xu +# Authored by Shane Wang +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +import gtk +import gobject +import os +import os.path +import sys +import pango, pangocairo +import cairo +import math + +from bb.ui.crumbs.hobcolor import HobColors +from bb.ui.crumbs.persistenttooltip import PersistentTooltip + +class hwc: + + MAIN_WIN_WIDTH = 1024 + MAIN_WIN_HEIGHT = 700 + +class hic: + + HOB_ICON_BASE_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), ("ui/icons/")) + + ICON_RCIPE_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('recipe/recipe_display.png')) + ICON_RCIPE_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('recipe/recipe_hover.png')) + ICON_PACKAGES_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('packages/packages_display.png')) + ICON_PACKAGES_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('packages/packages_hover.png')) + ICON_LAYERS_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('layers/layers_display.png')) + ICON_LAYERS_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('layers/layers_hover.png')) + ICON_IMAGES_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('images/images_display.png')) + ICON_IMAGES_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('images/images_hover.png')) + ICON_SETTINGS_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('settings/settings_display.png')) + ICON_SETTINGS_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('settings/settings_hover.png')) + ICON_INFO_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('info/info_display.png')) + ICON_INFO_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('info/info_hover.png')) + ICON_INDI_CONFIRM_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/confirmation.png')) + ICON_INDI_ERROR_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/denied.png')) + ICON_INDI_REMOVE_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/remove.png')) + ICON_INDI_REMOVE_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/remove-hover.png')) + ICON_INDI_ADD_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/add.png')) + ICON_INDI_ADD_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/add-hover.png')) + ICON_INDI_REFRESH_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/refresh.png')) + ICON_INDI_ALERT_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/alert.png')) + ICON_INDI_TICK_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/tick.png')) + ICON_INDI_INFO_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/info.png')) + +class HobViewTable (gtk.VBox): + """ + A VBox to contain the table for different recipe views and package view + """ + __gsignals__ = { + "toggled" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_PYOBJECT, + gobject.TYPE_STRING, + gobject.TYPE_INT, + gobject.TYPE_PYOBJECT,)), + "row-activated" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_PYOBJECT, + gobject.TYPE_PYOBJECT,)), + "cell-fadeinout-stopped" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_PYOBJECT, + gobject.TYPE_PYOBJECT, + gobject.TYPE_PYOBJECT,)), + } + + def __init__(self, columns, name): + gtk.VBox.__init__(self, False, 6) + self.table_tree = gtk.TreeView() + self.table_tree.set_headers_visible(True) + self.table_tree.set_headers_clickable(True) + self.table_tree.set_rules_hint(True) + self.table_tree.set_enable_tree_lines(True) + self.table_tree.get_selection().set_mode(gtk.SELECTION_SINGLE) + self.toggle_columns = [] + self.table_tree.connect("row-activated", self.row_activated_cb) + self.top_bar = None + self.tab_name = name + + for i, column in enumerate(columns): + col_name = column['col_name'] + col = gtk.TreeViewColumn(col_name) + col.set_clickable(True) + col.set_resizable(True) + if self.tab_name.startswith('Included'): + if col_name!='Included': + col.set_sort_column_id(column['col_id']) + else: + col.set_sort_column_id(column['col_id']) + if 'col_min' in column.keys(): + col.set_min_width(column['col_min']) + if 'col_max' in column.keys(): + col.set_max_width(column['col_max']) + if 'expand' in column.keys(): + col.set_expand(True) + self.table_tree.append_column(col) + + if (not 'col_style' in column.keys()) or column['col_style'] == 'text': + cell = gtk.CellRendererText() + col.pack_start(cell, True) + col.set_attributes(cell, text=column['col_id']) + if 'col_t_id' in column.keys(): + col.add_attribute(cell, 'font', column['col_t_id']) + elif column['col_style'] == 'check toggle': + cell = HobCellRendererToggle() + cell.set_property('activatable', True) + cell.connect("toggled", self.toggled_cb, i, self.table_tree) + cell.connect_render_state_changed(self.stop_cell_fadeinout_cb, self.table_tree) + self.toggle_id = i + col.pack_end(cell, True) + col.set_attributes(cell, active=column['col_id']) + self.toggle_columns.append(col_name) + if 'col_group' in column.keys(): + col.set_cell_data_func(cell, self.set_group_number_cb) + elif column['col_style'] == 'radio toggle': + cell = gtk.CellRendererToggle() + cell.set_property('activatable', True) + cell.set_radio(True) + cell.connect("toggled", self.toggled_cb, i, self.table_tree) + self.toggle_id = i + col.pack_end(cell, True) + col.set_attributes(cell, active=column['col_id']) + self.toggle_columns.append(col_name) + elif column['col_style'] == 'binb': + cell = gtk.CellRendererText() + col.pack_start(cell, True) + col.set_cell_data_func(cell, self.display_binb_cb, column['col_id']) + if 'col_t_id' in column.keys(): + col.add_attribute(cell, 'font', column['col_t_id']) + + self.scroll = gtk.ScrolledWindow() + self.scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + self.scroll.add(self.table_tree) + + self.pack_end(self.scroll, True, True, 0) + + def add_no_result_bar(self, entry): + color = HobColors.KHAKI + self.top_bar = gtk.EventBox() + self.top_bar.set_size_request(-1, 70) + self.top_bar.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(color)) + self.top_bar.set_flags(gtk.CAN_DEFAULT) + self.top_bar.grab_default() + + no_result_tab = gtk.Table(5, 20, True) + self.top_bar.add(no_result_tab) + + label = gtk.Label() + label.set_alignment(0.0, 0.5) + title = "No results matching your search" + label.set_markup("%s" % title) + no_result_tab.attach(label, 1, 14, 1, 4) + + clear_button = HobButton("Clear search") + clear_button.set_tooltip_text("Clear search query") + clear_button.connect('clicked', self.set_search_entry_clear_cb, entry) + no_result_tab.attach(clear_button, 16, 19, 1, 4) + + self.pack_start(self.top_bar, False, True, 12) + self.top_bar.show_all() + + def set_search_entry_clear_cb(self, button, search): + if search.get_editable() == True: + search.set_text("") + search.set_icon_sensitive(gtk.ENTRY_ICON_SECONDARY, False) + search.grab_focus() + + def display_binb_cb(self, col, cell, model, it, col_id): + binb = model.get_value(it, col_id) + # Just display the first item + if binb: + bin = binb.split(', ') + total_no = len(bin) + if total_no > 1 and bin[0] == "User Selected": + if total_no > 2: + present_binb = bin[1] + ' (+' + str(total_no - 1) + ')' + else: + present_binb = bin[1] + else: + if total_no > 1: + present_binb = bin[0] + ' (+' + str(total_no - 1) + ')' + else: + present_binb = bin[0] + cell.set_property('text', present_binb) + else: + cell.set_property('text', "") + return True + + def set_model(self, tree_model): + self.table_tree.set_model(tree_model) + + def toggle_default(self): + model = self.table_tree.get_model() + if not model: + return + iter = model.get_iter_first() + if iter: + rowpath = model.get_path(iter) + model[rowpath][self.toggle_id] = True + + def toggled_cb(self, cell, path, columnid, tree): + self.emit("toggled", cell, path, columnid, tree) + + def row_activated_cb(self, tree, path, view_column): + if not view_column.get_title() in self.toggle_columns: + self.emit("row-activated", tree.get_model(), path) + + def stop_cell_fadeinout_cb(self, ctrl, cell, tree): + self.emit("cell-fadeinout-stopped", ctrl, cell, tree) + + def set_group_number_cb(self, col, cell, model, iter): + if model and (model.iter_parent(iter) == None): + cell.cell_attr["number_of_children"] = model.iter_n_children(iter) + else: + cell.cell_attr["number_of_children"] = 0 + + def connect_group_selection(self, cb_func): + self.table_tree.get_selection().connect("changed", cb_func) + +""" +A method to calculate a softened value for the colour of widget when in the +provided state. + +widget: the widget whose style to use +state: the state of the widget to use the style for + +Returns a string value representing the softened colour +""" +def soften_color(widget, state=gtk.STATE_NORMAL): + # this colour munging routine is heavily inspired bu gdu_util_get_mix_color() + # from gnome-disk-utility: + # http://git.gnome.org/browse/gnome-disk-utility/tree/src/gdu-gtk/gdu-gtk.c?h=gnome-3-0 + blend = 0.7 + style = widget.get_style() + color = style.text[state] + color.red = color.red * blend + style.base[state].red * (1.0 - blend) + color.green = color.green * blend + style.base[state].green * (1.0 - blend) + color.blue = color.blue * blend + style.base[state].blue * (1.0 - blend) + return color.to_string() + +class BaseHobButton(gtk.Button): + """ + A gtk.Button subclass which follows the visual design of Hob for primary + action buttons + + label: the text to display as the button's label + """ + def __init__(self, label): + gtk.Button.__init__(self, label) + HobButton.style_button(self) + + @staticmethod + def style_button(button): + style = button.get_style() + style = gtk.rc_get_style_by_paths(gtk.settings_get_default(), 'gtk-button', 'gtk-button', gobject.TYPE_NONE) + + button.set_flags(gtk.CAN_DEFAULT) + button.grab_default() + +# label = "%s" % gobject.markup_escape_text(button.get_label()) + label = button.get_label() + button.set_label(label) + button.child.set_use_markup(True) + +class HobButton(BaseHobButton): + """ + A gtk.Button subclass which follows the visual design of Hob for primary + action buttons + + label: the text to display as the button's label + """ + def __init__(self, label): + BaseHobButton.__init__(self, label) + HobButton.style_button(self) + +class HobAltButton(BaseHobButton): + """ + A gtk.Button subclass which has no relief, and so is more discrete + """ + def __init__(self, label): + BaseHobButton.__init__(self, label) + HobAltButton.style_button(self) + + """ + A callback for the state-changed event to ensure the text is displayed + differently when the widget is not sensitive + """ + @staticmethod + def desensitise_on_state_change_cb(button, state): + if not button.get_property("sensitive"): + HobAltButton.set_text(button, False) + else: + HobAltButton.set_text(button, True) + + """ + Set the button label with an appropriate colour for the current widget state + """ + @staticmethod + def set_text(button, sensitive=True): + if sensitive: + colour = HobColors.PALE_BLUE + else: + colour = HobColors.LIGHT_GRAY + button.set_label("%s" % (colour, gobject.markup_escape_text(button.text))) + button.child.set_use_markup(True) + +class HobImageButton(gtk.Button): + """ + A gtk.Button with an icon and two rows of text, the second of which is + displayed in a blended colour. + + primary_text: the main button label + secondary_text: optional second line of text + icon_path: path to the icon file to display on the button + """ + def __init__(self, primary_text, secondary_text="", icon_path="", hover_icon_path=""): + gtk.Button.__init__(self) + self.set_relief(gtk.RELIEF_NONE) + + self.icon_path = icon_path + self.hover_icon_path = hover_icon_path + + hbox = gtk.HBox(False, 10) + hbox.show() + self.add(hbox) + self.icon = gtk.Image() + self.icon.set_from_file(self.icon_path) + self.icon.set_alignment(0.5, 0.0) + self.icon.show() + if self.hover_icon_path and len(self.hover_icon_path): + self.connect("enter-notify-event", self.set_hover_icon_cb) + self.connect("leave-notify-event", self.set_icon_cb) + hbox.pack_start(self.icon, False, False, 0) + label = gtk.Label() + label.set_alignment(0.0, 0.5) + colour = soften_color(label) + mark = "%s\n%s" % (primary_text, colour, secondary_text) + label.set_markup(mark) + label.show() + hbox.pack_start(label, True, True, 0) + + def set_hover_icon_cb(self, widget, event): + self.icon.set_from_file(self.hover_icon_path) + + def set_icon_cb(self, widget, event): + self.icon.set_from_file(self.icon_path) + +class HobInfoButton(gtk.EventBox): + """ + This class implements a button-like widget per the Hob visual and UX designs + which will display a persistent tooltip, with the contents of tip_markup, when + clicked. + + tip_markup: the Pango Markup to be displayed in the persistent tooltip + """ + def __init__(self, tip_markup, parent=None): + gtk.EventBox.__init__(self) + self.image = gtk.Image() + self.image.set_from_file( + hic.ICON_INFO_DISPLAY_FILE) + self.image.show() + self.add(self.image) + self.tip_markup = tip_markup + self.my_parent = parent + + self.set_events(gtk.gdk.BUTTON_RELEASE | + gtk.gdk.ENTER_NOTIFY_MASK | + gtk.gdk.LEAVE_NOTIFY_MASK) + + self.connect("button-release-event", self.button_release_cb) + self.connect("enter-notify-event", self.mouse_in_cb) + self.connect("leave-notify-event", self.mouse_out_cb) + + """ + When the mouse click is released emulate a button-click and show the associated + PersistentTooltip + """ + def button_release_cb(self, widget, event): + from bb.ui.crumbs.hig.propertydialog import PropertyDialog + self.dialog = PropertyDialog(title = '', + parent = self.my_parent, + information = self.tip_markup, + flags = gtk.DIALOG_DESTROY_WITH_PARENT + | gtk.DIALOG_NO_SEPARATOR) + + button = self.dialog.add_button("Close", gtk.RESPONSE_CANCEL) + HobAltButton.style_button(button) + button.connect("clicked", lambda w: self.dialog.destroy()) + self.dialog.show_all() + self.dialog.run() + + """ + Change to the prelight image when the mouse enters the widget + """ + def mouse_in_cb(self, widget, event): + self.image.set_from_file(hic.ICON_INFO_HOVER_FILE) + + """ + Change to the stock image when the mouse enters the widget + """ + def mouse_out_cb(self, widget, event): + self.image.set_from_file(hic.ICON_INFO_DISPLAY_FILE) + +class HobIndicator(gtk.DrawingArea): + def __init__(self, count): + gtk.DrawingArea.__init__(self) + # Set no window for transparent background + self.set_has_window(False) + self.set_size_request(38,38) + # We need to pass through button clicks + self.add_events(gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK) + + self.connect('expose-event', self.expose) + + self.count = count + self.color = HobColors.GRAY + + def expose(self, widget, event): + if self.count and self.count > 0: + ctx = widget.window.cairo_create() + + x, y, w, h = self.allocation + + ctx.set_operator(cairo.OPERATOR_OVER) + ctx.set_source_color(gtk.gdk.color_parse(self.color)) + ctx.translate(w/2, h/2) + ctx.arc(x, y, min(w,h)/2 - 2, 0, 2*math.pi) + ctx.fill_preserve() + + layout = self.create_pango_layout(str(self.count)) + textw, texth = layout.get_pixel_size() + x = (w/2)-(textw/2) + x + y = (h/2) - (texth/2) + y + ctx.move_to(x, y) + self.window.draw_layout(self.style.light_gc[gtk.STATE_NORMAL], int(x), int(y), layout) + + def set_count(self, count): + self.count = count + + def set_active(self, active): + if active: + self.color = HobColors.DEEP_RED + else: + self.color = HobColors.GRAY + +class HobTabLabel(gtk.HBox): + def __init__(self, text, count=0): + gtk.HBox.__init__(self, False, 0) + self.indicator = HobIndicator(count) + self.indicator.show() + self.pack_end(self.indicator, False, False) + self.lbl = gtk.Label(text) + self.lbl.set_alignment(0.0, 0.5) + self.lbl.show() + self.pack_end(self.lbl, True, True, 6) + + def set_count(self, count): + self.indicator.set_count(count) + + def set_active(self, active=True): + self.indicator.set_active(active) + +class HobNotebook(gtk.Notebook): + def __init__(self): + gtk.Notebook.__init__(self) + self.set_property('homogeneous', True) + + self.pages = [] + + self.search = None + self.search_focus = False + self.page_changed = False + + self.connect("switch-page", self.page_changed_cb) + + self.show_all() + + def page_changed_cb(self, nb, page, page_num): + for p, lbl in enumerate(self.pages): + if p == page_num: + lbl.set_active() + else: + lbl.set_active(False) + + if self.search: + self.page_changed = True + self.reset_entry(self.search, page_num) + + def append_page(self, child, tab_label, tab_tooltip=None): + label = HobTabLabel(tab_label) + if tab_tooltip: + label.set_tooltip_text(tab_tooltip) + label.set_active(False) + self.pages.append(label) + gtk.Notebook.append_page(self, child, label) + + def set_entry(self, names, tips): + self.search = gtk.Entry() + self.search_names = names + self.search_tips = tips + style = self.search.get_style() + style.text[gtk.STATE_NORMAL] = self.get_colormap().alloc_color(HobColors.GRAY, False, False) + self.search.set_style(style) + self.search.set_text(names[0]) + self.search.set_tooltip_text(self.search_tips[0]) + self.search.props.has_tooltip = True + + self.search.set_editable(False) + self.search.set_icon_from_stock(gtk.ENTRY_ICON_SECONDARY, gtk.STOCK_CLEAR) + self.search.set_icon_sensitive(gtk.ENTRY_ICON_SECONDARY, False) + self.search.connect("icon-release", self.set_search_entry_clear_cb) + self.search.set_width_chars(30) + self.search.show() + + self.search.connect("focus-in-event", self.set_search_entry_editable_cb) + self.search.connect("focus-out-event", self.set_search_entry_reset_cb) + self.set_action_widget(self.search, gtk.PACK_END) + + def show_indicator_icon(self, title, number): + for child in self.pages: + if child.lbl.get_label() == title: + child.set_count(number) + + def hide_indicator_icon(self, title): + for child in self.pages: + if child.lbl.get_label() == title: + child.set_count(0) + + def set_search_entry_editable_cb(self, search, event): + self.search_focus = True + search.set_editable(True) + text = search.get_text() + if text in self.search_names: + search.set_text("") + style = self.search.get_style() + style.text[gtk.STATE_NORMAL] = self.get_colormap().alloc_color(HobColors.BLACK, False, False) + search.set_style(style) + + def set_search_entry_reset_cb(self, search, event): + page_num = self.get_current_page() + text = search.get_text() + if not text: + self.reset_entry(search, page_num) + + def reset_entry(self, entry, page_num): + style = entry.get_style() + style.text[gtk.STATE_NORMAL] = self.get_colormap().alloc_color(HobColors.GRAY, False, False) + entry.set_style(style) + entry.set_text(self.search_names[page_num]) + entry.set_tooltip_text(self.search_tips[page_num]) + entry.set_editable(False) + entry.set_icon_sensitive(gtk.ENTRY_ICON_SECONDARY, False) + + def set_search_entry_clear_cb(self, search, icon_pos, event): + if search.get_editable() == True: + search.set_text("") + search.set_icon_sensitive(gtk.ENTRY_ICON_SECONDARY, False) + search.grab_focus() + + def set_page(self, title): + for child in self.pages: + if child.lbl.get_label() == title: + child.grab_focus() + self.set_current_page(self.pages.index(child)) + return + +class HobWarpCellRendererText(gtk.CellRendererText): + def __init__(self, col_number): + gtk.CellRendererText.__init__(self) + self.set_property("wrap-mode", pango.WRAP_WORD_CHAR) + self.set_property("wrap-width", 300) # default value wrap width is 300 + self.col_n = col_number + + def do_render(self, window, widget, background_area, cell_area, expose_area, flags): + if widget: + self.props.wrap_width = self.get_resized_wrap_width(widget, widget.get_column(self.col_n)) + return gtk.CellRendererText.do_render(self, window, widget, background_area, cell_area, expose_area, flags) + + def get_resized_wrap_width(self, treeview, column): + otherCols = [] + for col in treeview.get_columns(): + if col != column: + otherCols.append(col) + adjwidth = treeview.allocation.width - sum(c.get_width() for c in otherCols) + adjwidth -= treeview.style_get_property("horizontal-separator") * 4 + if self.props.wrap_width == adjwidth or adjwidth <= 0: + adjwidth = self.props.wrap_width + return adjwidth + +gobject.type_register(HobWarpCellRendererText) + +class HobIconChecker(hic): + def set_hob_icon_to_stock_icon(self, file_path, stock_id=""): + try: + pixbuf = gtk.gdk.pixbuf_new_from_file(file_path) + except Exception, e: + return None + + if stock_id and (gtk.icon_factory_lookup_default(stock_id) == None): + icon_factory = gtk.IconFactory() + icon_factory.add_default() + icon_factory.add(stock_id, gtk.IconSet(pixbuf)) + gtk.stock_add([(stock_id, '_label', 0, 0, '')]) + + return icon_factory.lookup(stock_id) + + return None + + """ + 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'. + this function check the stock_id and make hob_id to replaced the gtk_id then return it or "" + """ + def check_stock_icon(self, stock_name=""): + HOB_CHECK_STOCK_NAME = { + ('hic-dialog-info', 'gtk-dialog-info', 'dialog-info') : self.ICON_INDI_INFO_FILE, + ('hic-ok', 'gtk-ok', 'ok') : self.ICON_INDI_TICK_FILE, + ('hic-dialog-error', 'gtk-dialog-error', 'dialog-error') : self.ICON_INDI_ERROR_FILE, + ('hic-dialog-warning', 'gtk-dialog-warning', 'dialog-warning') : self.ICON_INDI_ALERT_FILE, + ('hic-task-refresh', 'gtk-execute', 'execute') : self.ICON_INDI_REFRESH_FILE, + } + valid_stock_id = stock_name + if stock_name: + for names, path in HOB_CHECK_STOCK_NAME.iteritems(): + if stock_name in names: + valid_stock_id = names[0] + if not gtk.icon_factory_lookup_default(valid_stock_id): + self.set_hob_icon_to_stock_icon(path, valid_stock_id) + + return valid_stock_id + +class HobCellRendererController(gobject.GObject): + (MODE_CYCLE_RUNNING, MODE_ONE_SHORT) = range(2) + __gsignals__ = { + "run-timer-stopped" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + ()), + } + def __init__(self, runningmode=MODE_CYCLE_RUNNING, is_draw_row=False): + gobject.GObject.__init__(self) + self.timeout_id = None + self.current_angle_pos = 0.0 + self.step_angle = 0.0 + self.tree_headers_height = 0 + self.running_cell_areas = [] + self.running_mode = runningmode + self.is_queue_draw_row_area = is_draw_row + self.force_stop_enable = False + + def is_active(self): + if self.timeout_id: + return True + else: + return False + + def reset_run(self): + self.force_stop() + self.running_cell_areas = [] + self.current_angle_pos = 0.0 + self.step_angle = 0.0 + + ''' time_iterval: (1~1000)ms, which will be as the basic interval count for timer + init_usrdata: the current data which related the progress-bar will be at + min_usrdata: the range of min of user data + max_usrdata: the range of max of user data + step: each step which you want to progress + Note: the init_usrdata should in the range of from min to max, and max should > min + step should < (max - min) + ''' + def start_run(self, time_iterval, init_usrdata, min_usrdata, max_usrdata, step, tree): + if (not time_iterval) or (not max_usrdata): + return + usr_range = (max_usrdata - min_usrdata) * 1.0 + self.current_angle_pos = (init_usrdata * 1.0) / usr_range + self.step_angle = (step * 1) / usr_range + self.timeout_id = gobject.timeout_add(int(time_iterval), + self.make_image_on_progressing_cb, tree) + self.tree_headers_height = self.get_treeview_headers_height(tree) + self.force_stop_enable = False + + def force_stop(self): + self.emit("run-timer-stopped") + self.force_stop_enable = True + if self.timeout_id: + if gobject.source_remove(self.timeout_id): + self.timeout_id = None + + def on_draw_pixbuf_cb(self, pixbuf, cr, x, y, img_width, img_height, do_refresh=True): + if pixbuf: + r = max(img_width/2, img_height/2) + cr.translate(x + r, y + r) + if do_refresh: + cr.rotate(2 * math.pi * self.current_angle_pos) + + cr.set_source_pixbuf(pixbuf, -img_width/2, -img_height/2) + cr.paint() + + def on_draw_fadeinout_cb(self, cr, color, x, y, width, height, do_fadeout=True): + if do_fadeout: + alpha = self.current_angle_pos * 0.8 + else: + alpha = (1.0 - self.current_angle_pos) * 0.8 + + cr.set_source_rgba(color.red, color.green, color.blue, alpha) + cr.rectangle(x, y, width, height) + cr.fill() + + def get_treeview_headers_height(self, tree): + if tree and (tree.get_property("headers-visible") == True): + height = tree.get_allocation().height - tree.get_bin_window().get_size()[1] + return height + + return 0 + + def make_image_on_progressing_cb(self, tree): + self.current_angle_pos += self.step_angle + if self.running_mode == self.MODE_CYCLE_RUNNING: + if (self.current_angle_pos >= 1): + self.current_angle_pos = 0 + else: + if self.current_angle_pos > 1: + self.force_stop() + return False + + if self.is_queue_draw_row_area: + for path in self.running_cell_areas: + rect = tree.get_cell_area(path, tree.get_column(0)) + row_x, _, row_width, _ = tree.get_visible_rect() + tree.queue_draw_area(row_x, rect.y + self.tree_headers_height, row_width, rect.height) + else: + for rect in self.running_cell_areas: + tree.queue_draw_area(rect.x, rect.y + self.tree_headers_height, rect.width, rect.height) + + return (not self.force_stop_enable) + + def append_running_cell_area(self, cell_area): + if cell_area and (cell_area not in self.running_cell_areas): + self.running_cell_areas.append(cell_area) + + def remove_running_cell_area(self, cell_area): + if cell_area in self.running_cell_areas: + self.running_cell_areas.remove(cell_area) + if not self.running_cell_areas: + self.reset_run() + +gobject.type_register(HobCellRendererController) + +class HobCellRendererPixbuf(gtk.CellRendererPixbuf): + def __init__(self): + gtk.CellRendererPixbuf.__init__(self) + self.control = HobCellRendererController() + # add icon checker for make the gtk-icon transfer to hob-icon + self.checker = HobIconChecker() + self.set_property("stock-size", gtk.ICON_SIZE_DND) + + def get_pixbuf_from_stock_icon(self, widget, stock_id="", size=gtk.ICON_SIZE_DIALOG): + if widget and stock_id and gtk.icon_factory_lookup_default(stock_id): + return widget.render_icon(stock_id, size) + + return None + + def set_icon_name_to_id(self, new_name): + if new_name and type(new_name) == str: + # check the name is need to transfer to hob icon or not + name = self.checker.check_stock_icon(new_name) + if name.startswith("hic") or name.startswith("gtk"): + stock_id = name + else: + stock_id = 'gtk-' + name + + return stock_id + + ''' render cell exactly, "icon-name" is priority + if use the 'hic-task-refresh' will make the pix animation + if 'pix' will change the pixbuf for it from the pixbuf or image. + ''' + def do_render(self, window, tree, background_area,cell_area, expose_area, flags): + if (not self.control) or (not tree): + return + + x, y, w, h = self.on_get_size(tree, cell_area) + x += cell_area.x + y += cell_area.y + w -= 2 * self.get_property("xpad") + h -= 2 * self.get_property("ypad") + + stock_id = "" + if self.props.icon_name: + stock_id = self.set_icon_name_to_id(self.props.icon_name) + elif self.props.stock_id: + stock_id = self.props.stock_id + elif self.props.pixbuf: + pix = self.props.pixbuf + else: + return + + if stock_id: + pix = self.get_pixbuf_from_stock_icon(tree, stock_id, self.props.stock_size) + if stock_id == 'hic-task-refresh': + self.control.append_running_cell_area(cell_area) + if self.control.is_active(): + self.control.on_draw_pixbuf_cb(pix, window.cairo_create(), x, y, w, h, True) + else: + self.control.start_run(200, 0, 0, 1000, 150, tree) + else: + self.control.remove_running_cell_area(cell_area) + self.control.on_draw_pixbuf_cb(pix, window.cairo_create(), x, y, w, h, False) + + def on_get_size(self, widget, cell_area): + if self.props.icon_name or self.props.pixbuf or self.props.stock_id: + w, h = gtk.icon_size_lookup(self.props.stock_size) + calc_width = self.get_property("xpad") * 2 + w + calc_height = self.get_property("ypad") * 2 + h + x_offset = 0 + y_offset = 0 + if cell_area and w > 0 and h > 0: + x_offset = self.get_property("xalign") * (cell_area.width - calc_width - self.get_property("xpad")) + y_offset = self.get_property("yalign") * (cell_area.height - calc_height - self.get_property("ypad")) + + return x_offset, y_offset, w, h + + return 0, 0, 0, 0 + +gobject.type_register(HobCellRendererPixbuf) + +class HobCellRendererToggle(gtk.CellRendererToggle): + def __init__(self): + gtk.CellRendererToggle.__init__(self) + self.ctrl = HobCellRendererController(is_draw_row=True) + self.ctrl.running_mode = self.ctrl.MODE_ONE_SHORT + self.cell_attr = {"fadeout": False, "number_of_children": 0} + + def do_render(self, window, widget, background_area, cell_area, expose_area, flags): + if (not self.ctrl) or (not widget): + return + + if flags & gtk.CELL_RENDERER_SELECTED: + state = gtk.STATE_SELECTED + else: + state = gtk.STATE_NORMAL + + if self.ctrl.is_active(): + path = widget.get_path_at_pos(cell_area.x + cell_area.width/2, cell_area.y + cell_area.height/2) + # sometimes the parameters of cell_area will be a negative number,such as pull up down the scroll bar + # it's over the tree container range, so the path will be bad + if not path: return + path = path[0] + if path in self.ctrl.running_cell_areas: + cr = window.cairo_create() + color = widget.get_style().base[state] + + row_x, _, row_width, _ = widget.get_visible_rect() + border_y = self.get_property("ypad") + self.ctrl.on_draw_fadeinout_cb(cr, color, row_x, cell_area.y - border_y, row_width, \ + cell_area.height + border_y * 2, self.cell_attr["fadeout"]) + # draw number of a group + if self.cell_attr["number_of_children"]: + text = "%d pkg" % self.cell_attr["number_of_children"] + pangolayout = widget.create_pango_layout(text) + textw, texth = pangolayout.get_pixel_size() + x = cell_area.x + (cell_area.width/2) - (textw/2) + y = cell_area.y + (cell_area.height/2) - (texth/2) + + widget.style.paint_layout(window, state, True, cell_area, widget, "checkbox", x, y, pangolayout) + else: + return gtk.CellRendererToggle.do_render(self, window, widget, background_area, cell_area, expose_area, flags) + + '''delay: normally delay time is 1000ms + cell_list: whilch cells need to be render + ''' + def fadeout(self, tree, delay, cell_list=None): + if (delay < 200) or (not tree): + return + self.cell_attr["fadeout"] = True + self.ctrl.running_cell_areas = cell_list + self.ctrl.start_run(200, 0, 0, delay, (delay * 200 / 1000), tree) + + def connect_render_state_changed(self, func, usrdata=None): + if not func: + return + if usrdata: + self.ctrl.connect("run-timer-stopped", func, self, usrdata) + else: + self.ctrl.connect("run-timer-stopped", func, self) + +gobject.type_register(HobCellRendererToggle) diff --git a/bitbake/lib/bb/ui/crumbs/imageconfigurationpage.py b/bitbake/lib/bb/ui/crumbs/imageconfigurationpage.py new file mode 100644 index 0000000000..2766bea8c7 --- /dev/null +++ b/bitbake/lib/bb/ui/crumbs/imageconfigurationpage.py @@ -0,0 +1,561 @@ +#!/usr/bin/env python +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2012 Intel Corporation +# +# Authored by Dongxiao Xu +# Authored by Shane Wang +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import gtk +import glib +import re +from bb.ui.crumbs.progressbar import HobProgressBar +from bb.ui.crumbs.hobcolor import HobColors +from bb.ui.crumbs.hobwidget import hic, HobImageButton, HobInfoButton, HobAltButton, HobButton +from bb.ui.crumbs.hoblistmodel import RecipeListModel +from bb.ui.crumbs.hobpages import HobPage +from bb.ui.crumbs.hig.retrieveimagedialog import RetrieveImageDialog + +# +# ImageConfigurationPage +# +class ImageConfigurationPage (HobPage): + + __dummy_machine__ = "--select a machine--" + __dummy_image__ = "--select an image recipe--" + __custom_image__ = "Select from my image recipes" + + def __init__(self, builder): + super(ImageConfigurationPage, self).__init__(builder, "Image configuration") + + self.image_combo_id = None + # we use machine_combo_changed_by_manual to identify the machine is changed by code + # or by manual. If by manual, all user's recipe selection and package selection are + # cleared. + self.machine_combo_changed_by_manual = True + self.stopping = False + self.warning_shift = 0 + self.custom_image_selected = None + self.create_visual_elements() + + def create_visual_elements(self): + # create visual elements + self.toolbar = gtk.Toolbar() + self.toolbar.set_orientation(gtk.ORIENTATION_HORIZONTAL) + self.toolbar.set_style(gtk.TOOLBAR_BOTH) + + my_images_button = self.append_toolbar_button(self.toolbar, + "Images", + hic.ICON_IMAGES_DISPLAY_FILE, + hic.ICON_IMAGES_HOVER_FILE, + "Open previously built images", + self.my_images_button_clicked_cb) + settings_button = self.append_toolbar_button(self.toolbar, + "Settings", + hic.ICON_SETTINGS_DISPLAY_FILE, + hic.ICON_SETTINGS_HOVER_FILE, + "View additional build settings", + self.settings_button_clicked_cb) + + self.config_top_button = self.add_onto_top_bar(self.toolbar) + + self.gtable = gtk.Table(40, 40, True) + self.create_config_machine() + self.create_config_baseimg() + self.config_build_button = self.create_config_build_button() + + def _remove_all_widget(self): + children = self.gtable.get_children() or [] + for child in children: + self.gtable.remove(child) + children = self.box_group_area.get_children() or [] + for child in children: + self.box_group_area.remove(child) + children = self.get_children() or [] + for child in children: + self.remove(child) + + def _pack_components(self, pack_config_build_button = False): + self._remove_all_widget() + self.pack_start(self.config_top_button, expand=False, fill=False) + self.pack_start(self.group_align, expand=True, fill=True) + + self.box_group_area.pack_start(self.gtable, expand=True, fill=True) + if pack_config_build_button: + self.box_group_area.pack_end(self.config_build_button, expand=False, fill=False) + else: + box = gtk.HBox(False, 6) + box.show() + subbox = gtk.HBox(False, 0) + subbox.set_size_request(205, 49) + subbox.show() + box.add(subbox) + self.box_group_area.pack_end(box, False, False) + + def show_machine(self): + self.progress_bar.reset() + self._pack_components(pack_config_build_button = False) + self.set_config_machine_layout(show_progress_bar = False) + self.show_all() + + def update_progress_bar(self, title, fraction, status=None): + if self.stopping == False: + self.progress_bar.update(fraction) + self.progress_bar.set_text(title) + self.progress_bar.set_rcstyle(status) + + def show_info_populating(self): + self._pack_components(pack_config_build_button = False) + self.set_config_machine_layout(show_progress_bar = True) + self.show_all() + + def show_info_populated(self): + self.progress_bar.reset() + self._pack_components(pack_config_build_button = False) + self.set_config_machine_layout(show_progress_bar = False) + self.set_config_baseimg_layout() + self.show_all() + + def show_baseimg_selected(self): + self.progress_bar.reset() + self._pack_components(pack_config_build_button = True) + self.set_config_machine_layout(show_progress_bar = False) + self.set_config_baseimg_layout() + self.show_all() + if self.builder.recipe_model.get_selected_image() == self.builder.recipe_model.__custom_image__: + self.just_bake_button.hide() + + def add_warnings_bar(self): + #create the warnings bar shown when recipes parsing generates warnings + color = HobColors.KHAKI + warnings_bar = gtk.EventBox() + warnings_bar.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(color)) + warnings_bar.set_flags(gtk.CAN_DEFAULT) + warnings_bar.grab_default() + + build_stop_tab = gtk.Table(10, 20, True) + warnings_bar.add(build_stop_tab) + + icon = gtk.Image() + icon_pix_buffer = gtk.gdk.pixbuf_new_from_file(hic.ICON_INDI_ALERT_FILE) + icon.set_from_pixbuf(icon_pix_buffer) + build_stop_tab.attach(icon, 0, 2, 0, 10) + + label = gtk.Label() + label.set_alignment(0.0, 0.5) + warnings_nb = len(self.builder.parsing_warnings) + if warnings_nb == 1: + label.set_markup("1 recipe parsing warning") + else: + label.set_markup("%s recipe parsing warnings" % warnings_nb) + build_stop_tab.attach(label, 2, 12, 0, 10) + + view_warnings_button = HobButton("View warnings") + view_warnings_button.connect('clicked', self.view_warnings_button_clicked_cb) + build_stop_tab.attach(view_warnings_button, 15, 19, 1, 9) + + return warnings_bar + + def disable_warnings_bar(self): + if self.builder.parsing_warnings: + if hasattr(self, 'warnings_bar'): + self.warnings_bar.hide_all() + self.builder.parsing_warnings = [] + + def create_config_machine(self): + self.machine_title = gtk.Label() + self.machine_title.set_alignment(0.0, 0.5) + mark = "Select a machine" % self.span_tag('x-large', 'bold') + self.machine_title.set_markup(mark) + + self.machine_title_desc = gtk.Label() + self.machine_title_desc.set_alignment(0.0, 0.5) + mark = ("Your selection is the profile of the target machine for which you" + " are building the image.\n") % (self.span_tag('medium')) + self.machine_title_desc.set_markup(mark) + + self.machine_combo = gtk.combo_box_new_text() + self.machine_combo.connect("changed", self.machine_combo_changed_cb) + + icon_file = hic.ICON_LAYERS_DISPLAY_FILE + hover_file = hic.ICON_LAYERS_HOVER_FILE + self.layer_button = HobImageButton("Layers", "Add support for machines, software, etc.", + icon_file, hover_file) + self.layer_button.connect("clicked", self.layer_button_clicked_cb) + + markup = "Layers are a powerful mechanism to extend the Yocto Project " + markup += "with your own functionality.\n" + markup += "For more on layers, check the reference manual." + self.layer_info_icon = HobInfoButton("Layers" + "*" + markup, self.get_parent()) + self.progress_bar = HobProgressBar() + self.stop_button = HobAltButton("Stop") + self.stop_button.connect("clicked", self.stop_button_clicked_cb) + self.machine_separator = gtk.HSeparator() + + def set_config_machine_layout(self, show_progress_bar = False): + self.gtable.attach(self.machine_title, 0, 40, 0, 4) + self.gtable.attach(self.machine_title_desc, 0, 40, 4, 6) + self.gtable.attach(self.machine_combo, 0, 12, 7, 10) + self.gtable.attach(self.layer_button, 14, 36, 7, 12) + self.gtable.attach(self.layer_info_icon, 36, 40, 7, 11) + if show_progress_bar: + #self.gtable.attach(self.progress_box, 0, 40, 15, 18) + self.gtable.attach(self.progress_bar, 0, 37, 15, 18) + self.gtable.attach(self.stop_button, 37, 40, 15, 18, 0, 0) + if self.builder.parsing_warnings: + self.warnings_bar = self.add_warnings_bar() + self.gtable.attach(self.warnings_bar, 0, 40, 14, 18) + self.warning_shift = 4 + else: + self.warning_shift = 0 + self.gtable.attach(self.machine_separator, 0, 40, 13, 14) + + def create_config_baseimg(self): + self.image_title = gtk.Label() + self.image_title.set_alignment(0, 1.0) + mark = "Select an image recipe" % self.span_tag('x-large', 'bold') + self.image_title.set_markup(mark) + + self.image_title_desc = gtk.Label() + self.image_title_desc.set_alignment(0, 0.5) + + mark = ("Image recipes are a starting point for the type of image you want. " + "You can build them as \n" + "they are or edit them to suit your needs.\n") % self.span_tag('medium') + self.image_title_desc.set_markup(mark) + + self.image_combo = gtk.combo_box_new_text() + self.image_combo.set_row_separator_func(self.combo_separator_func, None) + self.image_combo_id = self.image_combo.connect("changed", self.image_combo_changed_cb) + + self.image_desc = gtk.Label() + self.image_desc.set_alignment(0.0, 0.5) + self.image_desc.set_size_request(256, -1) + self.image_desc.set_justify(gtk.JUSTIFY_LEFT) + self.image_desc.set_line_wrap(True) + + # button to view recipes + icon_file = hic.ICON_RCIPE_DISPLAY_FILE + hover_file = hic.ICON_RCIPE_HOVER_FILE + self.view_adv_configuration_button = HobImageButton("Advanced configuration", + "Select image types, package formats, etc", + icon_file, hover_file) + self.view_adv_configuration_button.connect("clicked", self.view_adv_configuration_button_clicked_cb) + + self.image_separator = gtk.HSeparator() + + def combo_separator_func(self, model, iter, user_data): + name = model.get_value(iter, 0) + if name == "--Separator--": + return True + + def set_config_baseimg_layout(self): + self.gtable.attach(self.image_title, 0, 40, 15+self.warning_shift, 17+self.warning_shift) + self.gtable.attach(self.image_title_desc, 0, 40, 18+self.warning_shift, 22+self.warning_shift) + self.gtable.attach(self.image_combo, 0, 12, 23+self.warning_shift, 26+self.warning_shift) + self.gtable.attach(self.image_desc, 0, 12, 27+self.warning_shift, 33+self.warning_shift) + self.gtable.attach(self.view_adv_configuration_button, 14, 36, 23+self.warning_shift, 28+self.warning_shift) + self.gtable.attach(self.image_separator, 0, 40, 35+self.warning_shift, 36+self.warning_shift) + + def create_config_build_button(self): + # Create the "Build packages" and "Build image" buttons at the bottom + button_box = gtk.HBox(False, 6) + + # create button "Build image" + self.just_bake_button = HobButton("Build image") + self.just_bake_button.set_tooltip_text("Build the image recipe as it is") + self.just_bake_button.connect("clicked", self.just_bake_button_clicked_cb) + button_box.pack_end(self.just_bake_button, expand=False, fill=False) + + # create button "Edit image recipe" + self.edit_image_button = HobAltButton("Edit image recipe") + self.edit_image_button.set_tooltip_text("Customize the recipes and packages to be included in your image") + self.edit_image_button.connect("clicked", self.edit_image_button_clicked_cb) + button_box.pack_end(self.edit_image_button, expand=False, fill=False) + + return button_box + + def stop_button_clicked_cb(self, button): + self.stopping = True + self.progress_bar.set_text("Stopping recipe parsing") + self.progress_bar.set_rcstyle("stop") + self.builder.cancel_parse_sync() + + def view_warnings_button_clicked_cb(self, button): + self.builder.show_warning_dialog() + + def machine_combo_changed_idle_cb(self): + self.builder.window.set_cursor(None) + + def machine_combo_changed_cb(self, machine_combo): + self.stopping = False + self.builder.parsing_warnings = [] + combo_item = machine_combo.get_active_text() + if not combo_item or combo_item == self.__dummy_machine__: + return + + self.builder.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) + self.builder.wait(0.1) #wait for combo and cursor to update + + # remove __dummy_machine__ item from the store list after first user selection + # because it is no longer valid + combo_store = machine_combo.get_model() + if len(combo_store) and (combo_store[0][0] == self.__dummy_machine__): + machine_combo.remove_text(0) + + self.builder.configuration.curr_mach = combo_item + if self.machine_combo_changed_by_manual: + self.builder.configuration.clear_selection() + # reset machine_combo_changed_by_manual + self.machine_combo_changed_by_manual = True + + self.builder.configuration.selected_image = None + + # Do reparse recipes + self.builder.populate_recipe_package_info_async() + + glib.idle_add(self.machine_combo_changed_idle_cb) + + def update_machine_combo(self): + self.disable_warnings_bar() + all_machines = [self.__dummy_machine__] + self.builder.parameters.all_machines + + model = self.machine_combo.get_model() + model.clear() + for machine in all_machines: + self.machine_combo.append_text(machine) + self.machine_combo.set_active(0) + + def switch_machine_combo(self): + self.disable_warnings_bar() + self.machine_combo_changed_by_manual = False + model = self.machine_combo.get_model() + active = 0 + while active < len(model): + if model[active][0] == self.builder.configuration.curr_mach: + self.machine_combo.set_active(active) + return + active += 1 + + if model[0][0] != self.__dummy_machine__: + self.machine_combo.insert_text(0, self.__dummy_machine__) + + self.machine_combo.set_active(0) + + def update_image_desc(self): + desc = "" + selected_image = self.image_combo.get_active_text() + if selected_image and selected_image in self.builder.recipe_model.pn_path.keys(): + image_path = self.builder.recipe_model.pn_path[selected_image] + image_iter = self.builder.recipe_model.get_iter(image_path) + desc = self.builder.recipe_model.get_value(image_iter, self.builder.recipe_model.COL_DESC) + + mark = ("%s\n") % (self.span_tag('small'), desc) + self.image_desc.set_markup(mark) + + def image_combo_changed_idle_cb(self, selected_image, selected_recipes, selected_packages): + self.builder.update_recipe_model(selected_image, selected_recipes) + self.builder.update_package_model(selected_packages) + self.builder.window_sensitive(True) + + def image_combo_changed_cb(self, combo): + self.builder.window_sensitive(False) + selected_image = self.image_combo.get_active_text() + if selected_image == self.__custom_image__: + topdir = self.builder.get_topdir() + images_dir = topdir + "/recipes/images/custom/" + self.builder.ensure_dir(images_dir) + + dialog = RetrieveImageDialog(images_dir, "Select from my image recipes", + self.builder, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT) + response = dialog.run() + if response == gtk.RESPONSE_OK: + image_name = dialog.get_filename() + head, tail = os.path.split(image_name) + selected_image = os.path.splitext(tail)[0] + self.custom_image_selected = selected_image + self.update_image_combo(self.builder.recipe_model, selected_image) + else: + selected_image = self.__dummy_image__ + self.update_image_combo(self.builder.recipe_model, None) + dialog.destroy() + else: + if self.custom_image_selected: + self.custom_image_selected = None + self.update_image_combo(self.builder.recipe_model, selected_image) + + if not selected_image or (selected_image == self.__dummy_image__): + self.builder.window_sensitive(True) + self.just_bake_button.hide() + self.edit_image_button.hide() + return + + # remove __dummy_image__ item from the store list after first user selection + # because it is no longer valid + combo_store = combo.get_model() + if len(combo_store) and (combo_store[0][0] == self.__dummy_image__): + combo.remove_text(0) + + self.builder.customized = False + + selected_recipes = [] + + image_path = self.builder.recipe_model.pn_path[selected_image] + image_iter = self.builder.recipe_model.get_iter(image_path) + selected_packages = self.builder.recipe_model.get_value(image_iter, self.builder.recipe_model.COL_INSTALL).split() + self.update_image_desc() + + self.builder.recipe_model.reset() + self.builder.package_model.reset() + + self.show_baseimg_selected() + + if selected_image == self.builder.recipe_model.__custom_image__: + self.just_bake_button.hide() + + glib.idle_add(self.image_combo_changed_idle_cb, selected_image, selected_recipes, selected_packages) + + def _image_combo_connect_signal(self): + if not self.image_combo_id: + self.image_combo_id = self.image_combo.connect("changed", self.image_combo_changed_cb) + + def _image_combo_disconnect_signal(self): + if self.image_combo_id: + self.image_combo.disconnect(self.image_combo_id) + self.image_combo_id = None + + def update_image_combo(self, recipe_model, selected_image): + # Update the image combo according to the images in the recipe_model + # populate image combo + filter = {RecipeListModel.COL_TYPE : ['image']} + image_model = recipe_model.tree_model(filter) + image_model.set_sort_column_id(recipe_model.COL_NAME, gtk.SORT_ASCENDING) + active = 0 + cnt = 0 + + white_pattern = [] + if self.builder.parameters.image_white_pattern: + for i in self.builder.parameters.image_white_pattern.split(): + white_pattern.append(re.compile(i)) + + black_pattern = [] + if self.builder.parameters.image_black_pattern: + for i in self.builder.parameters.image_black_pattern.split(): + black_pattern.append(re.compile(i)) + black_pattern.append(re.compile("hob-image")) + black_pattern.append(re.compile("edited(-[0-9]*)*.bb$")) + + it = image_model.get_iter_first() + self._image_combo_disconnect_signal() + model = self.image_combo.get_model() + model.clear() + # Set a indicator text to combo store when first open + if not selected_image: + self.image_combo.append_text(self.__dummy_image__) + cnt = cnt + 1 + + self.image_combo.append_text(self.__custom_image__) + self.image_combo.append_text("--Separator--") + cnt = cnt + 2 + + topdir = self.builder.get_topdir() + # append and set active + while it: + path = image_model.get_path(it) + it = image_model.iter_next(it) + image_name = image_model[path][recipe_model.COL_NAME] + if image_name == self.builder.recipe_model.__custom_image__: + continue + + if black_pattern: + allow = True + for pattern in black_pattern: + if pattern.search(image_name): + allow = False + break + elif white_pattern: + allow = False + for pattern in white_pattern: + if pattern.search(image_name): + allow = True + break + else: + allow = True + + file_name = image_model[path][recipe_model.COL_FILE] + if file_name and topdir in file_name: + allow = False + + if allow: + self.image_combo.append_text(image_name) + if image_name == selected_image: + active = cnt + cnt = cnt + 1 + self.image_combo.append_text(self.builder.recipe_model.__custom_image__) + + if selected_image == self.builder.recipe_model.__custom_image__: + active = cnt + + if self.custom_image_selected: + self.image_combo.append_text("--Separator--") + self.image_combo.append_text(self.custom_image_selected) + cnt = cnt + 2 + if self.custom_image_selected == selected_image: + active = cnt + + self.image_combo.set_active(active) + + if active != 0: + self.show_baseimg_selected() + + self._image_combo_connect_signal() + + def layer_button_clicked_cb(self, button): + # Create a layer selection dialog + self.builder.show_layer_selection_dialog() + + def view_adv_configuration_button_clicked_cb(self, button): + # Create an advanced settings dialog + response, settings_changed = self.builder.show_adv_settings_dialog() + if not response: + return + if settings_changed: + self.builder.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) + self.builder.wait(0.1) #wait for adv_settings_dialog to terminate + self.builder.reparse_post_adv_settings() + self.builder.window.set_cursor(None) + + def just_bake_button_clicked_cb(self, button): + self.builder.parsing_warnings = [] + self.builder.just_bake() + + def edit_image_button_clicked_cb(self, button): + self.builder.set_base_image() + self.builder.show_recipes() + + def my_images_button_clicked_cb(self, button): + self.builder.show_load_my_images_dialog() + + def settings_button_clicked_cb(self, button): + # Create an advanced settings dialog + response, settings_changed = self.builder.show_simple_settings_dialog() + if not response: + return + if settings_changed: + self.builder.reparse_post_adv_settings() diff --git a/bitbake/lib/bb/ui/crumbs/imagedetailspage.py b/bitbake/lib/bb/ui/crumbs/imagedetailspage.py new file mode 100755 index 0000000000..352e9489fd --- /dev/null +++ b/bitbake/lib/bb/ui/crumbs/imagedetailspage.py @@ -0,0 +1,669 @@ +#!/usr/bin/env python +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2012 Intel Corporation +# +# Authored by Dongxiao Xu +# Authored by Shane Wang +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import gobject +import gtk +from bb.ui.crumbs.hobcolor import HobColors +from bb.ui.crumbs.hobwidget import hic, HobViewTable, HobAltButton, HobButton +from bb.ui.crumbs.hobpages import HobPage +import subprocess +from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog +from bb.ui.crumbs.hig.saveimagedialog import SaveImageDialog + +# +# ImageDetailsPage +# +class ImageDetailsPage (HobPage): + + class DetailBox (gtk.EventBox): + def __init__(self, widget = None, varlist = None, vallist = None, icon = None, button = None, button2=None, color = HobColors.LIGHT_GRAY): + gtk.EventBox.__init__(self) + + # set color + style = self.get_style().copy() + style.bg[gtk.STATE_NORMAL] = self.get_colormap().alloc_color(color, False, False) + self.set_style(style) + + self.row = gtk.Table(1, 2, False) + self.row.set_border_width(10) + self.add(self.row) + + total_rows = 0 + if widget: + total_rows = 10 + if varlist and vallist: + # pack the icon and the text on the left + total_rows += len(varlist) + self.table = gtk.Table(total_rows, 20, True) + self.table.set_row_spacings(6) + self.table.set_size_request(100, -1) + self.row.attach(self.table, 0, 1, 0, 1, xoptions=gtk.FILL|gtk.EXPAND, yoptions=gtk.FILL) + + colid = 0 + rowid = 0 + self.line_widgets = {} + if icon: + self.table.attach(icon, colid, colid + 2, 0, 1) + colid = colid + 2 + if widget: + self.table.attach(widget, colid, 20, 0, 10) + rowid = 10 + if varlist and vallist: + for row in range(rowid, total_rows): + index = row - rowid + self.line_widgets[varlist[index]] = self.text2label(varlist[index], vallist[index]) + self.table.attach(self.line_widgets[varlist[index]], colid, 20, row, row + 1) + # pack the button on the right + if button: + self.bbox = gtk.VBox() + self.bbox.pack_start(button, expand=True, fill=False) + if button2: + self.bbox.pack_start(button2, expand=True, fill=False) + self.bbox.set_size_request(150,-1) + self.row.attach(self.bbox, 1, 2, 0, 1, xoptions=gtk.FILL, yoptions=gtk.EXPAND) + + def update_line_widgets(self, variable, value): + if len(self.line_widgets) == 0: + return + if not isinstance(self.line_widgets[variable], gtk.Label): + return + self.line_widgets[variable].set_markup(self.format_line(variable, value)) + + def wrap_line(self, inputs): + # wrap the long text of inputs + wrap_width_chars = 75 + outputs = "" + tmps = inputs + less_chars = len(inputs) + while (less_chars - wrap_width_chars) > 0: + less_chars -= wrap_width_chars + outputs += tmps[:wrap_width_chars] + "\n " + tmps = inputs[less_chars:] + outputs += tmps + return outputs + + def format_line(self, variable, value): + wraped_value = self.wrap_line(value) + markup = "%s" % variable + markup += "%s" % wraped_value + return markup + + def text2label(self, variable, value): + # append the name:value to the left box + # such as "Name: hob-core-minimal-variant-2011-12-15-beagleboard" + label = gtk.Label() + label.set_alignment(0.0, 0.5) + label.set_markup(self.format_line(variable, value)) + return label + + class BuildDetailBox (gtk.EventBox): + def __init__(self, varlist = None, vallist = None, icon = None, color = HobColors.LIGHT_GRAY): + gtk.EventBox.__init__(self) + + # set color + style = self.get_style().copy() + style.bg[gtk.STATE_NORMAL] = self.get_colormap().alloc_color(color, False, False) + self.set_style(style) + + self.hbox = gtk.HBox() + self.hbox.set_border_width(10) + self.add(self.hbox) + + total_rows = 0 + if varlist and vallist: + # pack the icon and the text on the left + total_rows += len(varlist) + self.table = gtk.Table(total_rows, 20, True) + self.table.set_row_spacings(6) + self.table.set_size_request(100, -1) + self.hbox.pack_start(self.table, expand=True, fill=True, padding=15) + + colid = 0 + rowid = 0 + self.line_widgets = {} + if icon: + self.table.attach(icon, colid, colid + 2, 0, 1) + colid = colid + 2 + if varlist and vallist: + for row in range(rowid, total_rows): + index = row - rowid + self.line_widgets[varlist[index]] = self.text2label(varlist[index], vallist[index]) + self.table.attach(self.line_widgets[varlist[index]], colid, 20, row, row + 1) + + def update_line_widgets(self, variable, value): + if len(self.line_widgets) == 0: + return + if not isinstance(self.line_widgets[variable], gtk.Label): + return + self.line_widgets[variable].set_markup(self.format_line(variable, value)) + + def wrap_line(self, inputs): + # wrap the long text of inputs + wrap_width_chars = 75 + outputs = "" + tmps = inputs + less_chars = len(inputs) + while (less_chars - wrap_width_chars) > 0: + less_chars -= wrap_width_chars + outputs += tmps[:wrap_width_chars] + "\n " + tmps = inputs[less_chars:] + outputs += tmps + return outputs + + def format_line(self, variable, value): + wraped_value = self.wrap_line(value) + markup = "%s" % variable + markup += "%s" % wraped_value + return markup + + def text2label(self, variable, value): + # append the name:value to the left box + # such as "Name: hob-core-minimal-variant-2011-12-15-beagleboard" + label = gtk.Label() + label.set_alignment(0.0, 0.5) + label.set_markup(self.format_line(variable, value)) + return label + + def __init__(self, builder): + super(ImageDetailsPage, self).__init__(builder, "Image details") + + self.image_store = [] + self.button_ids = {} + self.details_bottom_buttons = gtk.HBox(False, 6) + self.image_saved = False + self.create_visual_elements() + self.name_field_template = "" + self.description_field_template = "" + + def create_visual_elements(self): + # create visual elements + # create the toolbar + self.toolbar = gtk.Toolbar() + self.toolbar.set_orientation(gtk.ORIENTATION_HORIZONTAL) + self.toolbar.set_style(gtk.TOOLBAR_BOTH) + + my_images_button = self.append_toolbar_button(self.toolbar, + "Images", + hic.ICON_IMAGES_DISPLAY_FILE, + hic.ICON_IMAGES_HOVER_FILE, + "Open previously built images", + self.my_images_button_clicked_cb) + settings_button = self.append_toolbar_button(self.toolbar, + "Settings", + hic.ICON_SETTINGS_DISPLAY_FILE, + hic.ICON_SETTINGS_HOVER_FILE, + "View additional build settings", + self.settings_button_clicked_cb) + + self.details_top_buttons = self.add_onto_top_bar(self.toolbar) + + def _remove_all_widget(self): + children = self.get_children() or [] + for child in children: + self.remove(child) + children = self.box_group_area.get_children() or [] + for child in children: + self.box_group_area.remove(child) + children = self.details_bottom_buttons.get_children() or [] + for child in children: + self.details_bottom_buttons.remove(child) + + def show_page(self, step): + self.build_succeeded = (step == self.builder.IMAGE_GENERATED) + image_addr = self.builder.parameters.image_addr + image_names = self.builder.parameters.image_names + if self.build_succeeded: + machine = self.builder.configuration.curr_mach + base_image = self.builder.recipe_model.get_selected_image() + layers = self.builder.configuration.layers + pkg_num = "%s" % len(self.builder.package_model.get_selected_packages()) + log_file = self.builder.current_logfile + else: + pkg_num = "N/A" + log_file = None + + # remove + for button_id, button in self.button_ids.items(): + button.disconnect(button_id) + self._remove_all_widget() + + # repack + self.pack_start(self.details_top_buttons, expand=False, fill=False) + self.pack_start(self.group_align, expand=True, fill=True) + + self.build_result = None + if self.image_saved or (self.build_succeeded and self.builder.current_step == self.builder.IMAGE_GENERATING): + # building is the previous step + icon = gtk.Image() + pixmap_path = hic.ICON_INDI_CONFIRM_FILE + color = HobColors.RUNNING + pix_buffer = gtk.gdk.pixbuf_new_from_file(pixmap_path) + icon.set_from_pixbuf(pix_buffer) + varlist = [""] + if self.image_saved: + vallist = ["Your image recipe has been saved"] + else: + vallist = ["Your image is ready"] + self.build_result = self.BuildDetailBox(varlist=varlist, vallist=vallist, icon=icon, color=color) + self.box_group_area.pack_start(self.build_result, expand=False, fill=False) + + self.buttonlist = ["Build new image", "Save image recipe", "Run image", "Deploy image"] + + # Name + self.image_store = [] + self.toggled_image = "" + default_image_size = 0 + self.num_toggled = 0 + i = 0 + for image_name in image_names: + image_size = HobPage._size_to_string(os.stat(os.path.join(image_addr, image_name)).st_size) + + image_attr = ("run" if (self.test_type_runnable(image_name) and self.test_mach_runnable(image_name)) else \ + ("deploy" if self.test_deployable(image_name) else "")) + is_toggled = (image_attr != "") + + if not self.toggled_image: + if i == (len(image_names) - 1): + is_toggled = True + if is_toggled: + default_image_size = image_size + self.toggled_image = image_name + + split_stuff = image_name.split('.') + if "rootfs" in split_stuff: + image_type = image_name[(len(split_stuff[0]) + len(".rootfs") + 1):] + else: + image_type = image_name[(len(split_stuff[0]) + 1):] + + self.image_store.append({'name': image_name, + 'type': image_type, + 'size': image_size, + 'is_toggled': is_toggled, + 'action_attr': image_attr,}) + + i = i + 1 + self.num_toggled += is_toggled + + is_runnable = self.create_bottom_buttons(self.buttonlist, self.toggled_image) + + # Generated image files info + varlist = ["Name: ", "Files created: ", "Directory: "] + vallist = [] + + vallist.append(image_name.split('.')[0]) + vallist.append(', '.join(fileitem['type'] for fileitem in self.image_store)) + vallist.append(image_addr) + + view_files_button = HobAltButton("View files") + view_files_button.connect("clicked", self.view_files_clicked_cb, image_addr) + view_files_button.set_tooltip_text("Open the directory containing the image files") + open_log_button = None + if log_file: + open_log_button = HobAltButton("Open log") + open_log_button.connect("clicked", self.open_log_clicked_cb, log_file) + open_log_button.set_tooltip_text("Open the build's log file") + self.image_detail = self.DetailBox(varlist=varlist, vallist=vallist, button=view_files_button, button2=open_log_button) + self.box_group_area.pack_start(self.image_detail, expand=False, fill=True) + + # The default kernel box for the qemu images + self.sel_kernel = "" + self.kernel_detail = None + if 'qemu' in image_name: + self.sel_kernel = self.get_kernel_file_name() + + # varlist = ["Kernel: "] + # vallist = [] + # vallist.append(self.sel_kernel) + + # change_kernel_button = HobAltButton("Change") + # change_kernel_button.connect("clicked", self.change_kernel_cb) + # change_kernel_button.set_tooltip_text("Change qemu kernel file") + # self.kernel_detail = self.DetailBox(varlist=varlist, vallist=vallist, button=change_kernel_button) + # self.box_group_area.pack_start(self.kernel_detail, expand=True, fill=True) + + # Machine, Image recipe and Layers + layer_num_limit = 15 + varlist = ["Machine: ", "Image recipe: ", "Layers: "] + vallist = [] + self.setting_detail = None + if self.build_succeeded: + vallist.append(machine) + if self.builder.recipe_model.is_custom_image(): + if self.builder.configuration.initial_selected_image == self.builder.recipe_model.__custom_image__: + base_image ="New image recipe" + else: + base_image = self.builder.configuration.initial_selected_image + " (edited)" + vallist.append(base_image) + i = 0 + for layer in layers: + if i > layer_num_limit: + break + varlist.append(" - ") + i += 1 + vallist.append("") + i = 0 + for layer in layers: + if i > layer_num_limit: + break + elif i == layer_num_limit: + vallist.append("and more...") + else: + vallist.append(layer) + i += 1 + + edit_config_button = HobAltButton("Edit configuration") + edit_config_button.set_tooltip_text("Edit machine and image recipe") + edit_config_button.connect("clicked", self.edit_config_button_clicked_cb) + self.setting_detail = self.DetailBox(varlist=varlist, vallist=vallist, button=edit_config_button) + self.box_group_area.pack_start(self.setting_detail, expand=True, fill=True) + + # Packages included, and Total image size + varlist = ["Packages included: ", "Total image size: "] + vallist = [] + vallist.append(pkg_num) + vallist.append(default_image_size) + self.builder.configuration.image_size = default_image_size + self.builder.configuration.image_packages = self.builder.configuration.selected_packages + if self.build_succeeded: + edit_packages_button = HobAltButton("Edit packages") + edit_packages_button.set_tooltip_text("Edit the packages included in your image") + edit_packages_button.connect("clicked", self.edit_packages_button_clicked_cb) + else: # get to this page from "My images" + edit_packages_button = None + self.package_detail = self.DetailBox(varlist=varlist, vallist=vallist, button=edit_packages_button) + self.box_group_area.pack_start(self.package_detail, expand=True, fill=True) + + # pack the buttons at the bottom, at this time they are already created. + if self.build_succeeded: + self.box_group_area.pack_end(self.details_bottom_buttons, expand=False, fill=False) + else: # for "My images" page + self.details_separator = gtk.HSeparator() + self.box_group_area.pack_start(self.details_separator, expand=False, fill=False) + self.box_group_area.pack_start(self.details_bottom_buttons, expand=False, fill=False) + + self.show_all() + if self.kernel_detail and (not is_runnable): + self.kernel_detail.hide() + self.image_saved = False + + def view_files_clicked_cb(self, button, image_addr): + subprocess.call("xdg-open /%s" % image_addr, shell=True) + + def open_log_clicked_cb(self, button, log_file): + if log_file: + log_file = "file:///" + log_file + gtk.show_uri(screen=button.get_screen(), uri=log_file, timestamp=0) + + def refresh_package_detail_box(self, image_size): + self.package_detail.update_line_widgets("Total image size: ", image_size) + + def test_type_runnable(self, image_name): + type_runnable = False + for t in self.builder.parameters.runnable_image_types: + if image_name.endswith(t): + type_runnable = True + break + return type_runnable + + def test_mach_runnable(self, image_name): + mach_runnable = False + for t in self.builder.parameters.runnable_machine_patterns: + if t in image_name: + mach_runnable = True + break + return mach_runnable + + def test_deployable(self, image_name): + if self.builder.configuration.curr_mach.startswith("qemu"): + return False + deployable = False + for t in self.builder.parameters.deployable_image_types: + if image_name.endswith(t): + deployable = True + break + return deployable + + def get_kernel_file_name(self, kernel_addr=""): + kernel_name = "" + + if not kernel_addr: + kernel_addr = self.builder.parameters.image_addr + + files = [f for f in os.listdir(kernel_addr) if f[0] <> '.'] + for check_file in files: + if check_file.endswith(".bin"): + name_splits = check_file.split(".")[0] + if self.builder.parameters.kernel_image_type in name_splits.split("-"): + kernel_name = check_file + break + + return kernel_name + + def show_builded_images_dialog(self, widget, primary_action=""): + title = primary_action if primary_action else "Your builded images" + dialog = CrumbsDialog(title, self.builder, + gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT) + dialog.set_border_width(12) + + label = gtk.Label() + label.set_use_markup(True) + label.set_alignment(0.0, 0.5) + label.set_padding(12,0) + if primary_action == "Run image": + label.set_markup("Select the image file you want to run:") + elif primary_action == "Deploy image": + label.set_markup("Select the image file you want to deploy:") + else: + label.set_markup("Select the image file you want to %s" % primary_action) + dialog.vbox.pack_start(label, expand=False, fill=False) + + # filter created images as action attribution (deploy or run) + action_attr = "" + action_images = [] + for fileitem in self.image_store: + action_attr = fileitem['action_attr'] + if (action_attr == 'run' and primary_action == "Run image") \ + or (action_attr == 'deploy' and primary_action == "Deploy image"): + action_images.append(fileitem) + + # pack the corresponding 'runnable' or 'deploy' radio_buttons, if there has no more than one file. + # assume that there does not both have 'deploy' and 'runnable' files in the same building result + # in possible as design. + curr_row = 0 + rows = (len(action_images)) if len(action_images) < 10 else 10 + table = gtk.Table(rows, 10, True) + table.set_row_spacings(6) + table.set_col_spacing(0, 12) + table.set_col_spacing(5, 12) + + sel_parent_btn = None + for fileitem in action_images: + sel_btn = gtk.RadioButton(sel_parent_btn, fileitem['type']) + sel_parent_btn = sel_btn if not sel_parent_btn else sel_parent_btn + sel_btn.set_active(fileitem['is_toggled']) + sel_btn.connect('toggled', self.table_selected_cb, fileitem) + if curr_row < 10: + table.attach(sel_btn, 0, 4, curr_row, curr_row + 1, xpadding=24) + else: + table.attach(sel_btn, 5, 9, curr_row - 10, curr_row - 9, xpadding=24) + curr_row += 1 + + dialog.vbox.pack_start(table, expand=False, fill=False, padding=6) + + button = dialog.add_button("Cancel", gtk.RESPONSE_CANCEL) + HobAltButton.style_button(button) + + if primary_action: + button = dialog.add_button(primary_action, gtk.RESPONSE_YES) + HobButton.style_button(button) + + dialog.show_all() + + response = dialog.run() + dialog.destroy() + + if response != gtk.RESPONSE_YES: + return + + for fileitem in self.image_store: + if fileitem['is_toggled']: + if fileitem['action_attr'] == 'run': + self.builder.runqemu_image(fileitem['name'], self.sel_kernel) + elif fileitem['action_attr'] == 'deploy': + self.builder.deploy_image(fileitem['name']) + + def table_selected_cb(self, tbutton, image): + image['is_toggled'] = tbutton.get_active() + if image['is_toggled']: + self.toggled_image = image['name'] + + def change_kernel_cb(self, widget): + kernel_path = self.builder.show_load_kernel_dialog() + if kernel_path and self.kernel_detail: + import os.path + self.sel_kernel = os.path.basename(kernel_path) + markup = self.kernel_detail.format_line("Kernel: ", self.sel_kernel) + label = ((self.kernel_detail.get_children()[0]).get_children()[0]).get_children()[0] + label.set_markup(markup) + + def create_bottom_buttons(self, buttonlist, image_name): + # Create the buttons at the bottom + created = False + packed = False + self.button_ids = {} + is_runnable = False + + # create button "Deploy image" + name = "Deploy image" + if name in buttonlist and self.test_deployable(image_name): + deploy_button = HobButton('Deploy image') + #deploy_button.set_size_request(205, 49) + deploy_button.set_tooltip_text("Burn a live image to a USB drive or flash memory") + deploy_button.set_flags(gtk.CAN_DEFAULT) + button_id = deploy_button.connect("clicked", self.deploy_button_clicked_cb) + self.button_ids[button_id] = deploy_button + self.details_bottom_buttons.pack_end(deploy_button, expand=False, fill=False) + created = True + packed = True + + name = "Run image" + if name in buttonlist and self.test_type_runnable(image_name) and self.test_mach_runnable(image_name): + if created == True: + # separator + #label = gtk.Label(" or ") + #self.details_bottom_buttons.pack_end(label, expand=False, fill=False) + + # create button "Run image" + run_button = HobAltButton("Run image") + else: + # create button "Run image" as the primary button + run_button = HobButton("Run image") + #run_button.set_size_request(205, 49) + run_button.set_flags(gtk.CAN_DEFAULT) + packed = True + run_button.set_tooltip_text("Start up an image with qemu emulator") + button_id = run_button.connect("clicked", self.run_button_clicked_cb) + self.button_ids[button_id] = run_button + self.details_bottom_buttons.pack_end(run_button, expand=False, fill=False) + created = True + is_runnable = True + + name = "Save image recipe" + if name in buttonlist and self.builder.recipe_model.is_custom_image(): + save_button = HobAltButton("Save image recipe") + save_button.set_tooltip_text("Keep your changes saving them as an image recipe") + save_button.set_sensitive(not self.image_saved) + button_id = save_button.connect("clicked", self.save_button_clicked_cb) + self.button_ids[button_id] = save_button + self.details_bottom_buttons.pack_end(save_button, expand=False, fill=False) + + name = "Build new image" + if name in buttonlist: + # create button "Build new image" + if packed: + build_new_button = HobAltButton("Build new image") + else: + build_new_button = HobButton("Build new image") + build_new_button.set_flags(gtk.CAN_DEFAULT) + #build_new_button.set_size_request(205, 49) + self.details_bottom_buttons.pack_end(build_new_button, expand=False, fill=False) + build_new_button.set_tooltip_text("Create a new image from scratch") + button_id = build_new_button.connect("clicked", self.build_new_button_clicked_cb) + self.button_ids[button_id] = build_new_button + + return is_runnable + + def deploy_button_clicked_cb(self, button): + if self.toggled_image: + if self.num_toggled > 1: + self.set_sensitive(False) + self.show_builded_images_dialog(None, "Deploy image") + self.set_sensitive(True) + else: + self.builder.deploy_image(self.toggled_image) + + def run_button_clicked_cb(self, button): + if self.toggled_image: + if self.num_toggled > 1: + self.set_sensitive(False) + self.show_builded_images_dialog(None, "Run image") + self.set_sensitive(True) + else: + self.builder.runqemu_image(self.toggled_image, self.sel_kernel) + + def save_button_clicked_cb(self, button): + topdir = self.builder.get_topdir() + images_dir = topdir + "/recipes/images/custom/" + self.builder.ensure_dir(images_dir) + + self.name_field_template = self.builder.image_configuration_page.custom_image_selected + if self.name_field_template: + image_path = self.builder.recipe_model.pn_path[self.name_field_template] + image_iter = self.builder.recipe_model.get_iter(image_path) + self.description_field_template = self.builder.recipe_model.get_value(image_iter, self.builder.recipe_model.COL_DESC) + else: + self.name_field_template = "" + + dialog = SaveImageDialog(images_dir, self.name_field_template, self.description_field_template, + "Save image recipe", self.builder, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT) + response = dialog.run() + dialog.destroy() + + def build_new_button_clicked_cb(self, button): + self.builder.initiate_new_build_async() + + def edit_config_button_clicked_cb(self, button): + self.builder.show_configuration() + + def edit_packages_button_clicked_cb(self, button): + self.builder.show_packages() + + def my_images_button_clicked_cb(self, button): + self.builder.show_load_my_images_dialog() + + def settings_button_clicked_cb(self, button): + # Create an advanced settings dialog + response, settings_changed = self.builder.show_simple_settings_dialog() + if not response: + return + if settings_changed: + self.builder.reparse_post_adv_settings() diff --git a/bitbake/lib/bb/ui/crumbs/packageselectionpage.py b/bitbake/lib/bb/ui/crumbs/packageselectionpage.py new file mode 100755 index 0000000000..7c62b36e6b --- /dev/null +++ b/bitbake/lib/bb/ui/crumbs/packageselectionpage.py @@ -0,0 +1,355 @@ +#!/usr/bin/env python +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2012 Intel Corporation +# +# Authored by Dongxiao Xu +# Authored by Shane Wang +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import gtk +import glib +from bb.ui.crumbs.hobcolor import HobColors +from bb.ui.crumbs.hobwidget import HobViewTable, HobNotebook, HobAltButton, HobButton +from bb.ui.crumbs.hoblistmodel import PackageListModel +from bb.ui.crumbs.hobpages import HobPage + +# +# PackageSelectionPage +# +class PackageSelectionPage (HobPage): + + pages = [ + { + 'name' : 'Included packages', + 'tooltip' : 'The packages currently included for your image', + 'filter' : { PackageListModel.COL_INC : [True] }, + 'search' : 'Search packages by name', + 'searchtip' : 'Enter a package name to find it', + 'columns' : [{ + 'col_name' : 'Package name', + 'col_id' : PackageListModel.COL_NAME, + 'col_style': 'text', + 'col_min' : 100, + 'col_max' : 300, + 'expand' : 'True' + }, { + 'col_name' : 'Size', + 'col_id' : PackageListModel.COL_SIZE, + 'col_style': 'text', + 'col_min' : 100, + 'col_max' : 300, + 'expand' : 'True' + }, { + 'col_name' : 'Recipe', + 'col_id' : PackageListModel.COL_RCP, + 'col_style': 'text', + 'col_min' : 100, + 'col_max' : 250, + 'expand' : 'True' + }, { + 'col_name' : 'Brought in by (+others)', + 'col_id' : PackageListModel.COL_BINB, + 'col_style': 'binb', + 'col_min' : 100, + 'col_max' : 350, + 'expand' : 'True' + }, { + 'col_name' : 'Included', + 'col_id' : PackageListModel.COL_INC, + 'col_style': 'check toggle', + 'col_min' : 100, + 'col_max' : 100 + }] + }, { + 'name' : 'All packages', + 'tooltip' : 'All packages that have been built', + 'filter' : {}, + 'search' : 'Search packages by name', + 'searchtip' : 'Enter a package name to find it', + 'columns' : [{ + 'col_name' : 'Package name', + 'col_id' : PackageListModel.COL_NAME, + 'col_style': 'text', + 'col_min' : 100, + 'col_max' : 400, + 'expand' : 'True' + }, { + 'col_name' : 'Size', + 'col_id' : PackageListModel.COL_SIZE, + 'col_style': 'text', + 'col_min' : 100, + 'col_max' : 500, + 'expand' : 'True' + }, { + 'col_name' : 'Recipe', + 'col_id' : PackageListModel.COL_RCP, + 'col_style': 'text', + 'col_min' : 100, + 'col_max' : 250, + 'expand' : 'True' + }, { + 'col_name' : 'Included', + 'col_id' : PackageListModel.COL_INC, + 'col_style': 'check toggle', + 'col_min' : 100, + 'col_max' : 100 + }] + } + ] + + (INCLUDED, + ALL) = range(2) + + def __init__(self, builder): + super(PackageSelectionPage, self).__init__(builder, "Edit packages") + + # set invisible members + self.recipe_model = self.builder.recipe_model + self.package_model = self.builder.package_model + + # create visual elements + self.create_visual_elements() + + def included_clicked_cb(self, button): + self.ins.set_current_page(self.INCLUDED) + + def create_visual_elements(self): + self.label = gtk.Label("Packages included: 0\nSelected packages size: 0 MB") + self.eventbox = self.add_onto_top_bar(self.label, 73) + self.pack_start(self.eventbox, expand=False, fill=False) + self.pack_start(self.group_align, expand=True, fill=True) + + # set visible members + self.ins = HobNotebook() + self.tables = [] # we need to modify table when the dialog is shown + + search_names = [] + search_tips = [] + # append the tab + for page in self.pages: + columns = page['columns'] + name = page['name'] + tab = HobViewTable(columns, name) + search_names.append(page['search']) + search_tips.append(page['searchtip']) + filter = page['filter'] + sort_model = self.package_model.tree_model(filter, initial=True) + tab.set_model(sort_model) + tab.connect("toggled", self.table_toggled_cb, name) + tab.connect("button-release-event", self.button_click_cb) + tab.connect("cell-fadeinout-stopped", self.after_fadeout_checkin_include, filter) + self.ins.append_page(tab, page['name'], page['tooltip']) + self.tables.append(tab) + + self.ins.set_entry(search_names, search_tips) + self.ins.search.connect("changed", self.search_entry_changed) + + # add all into the dialog + self.box_group_area.pack_start(self.ins, expand=True, fill=True) + + self.button_box = gtk.HBox(False, 6) + self.box_group_area.pack_start(self.button_box, expand=False, fill=False) + + self.build_image_button = HobButton('Build image') + #self.build_image_button.set_size_request(205, 49) + self.build_image_button.set_tooltip_text("Build target image") + self.build_image_button.set_flags(gtk.CAN_DEFAULT) + self.build_image_button.grab_default() + self.build_image_button.connect("clicked", self.build_image_clicked_cb) + self.button_box.pack_end(self.build_image_button, expand=False, fill=False) + + self.back_button = HobAltButton('Cancel') + self.back_button.connect("clicked", self.back_button_clicked_cb) + self.button_box.pack_end(self.back_button, expand=False, fill=False) + + def search_entry_changed(self, entry): + text = entry.get_text() + if self.ins.search_focus: + self.ins.search_focus = False + elif self.ins.page_changed: + self.ins.page_change = False + self.filter_search(entry) + elif text not in self.ins.search_names: + self.filter_search(entry) + + def filter_search(self, entry): + text = entry.get_text() + current_tab = self.ins.get_current_page() + filter = self.pages[current_tab]['filter'] + filter[PackageListModel.COL_NAME] = text + self.tables[current_tab].set_model(self.package_model.tree_model(filter, search_data=text)) + if self.package_model.filtered_nb == 0: + if not self.ins.get_nth_page(current_tab).top_bar: + self.ins.get_nth_page(current_tab).add_no_result_bar(entry) + self.ins.get_nth_page(current_tab).top_bar.set_no_show_all(True) + self.ins.get_nth_page(current_tab).top_bar.show() + self.ins.get_nth_page(current_tab).scroll.hide() + else: + if self.ins.get_nth_page(current_tab).top_bar: + self.ins.get_nth_page(current_tab).top_bar.hide() + self.ins.get_nth_page(current_tab).scroll.show() + if entry.get_text() == '': + entry.set_icon_sensitive(gtk.ENTRY_ICON_SECONDARY, False) + else: + entry.set_icon_sensitive(gtk.ENTRY_ICON_SECONDARY, True) + + def button_click_cb(self, widget, event): + path, col = widget.table_tree.get_cursor() + tree_model = widget.table_tree.get_model() + if path and col.get_title() != 'Included': # else activation is likely a removal + properties = {'binb': '' , 'name': '', 'size':'', 'recipe':'', 'files_list':''} + properties['binb'] = tree_model.get_value(tree_model.get_iter(path), PackageListModel.COL_BINB) + properties['name'] = tree_model.get_value(tree_model.get_iter(path), PackageListModel.COL_NAME) + properties['size'] = tree_model.get_value(tree_model.get_iter(path), PackageListModel.COL_SIZE) + properties['recipe'] = tree_model.get_value(tree_model.get_iter(path), PackageListModel.COL_RCP) + properties['files_list'] = tree_model.get_value(tree_model.get_iter(path), PackageListModel.COL_FLIST) + + self.builder.show_recipe_property_dialog(properties) + + def open_log_clicked_cb(self, button, log_file): + if log_file: + log_file = "file:///" + log_file + gtk.show_uri(screen=button.get_screen(), uri=log_file, timestamp=0) + + def show_page(self, log_file): + children = self.button_box.get_children() or [] + for child in children: + self.button_box.remove(child) + # re-packed the buttons as request, add the 'open log' button if build success + self.button_box.pack_end(self.build_image_button, expand=False, fill=False) + if log_file: + open_log_button = HobAltButton("Open log") + open_log_button.connect("clicked", self.open_log_clicked_cb, log_file) + open_log_button.set_tooltip_text("Open the build's log file") + self.button_box.pack_end(open_log_button, expand=False, fill=False) + self.button_box.pack_end(self.back_button, expand=False, fill=False) + self.show_all() + + def build_image_clicked_cb(self, button): + self.builder.parsing_warnings = [] + self.builder.build_image() + + def refresh_tables(self): + self.ins.reset_entry(self.ins.search, 0) + for tab in self.tables: + index = self.tables.index(tab) + filter = self.pages[index]['filter'] + tab.set_model(self.package_model.tree_model(filter, initial=True)) + + def back_button_clicked_cb(self, button): + if self.builder.previous_step == self.builder.IMAGE_GENERATED: + self.builder.restore_initial_selected_packages() + self.refresh_selection() + self.builder.show_image_details() + else: + self.builder.show_configuration() + self.refresh_tables() + + def refresh_selection(self): + self.builder.configuration.selected_packages = self.package_model.get_selected_packages() + self.builder.configuration.user_selected_packages = self.package_model.get_user_selected_packages() + selected_packages_num = len(self.builder.configuration.selected_packages) + selected_packages_size = self.package_model.get_packages_size() + selected_packages_size_str = HobPage._size_to_string(selected_packages_size) + + if self.builder.configuration.image_packages == self.builder.configuration.selected_packages: + image_total_size_str = self.builder.configuration.image_size + else: + image_overhead_factor = self.builder.configuration.image_overhead_factor + image_rootfs_size = self.builder.configuration.image_rootfs_size / 1024 # image_rootfs_size is KB + image_extra_size = self.builder.configuration.image_extra_size / 1024 # image_extra_size is KB + base_size = image_overhead_factor * selected_packages_size + image_total_size = max(base_size, image_rootfs_size) + image_extra_size + if "zypper" in self.builder.configuration.selected_packages: + image_total_size += (51200 * 1024) + image_total_size_str = HobPage._size_to_string(image_total_size) + + self.label.set_label("Packages included: %s\nSelected packages size: %s\nEstimated image size: %s" % + (selected_packages_num, selected_packages_size_str, image_total_size_str)) + self.ins.show_indicator_icon("Included packages", selected_packages_num) + + def toggle_item_idle_cb(self, path, view_tree, cell, pagename): + if not self.package_model.path_included(path): + self.package_model.include_item(item_path=path, binb="User Selected") + else: + self.pre_fadeout_checkout_include(view_tree) + self.package_model.exclude_item(item_path=path) + self.render_fadeout(view_tree, cell) + + self.refresh_selection() + if not self.builder.customized: + self.builder.customized = True + self.builder.set_base_image() + self.builder.configuration.selected_image = self.recipe_model.__custom_image__ + self.builder.rcppkglist_populated() + + self.builder.window_sensitive(True) + view_model = view_tree.get_model() + vpath = self.package_model.convert_path_to_vpath(view_model, path) + view_tree.set_cursor(vpath) + + def table_toggled_cb(self, table, cell, view_path, toggled_columnid, view_tree, pagename): + # Click to include a package + self.builder.window_sensitive(False) + view_model = view_tree.get_model() + path = self.package_model.convert_vpath_to_path(view_model, view_path) + glib.idle_add(self.toggle_item_idle_cb, path, view_tree, cell, pagename) + + def pre_fadeout_checkout_include(self, tree): + #after the fadeout the table will be sorted as before + self.sort_column_id = self.package_model.sort_column_id + self.sort_order = self.package_model.sort_order + + self.package_model.resync_fadeout_column(self.package_model.get_iter_first()) + # Check out a model which base on the column COL_FADE_INC, + # it's save the prev state of column COL_INC before do exclude_item + filter = { PackageListModel.COL_FADE_INC : [True]} + new_model = self.package_model.tree_model(filter, excluded_items_ahead=True) + tree.set_model(new_model) + tree.expand_all() + + def get_excluded_rows(self, to_render_cells, model, it): + while it: + path = model.get_path(it) + prev_cell_is_active = model.get_value(it, PackageListModel.COL_FADE_INC) + curr_cell_is_active = model.get_value(it, PackageListModel.COL_INC) + if (prev_cell_is_active == True) and (curr_cell_is_active == False): + to_render_cells.append(path) + if model.iter_has_child(it): + self.get_excluded_rows(to_render_cells, model, model.iter_children(it)) + it = model.iter_next(it) + + return to_render_cells + + def render_fadeout(self, tree, cell): + if (not cell) or (not tree): + return + to_render_cells = [] + view_model = tree.get_model() + self.get_excluded_rows(to_render_cells, view_model, view_model.get_iter_first()) + + cell.fadeout(tree, 1000, to_render_cells) + + def after_fadeout_checkin_include(self, table, ctrl, cell, tree, filter): + self.package_model.sort_column_id = self.sort_column_id + self.package_model.sort_order = self.sort_order + tree.set_model(self.package_model.tree_model(filter)) + tree.expand_all() + + def set_packages_curr_tab(self, curr_page): + self.ins.set_current_page(curr_page) + 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 @@ +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2012 Intel Corporation +# +# Authored by Joshua Lock +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import gobject +import gtk +try: + import gconf +except: + pass + +class PersistentTooltip(gtk.Window): + """ + A tooltip which persists once shown until the user dismisses it with the Esc + key or by clicking the close button. + + # FIXME: the PersistentTooltip should be disabled when the user clicks anywhere off + # it. We can't do this with focus-out-event becuase modal ensures we have focus? + + markup: some Pango text markup to display in the tooltip + """ + def __init__(self, markup, parent_win=None): + gtk.Window.__init__(self, gtk.WINDOW_POPUP) + + # Inherit the system theme for a tooltip + style = gtk.rc_get_style_by_paths(gtk.settings_get_default(), + 'gtk-tooltip', 'gtk-tooltip', gobject.TYPE_NONE) + self.set_style(style) + + # The placement of the close button on the tip should reflect how the + # window manager of the users system places close buttons. Try to read + # the metacity gconf key to determine whether the close button is on the + # left or the right. + # In the case that we can't determine the users configuration we default + # to close buttons being on the right. + __button_right = True + try: + client = gconf.client_get_default() + order = client.get_string("/apps/metacity/general/button_layout") + if order and order.endswith(":"): + __button_right = False + except NameError: + pass + + # We need to ensure we're only shown once + self.shown = False + + # We don't want any WM decorations + self.set_decorated(False) + # We don't want to show in the taskbar or window switcher + self.set_skip_pager_hint(True) + self.set_skip_taskbar_hint(True) + # We must be modal to ensure we grab focus when presented from a gtk.Dialog + self.set_modal(True) + + self.set_border_width(0) + self.set_position(gtk.WIN_POS_MOUSE) + self.set_opacity(0.95) + + # Ensure a reasonable minimum size + self.set_geometry_hints(self, 100, 50) + + # Set this window as a transient window for parent(main window) + if parent_win: + self.set_transient_for(parent_win) + self.set_destroy_with_parent(True) + # Draw our label and close buttons + hbox = gtk.HBox(False, 0) + hbox.show() + self.add(hbox) + + img = gtk.Image() + img.set_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_BUTTON) + + self.button = gtk.Button() + self.button.set_image(img) + self.button.connect("clicked", self._dismiss_cb) + self.button.set_flags(gtk.CAN_DEFAULT) + self.button.grab_focus() + self.button.show() + vbox = gtk.VBox(False, 0) + vbox.show() + vbox.pack_start(self.button, False, False, 0) + if __button_right: + hbox.pack_end(vbox, True, True, 0) + else: + hbox.pack_start(vbox, True, True, 0) + + self.set_default(self.button) + + bin = gtk.HBox(True, 6) + bin.set_border_width(6) + bin.show() + self.label = gtk.Label() + self.label.set_line_wrap(True) + # We want to match the colours of the normal tooltips, as dictated by + # the users gtk+-2.0 theme, wherever possible - on some systems this + # requires explicitly setting a fg_color for the label which matches the + # tooltip_fg_color + settings = gtk.settings_get_default() + colours = settings.get_property('gtk-color-scheme').split('\n') + # remove any empty lines, there's likely to be a trailing one after + # calling split on a dictionary-like string + colours = filter(None, colours) + for col in colours: + item, val = col.split(': ') + if item == 'tooltip_fg_color': + style = self.label.get_style() + style.fg[gtk.STATE_NORMAL] = gtk.gdk.color_parse(val) + self.label.set_style(style) + break # we only care for the tooltip_fg_color + + self.label.set_markup(markup) + self.label.show() + bin.add(self.label) + hbox.pack_end(bin, True, True, 6) + + # add the original URL display for user reference + if 'a href' in markup: + hbox.set_tooltip_text(self.get_markup_url(markup)) + hbox.show() + + self.connect("key-press-event", self._catch_esc_cb) + + """ + Callback when the PersistentTooltip's close button is clicked. + Hides the PersistentTooltip. + """ + def _dismiss_cb(self, button): + self.hide() + return True + + """ + Callback when the Esc key is detected. Hides the PersistentTooltip. + """ + def _catch_esc_cb(self, widget, event): + keyname = gtk.gdk.keyval_name(event.keyval) + if keyname == "Escape": + self.hide() + return True + + """ + Called to present the PersistentTooltip. + Overrides the superclasses show() method to include state tracking. + """ + def show(self): + if not self.shown: + self.shown = True + gtk.Window.show(self) + + """ + Called to hide the PersistentTooltip. + Overrides the superclasses hide() method to include state tracking. + """ + def hide(self): + self.shown = False + gtk.Window.hide(self) + + """ + Called to get the hyperlink URL from markup text. + """ + def get_markup_url(self, markup): + url = "http:" + if markup and type(markup) == str: + s = markup + if 'http:' in s: + import re + url = re.search('(http:[^,\\ "]+)', s).group(0) + + 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 @@ +import gtk + +class ProgressBar(gtk.Dialog): + def __init__(self, parent): + + gtk.Dialog.__init__(self, flags=(gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)) + self.set_title("Parsing metadata, please wait...") + self.set_default_size(500, 0) + self.set_transient_for(parent) + self.progress = gtk.ProgressBar() + self.vbox.pack_start(self.progress) + self.show_all() + + def set_text(self, msg): + self.progress.set_text(msg) + + def update(self, x, y): + self.progress.set_fraction(float(x)/float(y)) + self.progress.set_text("%2d %%" % (x*100/y)) + + def pulse(self): + self.progress.set_text("Loading...") + 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 @@ +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2011 Intel Corporation +# +# Authored by Shane Wang +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import gtk +from bb.ui.crumbs.hobcolor import HobColors + +class HobProgressBar (gtk.ProgressBar): + def __init__(self): + gtk.ProgressBar.__init__(self) + self.set_rcstyle(True) + self.percentage = 0 + + def set_rcstyle(self, status): + rcstyle = gtk.RcStyle() + rcstyle.fg[2] = gtk.gdk.Color(HobColors.BLACK) + if status == "stop": + rcstyle.bg[3] = gtk.gdk.Color(HobColors.WARNING) + elif status == "fail": + rcstyle.bg[3] = gtk.gdk.Color(HobColors.ERROR) + else: + rcstyle.bg[3] = gtk.gdk.Color(HobColors.RUNNING) + self.modify_style(rcstyle) + + def set_title(self, text=None): + if not text: + text = "" + text += " %.0f%%" % self.percentage + self.set_text(text) + + def set_stop_title(self, text=None): + if not text: + text = "" + self.set_text(text) + + def reset(self): + self.set_fraction(0) + self.set_text("") + self.set_rcstyle(True) + self.percentage = 0 + + def update(self, fraction): + self.percentage = int(fraction * 100) + 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 @@ + + + + + + Start a build + GTK_WIN_POS_CENTER_ON_PARENT + GDK_WINDOW_TYPE_HINT_DIALOG + False + + + True + 2 + + + True + 6 + 7 + 3 + 5 + 6 + + + True + 12 + + + 6 + + + True + True + 0 + gtk-dialog-error + + + False + False + + + + + True + 0 + If you see this text something is wrong... + True + True + + + 1 + + + + + + + 3 + 2 + 3 + + + + + True + 0 + <b>Build configuration</b> + True + + + 3 + 3 + 4 + + + + + + True + False + + + 1 + 2 + 6 + 7 + + + + + + True + False + 0 + 12 + Image: + + + 6 + 7 + + + + + + True + False + + + 1 + 2 + 5 + 6 + + + + + + True + False + 0 + 12 + Distribution: + + + 5 + 6 + + + + + + True + False + + + 1 + 2 + 4 + 5 + + + + + + True + False + 0 + 12 + Machine: + + + 4 + 5 + + + + + + True + False + True + True + gtk-refresh + True + 0 + + + 2 + 3 + 1 + 2 + + + + + + True + True + 32 + + + 1 + 2 + 1 + 2 + + + + + + True + 0 + 12 + Location: + + + 1 + 2 + + + + + + True + 0 + <b>Repository</b> + True + + + 3 + + + + + + True + + + + + + 2 + 3 + 4 + 5 + + + + + + True + + + + + + 2 + 3 + 5 + 6 + + + + + + True + + + + + + 2 + 3 + 6 + 7 + + + + + + 1 + + + + + True + GTK_BUTTONBOX_END + + + + + + + + + + + + False + GTK_PACK_END + + + + + + + GTK_WIN_POS_CENTER_ON_PARENT + GDK_WINDOW_TYPE_HINT_DIALOG + False + + + True + 2 + + + True + 6 + 7 + 3 + 6 + 6 + + + True + 0 + <b>Repositories</b> + True + + + 3 + + + + + + True + 0 + 12 + + + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + True + True + + + + + + + 3 + 2 + 3 + + + + + + True + True + + + 1 + 3 + 1 + 2 + + + + + + True + 0 + <b>Additional packages</b> + True + + + 3 + 4 + 5 + + + + + + True + 0 + 0 + + + True + 0 + 0 + 12 + Location: + + + + + 1 + 2 + + + + + + True + 1 + 0 + + + True + 5 + + + True + True + True + gtk-remove + True + 0 + + + + + True + True + True + gtk-edit + True + 0 + + + 1 + + + + + True + True + True + gtk-add + True + 0 + + + 2 + + + + + + + 1 + 3 + 3 + 4 + + + + + + True + + + + + + 3 + 4 + + + + + + True + 0 + 0 + 12 + Search: + + + 5 + 6 + + + + + + True + True + + + 1 + 3 + 5 + 6 + + + + + + True + 0 + 12 + + + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + True + True + + + + + + + 3 + 6 + 7 + + + + + + 1 + + + + + True + GTK_BUTTONBOX_END + + + True + True + True + gtk-close + True + 0 + + + + + False + GTK_PACK_END + + + + + + + + + True + + + True + + + True + Build + gtk-execute + + + False + + + + + False + + + + + True + True + + + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + + + + False + True + + + + + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + + + + True + True + + + + + 1 + + + + + + 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 @@ +#!/usr/bin/env python +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2012 Intel Corporation +# +# Authored by Dongxiao Xu +# Authored by Shane Wang +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import gtk +import glib +from bb.ui.crumbs.hobcolor import HobColors +from bb.ui.crumbs.hobwidget import HobViewTable, HobNotebook, HobAltButton, HobButton +from bb.ui.crumbs.hoblistmodel import RecipeListModel +from bb.ui.crumbs.hobpages import HobPage + +# +# RecipeSelectionPage +# +class RecipeSelectionPage (HobPage): + pages = [ + { + 'name' : 'Included recipes', + 'tooltip' : 'The recipes currently included for your image', + 'filter' : { RecipeListModel.COL_INC : [True], + RecipeListModel.COL_TYPE : ['recipe', 'packagegroup'] }, + 'search' : 'Search recipes by name', + 'searchtip' : 'Enter a recipe name to find it', + 'columns' : [{ + 'col_name' : 'Recipe name', + 'col_id' : RecipeListModel.COL_NAME, + 'col_style': 'text', + 'col_min' : 100, + 'col_max' : 400, + 'expand' : 'True' + }, { + 'col_name' : 'Group', + 'col_id' : RecipeListModel.COL_GROUP, + 'col_style': 'text', + 'col_min' : 100, + 'col_max' : 300, + 'expand' : 'True' + }, { + 'col_name' : 'Brought in by (+others)', + 'col_id' : RecipeListModel.COL_BINB, + 'col_style': 'binb', + 'col_min' : 100, + 'col_max' : 500, + 'expand' : 'True' + }, { + 'col_name' : 'Included', + 'col_id' : RecipeListModel.COL_INC, + 'col_style': 'check toggle', + 'col_min' : 100, + 'col_max' : 100 + }] + }, { + 'name' : 'All recipes', + 'tooltip' : 'All recipes in your configured layers', + 'filter' : { RecipeListModel.COL_TYPE : ['recipe'] }, + 'search' : 'Search recipes by name', + 'searchtip' : 'Enter a recipe name to find it', + 'columns' : [{ + 'col_name' : 'Recipe name', + 'col_id' : RecipeListModel.COL_NAME, + 'col_style': 'text', + 'col_min' : 100, + 'col_max' : 400, + 'expand' : 'True' + }, { + 'col_name' : 'Group', + 'col_id' : RecipeListModel.COL_GROUP, + 'col_style': 'text', + 'col_min' : 100, + 'col_max' : 400, + 'expand' : 'True' + }, { + 'col_name' : 'License', + 'col_id' : RecipeListModel.COL_LIC, + 'col_style': 'text', + 'col_min' : 100, + 'col_max' : 400, + 'expand' : 'True' + }, { + 'col_name' : 'Included', + 'col_id' : RecipeListModel.COL_INC, + 'col_style': 'check toggle', + 'col_min' : 100, + 'col_max' : 100 + }] + }, { + 'name' : 'Package Groups', + 'tooltip' : 'All package groups in your configured layers', + 'filter' : { RecipeListModel.COL_TYPE : ['packagegroup'] }, + 'search' : 'Search package groups by name', + 'searchtip' : 'Enter a package group name to find it', + 'columns' : [{ + 'col_name' : 'Package group name', + 'col_id' : RecipeListModel.COL_NAME, + 'col_style': 'text', + 'col_min' : 100, + 'col_max' : 400, + 'expand' : 'True' + }, { + 'col_name' : 'Included', + 'col_id' : RecipeListModel.COL_INC, + 'col_style': 'check toggle', + 'col_min' : 100, + 'col_max' : 100 + }] + } + ] + + (INCLUDED, + ALL, + TASKS) = range(3) + + def __init__(self, builder = None): + super(RecipeSelectionPage, self).__init__(builder, "Step 1 of 2: Edit recipes") + + # set invisible members + self.recipe_model = self.builder.recipe_model + + # create visual elements + self.create_visual_elements() + + def included_clicked_cb(self, button): + self.ins.set_current_page(self.INCLUDED) + + def create_visual_elements(self): + self.eventbox = self.add_onto_top_bar(None, 73) + self.pack_start(self.eventbox, expand=False, fill=False) + self.pack_start(self.group_align, expand=True, fill=True) + + # set visible members + self.ins = HobNotebook() + self.tables = [] # we need modify table when the dialog is shown + + search_names = [] + search_tips = [] + # append the tabs in order + for page in self.pages: + columns = page['columns'] + name = page['name'] + tab = HobViewTable(columns, name) + search_names.append(page['search']) + search_tips.append(page['searchtip']) + filter = page['filter'] + sort_model = self.recipe_model.tree_model(filter, initial=True) + tab.set_model(sort_model) + tab.connect("toggled", self.table_toggled_cb, name) + tab.connect("button-release-event", self.button_click_cb) + tab.connect("cell-fadeinout-stopped", self.after_fadeout_checkin_include, filter) + self.ins.append_page(tab, page['name'], page['tooltip']) + self.tables.append(tab) + + self.ins.set_entry(search_names, search_tips) + self.ins.search.connect("changed", self.search_entry_changed) + + # add all into the window + self.box_group_area.pack_start(self.ins, expand=True, fill=True) + + button_box = gtk.HBox(False, 6) + self.box_group_area.pack_end(button_box, expand=False, fill=False) + + self.build_packages_button = HobButton('Build packages') + #self.build_packages_button.set_size_request(205, 49) + self.build_packages_button.set_tooltip_text("Build selected recipes into packages") + self.build_packages_button.set_flags(gtk.CAN_DEFAULT) + self.build_packages_button.grab_default() + self.build_packages_button.connect("clicked", self.build_packages_clicked_cb) + button_box.pack_end(self.build_packages_button, expand=False, fill=False) + + self.back_button = HobAltButton('Cancel') + self.back_button.connect("clicked", self.back_button_clicked_cb) + button_box.pack_end(self.back_button, expand=False, fill=False) + + def search_entry_changed(self, entry): + text = entry.get_text() + if self.ins.search_focus: + self.ins.search_focus = False + elif self.ins.page_changed: + self.ins.page_change = False + self.filter_search(entry) + elif text not in self.ins.search_names: + self.filter_search(entry) + + def filter_search(self, entry): + text = entry.get_text() + current_tab = self.ins.get_current_page() + filter = self.pages[current_tab]['filter'] + filter[RecipeListModel.COL_NAME] = text + self.tables[current_tab].set_model(self.recipe_model.tree_model(filter, search_data=text)) + if self.recipe_model.filtered_nb == 0: + if not self.ins.get_nth_page(current_tab).top_bar: + self.ins.get_nth_page(current_tab).add_no_result_bar(entry) + self.ins.get_nth_page(current_tab).top_bar.set_no_show_all(True) + self.ins.get_nth_page(current_tab).top_bar.show() + self.ins.get_nth_page(current_tab).scroll.hide() + else: + if self.ins.get_nth_page(current_tab).top_bar: + self.ins.get_nth_page(current_tab).top_bar.hide() + self.ins.get_nth_page(current_tab).scroll.show() + if entry.get_text() == '': + entry.set_icon_sensitive(gtk.ENTRY_ICON_SECONDARY, False) + else: + entry.set_icon_sensitive(gtk.ENTRY_ICON_SECONDARY, True) + + def button_click_cb(self, widget, event): + path, col = widget.table_tree.get_cursor() + tree_model = widget.table_tree.get_model() + if path and col.get_title() != 'Included': # else activation is likely a removal + properties = {'summary': '', 'name': '', 'version': '', 'revision': '', 'binb': '', 'group': '', 'license': '', 'homepage': '', 'bugtracker': '', 'description': ''} + properties['summary'] = tree_model.get_value(tree_model.get_iter(path), RecipeListModel.COL_SUMMARY) + properties['name'] = tree_model.get_value(tree_model.get_iter(path), RecipeListModel.COL_NAME) + properties['version'] = tree_model.get_value(tree_model.get_iter(path), RecipeListModel.COL_VERSION) + properties['revision'] = tree_model.get_value(tree_model.get_iter(path), RecipeListModel.COL_REVISION) + properties['binb'] = tree_model.get_value(tree_model.get_iter(path), RecipeListModel.COL_BINB) + properties['group'] = tree_model.get_value(tree_model.get_iter(path), RecipeListModel.COL_GROUP) + properties['license'] = tree_model.get_value(tree_model.get_iter(path), RecipeListModel.COL_LIC) + properties['homepage'] = tree_model.get_value(tree_model.get_iter(path), RecipeListModel.COL_HOMEPAGE) + properties['bugtracker'] = tree_model.get_value(tree_model.get_iter(path), RecipeListModel.COL_BUGTRACKER) + properties['description'] = tree_model.get_value(tree_model.get_iter(path), RecipeListModel.COL_DESC) + self.builder.show_recipe_property_dialog(properties) + + def build_packages_clicked_cb(self, button): + self.refresh_tables() + self.builder.build_packages() + + def refresh_tables(self): + self.ins.reset_entry(self.ins.search, 0) + for tab in self.tables: + index = self.tables.index(tab) + filter = self.pages[index]['filter'] + tab.set_model(self.recipe_model.tree_model(filter, search_data="", initial=True)) + + def back_button_clicked_cb(self, button): + self.builder.recipe_model.set_selected_image(self.builder.configuration.initial_selected_image) + self.builder.image_configuration_page.update_image_combo(self.builder.recipe_model, self.builder.configuration.initial_selected_image) + self.builder.image_configuration_page.update_image_desc() + self.builder.show_configuration() + self.refresh_tables() + + def refresh_selection(self): + self.builder.configuration.selected_image = self.recipe_model.get_selected_image() + _, self.builder.configuration.selected_recipes = self.recipe_model.get_selected_recipes() + self.ins.show_indicator_icon("Included recipes", len(self.builder.configuration.selected_recipes)) + + def toggle_item_idle_cb(self, path, view_tree, cell, pagename): + if not self.recipe_model.path_included(path): + self.recipe_model.include_item(item_path=path, binb="User Selected", image_contents=False) + else: + self.pre_fadeout_checkout_include(view_tree, pagename) + self.recipe_model.exclude_item(item_path=path) + self.render_fadeout(view_tree, cell) + + self.refresh_selection() + if not self.builder.customized: + self.builder.customized = True + self.builder.configuration.selected_image = self.recipe_model.__custom_image__ + self.builder.rcppkglist_populated() + + self.builder.window_sensitive(True) + + view_model = view_tree.get_model() + vpath = self.recipe_model.convert_path_to_vpath(view_model, path) + view_tree.set_cursor(vpath) + + def table_toggled_cb(self, table, cell, view_path, toggled_columnid, view_tree, pagename): + # Click to include a recipe + self.builder.window_sensitive(False) + view_model = view_tree.get_model() + path = self.recipe_model.convert_vpath_to_path(view_model, view_path) + glib.idle_add(self.toggle_item_idle_cb, path, view_tree, cell, pagename) + + def pre_fadeout_checkout_include(self, tree, pagename): + #after the fadeout the table will be sorted as before + self.sort_column_id = self.recipe_model.sort_column_id + self.sort_order = self.recipe_model.sort_order + + #resync the included items to a backup fade include column + it = self.recipe_model.get_iter_first() + while it: + active = self.recipe_model.get_value(it, self.recipe_model.COL_INC) + self.recipe_model.set(it, self.recipe_model.COL_FADE_INC, active) + it = self.recipe_model.iter_next(it) + # Check out a model which base on the column COL_FADE_INC, + # it's save the prev state of column COL_INC before do exclude_item + filter = { RecipeListModel.COL_FADE_INC:[True] } + if pagename == "Included recipes": + filter[RecipeListModel.COL_TYPE] = ['recipe', 'packagegroup'] + elif pagename == "All recipes": + filter[RecipeListModel.COL_TYPE] = ['recipe'] + else: + filter[RecipeListModel.COL_TYPE] = ['packagegroup'] + + new_model = self.recipe_model.tree_model(filter, excluded_items_ahead=True) + tree.set_model(new_model) + + def render_fadeout(self, tree, cell): + if (not cell) or (not tree): + return + to_render_cells = [] + model = tree.get_model() + it = model.get_iter_first() + while it: + path = model.get_path(it) + prev_cell_is_active = model.get_value(it, RecipeListModel.COL_FADE_INC) + curr_cell_is_active = model.get_value(it, RecipeListModel.COL_INC) + if (prev_cell_is_active == True) and (curr_cell_is_active == False): + to_render_cells.append(path) + it = model.iter_next(it) + + cell.fadeout(tree, 1000, to_render_cells) + + def after_fadeout_checkin_include(self, table, ctrl, cell, tree, filter): + self.recipe_model.sort_column_id = self.sort_column_id + self.recipe_model.sort_order = self.sort_order + tree.set_model(self.recipe_model.tree_model(filter)) + + def set_recipe_curr_tab(self, curr_page): + self.ins.set_current_page(curr_page) diff --git a/bitbake/lib/bb/ui/crumbs/runningbuild.py b/bitbake/lib/bb/ui/crumbs/runningbuild.py new file mode 100644 index 0000000000..16a955d2b1 --- /dev/null +++ b/bitbake/lib/bb/ui/crumbs/runningbuild.py @@ -0,0 +1,551 @@ + +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2008 Intel Corporation +# +# Authored by Rob Bradford +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import gtk +import gobject +import logging +import time +import urllib +import urllib2 +import pango +from bb.ui.crumbs.hobcolor import HobColors +from bb.ui.crumbs.hobwidget import HobWarpCellRendererText, HobCellRendererPixbuf + +class RunningBuildModel (gtk.TreeStore): + (COL_LOG, COL_PACKAGE, COL_TASK, COL_MESSAGE, COL_ICON, COL_COLOR, COL_NUM_ACTIVE) = range(7) + + def __init__ (self): + gtk.TreeStore.__init__ (self, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_INT) + + def failure_model_filter(self, model, it): + color = model.get(it, self.COL_COLOR)[0] + if not color: + return False + if color == HobColors.ERROR or color == HobColors.WARNING: + return True + return False + + def failure_model(self): + model = self.filter_new() + model.set_visible_func(self.failure_model_filter) + return model + + def foreach_cell_func(self, model, path, iter, usr_data=None): + if model.get_value(iter, self.COL_ICON) == "gtk-execute": + model.set(iter, self.COL_ICON, "") + + def close_task_refresh(self): + self.foreach(self.foreach_cell_func, None) + +class RunningBuild (gobject.GObject): + __gsignals__ = { + 'build-started' : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + ()), + 'build-succeeded' : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + ()), + 'build-failed' : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + ()), + 'build-complete' : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + ()), + 'build-aborted' : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + ()), + 'task-started' : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_PYOBJECT,)), + 'log-error' : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + ()), + 'log-warning' : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + ()), + 'disk-full' : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + ()), + 'no-provider' : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_PYOBJECT,)), + 'log' : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_STRING, gobject.TYPE_PYOBJECT,)), + } + pids_to_task = {} + tasks_to_iter = {} + + def __init__ (self, sequential=False): + gobject.GObject.__init__ (self) + self.model = RunningBuildModel() + self.sequential = sequential + self.buildaborted = False + + def reset (self): + self.pids_to_task.clear() + self.tasks_to_iter.clear() + self.model.clear() + + def handle_event (self, event, pbar=None): + # Handle an event from the event queue, this may result in updating + # the model and thus the UI. Or it may be to tell us that the build + # has finished successfully (or not, as the case may be.) + + parent = None + pid = 0 + package = None + task = None + + # If we have a pid attached to this message/event try and get the + # (package, task) pair for it. If we get that then get the parent iter + # for the message. + if hasattr(event, 'pid'): + pid = event.pid + if hasattr(event, 'process'): + pid = event.process + + if pid and pid in self.pids_to_task: + (package, task) = self.pids_to_task[pid] + parent = self.tasks_to_iter[(package, task)] + + if(isinstance(event, logging.LogRecord)): + if event.taskpid == 0 or event.levelno > logging.INFO: + self.emit("log", "handle", event) + # FIXME: this is a hack! More info in Yocto #1433 + # http://bugzilla.pokylinux.org/show_bug.cgi?id=1433, temporarily + # mask the error message as it's not informative for the user. + if event.msg.startswith("Execution of event handler 'run_buildstats' failed"): + return + + if (event.levelno < logging.INFO or + event.msg.startswith("Running task")): + return # don't add these to the list + + if event.levelno >= logging.ERROR: + icon = "dialog-error" + color = HobColors.ERROR + self.emit("log-error") + elif event.levelno >= logging.WARNING: + icon = "dialog-warning" + color = HobColors.WARNING + self.emit("log-warning") + else: + icon = None + color = HobColors.OK + + # if we know which package we belong to, we'll append onto its list. + # otherwise, we'll jump to the top of the master list + if self.sequential or not parent: + tree_add = self.model.append + else: + tree_add = self.model.prepend + tree_add(parent, + (None, + package, + task, + event.getMessage(), + icon, + color, + 0)) + + # if there are warnings while processing a package + # (parent), mark the task with warning color; + # in case there are errors, the updates will be + # handled on TaskFailed. + if color == HobColors.WARNING and parent: + self.model.set(parent, self.model.COL_COLOR, color) + if task: #then we have a parent (package), and update it's color + self.model.set(self.tasks_to_iter[(package, None)], self.model.COL_COLOR, color) + + elif isinstance(event, bb.build.TaskStarted): + (package, task) = (event._package, event._task) + + # Save out this PID. + self.pids_to_task[pid] = (package, task) + + # Check if we already have this package in our model. If so then + # that can be the parent for the task. Otherwise we create a new + # top level for the package. + if ((package, None) in self.tasks_to_iter): + parent = self.tasks_to_iter[(package, None)] + else: + if self.sequential: + add = self.model.append + else: + add = self.model.prepend + parent = add(None, (None, + package, + None, + "Package: %s" % (package), + None, + HobColors.OK, + 0)) + self.tasks_to_iter[(package, None)] = parent + + # Because this parent package now has an active child mark it as + # such. + self.model.set(parent, self.model.COL_ICON, "gtk-execute") + parent_color = self.model.get(parent, self.model.COL_COLOR)[0] + if parent_color != HobColors.ERROR and parent_color != HobColors.WARNING: + self.model.set(parent, self.model.COL_COLOR, HobColors.RUNNING) + + # Add an entry in the model for this task + i = self.model.append (parent, (None, + package, + task, + "Task: %s" % (task), + "gtk-execute", + HobColors.RUNNING, + 0)) + + # update the parent's active task count + num_active = self.model.get(parent, self.model.COL_NUM_ACTIVE)[0] + 1 + self.model.set(parent, self.model.COL_NUM_ACTIVE, num_active) + + # Save out the iter so that we can find it when we have a message + # that we need to attach to a task. + self.tasks_to_iter[(package, task)] = i + + elif isinstance(event, bb.build.TaskBase): + self.emit("log", "info", event._message) + current = self.tasks_to_iter[(package, task)] + parent = self.tasks_to_iter[(package, None)] + + # remove this task from the parent's active count + num_active = self.model.get(parent, self.model.COL_NUM_ACTIVE)[0] - 1 + self.model.set(parent, self.model.COL_NUM_ACTIVE, num_active) + + if isinstance(event, bb.build.TaskFailed): + # Mark the task and parent as failed + icon = "dialog-error" + color = HobColors.ERROR + + logfile = event.logfile + if logfile and os.path.exists(logfile): + with open(logfile) as f: + logdata = f.read() + self.model.append(current, ('pastebin', None, None, logdata, 'gtk-error', HobColors.OK, 0)) + + for i in (current, parent): + self.model.set(i, self.model.COL_ICON, icon, + self.model.COL_COLOR, color) + else: + # Mark the parent package and the task as inactive, + # but make sure to preserve error, warnings and active + # states + parent_color = self.model.get(parent, self.model.COL_COLOR)[0] + task_color = self.model.get(current, self.model.COL_COLOR)[0] + + # Mark the task as inactive + self.model.set(current, self.model.COL_ICON, None) + if task_color != HobColors.ERROR: + if task_color == HobColors.WARNING: + self.model.set(current, self.model.COL_ICON, 'dialog-warning') + else: + self.model.set(current, self.model.COL_COLOR, HobColors.OK) + + # Mark the parent as inactive + if parent_color != HobColors.ERROR: + if parent_color == HobColors.WARNING: + self.model.set(parent, self.model.COL_ICON, "dialog-warning") + else: + self.model.set(parent, self.model.COL_ICON, None) + if num_active == 0: + self.model.set(parent, self.model.COL_COLOR, HobColors.OK) + + # Clear the iters and the pids since when the task goes away the + # pid will no longer be used for messages + del self.tasks_to_iter[(package, task)] + del self.pids_to_task[pid] + + elif isinstance(event, bb.event.BuildStarted): + + self.emit("build-started") + self.model.prepend(None, (None, + None, + None, + "Build Started (%s)" % time.strftime('%m/%d/%Y %H:%M:%S'), + None, + HobColors.OK, + 0)) + if pbar: + pbar.update(0, self.progress_total) + pbar.set_title(bb.event.getName(event)) + + elif isinstance(event, bb.event.BuildCompleted): + failures = int (event._failures) + self.model.prepend(None, (None, + None, + None, + "Build Completed (%s)" % time.strftime('%m/%d/%Y %H:%M:%S'), + None, + HobColors.OK, + 0)) + + # Emit the appropriate signal depending on the number of failures + if self.buildaborted: + self.emit ("build-aborted") + self.buildaborted = False + elif (failures >= 1): + self.emit ("build-failed") + else: + self.emit ("build-succeeded") + # Emit a generic "build-complete" signal for things wishing to + # handle when the build is finished + self.emit("build-complete") + # reset the all cell's icon indicator + self.model.close_task_refresh() + if pbar: + pbar.set_text(event.msg) + + elif isinstance(event, bb.event.DiskFull): + self.buildaborted = True + self.emit("disk-full") + + elif isinstance(event, bb.command.CommandFailed): + self.emit("log", "error", "Command execution failed: %s" % (event.error)) + if event.error.startswith("Exited with"): + # If the command fails with an exit code we're done, emit the + # generic signal for the UI to notify the user + self.emit("build-complete") + # reset the all cell's icon indicator + self.model.close_task_refresh() + + elif isinstance(event, bb.event.CacheLoadStarted) and pbar: + pbar.set_title("Loading cache") + self.progress_total = event.total + pbar.update(0, self.progress_total) + elif isinstance(event, bb.event.CacheLoadProgress) and pbar: + pbar.update(event.current, self.progress_total) + elif isinstance(event, bb.event.CacheLoadCompleted) and pbar: + pbar.update(self.progress_total, self.progress_total) + pbar.hide() + elif isinstance(event, bb.event.ParseStarted) and pbar: + if event.total == 0: + return + pbar.set_title("Processing recipes") + self.progress_total = event.total + pbar.update(0, self.progress_total) + elif isinstance(event, bb.event.ParseProgress) and pbar: + pbar.update(event.current, self.progress_total) + elif isinstance(event, bb.event.ParseCompleted) and pbar: + pbar.hide() + #using runqueue events as many as possible to update the progress bar + elif isinstance(event, bb.runqueue.runQueueTaskFailed): + self.emit("log", "error", "Task %s (%s) failed with exit code '%s'" % (event.taskid, event.taskstring, event.exitcode)) + elif isinstance(event, bb.runqueue.sceneQueueTaskFailed): + self.emit("log", "warn", "Setscene task %s (%s) failed with exit code '%s' - real task will be run instead" \ + % (event.taskid, event.taskstring, event.exitcode)) + elif isinstance(event, (bb.runqueue.runQueueTaskStarted, bb.runqueue.sceneQueueTaskStarted)): + if isinstance(event, bb.runqueue.sceneQueueTaskStarted): + self.emit("log", "info", "Running setscene task %d of %d (%s)" % \ + (event.stats.completed + event.stats.active + event.stats.failed + 1, + event.stats.total, event.taskstring)) + else: + if event.noexec: + tasktype = 'noexec task' + else: + tasktype = 'task' + self.emit("log", "info", "Running %s %s of %s (ID: %s, %s)" % \ + (tasktype, event.stats.completed + event.stats.active + event.stats.failed + 1, + event.stats.total, event.taskid, event.taskstring)) + message = {} + message["eventname"] = bb.event.getName(event) + num_of_completed = event.stats.completed + event.stats.failed + message["current"] = num_of_completed + message["total"] = event.stats.total + message["title"] = "" + message["task"] = event.taskstring + self.emit("task-started", message) + elif isinstance(event, bb.event.MultipleProviders): + self.emit("log", "info", "multiple providers are available for %s%s (%s)" \ + % (event._is_runtime and "runtime " or "", event._item, ", ".join(event._candidates))) + self.emit("log", "info", "consider defining a PREFERRED_PROVIDER entry to match %s" % (event._item)) + elif isinstance(event, bb.event.NoProvider): + msg = "" + if event._runtime: + r = "R" + else: + r = "" + + extra = '' + if not event._reasons: + if event._close_matches: + extra = ". Close matches:\n %s" % '\n '.join(event._close_matches) + + if event._dependees: + msg = "Nothing %sPROVIDES '%s' (but %s %sDEPENDS on or otherwise requires it)%s\n" % (r, event._item, ", ".join(event._dependees), r, extra) + else: + msg = "Nothing %sPROVIDES '%s'%s\n" % (r, event._item, extra) + if event._reasons: + for reason in event._reasons: + msg += ("%s\n" % reason) + self.emit("no-provider", msg) + self.emit("log", "error", msg) + elif isinstance(event, bb.event.LogExecTTY): + icon = "dialog-warning" + color = HobColors.WARNING + if self.sequential or not parent: + tree_add = self.model.append + else: + tree_add = self.model.prepend + tree_add(parent, + (None, + package, + task, + event.msg, + icon, + color, + 0)) + else: + if not isinstance(event, (bb.event.BuildBase, + bb.event.StampUpdate, + bb.event.ConfigParsed, + bb.event.RecipeParsed, + bb.event.RecipePreFinalise, + bb.runqueue.runQueueEvent, + bb.runqueue.runQueueExitWait, + bb.event.OperationStarted, + bb.event.OperationCompleted, + bb.event.OperationProgress)): + self.emit("log", "error", "Unknown event: %s" % (event.error if hasattr(event, 'error') else 'error')) + + return + + +def do_pastebin(text): + url = 'http://pastebin.com/api_public.php' + params = {'paste_code': text, 'paste_format': 'text'} + + req = urllib2.Request(url, urllib.urlencode(params)) + response = urllib2.urlopen(req) + paste_url = response.read() + + return paste_url + + +class RunningBuildTreeView (gtk.TreeView): + __gsignals__ = { + "button_press_event" : "override" + } + def __init__ (self, readonly=False, hob=False): + gtk.TreeView.__init__ (self) + self.readonly = readonly + + # The icon that indicates whether we're building or failed. + # add 'hob' flag because there has not only hob to share this code + if hob: + renderer = HobCellRendererPixbuf () + else: + renderer = gtk.CellRendererPixbuf() + col = gtk.TreeViewColumn ("Status", renderer) + col.add_attribute (renderer, "icon-name", 4) + self.append_column (col) + + # The message of the build. + # add 'hob' flag because there has not only hob to share this code + if hob: + self.message_renderer = HobWarpCellRendererText (col_number=1) + else: + self.message_renderer = gtk.CellRendererText () + self.message_column = gtk.TreeViewColumn ("Message", self.message_renderer, text=3) + self.message_column.add_attribute(self.message_renderer, 'background', 5) + self.message_renderer.set_property('editable', (not self.readonly)) + self.append_column (self.message_column) + + def do_button_press_event(self, event): + gtk.TreeView.do_button_press_event(self, event) + + if event.button == 3: + selection = super(RunningBuildTreeView, self).get_selection() + (model, it) = selection.get_selected() + if it is not None: + can_paste = model.get(it, model.COL_LOG)[0] + if can_paste == 'pastebin': + # build a simple menu with a pastebin option + menu = gtk.Menu() + menuitem = gtk.MenuItem("Copy") + menu.append(menuitem) + menuitem.connect("activate", self.clipboard_handler, (model, it)) + menuitem.show() + menuitem = gtk.MenuItem("Send log to pastebin") + menu.append(menuitem) + menuitem.connect("activate", self.pastebin_handler, (model, it)) + menuitem.show() + menu.show() + menu.popup(None, None, None, event.button, event.time) + + def _add_to_clipboard(self, clipping): + """ + Add the contents of clipping to the system clipboard. + """ + clipboard = gtk.clipboard_get() + clipboard.set_text(clipping) + clipboard.store() + + def pastebin_handler(self, widget, data): + """ + Send the log data to pastebin, then add the new paste url to the + clipboard. + """ + (model, it) = data + paste_url = do_pastebin(model.get(it, model.COL_MESSAGE)[0]) + + # @todo Provide visual feedback to the user that it is done and that + # it worked. + print paste_url + + self._add_to_clipboard(paste_url) + + def clipboard_handler(self, widget, data): + """ + """ + (model, it) = data + message = model.get(it, model.COL_MESSAGE)[0] + + self._add_to_clipboard(message) + +class BuildFailureTreeView(gtk.TreeView): + + def __init__ (self): + gtk.TreeView.__init__(self) + self.set_rules_hint(False) + self.set_headers_visible(False) + self.get_selection().set_mode(gtk.SELECTION_SINGLE) + + # The icon that indicates whether we're building or failed. + renderer = HobCellRendererPixbuf () + col = gtk.TreeViewColumn ("Status", renderer) + col.add_attribute (renderer, "icon-name", RunningBuildModel.COL_ICON) + self.append_column (col) + + # The message of the build. + self.message_renderer = HobWarpCellRendererText (col_number=1) + self.message_column = gtk.TreeViewColumn ("Message", self.message_renderer, text=RunningBuildModel.COL_MESSAGE, background=RunningBuildModel.COL_COLOR) + 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 @@ +#!/usr/bin/env python +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2012 Intel Corporation +# +# Authored by Bogdan Marinescu +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import gtk, gobject +from bb.ui.crumbs.progressbar import HobProgressBar +from bb.ui.crumbs.hobwidget import hic +from bb.ui.crumbs.hobpages import HobPage + +# +# SanityCheckPage +# +class SanityCheckPage (HobPage): + + def __init__(self, builder): + super(SanityCheckPage, self).__init__(builder) + self.running = False + self.create_visual_elements() + self.show_all() + + def make_label(self, text, bold=True): + label = gtk.Label() + label.set_alignment(0.0, 0.5) + mark = "%s" % (self.span_tag('x-large', 'bold') if bold else self.span_tag('medium'), text) + label.set_markup(mark) + return label + + def start(self): + if not self.running: + self.running = True + gobject.timeout_add(100, self.timer_func) + + def stop(self): + self.running = False + + def is_running(self): + return self.running + + def timer_func(self): + self.progress_bar.pulse() + return self.running + + def create_visual_elements(self): + # Table'd layout. 'rows' and 'cols' give the table size + rows, cols = 30, 50 + self.table = gtk.Table(rows, cols, True) + self.pack_start(self.table, expand=False, fill=False) + sx, sy = 2, 2 + # 'info' icon + image = gtk.Image() + image.set_from_file(hic.ICON_INFO_DISPLAY_FILE) + self.table.attach(image, sx, sx + 2, sy, sy + 3 ) + image.show() + # 'Checking' message + label = self.make_label('Hob is checking for correct build system setup') + self.table.attach(label, sx + 2, cols, sy, sy + 3, xpadding=5 ) + label.show() + # 'Shouldn't take long' message. + label = self.make_label("The check shouldn't take long.", False) + self.table.attach(label, sx + 2, cols, sy + 3, sy + 4, xpadding=5) + label.show() + # Progress bar + self.progress_bar = HobProgressBar() + self.table.attach(self.progress_bar, sx + 2, cols - 3, sy + 5, sy + 7, xpadding=5) + self.progress_bar.show() + # All done + self.table.show() + 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 @@ +# +# BitBake UI Utils +# +# Copyright (C) 2012 Intel Corporation +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +# This utility method looks for xterm or vte and return the +# frist to exist, currently we are keeping this simple, but +# we will likely move the oe.terminal implementation into +# bitbake which will allow more flexibility. + +import os +import bb + +def which_terminal(): + term = bb.utils.which(os.environ["PATH"], "xterm") + if term: + return term + " -e " + term = bb.utils.which(os.environ["PATH"], "vte") + if term: + return term + " -c " + return None -- cgit v1.2.3-54-g00ecf