Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame^] | 1 | # |
| 2 | # BitBake Graphical GTK User Interface |
| 3 | # |
| 4 | # Copyright (C) 2011-2012 Intel Corporation |
| 5 | # |
| 6 | # Authored by Joshua Lock <josh@linux.intel.com> |
| 7 | # Authored by Dongxiao Xu <dongxiao.xu@intel.com> |
| 8 | # Authored by Shane Wang <shane.wang@intel.com> |
| 9 | # |
| 10 | # This program is free software; you can redistribute it and/or modify |
| 11 | # it under the terms of the GNU General Public License version 2 as |
| 12 | # published by the Free Software Foundation. |
| 13 | # |
| 14 | # This program is distributed in the hope that it will be useful, |
| 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 17 | # GNU General Public License for more details. |
| 18 | # |
| 19 | # You should have received a copy of the GNU General Public License along |
| 20 | # with this program; if not, write to the Free Software Foundation, Inc., |
| 21 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| 22 | |
| 23 | import gtk |
| 24 | import gobject |
| 25 | import os |
| 26 | import tempfile |
| 27 | from bb.ui.crumbs.hobwidget import hic, HobButton, HobAltButton |
| 28 | from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog |
| 29 | from bb.ui.crumbs.hig.crumbsmessagedialog import CrumbsMessageDialog |
| 30 | |
| 31 | """ |
| 32 | The following are convenience classes for implementing GNOME HIG compliant |
| 33 | BitBake GUI's |
| 34 | In summary: spacing = 12px, border-width = 6px |
| 35 | """ |
| 36 | |
| 37 | class CellRendererPixbufActivatable(gtk.CellRendererPixbuf): |
| 38 | """ |
| 39 | A custom CellRenderer implementation which is activatable |
| 40 | so that we can handle user clicks |
| 41 | """ |
| 42 | __gsignals__ = { 'clicked' : (gobject.SIGNAL_RUN_LAST, |
| 43 | gobject.TYPE_NONE, |
| 44 | (gobject.TYPE_STRING,)), } |
| 45 | |
| 46 | def __init__(self): |
| 47 | gtk.CellRendererPixbuf.__init__(self) |
| 48 | self.set_property('mode', gtk.CELL_RENDERER_MODE_ACTIVATABLE) |
| 49 | self.set_property('follow-state', True) |
| 50 | |
| 51 | """ |
| 52 | Respond to a user click on a cell |
| 53 | """ |
| 54 | def do_activate(self, even, widget, path, background_area, cell_area, flags): |
| 55 | self.emit('clicked', path) |
| 56 | |
| 57 | # |
| 58 | # LayerSelectionDialog |
| 59 | # |
| 60 | class LayerSelectionDialog (CrumbsDialog): |
| 61 | |
| 62 | TARGETS = [ |
| 63 | ("MY_TREE_MODEL_ROW", gtk.TARGET_SAME_WIDGET, 0), |
| 64 | ("text/plain", 0, 1), |
| 65 | ("TEXT", 0, 2), |
| 66 | ("STRING", 0, 3), |
| 67 | ] |
| 68 | |
| 69 | def gen_label_widget(self, content): |
| 70 | label = gtk.Label() |
| 71 | label.set_alignment(0, 0) |
| 72 | label.set_markup(content) |
| 73 | label.show() |
| 74 | return label |
| 75 | |
| 76 | def layer_widget_toggled_cb(self, cell, path, layer_store): |
| 77 | name = layer_store[path][0] |
| 78 | toggle = not layer_store[path][1] |
| 79 | layer_store[path][1] = toggle |
| 80 | |
| 81 | def layer_widget_add_clicked_cb(self, action, layer_store, parent): |
| 82 | dialog = gtk.FileChooserDialog("Add new layer", parent, |
| 83 | gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER) |
| 84 | button = dialog.add_button("Cancel", gtk.RESPONSE_NO) |
| 85 | HobAltButton.style_button(button) |
| 86 | button = dialog.add_button("Open", gtk.RESPONSE_YES) |
| 87 | HobButton.style_button(button) |
| 88 | label = gtk.Label("Select the layer you wish to add") |
| 89 | label.show() |
| 90 | dialog.set_extra_widget(label) |
| 91 | response = dialog.run() |
| 92 | path = dialog.get_filename() |
| 93 | dialog.destroy() |
| 94 | |
| 95 | lbl = "<b>Error</b>" |
| 96 | msg = "Unable to load layer <i>%s</i> because " % path |
| 97 | if response == gtk.RESPONSE_YES: |
| 98 | import os |
| 99 | import os.path |
| 100 | layers = [] |
| 101 | it = layer_store.get_iter_first() |
| 102 | while it: |
| 103 | layers.append(layer_store.get_value(it, 0)) |
| 104 | it = layer_store.iter_next(it) |
| 105 | |
| 106 | if not path: |
| 107 | msg += "it is an invalid path." |
| 108 | elif not os.path.exists(path+"/conf/layer.conf"): |
| 109 | msg += "there is no layer.conf inside the directory." |
| 110 | elif path in layers: |
| 111 | msg += "it is already in loaded layers." |
| 112 | else: |
| 113 | layer_store.append([path]) |
| 114 | return |
| 115 | dialog = CrumbsMessageDialog(parent, lbl, gtk.MESSAGE_ERROR, msg) |
| 116 | dialog.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_OK) |
| 117 | response = dialog.run() |
| 118 | dialog.destroy() |
| 119 | |
| 120 | def layer_widget_del_clicked_cb(self, action, tree_selection, layer_store): |
| 121 | model, iter = tree_selection.get_selected() |
| 122 | if iter: |
| 123 | layer_store.remove(iter) |
| 124 | |
| 125 | |
| 126 | def gen_layer_widget(self, layers, layers_avail, window, tooltip=""): |
| 127 | hbox = gtk.HBox(False, 6) |
| 128 | |
| 129 | layer_tv = gtk.TreeView() |
| 130 | layer_tv.set_rules_hint(True) |
| 131 | layer_tv.set_headers_visible(False) |
| 132 | tree_selection = layer_tv.get_selection() |
| 133 | tree_selection.set_mode(gtk.SELECTION_SINGLE) |
| 134 | |
| 135 | # Allow enable drag and drop of rows including row move |
| 136 | dnd_internal_target = '' |
| 137 | dnd_targets = [(dnd_internal_target, gtk.TARGET_SAME_WIDGET, 0)] |
| 138 | layer_tv.enable_model_drag_source( gtk.gdk.BUTTON1_MASK, |
| 139 | dnd_targets, |
| 140 | gtk.gdk.ACTION_MOVE) |
| 141 | layer_tv.enable_model_drag_dest(dnd_targets, |
| 142 | gtk.gdk.ACTION_MOVE) |
| 143 | layer_tv.connect("drag_data_get", self.drag_data_get_cb) |
| 144 | layer_tv.connect("drag_data_received", self.drag_data_received_cb) |
| 145 | |
| 146 | col0= gtk.TreeViewColumn('Path') |
| 147 | cell0 = gtk.CellRendererText() |
| 148 | cell0.set_padding(5,2) |
| 149 | col0.pack_start(cell0, True) |
| 150 | col0.set_cell_data_func(cell0, self.draw_layer_path_cb) |
| 151 | layer_tv.append_column(col0) |
| 152 | |
| 153 | scroll = gtk.ScrolledWindow() |
| 154 | scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) |
| 155 | scroll.set_shadow_type(gtk.SHADOW_IN) |
| 156 | scroll.add(layer_tv) |
| 157 | |
| 158 | table_layer = gtk.Table(2, 10, False) |
| 159 | hbox.pack_start(table_layer, expand=True, fill=True) |
| 160 | |
| 161 | table_layer.attach(scroll, 0, 10, 0, 1) |
| 162 | |
| 163 | layer_store = gtk.ListStore(gobject.TYPE_STRING) |
| 164 | for layer in layers: |
| 165 | layer_store.append([layer]) |
| 166 | |
| 167 | col1 = gtk.TreeViewColumn('Enabled') |
| 168 | layer_tv.append_column(col1) |
| 169 | |
| 170 | cell1 = CellRendererPixbufActivatable() |
| 171 | cell1.set_fixed_size(-1,35) |
| 172 | cell1.connect("clicked", self.del_cell_clicked_cb, layer_store) |
| 173 | col1.pack_start(cell1, True) |
| 174 | col1.set_cell_data_func(cell1, self.draw_delete_button_cb, layer_tv) |
| 175 | |
| 176 | add_button = gtk.Button() |
| 177 | add_button.set_relief(gtk.RELIEF_NONE) |
| 178 | box = gtk.HBox(False, 6) |
| 179 | box.show() |
| 180 | add_button.add(box) |
| 181 | add_button.connect("enter-notify-event", self.add_hover_cb) |
| 182 | add_button.connect("leave-notify-event", self.add_leave_cb) |
| 183 | self.im = gtk.Image() |
| 184 | self.im.set_from_file(hic.ICON_INDI_ADD_FILE) |
| 185 | self.im.show() |
| 186 | box.pack_start(self.im, expand=False, fill=False, padding=6) |
| 187 | lbl = gtk.Label("Add layer") |
| 188 | lbl.set_alignment(0.0, 0.5) |
| 189 | lbl.show() |
| 190 | box.pack_start(lbl, expand=True, fill=True, padding=6) |
| 191 | add_button.connect("clicked", self.layer_widget_add_clicked_cb, layer_store, window) |
| 192 | table_layer.attach(add_button, 0, 10, 1, 2, gtk.EXPAND | gtk.FILL, 0, 0, 6) |
| 193 | layer_tv.set_model(layer_store) |
| 194 | |
| 195 | hbox.show_all() |
| 196 | |
| 197 | return hbox, layer_store |
| 198 | |
| 199 | def drag_data_get_cb(self, treeview, context, selection, target_id, etime): |
| 200 | treeselection = treeview.get_selection() |
| 201 | model, iter = treeselection.get_selected() |
| 202 | data = model.get_value(iter, 0) |
| 203 | selection.set(selection.target, 8, data) |
| 204 | |
| 205 | def drag_data_received_cb(self, treeview, context, x, y, selection, info, etime): |
| 206 | model = treeview.get_model() |
| 207 | data = selection.data |
| 208 | drop_info = treeview.get_dest_row_at_pos(x, y) |
| 209 | if drop_info: |
| 210 | path, position = drop_info |
| 211 | iter = model.get_iter(path) |
| 212 | if (position == gtk.TREE_VIEW_DROP_BEFORE or position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE): |
| 213 | model.insert_before(iter, [data]) |
| 214 | else: |
| 215 | model.insert_after(iter, [data]) |
| 216 | else: |
| 217 | model.append([data]) |
| 218 | if context.action == gtk.gdk.ACTION_MOVE: |
| 219 | context.finish(True, True, etime) |
| 220 | return |
| 221 | |
| 222 | def add_hover_cb(self, button, event): |
| 223 | self.im.set_from_file(hic.ICON_INDI_ADD_HOVER_FILE) |
| 224 | |
| 225 | def add_leave_cb(self, button, event): |
| 226 | self.im.set_from_file(hic.ICON_INDI_ADD_FILE) |
| 227 | |
| 228 | def __init__(self, title, layers, layers_non_removable, all_layers, parent, flags, buttons=None): |
| 229 | super(LayerSelectionDialog, self).__init__(title, parent, flags, buttons) |
| 230 | |
| 231 | # class members from other objects |
| 232 | self.layers = layers |
| 233 | self.layers_non_removable = layers_non_removable |
| 234 | self.all_layers = all_layers |
| 235 | self.layers_changed = False |
| 236 | |
| 237 | # icon for remove button in TreeView |
| 238 | im = gtk.Image() |
| 239 | im.set_from_file(hic.ICON_INDI_REMOVE_FILE) |
| 240 | self.rem_icon = im.get_pixbuf() |
| 241 | |
| 242 | # class members for internal use |
| 243 | self.layer_store = None |
| 244 | |
| 245 | # create visual elements on the dialog |
| 246 | self.create_visual_elements() |
| 247 | self.connect("response", self.response_cb) |
| 248 | |
| 249 | def create_visual_elements(self): |
| 250 | layer_widget, self.layer_store = self.gen_layer_widget(self.layers, self.all_layers, self, None) |
| 251 | layer_widget.set_size_request(450, 250) |
| 252 | self.vbox.pack_start(layer_widget, expand=True, fill=True) |
| 253 | self.show_all() |
| 254 | |
| 255 | def response_cb(self, dialog, response_id): |
| 256 | model = self.layer_store |
| 257 | it = model.get_iter_first() |
| 258 | layers = [] |
| 259 | while it: |
| 260 | layers.append(model.get_value(it, 0)) |
| 261 | it = model.iter_next(it) |
| 262 | |
| 263 | self.layers_changed = (self.layers != layers) |
| 264 | self.layers = layers |
| 265 | |
| 266 | """ |
| 267 | A custom cell_data_func to draw a delete 'button' in the TreeView for layers |
| 268 | other than the meta layer. The deletion of which is prevented so that the |
| 269 | user can't shoot themselves in the foot too badly. |
| 270 | """ |
| 271 | def draw_delete_button_cb(self, col, cell, model, it, tv): |
| 272 | path = model.get_value(it, 0) |
| 273 | if path in self.layers_non_removable: |
| 274 | cell.set_sensitive(False) |
| 275 | cell.set_property('pixbuf', None) |
| 276 | cell.set_property('mode', gtk.CELL_RENDERER_MODE_INERT) |
| 277 | else: |
| 278 | cell.set_property('pixbuf', self.rem_icon) |
| 279 | cell.set_sensitive(True) |
| 280 | cell.set_property('mode', gtk.CELL_RENDERER_MODE_ACTIVATABLE) |
| 281 | |
| 282 | return True |
| 283 | |
| 284 | """ |
| 285 | A custom cell_data_func to write an extra message into the layer path cell |
| 286 | for the meta layer. We should inform the user that they can't remove it for |
| 287 | their own safety. |
| 288 | """ |
| 289 | def draw_layer_path_cb(self, col, cell, model, it): |
| 290 | path = model.get_value(it, 0) |
| 291 | if path in self.layers_non_removable: |
| 292 | cell.set_property('markup', "<b>It cannot be removed</b>\n%s" % path) |
| 293 | else: |
| 294 | cell.set_property('text', path) |
| 295 | |
| 296 | def del_cell_clicked_cb(self, cell, path, model): |
| 297 | it = model.get_iter_from_string(path) |
| 298 | model.remove(it) |