# # BitBake Graphical GTK User Interface # # Copyright (C) 2011 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 from bb.ui.crumbs.progress import ProgressBar from bb.ui.crumbs.tasklistmodel import TaskListModel from bb.ui.crumbs.hobeventhandler import HobHandler from bb.ui.crumbs.runningbuild import RunningBuildTreeView, RunningBuild import xmlrpclib import logging import Queue class MainWindow (gtk.Window): def __init__(self, taskmodel, handler, curr_mach=None, curr_distro=None): gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL) self.model = taskmodel self.model.connect("tasklist-populated", self.update_model) self.curr_mach = curr_mach self.curr_distro = curr_distro 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.build = RunningBuild() self.build.connect("build-succeeded", self.running_build_succeeded_cb) self.build.connect("build-failed", self.running_build_failed_cb) createview = self.create_build_gui() 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 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 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 not response == gtk.RESPONSE_YES: self.model.reset() # NOTE: really? self.nb.set_current_page(0) return def machine_combo_changed_cb(self, combo, handler): mach = combo.get_active_text() if mach != self.curr_mach: self.curr_mach = mach handler.set_machine(mach) def update_machines(self, handler, machines): active = 0 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): if self.generating: pbar.pulse() return True else: pbar.hide() 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) def update_model(self, model): pkgsaz_model = gtk.TreeModelSort(self.model.packages_model()) pkgsaz_model.set_sort_column_id(self.model.COL_NAME, gtk.SORT_ASCENDING) 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()) 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() response = dialog.run() dialog.destroy() if (response == gtk.RESPONSE_ACCEPT): self.model.reset() return 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() response = dialog.run() dialog.destroy() if not response == gtk.RESPONSE_YES: 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 self.nb.set_current_page(1) self.handler.run_build(task_list) return 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) col = gtk.TreeViewColumn('Package') col1 = gtk.TreeViewColumn('Description') col2 = gtk.TreeViewColumn('License') col3 = gtk.TreeViewColumn('Include') col3.set_resizable(False) self.images_tree.append_column(col) self.images_tree.append_column(col1) self.images_tree.append_column(col2) self.images_tree.append_column(col3) 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 def toggle_package(self, path, model): # 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 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) def toggle_include_cb(self, cell, path, tv): model = tv.get_model() self.toggle_package(path, model) def toggle_pkg_include_cb(self, cell, path, tv): # there's an extra layer of models in the packages case. 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): 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) 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.pkgsaz_tree.append_column(col) self.pkgsaz_tree.append_column(col1) self.pkgsaz_tree.append_column(col2) self.pkgsaz_tree.append_column(col3) self.pkgsaz_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.pkgsaz_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.pkgsaz_tree.show() scroll = gtk.ScrolledWindow() scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS) scroll.set_shadow_type(gtk.SHADOW_IN) scroll.add(self.pkgsaz_tree) 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 scroll def tasks(self): 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) col = gtk.TreeViewColumn('Package') col1 = gtk.TreeViewColumn('Description') col2 = gtk.TreeViewColumn('Include') col2.set_resizable(False) self.tasks_tree.append_column(col) self.tasks_tree.append_column(col1) self.tasks_tree.append_column(col2) cell = gtk.CellRendererText() cell1 = gtk.CellRendererText() cell2 = gtk.CellRendererToggle() cell2.set_property('activatable', True) cell2.connect("toggled", self.toggle_include_cb, self.tasks_tree) 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_DESC) col2.set_attributes(cell2, active=self.model.COL_INC) self.tasks_tree.show() scroll = gtk.ScrolledWindow() scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS) scroll.set_shadow_type(gtk.SHADOW_IN) scroll.add(self.tasks_tree) return scroll 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() response = dialog.run() dialog.destroy() if response == gtk.RESPONSE_YES: self.handler.cancel_build() return def view_build_gui(self): vbox = gtk.VBox(False, 6) vbox.show() build_tv = RunningBuildTreeView() build_tv.show() build_tv.set_model(self.build.model) self.build.model.connect("row-inserted", self.scroll_tv_cb, build_tv) scrolled_view = gtk.ScrolledWindow () scrolled_view.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scrolled_view.add(build_tv) scrolled_view.show() vbox.pack_start(scrolled_view, expand=True, fill=True) hbox = gtk.HBox(False, 6) 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) return vbox def create_build_gui(self): vbox = gtk.VBox(False, 6) vbox.show() hbox = gtk.HBox(False, 6) hbox.show() vbox.pack_start(hbox, expand=False, fill=False) label = gtk.Label("Machine:") 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) ins = gtk.Notebook() vbox.pack_start(ins, expand=True, fill=True) ins.set_show_tabs(True) label = gtk.Label("Images") label.show() ins.append_page(self.images(), tab_label=label) label = gtk.Label("Tasks") 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:") label.show() hbox.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) reset = gtk.Button("Reset") reset.connect("clicked", self.reset_clicked_cb) reset.show() hbox.pack_end(reset, expand=False, fill=False, padding=6) return vbox 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) # allow searching in the package column self.contents_tree.set_search_column(0) col = gtk.TreeViewColumn('Package') col.set_sort_column_id(0) col1 = gtk.TreeViewColumn('Brought in by') col1.set_resizable(True) col2 = gtk.TreeViewColumn('Remove') col2.set_expand(False) 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) 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() scroll = gtk.ScrolledWindow() scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS) scroll.set_shadow_type(gtk.SHADOW_IN) scroll.add(self.contents_tree) return scroll def main (server, eventHandler): gobject.threads_init() gtk.gdk.threads_init() taskmodel = TaskListModel() handler = HobHandler(taskmodel, server) mach = server.runCommand(["getVariable", "MACHINE"]) distro = server.runCommand(["getVariable", "DISTRO"]) window = MainWindow(taskmodel, handler, mach, distro) window.show_all () handler.connect("machines-updated", window.update_machines) handler.connect("distros-updated", window.update_distros) handler.connect("generating-data", window.busy) handler.connect("data-generated", window.data_generated) pbar = ProgressBar(window) pbar.connect("delete-event", gtk.main_quit) try: # kick the while thing off handler.current_command = "findConfigFilesDistro" server.runCommand(["findConfigFiles", "DISTRO"]) except xmlrpclib.Fault: print("XMLRPC Fault getting commandline:\n %s" % x) return 1 # This timeout function regularly probes the event queue to find out if we # have any messages waiting for us. gobject.timeout_add (100, handler.event_handle_idle_func, eventHandler, window.build, pbar) try: gtk.main() except EnvironmentError as ioerror: # ignore interrupted io if ioerror.args[0] == 4: pass finally: server.runCommand(["stateStop"])