summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/bb/ui/hob.py
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake/lib/bb/ui/hob.py')
-rw-r--r--bitbake/lib/bb/ui/hob.py924
1 files changed, 625 insertions, 299 deletions
diff --git a/bitbake/lib/bb/ui/hob.py b/bitbake/lib/bb/ui/hob.py
index 175e5bd..fca41e4 100644
--- a/bitbake/lib/bb/ui/hob.py
+++ b/bitbake/lib/bb/ui/hob.py
@@ -18,12 +18,16 @@
# 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 gobject
import gtk
-from bb.ui.crumbs.progress import ProgressBar
-from bb.ui.crumbs.tasklistmodel import TaskListModel
+from bb.ui.crumbs.tasklistmodel import TaskListModel, BuildRep
from bb.ui.crumbs.hobeventhandler import HobHandler
+from bb.ui.crumbs.configurator import Configurator
+from bb.ui.crumbs.hobprefs import HobPrefs
+from bb.ui.crumbs.layereditor import LayerEditor
from bb.ui.crumbs.runningbuild import RunningBuildTreeView, RunningBuild
+from bb.ui.crumbs.hig import CrumbsDialog
import xmlrpclib
import logging
import Queue
@@ -32,226 +36,459 @@ extraCaches = ['bb.cache_extra:HobRecipeInfo']
class MainWindow (gtk.Window):
- def __init__(self, taskmodel, handler, curr_mach=None, curr_distro=None):
- gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
+ def __init__(self, taskmodel, handler, configurator, prefs, layers, mach):
+ gtk.Window.__init__(self)
+ # global state
+ self.curr_mach = mach
+ self.machine_handler_id = None
+ self.image_combo_id = None
+ self.generating = False
+ self.files_to_clean = []
+ self.selected_image = None
+ self.selected_packages = None
+
self.model = taskmodel
- self.model.connect("tasklist-populated", self.update_model)
- self.curr_mach = curr_mach
- self.curr_distro = curr_distro
+ self.model.connect("tasklist-populated", self.update_model)
+ self.model.connect("image-changed", self.image_changed_string_cb)
+ self.curr_image_path = None
self.handler = handler
- self.set_border_width(10)
- self.connect("delete-event", gtk.main_quit)
- self.set_title("BitBake Image Creator")
- self.set_default_size(700, 600)
+ self.configurator = configurator
+ self.prefs = prefs
+ self.layers = layers
+ self.save_path = None
+ self.dirty = False
+
+ self.connect("delete-event", self.destroy_window)
+ self.set_title("Image Creator")
+ self.set_icon_name("applications-development")
+ self.set_default_size(1000, 650)
self.build = RunningBuild()
- self.build.connect("build-succeeded", self.running_build_succeeded_cb)
self.build.connect("build-failed", self.running_build_failed_cb)
+ self.build.connect("build-complete", self.handler.build_complete_cb)
+ self.build.connect("build-started", self.build_started_cb)
- createview = self.create_build_gui()
+ self.handler.connect("build-complete", self.build_complete_cb)
+
+ vbox = gtk.VBox(False, 0)
+ vbox.set_border_width(0)
+ vbox.show()
+ self.add(vbox)
+ self.menu = self.create_menu()
+ vbox.pack_start(self.menu, False)
+ createview = self.create_build_gui()
+ self.back = None
+ self.cancel = None
buildview = self.view_build_gui()
- self.nb = gtk.Notebook()
- self.nb.append_page(createview)
- self.nb.append_page(buildview)
- self.nb.set_current_page(0)
- self.nb.set_show_tabs(False)
- self.add(self.nb)
- self.generating = False
+ self.nb = gtk.Notebook()
+ self.nb.append_page(createview)
+ self.nb.append_page(buildview)
+ self.nb.set_current_page(0)
+ self.nb.set_show_tabs(False)
+ vbox.pack_start(self.nb, expand=True, fill=True)
+
+ def destroy_window(self, widget, event):
+ self.quit()
+
+ def menu_quit(self, action):
+ self.quit()
+
+ def quit(self):
+ if self.dirty and len(self.model.contents):
+ question = "Would you like to save your customisations?"
+ dialog = CrumbsDialog(self, question, gtk.STOCK_DIALOG_WARNING)
+ dialog.add_buttons(gtk.STOCK_NO, gtk.RESPONSE_NO,
+ gtk.STOCK_YES, gtk.RESPONSE_YES)
+ resp = dialog.run()
+ if resp == gtk.RESPONSE_YES:
+ if not self.save_path:
+ self.get_save_path()
+ self.save_recipe_file()
+ rep = self.model.get_build_rep()
+ rep.writeRecipe(self.save_path, self.model)
+
+ gtk.main_quit()
def scroll_tv_cb(self, model, path, it, view):
view.scroll_to_cell(path)
def running_build_failed_cb(self, running_build):
# FIXME: handle this
- return
+ print("Build failed")
+
+ def image_changed_string_cb(self, model, new_image):
+ cnt = 0
+ it = self.model.images.get_iter_first()
+ while it:
+ path = self.model.images.get_path(it)
+ if self.model.images[path][self.model.COL_NAME] == new_image:
+ self.image_combo.set_active(cnt)
+ break
+ it = self.model.images.iter_next(it)
+ cnt = cnt + 1
+
+ def image_changed_cb(self, combo):
+ model = self.image_combo.get_model()
+ it = self.image_combo.get_active_iter()
+ if it:
+ path = model.get_path(it)
+ # Firstly, deselect the previous image
+ if self.curr_image_path:
+ self.toggle_package(self.curr_image_path, model)
+ # Now select the new image and save its path in case we
+ # change the image later
+ self.curr_image_path = path
+ self.toggle_package(path, model, image=True)
+
+ def reload_triggered_cb(self, handler, image, packages):
+ if image:
+ self.selected_image = image
+ if len(packages):
+ self.selected_packages = packages.split()
- def running_build_succeeded_cb(self, running_build):
- label = gtk.Label("Build completed, start another build?")
- dialog = gtk.Dialog("Build complete",
- self,
- gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
- (gtk.STOCK_NO, gtk.RESPONSE_NO,
- gtk.STOCK_YES, gtk.RESPONSE_YES))
- dialog.vbox.pack_start(label)
- label.show()
- response = dialog.run()
- dialog.destroy()
- if response == gtk.RESPONSE_YES:
- self.model.reset() # NOTE: really?
- self.nb.set_current_page(0)
- return
+ def data_generated(self, handler):
+ self.generating = False
+ self.image_combo.set_model(self.model.images_model())
+ if not self.image_combo_id:
+ self.image_combo_id = self.image_combo.connect("changed", self.image_changed_cb)
+ self.enable_widgets()
def machine_combo_changed_cb(self, combo, handler):
mach = combo.get_active_text()
- if mach != self.curr_mach:
- self.curr_mach = mach
+ if mach != self.curr_mach:
+ self.curr_mach = mach
+ # Flush this straight to the file as MACHINE is changed
+ # independently of other 'Preferences'
+ self.configurator.setLocalConfVar('MACHINE', mach)
+ self.configurator.writeLocalConf()
handler.set_machine(mach)
+ handler.reload_data()
def update_machines(self, handler, machines):
- active = 0
- for machine in machines:
- self.machine_combo.append_text(machine)
- if machine == self.curr_mach:
+ active = 0
+ # disconnect the signal handler before updating the combo model
+ if self.machine_handler_id:
+ self.machine_combo.disconnect(self.machine_handler_id)
+ self.machine_handler_id = None
+
+ model = self.machine_combo.get_model()
+ if model:
+ model.clear()
+
+ for machine in machines:
+ self.machine_combo.append_text(machine)
+ if machine == self.curr_mach:
self.machine_combo.set_active(active)
- active = active + 1
- self.machine_combo.connect("changed", self.machine_combo_changed_cb, handler)
-
- def update_distros(self, handler, distros):
- # FIXME: when we add UI for changing distro this will be used
- return
-
- def data_generated(self, handler):
- self.generating = False
-
- def spin_idle_func(self, pbar):
+ active = active + 1
+
+ self.machine_handler_id = self.machine_combo.connect("changed", self.machine_combo_changed_cb, handler)
+
+ def set_busy_cursor(self, busy=True):
+ """
+ Convenience method to set the cursor to a spinner when executing
+ a potentially lengthy process.
+ A busy value of False will set the cursor back to the default
+ left pointer.
+ """
+ if busy:
+ cursor = gtk.gdk.Cursor(gtk.gdk.WATCH)
+ else:
+ # TODO: presumably the default cursor is different on RTL
+ # systems. Can we determine the default cursor? Or at least
+ # the cursor which is set before we change it?
+ cursor = gtk.gdk.Cursor(gtk.gdk.LEFT_PTR)
+ window = self.get_root_window()
+ window.set_cursor(cursor)
+
+ def busy_idle_func(self):
if self.generating:
- pbar.pulse()
+ self.progress.set_text("Loading...")
+ self.progress.pulse()
return True
else:
- pbar.hide()
+ if not self.image_combo_id:
+ self.image_combo_id = self.image_combo.connect("changed", self.image_changed_cb)
+ self.progress.set_text("Loaded")
+ self.progress.set_fraction(0.0)
+ self.set_busy_cursor(False)
return False
def busy(self, handler):
self.generating = True
- pbar = ProgressBar(self)
- pbar.connect("delete-event", gtk.main_quit) # NOTE: questionable...
- pbar.pulse()
- gobject.timeout_add (200,
- self.spin_idle_func,
- pbar)
+ self.set_busy_cursor()
+ if self.image_combo_id:
+ self.image_combo.disconnect(self.image_combo_id)
+ self.image_combo_id = None
+ self.progress.pulse()
+ gobject.timeout_add (200, self.busy_idle_func)
+ self.disable_widgets()
+
+ def enable_widgets(self):
+ self.menu.set_sensitive(True)
+ self.machine_combo.set_sensitive(True)
+ self.image_combo.set_sensitive(True)
+ self.nb.set_sensitive(True)
+ self.contents_tree.set_sensitive(True)
+
+ def disable_widgets(self):
+ self.menu.set_sensitive(False)
+ self.machine_combo.set_sensitive(False)
+ self.image_combo.set_sensitive(False)
+ self.nb.set_sensitive(False)
+ self.contents_tree.set_sensitive(False)
def update_model(self, model):
- pkgsaz_model = gtk.TreeModelSort(self.model.packages_model())
+ # We want the packages model to be alphabetised and sortable so create
+ # a TreeModelSort to use in the view
+ pkgsaz_model = gtk.TreeModelSort(self.model.packages_model())
pkgsaz_model.set_sort_column_id(self.model.COL_NAME, gtk.SORT_ASCENDING)
+ # Unset default sort func so that we only toggle between A-Z and
+ # Z-A sorting
+ pkgsaz_model.set_default_sort_func(None)
self.pkgsaz_tree.set_model(pkgsaz_model)
- # FIXME: need to implement a custom sort function, as otherwise the column
- # is re-ordered when toggling the inclusion state (COL_INC)
- pkgsgrp_model = gtk.TreeModelSort(self.model.packages_model())
- pkgsgrp_model.set_sort_column_id(self.model.COL_GROUP, gtk.SORT_ASCENDING)
- self.pkgsgrp_tree.set_model(pkgsgrp_model)
-
- self.contents_tree.set_model(self.model.contents_model())
- self.images_tree.set_model(self.model.images_model())
- self.tasks_tree.set_model(self.model.tasks_model())
+ # We want the contents to be alphabetised so create a TreeModelSort to
+ # use in the view
+ contents_model = gtk.TreeModelSort(self.model.contents_model())
+ contents_model.set_sort_column_id(self.model.COL_NAME, gtk.SORT_ASCENDING)
+ # Unset default sort func so that we only toggle between A-Z and
+ # Z-A sorting
+ contents_model.set_default_sort_func(None)
+ self.contents_tree.set_model(contents_model)
+ self.tasks_tree.set_model(self.model.tasks_model())
+
+ if self.selected_image:
+ if self.image_combo_id:
+ self.image_combo.disconnect(self.image_combo_id)
+ self.image_combo_id = None
+ self.model.set_selected_image(self.selected_image)
+ self.selected_image = None
+ if not self.image_combo_id:
+ self.image_combo_id = self.image_combo.connect("changed", self.image_changed_cb)
+
+ if self.selected_packages:
+ self.model.set_selected_packages(self.selected_packages)
+ self.selected_packages = None
def reset_clicked_cb(self, button):
- label = gtk.Label("Are you sure you want to reset the image contents?")
- dialog = gtk.Dialog("Confirm reset", self,
- gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
- (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
- gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
- dialog.vbox.pack_start(label)
- label.show()
+ lbl = "<b>Reset your selections?</b>\n\nAny new changes you have made will be lost"
+ dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING)
+ dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
+ dialog.add_button("Reset", gtk.RESPONSE_OK)
response = dialog.run()
dialog.destroy()
- if (response == gtk.RESPONSE_ACCEPT):
- self.model.reset()
+ if response == gtk.RESPONSE_OK:
+ self.reset_build()
return
+ def reset_build(self):
+ self.image_combo.disconnect(self.image_combo_id)
+ self.image_combo_id = None
+ self.image_combo.set_active(-1)
+ self.image_combo_id = self.image_combo.connect("changed", self.image_changed_cb)
+ self.model.reset()
+
+ def layers_cb(self, action):
+ resp = self.layers.run()
+ self.layers.save_current_layers()
+ self.layers.hide()
+
+ def add_layer_cb(self, action):
+ self.layers.find_layer(self)
+
+ def preferences_cb(self, action):
+ resp = self.prefs.run()
+ self.prefs.write_changes()
+ self.prefs.hide()
+
+ def about_cb(self, action):
+ about = gtk.AboutDialog()
+ about.set_name("Image Creator")
+ about.set_copyright("Copyright (C) 2011 Intel Corporation")
+ about.set_authors(["Joshua Lock <josh@linux.intel.com>"])
+ about.set_logo_icon_name("applications-development")
+ about.run()
+ about.destroy()
+
+ def save_recipe_file(self):
+ rep = self.model.get_build_rep()
+ rep.writeRecipe(self.save_path, self.model)
+ self.dirty = False
+
+ def get_save_path(self):
+ chooser = gtk.FileChooserDialog(title=None, parent=self,
+ action=gtk.FILE_CHOOSER_ACTION_SAVE,
+ buttons=(gtk.STOCK_CANCEL,
+ gtk.RESPONSE_CANCEL,
+ gtk.STOCK_SAVE,
+ gtk.RESPONSE_OK,))
+ chooser.set_current_name("myimage.bb")
+ response = chooser.run()
+ if response == gtk.RESPONSE_OK:
+ self.save_path = chooser.get_filename()
+ chooser.destroy()
+
+ def save_cb(self, action):
+ if not self.save_path:
+ self.get_save_path()
+ self.save_recipe_file()
+
+ def save_as_cb(self, action):
+ self.get_save_path()
+ self.save_recipe_file()
+
+ def open_cb(self, action):
+ chooser = gtk.FileChooserDialog(title=None, parent=self,
+ action=gtk.FILE_CHOOSER_ACTION_OPEN,
+ buttons=(gtk.STOCK_CANCEL,
+ gtk.RESPONSE_CANCEL,
+ gtk.STOCK_OPEN,
+ gtk.RESPONSE_OK))
+ response = chooser.run()
+ rep = BuildRep(None, None, None)
+ if response == gtk.RESPONSE_OK:
+ rep.loadRecipe(chooser.get_filename())
+ chooser.destroy()
+ self.model.load_image_rep(rep)
+ self.dirty = False
+
def bake_clicked_cb(self, button):
- if not self.model.targets_contains_image():
- label = gtk.Label("No image was selected. Just build the selected packages?")
- dialog = gtk.Dialog("Warning, no image selected",
- self,
- gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
- (gtk.STOCK_NO, gtk.RESPONSE_NO,
- gtk.STOCK_YES, gtk.RESPONSE_YES))
- dialog.vbox.pack_start(label)
- label.show()
+ rep = self.model.get_build_rep()
+ if not rep.base_image:
+ lbl = "<b>Build only packages?</b>\n\nAn image has not been selected, so only the selected packages will be built."
+ dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING)
+ dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
+ dialog.add_button("Build", gtk.RESPONSE_YES)
response = dialog.run()
dialog.destroy()
- if not response == gtk.RESPONSE_YES:
+ if response == gtk.RESPONSE_CANCEL:
return
-
- # Note: We could "squash" the targets list to only include things not brought in by an image
- task_list = self.model.get_targets()
- if len(task_list):
- tasks = " ".join(task_list)
- # TODO: show a confirmation dialog
- print("Including these extra tasks in IMAGE_INSTALL: %s" % tasks)
else:
- return
-
+ # TODO: show a confirmation dialog ?
+ if not self.save_path:
+ import tempfile, datetime
+ image_name = "hob-%s-variant-%s.bb" % (rep.base_image, datetime.date.today().isoformat())
+ image_dir = os.path.join(tempfile.gettempdir(), 'hob-images')
+ bb.utils.mkdirhier(image_dir)
+ recipepath = os.path.join(image_dir, image_name)
+ else:
+ recipepath = self.save_path
+
+ rep.writeRecipe(recipepath, self.model)
+ # In the case where we saved the file for the purpose of building
+ # it we should then delete it so that the users workspace doesn't
+ # contain files they haven't explicitly saved there.
+ if not self.save_path:
+ self.files_to_clean.append(recipepath)
+
+ self.handler.queue_image_recipe_path(recipepath)
+
+ self.handler.build_packages(rep.allpkgs.split(" "))
self.nb.set_current_page(1)
- self.handler.run_build(task_list)
- return
+ def back_button_clicked_cb(self, button):
+ self.toggle_createview()
- def advanced_expander_cb(self, expander, param):
- return
-
- def images(self):
- self.images_tree = gtk.TreeView()
- self.images_tree.set_headers_visible(True)
- self.images_tree.set_headers_clickable(False)
- self.images_tree.set_enable_search(True)
- self.images_tree.set_search_column(0)
- self.images_tree.get_selection().set_mode(gtk.SELECTION_NONE)
+ def toggle_createview(self):
+ self.build.model.clear()
+ self.nb.set_current_page(0)
- col = gtk.TreeViewColumn('Package')
- col1 = gtk.TreeViewColumn('Description')
- col2 = gtk.TreeViewColumn('License')
- col3 = gtk.TreeViewColumn('Include')
- col3.set_resizable(False)
+ def build_complete_cb(self, running_build):
+ self.back.connect("clicked", self.back_button_clicked_cb)
+ self.back.set_sensitive(True)
+ self.cancel.set_sensitive(False)
+ for f in self.files_to_clean:
+ os.remove(f)
- self.images_tree.append_column(col)
- self.images_tree.append_column(col1)
- self.images_tree.append_column(col2)
- self.images_tree.append_column(col3)
+ lbl = "<b>Build completed</b>\n\nClick 'Edit Image' to start another build or 'View Log' to view the build log."
+ if self.handler.building == "image":
+ deploy = self.handler.get_image_deploy_dir()
+ lbl = lbl + "\n<a href=\"file://%s\" title=\"%s\">Browse folder of built images</a>." % (deploy, deploy)
- cell = gtk.CellRendererText()
- cell1 = gtk.CellRendererText()
- cell2 = gtk.CellRendererText()
- cell3 = gtk.CellRendererToggle()
- cell3.set_property('activatable', True)
- cell3.connect("toggled", self.toggle_include_cb, self.images_tree)
-
- col.pack_start(cell, True)
- col1.pack_start(cell1, True)
- col2.pack_start(cell2, True)
- col3.pack_start(cell3, True)
-
- col.set_attributes(cell, text=self.model.COL_NAME)
- col1.set_attributes(cell1, text=self.model.COL_DESC)
- col2.set_attributes(cell2, text=self.model.COL_LIC)
- col3.set_attributes(cell3, active=self.model.COL_INC)
-
- self.images_tree.show()
-
- scroll = gtk.ScrolledWindow()
- scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
- scroll.set_shadow_type(gtk.SHADOW_IN)
- scroll.add(self.images_tree)
-
- return scroll
+ dialog = CrumbsDialog(self, lbl)
+ dialog.add_button("View Log", gtk.RESPONSE_CANCEL)
+ dialog.add_button("Edit Image", gtk.RESPONSE_OK)
+ response = dialog.run()
+ dialog.destroy()
+ if response == gtk.RESPONSE_OK:
+ self.toggle_createview()
+
+ def build_started_cb(self, running_build):
+ self.back.set_sensitive(False)
+ self.cancel.set_sensitive(True)
+
+ def include_gplv3_cb(self, toggle):
+ excluded = toggle.get_active()
+ self.handler.toggle_gplv3(excluded)
+
+ def change_bb_threads(self, spinner):
+ val = spinner.get_value_as_int()
+ self.handler.set_bbthreads(val)
+
+ def change_make_threads(self, spinner):
+ val = spinner.get_value_as_int()
+ self.handler.set_pmake(val)
+
+ def toggle_toolchain(self, check):
+ enabled = check.get_active()
+ self.handler.toggle_toolchain(enabled)
+
+ def toggle_headers(self, check):
+ enabled = check.get_active()
+ self.handler.toggle_toolchain_headers(enabled)
+
+ def toggle_package_idle_cb(self, opath, image):
+ """
+ As the operations which we're calling on the model can take
+ a significant amount of time (in the order of seconds) during which
+ the GUI is unresponsive as the main loop is blocked perform them in
+ an idle function which at least enables us to set the busy cursor
+ before the UI is blocked giving the appearance of being responsive.
+ """
+ # Whether the item is currently included
+ inc = self.model[opath][self.model.COL_INC]
+ # If the item is already included, mark it for removal then
+ # the sweep_up() method finds affected items and marks them
+ # appropriately
+ if inc:
+ self.model.mark(opath)
+ self.model.sweep_up()
+ # If the item isn't included, mark it for inclusion
+ else:
+ self.model.include_item(item_path=opath,
+ binb="User Selected",
+ image_contents=image)
+
+ self.set_busy_cursor(False)
+ return False
+
+ def toggle_package(self, path, model, image=False):
+ # Warn user before removing packages
+ inc = model[path][self.model.COL_INC]
+ if inc:
+ pn = model[path][self.model.COL_NAME]
+ revdeps = self.model.find_reverse_depends(pn)
+ if len(revdeps):
+ lbl = "<b>Remove %s?</b>\n\nThis action cannot be undone and all packages which depend on this will be removed\nPackages which depend on %s include %s." % (pn, pn, ", ".join(revdeps).rstrip(","))
+ else:
+ lbl = "<b>Remove %s?</b>\n\nThis action cannot be undone." % pn
+ dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING)
+ dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
+ dialog.add_button("Remove", gtk.RESPONSE_OK)
+ response = dialog.run()
+ dialog.destroy()
+ if response == gtk.RESPONSE_CANCEL:
+ return
- def toggle_package(self, path, model):
+ self.set_busy_cursor()
# Convert path to path in original model
opath = model.convert_path_to_child_path(path)
- # current include status
- inc = self.model[opath][self.model.COL_INC]
- if inc:
- self.model.mark(opath)
- self.model.sweep_up()
- #self.model.remove_package_full(cpath)
- else:
- self.model.include_item(opath)
- return
+ # This is a potentially length call which can block the
+ # main loop, therefore do the work in an idle func to keep
+ # the UI responsive
+ glib.idle_add(self.toggle_package_idle_cb, opath, image)
- def remove_package_cb(self, cell, path):
- model = self.model.contents_model()
- label = gtk.Label("Are you sure you want to remove this item?")
- dialog = gtk.Dialog("Confirm removal", self,
- gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
- (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
- gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
- dialog.vbox.pack_start(label)
- label.show()
- response = dialog.run()
- dialog.destroy()
- if (response == gtk.RESPONSE_ACCEPT):
- self.toggle_package(path, model)
+ self.dirty = True
def toggle_include_cb(self, cell, path, tv):
model = tv.get_model()
@@ -262,23 +499,36 @@ class MainWindow (gtk.Window):
sort_model = tv.get_model()
cpath = sort_model.convert_path_to_child_path(path)
self.toggle_package(cpath, sort_model.get_model())
-
+
def pkgsaz(self):
+ vbox = gtk.VBox(False, 6)
+ vbox.show()
self.pkgsaz_tree = gtk.TreeView()
self.pkgsaz_tree.set_headers_visible(True)
self.pkgsaz_tree.set_headers_clickable(True)
self.pkgsaz_tree.set_enable_search(True)
self.pkgsaz_tree.set_search_column(0)
- self.pkgsaz_tree.get_selection().set_mode(gtk.SELECTION_NONE)
+ self.pkgsaz_tree.get_selection().set_mode(gtk.SELECTION_SINGLE)
col = gtk.TreeViewColumn('Package')
+ col.set_clickable(True)
+ col.set_sort_column_id(self.model.COL_NAME)
+ col.set_min_width(220)
col1 = gtk.TreeViewColumn('Description')
- col1.set_resizable(True)
+ col1.set_resizable(True)
+ col1.set_min_width(360)
col2 = gtk.TreeViewColumn('License')
- col2.set_resizable(True)
+ col2.set_resizable(True)
+ col2.set_clickable(True)
+ col2.set_sort_column_id(self.model.COL_LIC)
+ col2.set_min_width(170)
col3 = gtk.TreeViewColumn('Group')
- col4 = gtk.TreeViewColumn('Include')
- col4.set_resizable(False)
+ col3.set_clickable(True)
+ col3.set_sort_column_id(self.model.COL_GROUP)
+ col4 = gtk.TreeViewColumn('Included')
+ col4.set_min_width(80)
+ col4.set_max_width(90)
+ col4.set_sort_column_id(self.model.COL_INC)
self.pkgsaz_tree.append_column(col)
self.pkgsaz_tree.append_column(col1)
@@ -288,9 +538,9 @@ class MainWindow (gtk.Window):
cell = gtk.CellRendererText()
cell1 = gtk.CellRendererText()
- cell1.set_property('width-chars', 20)
+ cell1.set_property('width-chars', 20)
cell2 = gtk.CellRendererText()
- cell2.set_property('width-chars', 20)
+ cell2.set_property('width-chars', 20)
cell3 = gtk.CellRendererText()
cell4 = gtk.CellRendererToggle()
cell4.set_property('activatable', True)
@@ -300,7 +550,7 @@ class MainWindow (gtk.Window):
col1.pack_start(cell1, True)
col2.pack_start(cell2, True)
col3.pack_start(cell3, True)
- col4.pack_start(cell4, True)
+ col4.pack_end(cell4, True)
col.set_attributes(cell, text=self.model.COL_NAME)
col1.set_attributes(cell1, text=self.model.COL_DESC)
@@ -314,75 +564,43 @@ class MainWindow (gtk.Window):
scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
scroll.set_shadow_type(gtk.SHADOW_IN)
scroll.add(self.pkgsaz_tree)
+ vbox.pack_start(scroll, True, True, 0)
+
+ hb = gtk.HBox(False, 0)
+ hb.show()
+ search = gtk.Entry()
+ search.set_icon_from_stock(gtk.ENTRY_ICON_SECONDARY, "gtk-clear")
+ search.connect("icon-release", self.search_entry_clear_cb)
+ search.show()
+ self.pkgsaz_tree.set_search_entry(search)
+ hb.pack_end(search, False, False, 0)
+ label = gtk.Label("Search packages:")
+ label.show()
+ hb.pack_end(label, False, False, 6)
+ vbox.pack_start(hb, False, False, 0)
- return scroll
-
- def pkgsgrp(self):
- self.pkgsgrp_tree = gtk.TreeView()
- self.pkgsgrp_tree.set_headers_visible(True)
- self.pkgsgrp_tree.set_headers_clickable(False)
- self.pkgsgrp_tree.set_enable_search(True)
- self.pkgsgrp_tree.set_search_column(0)
- self.pkgsgrp_tree.get_selection().set_mode(gtk.SELECTION_NONE)
-
- col = gtk.TreeViewColumn('Package')
- col1 = gtk.TreeViewColumn('Description')
- col1.set_resizable(True)
- col2 = gtk.TreeViewColumn('License')
- col2.set_resizable(True)
- col3 = gtk.TreeViewColumn('Group')
- col4 = gtk.TreeViewColumn('Include')
- col4.set_resizable(False)
-
- self.pkgsgrp_tree.append_column(col)
- self.pkgsgrp_tree.append_column(col1)
- self.pkgsgrp_tree.append_column(col2)
- self.pkgsgrp_tree.append_column(col3)
- self.pkgsgrp_tree.append_column(col4)
-
- cell = gtk.CellRendererText()
- cell1 = gtk.CellRendererText()
- cell1.set_property('width-chars', 20)
- cell2 = gtk.CellRendererText()
- cell2.set_property('width-chars', 20)
- cell3 = gtk.CellRendererText()
- cell4 = gtk.CellRendererToggle()
- cell4.set_property("activatable", True)
- cell4.connect("toggled", self.toggle_pkg_include_cb, self.pkgsgrp_tree)
-
- col.pack_start(cell, True)
- col1.pack_start(cell1, True)
- col2.pack_start(cell2, True)
- col3.pack_start(cell3, True)
- col4.pack_start(cell4, True)
-
- col.set_attributes(cell, text=self.model.COL_NAME)
- col1.set_attributes(cell1, text=self.model.COL_DESC)
- col2.set_attributes(cell2, text=self.model.COL_LIC)
- col3.set_attributes(cell3, text=self.model.COL_GROUP)
- col4.set_attributes(cell4, active=self.model.COL_INC)
-
- self.pkgsgrp_tree.show()
-
- scroll = gtk.ScrolledWindow()
- scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
- scroll.set_shadow_type(gtk.SHADOW_IN)
- scroll.add(self.pkgsgrp_tree)
+ return vbox
- return scroll
+ def search_entry_clear_cb(self, entry, icon_pos, event):
+ entry.set_text("")
def tasks(self):
+ vbox = gtk.VBox(False, 6)
+ vbox.show()
self.tasks_tree = gtk.TreeView()
self.tasks_tree.set_headers_visible(True)
self.tasks_tree.set_headers_clickable(False)
self.tasks_tree.set_enable_search(True)
self.tasks_tree.set_search_column(0)
- self.tasks_tree.get_selection().set_mode(gtk.SELECTION_NONE)
+ self.tasks_tree.get_selection().set_mode(gtk.SELECTION_SINGLE)
col = gtk.TreeViewColumn('Package')
+ col.set_min_width(430)
col1 = gtk.TreeViewColumn('Description')
+ col1.set_min_width(430)
col2 = gtk.TreeViewColumn('Include')
- col2.set_resizable(False)
+ col2.set_min_width(70)
+ col2.set_max_width(80)
self.tasks_tree.append_column(col)
self.tasks_tree.append_column(col1)
@@ -396,7 +614,7 @@ class MainWindow (gtk.Window):
col.pack_start(cell, True)
col1.pack_start(cell1, True)
- col2.pack_start(cell2, True)
+ col2.pack_end(cell2, True)
col.set_attributes(cell, text=self.model.COL_NAME)
col1.set_attributes(cell1, text=self.model.COL_DESC)
@@ -408,26 +626,37 @@ class MainWindow (gtk.Window):
scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
scroll.set_shadow_type(gtk.SHADOW_IN)
scroll.add(self.tasks_tree)
+ vbox.pack_start(scroll, True, True, 0)
+
+ hb = gtk.HBox(False, 0)
+ hb.show()
+ search = gtk.Entry()
+ search.show()
+ self.tasks_tree.set_search_entry(search)
+ hb.pack_end(search, False, False, 0)
+ label = gtk.Label("Search collections:")
+ label.show()
+ hb.pack_end(label, False, False, 6)
+ vbox.pack_start(hb, False, False, 0)
- return scroll
+ return vbox
def cancel_build(self, button):
- label = gtk.Label("Do you really want to stop this build?")
- dialog = gtk.Dialog("Cancel build",
- self,
- gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
- (gtk.STOCK_NO, gtk.RESPONSE_NO,
- gtk.STOCK_YES, gtk.RESPONSE_YES))
- dialog.vbox.pack_start(label)
- label.show()
+ lbl = "<b>Stop build?</b>\n\nAre you sure you want to stop this build?"
+ dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING)
+ dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
+ dialog.add_button("Stop", gtk.RESPONSE_OK)
+ dialog.add_button("Force Stop", gtk.RESPONSE_YES)
response = dialog.run()
dialog.destroy()
- if response == gtk.RESPONSE_YES:
+ if response == gtk.RESPONSE_OK:
self.handler.cancel_build()
- return
+ elif response == gtk.RESPONSE_YES:
+ self.handler.cancel_build(True)
def view_build_gui(self):
- vbox = gtk.VBox(False, 6)
+ vbox = gtk.VBox(False, 12)
+ vbox.set_border_width(6)
vbox.show()
build_tv = RunningBuildTreeView()
build_tv.show()
@@ -438,20 +667,74 @@ class MainWindow (gtk.Window):
scrolled_view.add(build_tv)
scrolled_view.show()
vbox.pack_start(scrolled_view, expand=True, fill=True)
- hbox = gtk.HBox(False, 6)
+ hbox = gtk.HBox(False, 12)
hbox.show()
vbox.pack_start(hbox, expand=False, fill=False)
- cancel = gtk.Button(stock=gtk.STOCK_CANCEL)
- cancel.connect("clicked", self.cancel_build)
- cancel.show()
- hbox.pack_end(cancel, expand=False, fill=False)
+ self.back = gtk.Button("Back")
+ self.back.show()
+ self.back.set_sensitive(False)
+ hbox.pack_start(self.back, expand=False, fill=False)
+ self.cancel = gtk.Button("Stop Build")
+ self.cancel.connect("clicked", self.cancel_build)
+ self.cancel.show()
+ hbox.pack_end(self.cancel, expand=False, fill=False)
return vbox
+
+ def create_menu(self):
+ menu_items = '''<ui>
+ <menubar name="MenuBar">
+ <menu action="File">
+ <menuitem action="Save"/>
+ <menuitem action="Save As"/>
+ <menuitem action="Open"/>
+ <separator/>
+ <menuitem action="AddLayer" label="Add Layer"/>
+ <separator/>
+ <menuitem action="Quit"/>
+ </menu>
+ <menu action="Edit">
+ <menuitem action="Layers" label="Layers"/>
+ <menuitem action="Preferences"/>
+ </menu>
+ <menu action="Help">
+ <menuitem action="About"/>
+ </menu>
+ </menubar>
+ </ui>'''
+
+ uimanager = gtk.UIManager()
+ accel = uimanager.get_accel_group()
+ self.add_accel_group(accel)
+
+ actions = gtk.ActionGroup('ImageCreator')
+ self.actions = actions
+ actions.add_actions([('Quit', gtk.STOCK_QUIT, None, None,
+ None, self.menu_quit,),
+ ('File', None, '_File'),
+ ('Save', gtk.STOCK_SAVE, None, None, None, self.save_cb),
+ ('Save As', gtk.STOCK_SAVE_AS, None, None, None, self.save_as_cb),
+ ('Open', gtk.STOCK_OPEN, None, None, None, self.open_cb),
+ ('AddLayer', None, 'Add Layer', None, None, self.add_layer_cb),
+ ('Edit', None, '_Edit'),
+ ('Help', None, '_Help'),
+ ('Layers', None, 'Layers', None, None, self.layers_cb),
+ ('Preferences', gtk.STOCK_PREFERENCES, None, None, None, self.preferences_cb),
+ ('About', gtk.STOCK_ABOUT, None, None, None, self.about_cb)])
+ uimanager.insert_action_group(actions, 0)
+ uimanager.add_ui_from_string(menu_items)
+
+ menubar = uimanager.get_widget('/MenuBar')
+ menubar.show_all()
+
+ return menubar
def create_build_gui(self):
- vbox = gtk.VBox(False, 6)
+ vbox = gtk.VBox(False, 12)
+ vbox.set_border_width(6)
vbox.show()
- hbox = gtk.HBox(False, 6)
+
+ hbox = gtk.HBox(False, 12)
hbox.show()
vbox.pack_start(hbox, expand=False, fill=False)
@@ -459,90 +742,92 @@ class MainWindow (gtk.Window):
label.show()
hbox.pack_start(label, expand=False, fill=False, padding=6)
self.machine_combo = gtk.combo_box_new_text()
- self.machine_combo.set_active(0)
self.machine_combo.show()
self.machine_combo.set_tooltip_text("Selects the architecture of the target board for which you would like to build an image.")
hbox.pack_start(self.machine_combo, expand=False, fill=False, padding=6)
+ label = gtk.Label("Base image:")
+ label.show()
+ hbox.pack_start(label, expand=False, fill=False, padding=6)
+ self.image_combo = gtk.ComboBox()
+ self.image_combo.show()
+ self.image_combo.set_tooltip_text("Selects the image on which to base the created image")
+ image_combo_cell = gtk.CellRendererText()
+ self.image_combo.pack_start(image_combo_cell, True)
+ self.image_combo.add_attribute(image_combo_cell, 'text', self.model.COL_NAME)
+ hbox.pack_start(self.image_combo, expand=False, fill=False, padding=6)
+ self.progress = gtk.ProgressBar()
+ self.progress.set_size_request(250, -1)
+ hbox.pack_end(self.progress, expand=False, fill=False, padding=6)
ins = gtk.Notebook()
vbox.pack_start(ins, expand=True, fill=True)
ins.set_show_tabs(True)
- label = gtk.Label("Images")
+ label = gtk.Label("Packages")
label.show()
- ins.append_page(self.images(), tab_label=label)
- label = gtk.Label("Tasks")
+ ins.append_page(self.pkgsaz(), tab_label=label)
+ label = gtk.Label("Package Collections")
label.show()
ins.append_page(self.tasks(), tab_label=label)
- label = gtk.Label("Packages (by Group)")
- label.show()
- ins.append_page(self.pkgsgrp(), tab_label=label)
- label = gtk.Label("Packages (by Name)")
- label.show()
- ins.append_page(self.pkgsaz(), tab_label=label)
ins.set_current_page(0)
ins.show_all()
- hbox = gtk.HBox()
- hbox.show()
- vbox.pack_start(hbox, expand=False, fill=False)
label = gtk.Label("Image contents:")
+ self.model.connect("contents-changed", self.update_package_count_cb, label)
+ label.set_property("xalign", 0.00)
label.show()
- hbox.pack_start(label, expand=False, fill=False, padding=6)
+ vbox.pack_start(label, expand=False, fill=False, padding=6)
con = self.contents()
con.show()
vbox.pack_start(con, expand=True, fill=True)
- #advanced = gtk.Expander(label="Advanced")
- #advanced.connect("notify::expanded", self.advanced_expander_cb)
- #advanced.show()
- #vbox.pack_start(advanced, expand=False, fill=False)
-
- hbox = gtk.HBox()
- hbox.show()
- vbox.pack_start(hbox, expand=False, fill=False)
- bake = gtk.Button("Bake")
- bake.connect("clicked", self.bake_clicked_cb)
- bake.show()
- hbox.pack_end(bake, expand=False, fill=False, padding=6)
+ bbox = gtk.HButtonBox()
+ bbox.set_spacing(12)
+ bbox.set_layout(gtk.BUTTONBOX_END)
+ bbox.show()
+ vbox.pack_start(bbox, expand=False, fill=False)
reset = gtk.Button("Reset")
reset.connect("clicked", self.reset_clicked_cb)
reset.show()
- hbox.pack_end(reset, expand=False, fill=False, padding=6)
+ bbox.add(reset)
+ bake = gtk.Button("Bake")
+ bake.connect("clicked", self.bake_clicked_cb)
+ bake.show()
+ bbox.add(bake)
return vbox
+ def update_package_count_cb(self, model, count, label):
+ lbl = "Image contents (%s packages):" % count
+ label.set_text(lbl)
+
def contents(self):
self.contents_tree = gtk.TreeView()
self.contents_tree.set_headers_visible(True)
- self.contents_tree.get_selection().set_mode(gtk.SELECTION_NONE)
+ self.contents_tree.get_selection().set_mode(gtk.SELECTION_SINGLE)
# allow searching in the package column
self.contents_tree.set_search_column(0)
+ self.contents_tree.set_enable_search(True)
col = gtk.TreeViewColumn('Package')
- col.set_sort_column_id(0)
+ col.set_sort_column_id(0)
+ col.set_min_width(430)
col1 = gtk.TreeViewColumn('Brought in by')
- col1.set_resizable(True)
- col2 = gtk.TreeViewColumn('Remove')
- col2.set_expand(False)
+ col1.set_resizable(True)
+ col1.set_min_width(430)
self.contents_tree.append_column(col)
self.contents_tree.append_column(col1)
- self.contents_tree.append_column(col2)
cell = gtk.CellRendererText()
cell1 = gtk.CellRendererText()
- cell1.set_property('width-chars', 20)
- cell2 = gtk.CellRendererToggle()
- cell2.connect("toggled", self.remove_package_cb)
+ cell1.set_property('width-chars', 20)
col.pack_start(cell, True)
col1.pack_start(cell1, True)
- col2.pack_start(cell2, True)
col.set_attributes(cell, text=self.model.COL_NAME)
col1.set_attributes(cell1, text=self.model.COL_BINB)
- col2.set_attributes(cell2, active=self.model.COL_INC)
self.contents_tree.show()
@@ -554,26 +839,67 @@ class MainWindow (gtk.Window):
return scroll
def main (server, eventHandler):
+ import multiprocessing
+ cpu_cnt = multiprocessing.cpu_count()
+
gobject.threads_init()
taskmodel = TaskListModel()
+ configurator = Configurator()
handler = HobHandler(taskmodel, server)
mach = server.runCommand(["getVariable", "MACHINE"])
+ sdk_mach = server.runCommand(["getVariable", "SDKMACHINE"])
+ # If SDKMACHINE not set the default SDK_ARCH is used so we
+ # should represent that in the GUI
+ if not sdk_mach:
+ sdk_mach = server.runCommand(["getVariable", "SDK_ARCH"])
distro = server.runCommand(["getVariable", "DISTRO"])
-
- window = MainWindow(taskmodel, handler, mach, distro)
+ bbthread = server.runCommand(["getVariable", "BB_NUMBER_THREADS"])
+ if not bbthread:
+ bbthread = cpu_cnt
+ handler.set_bbthreads(cpu_cnt)
+ else:
+ bbthread = int(bbthread)
+ pmake = server.runCommand(["getVariable", "PARALLEL_MAKE"])
+ if not pmake:
+ pmake = cpu_cnt
+ handler.set_pmake(cpu_cnt)
+ else:
+ # The PARALLEL_MAKE variable will be of the format: "-j 3" and we only
+ # want a number for the spinner, so strip everything from the variable
+ # up to and including the space
+ pmake = int(pmake[pmake.find(" ")+1:])
+
+ image_types = server.runCommand(["getVariable", "IMAGE_TYPES"])
+
+ pclasses = server.runCommand(["getVariable", "PACKAGE_CLASSES"]).split(" ")
+ # NOTE: we're only supporting one value for PACKAGE_CLASSES being set
+ # this seems OK because we're using the first package format set in
+ # PACKAGE_CLASSES and that's the package manager used for the rootfs
+ pkg, sep, pclass = pclasses[0].rpartition("_")
+
+ prefs = HobPrefs(configurator, handler, sdk_mach, distro, pclass, cpu_cnt,
+ pmake, bbthread, image_types)
+ layers = LayerEditor(configurator, None)
+ window = MainWindow(taskmodel, handler, configurator, prefs, layers, mach)
+ prefs.set_parent_window(window)
+ layers.set_parent_window(window)
window.show_all ()
handler.connect("machines-updated", window.update_machines)
- handler.connect("distros-updated", window.update_distros)
+ handler.connect("sdk-machines-updated", prefs.update_sdk_machines)
+ handler.connect("distros-updated", prefs.update_distros)
+ handler.connect("package-formats-found", prefs.update_package_formats)
handler.connect("generating-data", window.busy)
handler.connect("data-generated", window.data_generated)
- pbar = ProgressBar(window)
- pbar.connect("delete-event", gtk.main_quit)
+ handler.connect("reload-triggered", window.reload_triggered_cb)
+ configurator.connect("layers-loaded", layers.load_current_layers)
+ configurator.connect("layers-changed", handler.reload_data)
+ handler.connect("config-found", configurator.configFound)
try:
# kick the while thing off
- handler.current_command = "findConfigFilesDistro"
- server.runCommand(["findConfigFiles", "DISTRO"])
+ handler.current_command = "findConfigFilePathLocal"
+ server.runCommand(["findConfigFilePath", "local.conf"])
except xmlrpclib.Fault:
print("XMLRPC Fault getting commandline:\n %s" % x)
return 1
@@ -584,7 +910,7 @@ def main (server, eventHandler):
handler.event_handle_idle_func,
eventHandler,
window.build,
- pbar)
+ window.progress)
try:
gtk.main()
OpenPOWER on IntegriCloud